diff --git a/README.md b/README.md
index e83623c..724a6af 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,168 @@
-# generator-react-webpack [](https://travis-ci.org/newtriks/generator-react-webpack)
+# generator-react-webpack [](https://travis-ci.org/newtriks/generator-react-webpack) [](http://gruntjs.com/)
-A generator for [Yeoman](http://yeoman.io).
+> Yeoman generator for [ReactJS](http://facebook.github.io/react/) - lets you quickly set up a project including karma test runner and [Webpack](http://webpack.github.io/) module system.
-## Getting Started
-
-### What is Yeoman?
-
-Trick question. It's not a thing. It's this guy:
-
-
-
-Basically, he wears a top hat, lives in your computer, and waits for you to tell him what kind of application you wish to create.
-
-Not every new computer comes with a Yeoman pre-installed. He lives in the [npm](https://npmjs.org) package repository. You only have to ask for him once, then he packs up and moves into your hard drive. *Make sure you clean up, he likes new and shiny things.*
+## Usage
+Install `generator-react-webpack`:
```
-$ npm install -g yo
+npm install -g generator-react-webpack
```
-### Yeoman Generators
-
-Yeoman travels light. He didn't pack any generators when he moved in. You can think of a generator like a plug-in. You get to choose what type of application you wish to create, such as a Backbone application or even a Chrome extension.
-
-To install generator-react-webpack from npm, run:
-
+Make a new directory, and `cd` into it:
```
-$ npm install -g generator-react-webpack
+mkdir my-new-project && cd $_
```
-Finally, initiate the generator:
-
+Run `yo react-webpack`, optionally passing an app name:
```
-$ yo react-webpack
+yo react-webpack [app-name]
```
-### Getting To Know Yeoman
+Run `grunt` for building and `grunt serve` for preview in the browser at [localhost](http://localhost:8000).
-Yeoman has a heart of gold. He's a person with feelings and opinions, but he's very easy to work with. If you think he's too opinionated, he can be easily convinced.
+## Generators
-If you'd like to get to know Yeoman better and meet some of his friends, [Grunt](http://gruntjs.com) and [Bower](http://bower.io), check out the complete [Getting Started Guide](https://github.com/yeoman/yeoman/wiki/Getting-Started).
+Available generators:
+* [react-webpack](#app) (aka [react-webpack:app](#app))
+* [react-webpack:component](#component)
+
+**Note: Generators are to be run from the root directory of your app.**
+
+### App
+
+Sets up a new ReactJS app, generating all the boilerplate you need to get started. The app generator also facilitates the following:
+
+1. Configures a Gruntfile to run the app on a local server.
+2. Configures Webpack to modularise the app enabling [loading of various file formats](http://webpack.github.io/docs/loader-list.html) e.g. JSON, CSS, PNG, etc.
+3. Configures [Karma](http://karma-runner.github.io) to run all tests.
+4. Watches for changes and recompiles JS and refreshes the browser.
+
+Example:
+```bash
+yo react-webpack
+```
+
+### Component
+
+Generates a [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) component in `src/scripts/components` and it's corresponding test in `src/spec/components`.
+
+Example:
+```bash
+yo react-webpack:component foo
+```
+
+Produces `src/scripts/components/Foo.js` (*javascript - JSX*):
+```
+/**
+ * @jsx React.DOM
+ */
+
+'use strict';
+
+var React = require('react/addons');
+
+var Foo = React.createClass({
+ /*jshint ignore:start */
+ render: function () {
+ return (
+
+ )
+ }
+ /*jshint ignore:end */
+});
+
+module.exports = Foo;
+```
+
+And `test/spec/components/Foo.js` (*javascript - jasmine*):
+```
+
+'use strict';
+
+describe('Foo', function () {
+ var Foo, component;
+
+ beforeEach(function () {
+ Foo = require('../../../src/scripts/components/Foo');
+ component = Foo();
+ });
+
+ it('should create a new instance of Foo', function () {
+ expect(component).toBeDefined();
+ });
+});
+```
+
+## Testing
+
+Running `grunt test` will run the unit tests with karma. Tests are written using [Jasmine](http://pivotal.github.io/jasmine/) by default.
+
+## Further Information
+
+### Project Structure
+
+The react-webpack generator automates the setup of a [ReactJS](http://facebook.github.io/react/) project using the specific structure detailed below:
+
+```
+project
+ - src
+ - scripts
+ -components
+ ComponentOne.js
+ ComponentTwo.js
+ main.js
+ - styles
+ main.css
+ reset.css
+ index.html
+ - test
+ - spec
+ - components
+ ComponentOne.js
+ ComponentTwo.js
+ - helpers
+ - react
+ addons.js
+ phantomjs-shims.js
+ Gruntfile.js
+ karma.conf.js
+```
+
+I have tried to keep the project structure as simple as possible and understand it may not suit everyone.
+
+### Naming Components
+
+I have opted to follow [@floydophone](https://twitter.com/floydophone) convention of uppercase for component file naming e.g. [Component.js](https://github.com/petehunt/ReactHack/tree/master/src/components). I am open to suggestions if there is a general objection to this decision.
+
+### Modules
+
+Each component is a module and can be required using the [Webpack](http://webpack.github.io/) module system. [Webpack](http://webpack.github.io/) uses [Loaders](http://webpack.github.io/docs/loaders.html) which means you can also require CSS and a host of other file types. Read the [Webpack documentation](http://webpack.github.io/docs/home.html) to find out more.
+
+### Grunt
+
+Out the box the [Gruntfile](http://gruntjs.com/api/grunt.file) is configured with the following:
+
+1. **webpack**: uses the [grunt-webpack](https://github.com/webpack/grunt-webpack) plugin to load all required modules and output to a single JS file `src/scripts/main.js`. This is included in the `src/index.html` file by default and will reload in the browser as and when it is recompiled.
+2. **watch**: uses the [grunt-watch](https://github.com/gruntjs/grunt-contrib-watch) plugin and watches for file changes. Livereload support is included by default and thus on file change the *webpack* task is run and the browser will auto update.
+3. **connect**: uses the [grunt-connect](https://github.com/gruntjs/grunt-contrib-connect) plugin to start a webserver at [localhost](http://localhost:8000).
+4. **karma**: uses the [grunt-karma](https://github.com/karma-runner/grunt-karma) plugin to load the Karma configuration file `karma.conf.js` located in the project root. This will run all tests using [PhantomJS](http://phantomjs.org/) by default but supports many other browsers.
+
+### CSS
+
+Included in the project is [Eric Meyer's reset.css](http://meyerweb.com/eric/tools/css/reset/) script. There is also a `src/styles/main.css` script that's required by the core `src/scripts/components/App.js` component using Webpack.
+
+## Props
+
+Thanks to all who contributed to [generator-angular](https://github.com/yeoman/generator-angular) as the majority of code here has been shamelessy sourced from that repos.
+
+## Contribute
+
+Contributions are welcomed. When submitting a bugfix, write a test that exposes the bug and fails before applying your fix. Submit the test alongside the fix.
## License
diff --git a/app/index.js b/app/index.js
index 740dc80..fb5b70c 100644
--- a/app/index.js
+++ b/app/index.js
@@ -72,3 +72,7 @@ ReactWebpackGenerator.prototype.imageFiles = function () {
this.sourceRoot(path.join(__dirname, 'templates'));
this.directory('images', 'src/images', true);
};
+
+ReactWebpackGenerator.prototype.karmaFiles = function () {
+ this.copy('../../templates/common/karma.conf.js', 'karma.conf.js');
+};
diff --git a/component/index.js b/component/index.js
new file mode 100644
index 0000000..043ac66
--- /dev/null
+++ b/component/index.js
@@ -0,0 +1,17 @@
+'use strict';
+var util = require('util');
+var ScriptBase = require('../script-base.js');
+
+var ComponentGenerator = module.exports = function ComponentGenerator(args, options, config) {
+ ScriptBase.apply(this, arguments);
+};
+
+util.inherits(ComponentGenerator, ScriptBase);
+
+ComponentGenerator.prototype.createComponentFile = function createComponentFile() {
+ this.generateSourceAndTest(
+ 'component',
+ 'spec/component',
+ 'components'
+ );
+};
diff --git a/script-base.js b/script-base.js
index 261047c..24a860c 100644
--- a/script-base.js
+++ b/script-base.js
@@ -14,6 +14,7 @@ var Generator = module.exports = function Generator() {
this.appname = this._.slugify(this._.humanize(this.appname));
this.scriptAppName = this._.capitalize(this.appname) + generalUtils.appName(this);
+ this.classedName = this._.capitalize(this.name);
if (typeof this.env.options.appPath === 'undefined') {
this.env.options.appPath = this.env.options.appPath || 'src';
@@ -52,10 +53,7 @@ Generator.prototype.htmlTemplate = function (src, dest) {
]);
};
-Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, targetDirectory, skipAdd) {
- this.appTemplate(appTemplate, path.join('scripts', targetDirectory, this.name));
- this.testTemplate(testTemplate, path.join(targetDirectory, this.name));
- if (!skipAdd) {
- this.addScriptToIndex(path.join(targetDirectory, this.name));
- }
+Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, targetDirectory) {
+ this.appTemplate(appTemplate, path.join('scripts', targetDirectory, this._.capitalize(this.name)));
+ this.testTemplate(testTemplate, path.join(targetDirectory, this._.capitalize(this.name)));
};
\ No newline at end of file
diff --git a/templates/common/Gruntfile.js b/templates/common/Gruntfile.js
index 3b10070..03ef33d 100644
--- a/templates/common/Gruntfile.js
+++ b/templates/common/Gruntfile.js
@@ -116,7 +116,7 @@ module.exports = function (grunt) {
}
});
- grunt.registerTask('server', function (target) {
+ grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
}
diff --git a/templates/common/karma.conf.js b/templates/common/karma.conf.js
new file mode 100644
index 0000000..3d7cfa4
--- /dev/null
+++ b/templates/common/karma.conf.js
@@ -0,0 +1,58 @@
+'use strict';
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine'],
+ files: [
+ 'test/helpers/**/*.js',
+ 'test/spec/components/**/*.js'
+ ],
+ preprocessors: {
+ 'test/spec/components/**/*.js': ['webpack']
+ },
+ webpack: {
+ cache: true,
+ module: {
+ loaders: [{
+ test: /\.css$/,
+ loader: 'style!css'
+ }, {
+ test: /\.gif/,
+ loader: 'url-loader?limit=10000&minetype=image/gif'
+ }, {
+ test: /\.jpg/,
+ loader: 'url-loader?limit=10000&minetype=image/jpg'
+ }, {
+ test: /\.png/,
+ loader: 'url-loader?limit=10000&minetype=image/png'
+ }, {
+ test: /\.js$/,
+ loader: 'jsx-loader'
+ }]
+ }
+ },
+ webpackServer: {
+ stats: {
+ colors: true
+ }
+ },
+ exclude: [],
+ port: 8080,
+ logLevel: config.LOG_INFO,
+ colors: true,
+ autoWatch: false,
+ // Start these browsers, currently available:
+ // - Chrome
+ // - ChromeCanary
+ // - Firefox
+ // - Opera
+ // - Safari (only Mac)
+ // - PhantomJS
+ // - IE (only Windows)
+ browsers: ['PhantomJS'],
+ reporters: ['progress'],
+ captureTimeout: 60000,
+ singleRun: true
+ });
+};
diff --git a/templates/javascript/Component.js b/templates/javascript/Component.js
new file mode 100644
index 0000000..2b12147
--- /dev/null
+++ b/templates/javascript/Component.js
@@ -0,0 +1,21 @@
+/**
+ * @jsx React.DOM
+ */
+
+'use strict';
+
+var React = require('react/addons');
+
+var <%= classedName %> = React.createClass({
+ /*jshint ignore:start */
+ render: function () {
+ return (
+
+
Content for <%= classedName %>
+
+ )
+ }
+ /*jshint ignore:end */
+});
+
+module.exports = <%= classedName %>;
diff --git a/templates/javascript/spec/Component.js b/templates/javascript/spec/Component.js
new file mode 100644
index 0000000..7f03497
--- /dev/null
+++ b/templates/javascript/spec/Component.js
@@ -0,0 +1,14 @@
+'use strict';
+
+describe('<%= classedName %>', function () {
+ var <%= classedName %>, component;
+
+ beforeEach(function () {
+ <%= classedName %> = require('../../../src/scripts/components/<%= classedName %>');
+ component = <%= classedName %>();
+ });
+
+ it('should create a new instance of <%= classedName %>', function () {
+ expect(component).toBeDefined();
+ });
+});