Merge pull request #83 from yonatanmn/master

Add Reflux Architecture option
This commit is contained in:
Simon Bailey 2015-04-11 11:10:38 +01:00
commit ddcba6d9d1
17 changed files with 279 additions and 56 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ $RECYCLE.BIN/
node_modules/ node_modules/
/test/* /test/*
npm-debug.log npm-debug.log
.idea/

170
README.md
View File

@ -29,6 +29,11 @@ Available generators:
* [react-webpack](#app) (aka [react-webpack:app](#app)) * [react-webpack](#app) (aka [react-webpack:app](#app))
* [react-webpack:component](#component) * [react-webpack:component](#component)
and for **Flux** or **Reflux** :
* [react-webpack:action](#action)
* [react-webpack:store](#store)
### App ### App
Sets up a new ReactJS app, generating all the boilerplate you need to get started. The app generator also facilitates the following: Sets up a new ReactJS app, generating all the boilerplate you need to get started. The app generator also facilitates the following:
@ -45,7 +50,7 @@ yo react-webpack
### Component ### 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`. Generates a [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) component in `src/scripts/components`, its corresponding test in `src/spec/components` and its style in `src/style`.
Example: Example:
```bash ```bash
@ -58,6 +63,8 @@ Produces `src/scripts/components/Foo.js` (*javascript - JSX*):
var React = require('react/addons'); var React = require('react/addons');
require('styles/componentName.css'); //or .sass,.less etc...
var Foo = React.createClass({ var Foo = React.createClass({
render: function () { render: function () {
return ( return (
@ -89,13 +96,131 @@ describe('Foo', function () {
}); });
``` ```
And `src/styles/Foo.css`: And `src/styles/Foo.css` (or .sass, .less etc...) :
``` ```
.Foo{ .Foo{
border: 1px dashed #f00; border: 1px dashed #f00;
} }
``` ```
### Action
When using Flux or Reflux architecture, it generates an actionCreator in `src/scripts/actions` and it's corresponding test in `src/spec/actions`.
Example:
```bash
yo react-webpack:action bar
```
Will create a file - `src/scripts/actions/BarActionCreators.js`
if 'architecture' is **Flux**, it Produces :
```
'use strict';
var BarActionCreators = {
}
module.exports = BarActionCreators;
```
And if it's **Reflux**:
```
'use strict';
var Reflux = require('reflux');
var BarActionCreators = Reflux.createActions([
]);
module.exports = BarActionCreators;
```
and same test for both architectures:
```
'use strict';
describe('BarActionCreators', function() {
var action;
beforeEach(function() {
action = require('actions/BarActionCreators.js');
});
it('should be defined', function() {
expect(action).toBeDefined();
});
});
```
### Store
When using Flux or Reflux architecture, it generates a store in `src/scripts/stores` and it's corresponding test in `src/spec/stores`.
Example:
```bash
yo react-webpack:store baz
```
Will create a file - `src/scripts/stores/BazStore.js`
if 'architecture' is **Flux**, it Produces :
```
'use strict';
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var MainAppDispatcher = require('../dispatcher/MainAppDispatcher');
var BazStore = assign({}, EventEmitter.prototype, {
});
BazStore.dispatchToken = MainAppDispatcher.register(function(action) {
switch(action.type) {
default:
}
});
module.exports = BazStore;
```
And if it's **Reflux**:
```
'use strict';
var Reflux = require('reflux');
//var Actions = require('actions/..');
var BazStore = Reflux.createStore({
listenables: Actions,
});
module.exports = BazStore;
```
and same test for both architectures:
```
'use strict';
describe('BazStore', function() {
var store;
beforeEach(function() {
store = require('stores/BazStore.js');
});
it('should be defined', function() {
expect(store).toBeDefined();
});
});
```
## Options ## Options
Options are available as additional installs to the initial application generation phase. Options are available as additional installs to the initial application generation phase.
@ -103,6 +228,16 @@ Options are available as additional installs to the initial application generati
A complete routing library for React. This option only adds the basic hooks to get started with [react router](https://github.com/rackt/react-router). A complete routing library for React. This option only adds the basic hooks to get started with [react router](https://github.com/rackt/react-router).
### styles language
css, sass, scss, less or stylus
Sets the style file's template and extension
### architecture
[flux](https://facebook.github.io/flux/) or [reflux](https://github.com/spoike/refluxjs)
## Testing ## Testing
Running `grunt test` will run the unit tests with karma. Tests are written using [Jasmine](http://jasmine.github.io/) by default. Running `grunt test` will run the unit tests with karma. Tests are written using [Jasmine](http://jasmine.github.io/) by default.
@ -118,23 +253,44 @@ project
- src - src
- scripts - scripts
-components -components
ComponentOne.js MainApp.js
ComponentTwo.js Foo.js
main.js AnotherComponent.js
//for flux/reflux
-actions
BarActionCreators.js
-stores
BazStore.js
//for flux
-dispatcher
FooAppDispatcher
- styles - styles
main.css main.css
index.html index.html
- test - test
- spec - spec
- components - components
ComponentOne.js MainApp.js
ComponentTwo.js Foo.js
AnotherComponent.js
//for flux/reflux
-actions
BarActionCreators.js
-stores
BazStore.js
- helpers - helpers
- react - react
addons.js addons.js
phantomjs-shims.js phantomjs-shims.js
Gruntfile.js Gruntfile.js
karma.conf.js karma.conf.js
package.json
webpack.config.js
webpack.dist.config.js
``` ```
I have tried to keep the project structure as simple as possible and understand it may not suit everyone. I have tried to keep the project structure as simple as possible and understand it may not suit everyone.

View File

@ -5,22 +5,29 @@ var ScriptBase = require('../script-base.js');
var ActionGenerator = module.exports = function ActionGenerator(args, options, config) { var ActionGenerator = module.exports = function ActionGenerator(args, options, config) {
args[0] += 'ActionCreators'; args[0] += 'ActionCreators';
ScriptBase.apply(this, arguments); ScriptBase.apply(this, arguments);
} };
util.inherits(ActionGenerator, ScriptBase); util.inherits(ActionGenerator, ScriptBase);
ActionGenerator.prototype.createActionFile = function createActionFile() { ActionGenerator.prototype.createActionFile = function createActionFile() {
this.option('es6'); this.option('es6');
this.es6 = this.options.es6; this.es6 = this.options.es6;
console.log(this.name); var actionTemplate;
switch (this.architecture){
case 'flux':
actionTemplate = 'FluxAction';
break;
case 'reflux':
actionTemplate = 'RefluxAction';
break;
}
console.log('Creating ' + this.architecture + ' action');
this.generateSourceAndTest( this.generateSourceAndTest(
'Action', actionTemplate,
'spec/Action', 'spec/Action',
void(0), 'actions'
'actions',
false
); );
} };

View File

@ -15,7 +15,6 @@ var ReactWebpackGenerator = module.exports = function ReactWebpackGenerator(args
this.config.set('app-name', this.appname); this.config.set('app-name', this.appname);
args = ['main'];
if (typeof this.options.appPath === 'undefined') { if (typeof this.options.appPath === 'undefined') {
this.options.appPath = this.options.appPath || 'src'; this.options.appPath = this.options.appPath || 'src';
@ -23,6 +22,8 @@ var ReactWebpackGenerator = module.exports = function ReactWebpackGenerator(args
this.appPath = this.options.appPath; this.appPath = this.options.appPath;
args = [this.scriptAppName];
this.composeWith('react-webpack:common', { this.composeWith('react-webpack:common', {
args: args args: args
}); });
@ -66,16 +67,21 @@ ReactWebpackGenerator.prototype.askForReactRouter = function () {
}.bind(this)); }.bind(this));
}; };
ReactWebpackGenerator.prototype.askForFlux = function() { ReactWebpackGenerator.prototype.askForArchitecture = function() {
var done = this.async(); var done = this.async();
this.prompt({ this.prompt({
type : 'confirm', type : 'list',
name : 'flux', name : 'architecture',
message : 'Would you like to include flux?', message : 'Would you like to use one of those architectures?',
choices: [
{name:'No need for that, thanks',value:false},
{name:'Flux',value:'flux'},
{name:'ReFlux',value:'reflux'}
],
default : false default : false
}, function(props) { }, function(props) {
this.env.options.flux = props.flux; this.env.options.architecture = props.architecture;
this.config.set('flux', props.flux); this.config.set('architecture', props.architecture);
done(); done();
}.bind(this)); }.bind(this));
}; };
@ -113,7 +119,7 @@ ReactWebpackGenerator.prototype.createIndexHtml = function createIndexHtml() {
ReactWebpackGenerator.prototype.packageFiles = function () { ReactWebpackGenerator.prototype.packageFiles = function () {
this.es6 = this.options.es6; this.es6 = this.options.es6;
this.reactRouter = this.env.options.reactRouter; this.reactRouter = this.env.options.reactRouter;
this.flux = this.env.options.flux; this.architecture = this.env.options.architecture;
this.stylesLanguage = this.env.options.stylesLanguage; this.stylesLanguage = this.env.options.stylesLanguage;
this.template('../../templates/common/_package.json', 'package.json'); this.template('../../templates/common/_package.json', 'package.json');
this.template('../../templates/common/_webpack.config.js', 'webpack.config.js'); this.template('../../templates/common/_webpack.config.js', 'webpack.config.js');

View File

@ -13,7 +13,7 @@ ComponentGenerator.prototype.createComponentFile = function createComponentFile(
this.es6 = this.options.es6; this.es6 = this.options.es6;
this.generateSourceAndTest( this.generateComponentTestAndStyle(
'Component', 'Component',
'spec/Component', 'spec/Component',
'styles/Component', 'styles/Component',

View File

@ -9,10 +9,11 @@ var MainGenerator = module.exports = function MainGenerator(args, options, confi
util.inherits(MainGenerator, ScriptBase); util.inherits(MainGenerator, ScriptBase);
MainGenerator.prototype.createAppFile = function createAppFile() { MainGenerator.prototype.createAppFile = function createAppFile(scriptAppName) {
this.reactRouter = this.env.options.reactRouter; this.reactRouter = this.env.options.reactRouter;
this.appTemplate('App', 'components/' + this.scriptAppName); this.scriptAppName = scriptAppName;
this.testTemplate('spec/App', 'components/' + this.scriptAppName); this.appTemplate('App', 'components/' + scriptAppName);
this.testTemplate('spec/App', 'components/' + scriptAppName);
}; };
MainGenerator.prototype.createMainFile = function createMainFile() { MainGenerator.prototype.createMainFile = function createMainFile() {
@ -22,7 +23,7 @@ MainGenerator.prototype.createMainFile = function createMainFile() {
}; };
MainGenerator.prototype.createDispatcher = function createDispatcher() { MainGenerator.prototype.createDispatcher = function createDispatcher() {
if(this.env.options.flux) { if(this.env.options.architecture=='flux') {
this.appTemplate('Dispatcher', 'dispatcher/' + this.scriptAppName + 'Dispatcher'); this.appTemplate('Dispatcher', 'dispatcher/' + this.scriptAppName + 'Dispatcher');
} }
}; };

View File

@ -20,6 +20,7 @@ var Generator = module.exports = function Generator() {
this.classedFileName = this._.capitalizeFile(this.name); this.classedFileName = this._.capitalizeFile(this.name);
this.classedName = this._.capitalizeClass(this.name); this.classedName = this._.capitalizeClass(this.name);
this.stylesLanguage = this.config.get('styles-language'); this.stylesLanguage = this.config.get('styles-language');
this.architecture = this.config.get('architecture');
if (typeof this.options.appPath === 'undefined') { if (typeof this.options.appPath === 'undefined') {
this.options.appPath = this.options.appPath || 'src/scripts'; this.options.appPath = this.options.appPath || 'src/scripts';
@ -60,6 +61,13 @@ var Generator = module.exports = function Generator() {
util.inherits(Generator, yeoman.generators.NamedBase); util.inherits(Generator, yeoman.generators.NamedBase);
Generator.prototype.appTemplate = function (src, dest) { Generator.prototype.appTemplate = function (src, dest) {
yeoman.generators.Base.prototype.template.apply(this, [
path.join('javascript', src + this.scriptSuffix),
path.join(this.options.appPath, dest) + this.scriptSuffix
]);
};
Generator.prototype.reactComponentTemplate = function (src, dest) {
yeoman.generators.Base.prototype.template.apply(this, [ yeoman.generators.Base.prototype.template.apply(this, [
path.join('javascript', src + this.reactSuffix), path.join('javascript', src + this.reactSuffix),
path.join(this.options.appPath, dest) + this.reactSuffix path.join(this.options.appPath, dest) + this.reactSuffix
@ -88,8 +96,13 @@ Generator.prototype.htmlTemplate = function (src, dest) {
]); ]);
}; };
Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, stylesTemplate, targetDirectory, includeStyles) { Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, targetDirectory) {
this.appTemplate(appTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name))); this.appTemplate(appTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name)));
this.testTemplate(testTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name))); this.testTemplate(testTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name)));
if(includeStyles) this.stylesTemplate(stylesTemplate, path.join(this._.capitalizeFile(this.name))); };
Generator.prototype.generateComponentTestAndStyle = function (componentTemplate, testTemplate, stylesTemplate, targetDirectory) {
this.reactComponentTemplate(componentTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name)));
this.testTemplate(testTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name)));
this.stylesTemplate(stylesTemplate, path.join(this._.capitalizeFile(this.name)));
}; };

View File

@ -2,24 +2,35 @@
var util = require('util'); var util = require('util');
var ScriptBase = require('../script-base.js'); var ScriptBase = require('../script-base.js');
var ActionGenerator = module.exports = function ActionGenerator(args, options, config) { var StoreGenerator = module.exports = function StoreGenerator(args, options, config) {
args[0] += 'Store'; args[0] += 'Store';
ScriptBase.apply(this, arguments); ScriptBase.apply(this, arguments);
} }
util.inherits(ActionGenerator, ScriptBase); util.inherits(StoreGenerator, ScriptBase);
ActionGenerator.prototype.createActionFile = function createActionFile() { StoreGenerator .prototype.createStoreFile = function createStoreFile() {
this.option('es6'); this.option('es6');
this.es6 = this.options.es6; this.es6 = this.options.es6;
var storeTemplate;
switch (this.architecture){
case 'flux':
storeTemplate = 'FluxStore';
this.dispatcherName = this._.capitalizeFile(this.config.get('app-name')) + 'AppDispatcher'; this.dispatcherName = this._.capitalizeFile(this.config.get('app-name')) + 'AppDispatcher';
break;
case 'reflux':
storeTemplate = 'RefluxStore';
break;
}
console.log('Creating ' + this.architecture + ' store');
this.generateSourceAndTest( this.generateSourceAndTest(
'Store', storeTemplate,
'spec/Store', 'spec/Store',
void(0), 'stores'
'stores',
false
); );
} };

View File

@ -10,10 +10,11 @@
"mainInput": "<% if (reactRouter) { %>main<% } else { %><%= scriptAppName %><% } %>", "mainInput": "<% if (reactRouter) { %>main<% } else { %><%= scriptAppName %><% } %>",
"mainOutput": "main", "mainOutput": "main",
"dependencies": {<% if (reactRouter) { %> "dependencies": {<% if (reactRouter) { %>
"react-router": "^0.11.6",<% } if (flux) { %> "react-router": "^0.11.6",<% } if (architecture === 'flux') { %>
"flux": "^2.0.1", "flux": "^2.0.1",
"events": "^1.0.2", "events": "^1.0.2",
"object-assign": "^2.0.0", <% } %> "object-assign": "^2.0.0", <% } if (architecture === 'reflux') {%>
"reflux": "^0.2.7", <% } %>
"react": "~0.12.2", "react": "~0.12.2",
"normalize.css": "~3.0.3" "normalize.css": "~3.0.3"
}, },

View File

@ -30,10 +30,11 @@ module.exports = {
resolve: { resolve: {
extensions: ['', '.js'], extensions: ['', '.js'],
alias: { alias: {
'styles': '../../../src/styles', 'styles': __dirname + '/src/styles',
'components': '../../../src/scripts/components/'<% if(flux) { %>, 'mixins': __dirname + '/src/scripts/mixins',
'stores': '../../../src/scripts/stores/', 'components': __dirname + '/src/scripts/components/'<% if(architecture==='flux'||architecture=='reflux') { %>,
'actions': '../../../src/scripts/actions/'<% } %> 'stores': __dirname + '/src/scripts/stores/',
'actions': __dirname + '/src/scripts/actions/'<% } %>
} }
}, },
module: { module: {

View File

@ -35,10 +35,11 @@ module.exports = {
resolve: { resolve: {
extensions: ['', '.js'], extensions: ['', '.js'],
alias: { alias: {
'styles': '../../../src/styles', 'styles': __dirname + '/src/styles',
'components': '../../../src/scripts/components/'<% if(flux) { %>, 'mixins': __dirname + '/src/scripts/mixins',
'stores': '../../../src/scripts/stores/', 'components': __dirname + '/src/scripts/components/'<% if(architecture==='flux'||architecture=='reflux') { %>,
'actions': '../../../src/scripts/actions/'<% } %> 'stores': __dirname + '/src/scripts/stores/',
'actions': __dirname + '/src/scripts/actions/'<% } %>
} }
}, },

View File

@ -8,12 +8,12 @@ module.exports = function (config) {
frameworks: ['jasmine'], frameworks: ['jasmine'],
files: [ files: [
'test/helpers/**/*.js', 'test/helpers/**/*.js',
'test/spec/components/**/*.js'<% if(flux) { %>, 'test/spec/components/**/*.js'<% if(architecture === 'flux'||architecture === 'reflux') { %>,
'test/spec/stores/**/*.js', 'test/spec/stores/**/*.js',
'test/spec/actions/**/*.js'<% } %> 'test/spec/actions/**/*.js'<% } %>
], ],
preprocessors: { preprocessors: {
'test/spec/components/**/*.js': ['webpack']<% if(flux) { %>, 'test/spec/components/**/*.js': ['webpack']<% if(architecture === 'flux'||architecture === 'reflux') { %>,
'test/spec/stores/**/*.js': ['webpack'], 'test/spec/stores/**/*.js': ['webpack'],
'test/spec/actions/**/*.js': ['webpack']<% } %> 'test/spec/actions/**/*.js': ['webpack']<% } %>
}, },
@ -52,7 +52,7 @@ module.exports = function (config) {
resolve: { resolve: {
alias: { alias: {
'styles': path.join(process.cwd(), './src/styles/'), 'styles': path.join(process.cwd(), './src/styles/'),
'components': path.join(process.cwd(), './src/scripts/components/')<% if(flux) { %>, 'components': path.join(process.cwd(), './src/scripts/components/')<% if(architecture === 'flux'||architecture === 'reflux') { %>,
'stores': '../../../src/scripts/stores/', 'stores': '../../../src/scripts/stores/',
'actions': '../../../src/scripts/actions/'<% } %> 'actions': '../../../src/scripts/actions/'<% } %>
} }

View File

@ -0,0 +1,11 @@
'use strict';
var Reflux = require('reflux');
var <%= classedName %> = Reflux.createActions([
]);
<% if (es6) { %> export default <%= classedName %>; <% }
else { %>module.exports = <%= classedName %>; <% } %>

View File

@ -0,0 +1,14 @@
'use strict';
var Reflux = require('reflux');
//var Actions = require('actions/..');
var <%= classedName %> = Reflux.createStore({
listenables: Actions,
});
<% if (es6) { %> export default <%= classedName %>; <% }
else { %>module.exports = <%= classedName %>; <% } %>

View File

@ -190,7 +190,7 @@ describe('react-webpack generator', function() {
beforeEach(function(done) { beforeEach(function(done) {
helpers.mockPrompt(react, { helpers.mockPrompt(react, {
flux: true architecture: 'flux'
}); });
react.run({}, function() { react.run({}, function() {
@ -277,7 +277,7 @@ describe('react-webpack generator', function() {
beforeEach(function(done){ beforeEach(function(done){
helpers.mockPrompt(react, { helpers.mockPrompt(react, {
flux: true architecture: 'flux'
}); });
react.run({}, function() { react.run({}, function() {
@ -312,7 +312,7 @@ describe('react-webpack generator', function() {
beforeEach(function(done) { beforeEach(function(done) {
helpers.mockPrompt(react, { helpers.mockPrompt(react, {
flux: true architecture: 'flux'
}); });
react.run({}, function() { react.run({}, function() {