├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .yo-rc.json ├── LICENSE ├── README.md ├── generators ├── app │ ├── index.js │ └── templates │ │ ├── _package.json │ │ ├── api.js │ │ ├── babelrc │ │ ├── db.json │ │ ├── editorconfig │ │ ├── eslintrc │ │ ├── gitignore │ │ ├── gulpfile.js │ │ ├── npmignore │ │ ├── server.js │ │ ├── src │ │ ├── actions │ │ │ ├── creator.js │ │ │ ├── index.js │ │ │ └── task.js │ │ ├── apis │ │ │ ├── index.js │ │ │ └── task.js │ │ ├── assets │ │ │ └── images │ │ │ │ └── icon.png │ │ ├── components │ │ │ ├── hello.js │ │ │ └── tasks.js │ │ ├── config.js │ │ ├── constants │ │ │ ├── code.js │ │ │ └── env.js │ │ ├── containers │ │ │ ├── about.js │ │ │ ├── app.js │ │ │ ├── devtools.js │ │ │ ├── home.css │ │ │ ├── home.js │ │ │ ├── root.js │ │ │ └── routes.js │ │ ├── index.js │ │ ├── libs.js │ │ ├── reducers │ │ │ ├── entities.js │ │ │ ├── index.js │ │ │ ├── reducer.js │ │ │ ├── result.js │ │ │ └── task.js │ │ ├── sagas │ │ │ ├── api.js │ │ │ ├── index.js │ │ │ └── task.js │ │ ├── schemas │ │ │ └── task.js │ │ ├── selectors │ │ │ └── task.js │ │ ├── store │ │ │ ├── devtools.js │ │ │ └── index.js │ │ ├── stores │ │ │ ├── index.js │ │ │ └── task.js │ │ ├── styles │ │ │ └── hello.css │ │ └── templates │ │ │ └── index.html │ │ ├── stylelintrc │ │ ├── test │ │ ├── components │ │ │ └── tasks.js │ │ ├── sagas │ │ │ └── api.js │ │ ├── setup.js │ │ └── stores │ │ │ └── task.js │ │ └── webpack.config.js └── feature │ ├── index.js │ └── templates │ ├── action.js │ ├── api.js │ ├── component.js │ ├── container.js │ ├── reducer.js │ ├── saga.js │ ├── schema.js │ ├── selector.js │ ├── store.js │ └── style.css ├── gulpfile.js ├── package.json └── test ├── app.js ├── store.js └── test-app.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "arrow-body-style": 0, 6 | "prefer-arrow-callback": 0, 7 | "func-names": 0, 8 | "react/jsx-no-bind": 0, 9 | "react/jsx-uses-react": 1, 10 | "react/prefer-stateless-function": 0 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 6, 14 | "ecmaFeatures": { 15 | "experimentalObjectRestSpread": true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "strict": true 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 6.0.0 5 | - v6 6 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-generator": { 3 | "structure": "nested" 4 | } 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jackong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-reactapp 2 | 3 | [![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] [![David deps][david-image]][david-url] [![node version][node-image]][node-url] [![Gittip][gittip-image]][gittip-url] 4 | 5 | [![NPM](https://nodei.co/npm/generator-reactapp.png?downloads=true)](https://nodei.co/npm/generator-reactapp/) 6 | 7 | -------------------------------------------------------------------------------- 8 | 9 | ## What 10 | 11 | `generator-reactapp` is a yeoman generator for react app. 12 | 13 | Dependencies: 14 | 15 | - [react](https://facebook.github.io/react/) 16 | 17 | - [react-router](https://github.com/reactjs/react-router) 18 | - [react-css-modules](https://github.com/gajus/react-css-modules) 19 | 20 | - [mobx](https://github.com/mobxjs/mobx) 21 | 22 | - [mobx-react](https://github.com/mobxjs/mobx-react) 23 | 24 | - [redux](http://redux.js.org/) 25 | - [redux-saga](https://github.com/yelouafi/redux-saga/) 26 | - [reselect](https://github.com/reactjs/reselect) 27 | - [immutable](https://facebook.github.io/immutable-js/) 28 | - [normalizr](https://github.com/paularmstrong/normalizr/) 29 | 30 | - [restful.js](https://github.com/marmelab/restful.js) 31 | 32 | - [fetch](https://github.com/github/fetch) 33 | - [request](https://github.com/request/request) 34 | 35 | - [store](https://github.com/marcuswestin/store.js) 36 | 37 | - test 38 | 39 | - [mocha](https://mochajs.org/) (BDD) 40 | - [chai](http://chaijs.com/) (Assertion) 41 | - [sinon](sinonjs.org) (Mock) 42 | - [nock](https://github.com/node-nock/nock) (HTTP Mocking and Expectations) 43 | - [jsdom](https://github.com/tmpvar/jsdom) (A JavaScript implementation of the WHATWG DOM and HTML standards) 44 | - [enzyme](https://github.com/airbnb/enzyme) 45 | 46 | - tools 47 | 48 | - [webpack](http://webpack.github.io/) 49 | 50 | - [webpack-dev-server](https://webpack.github.io/docs/webpack-dev-server.html)(Hot-reload & Mock-API) 51 | 52 | - [gulp](http://gulpjs.com/) 53 | 54 | - [eslint](http://eslint.org/) 55 | 56 | - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) 57 | 58 | - [stylelint](http://stylelint.io/) 59 | 60 | - [editorconfig](http://editorconfig.org/) 61 | 62 | - [cross-env](https://github.com/kentcdodds/cross-env) (Sets an environment using unix style and have it work on windows too) 63 | 64 | - [postcss](https://github.com/postcss/postcss) 65 | 66 | - [autoprefixer](https://github.com/postcss/autoprefixer) (Add vendor prefixes to CSS rules) 67 | - [precss](https://github.com/jonathantneal/precss) (Use Sass-like markup in your CSS files) 68 | 69 | -------------------------------------------------------------------------------- 70 | 71 | ## Why 72 | 73 | > Quickly start a project. 74 | 75 | -------------------------------------------------------------------------------- 76 | 77 | ## How 78 | 79 | ### Getting Started 80 | 81 | To install Yeoman 82 | 83 | ```bash 84 | npm install -g yo 85 | ``` 86 | 87 | To install generator-reactapp from npm, run: 88 | 89 | ```bash 90 | npm install -g generator-reactapp 91 | ``` 92 | 93 | Finally, initiate the generator: 94 | 95 | ```bash 96 | yo reactapp 97 | ``` 98 | 99 | And then, generate feature by sub-generator: 100 | 101 | ```bash 102 | yo reactapp:feature 103 | ``` 104 | 105 | ### Starting the dev-server and api-server 106 | 107 | ```bash 108 | npm start 109 | ``` 110 | 111 | ### Running Tests 112 | 113 | ```bash 114 | npm test 115 | ``` 116 | 117 | OR 118 | 119 | ```bash 120 | npm run testing 121 | ``` 122 | 123 | ### Building Project 124 | 125 | Building for production 126 | 127 | ```bash 128 | npm run build 129 | ``` 130 | 131 | ### Project Structure 132 | 133 | - .babelrc `babel config` 134 | - .editorconfig `editor config` 135 | - .eslintrc `eslint config` 136 | - gulpfile.js `gulp tasks` 137 | - server.js `dev-server` 138 | - api.js `mock api server` 139 | - db.json `mock database for api` 140 | - webpack.config.js `webpack config` 141 | - test/ `tests` 142 | - src/ 143 | 144 | - templates/index.html `html entry template` 145 | - assets/ 146 | - constants/ `common constants` 147 | - components/ `react components(reusability)` 148 | - styles/ `css styles` 149 | - containers/ `react containers(route)` 150 | - apis/ `api config` 151 | - stores/ `mobx stores` 152 | - actions/ `redux actions` 153 | - reducers/ `redux reducers` 154 | - sagas/ `redux sagas` 155 | - schemas/ `redux schemas` 156 | - selectors/ `redux selectors` 157 | - index.jsx `js entry` 158 | - libs.jsx `js vendor` 159 | - config.js `config` 160 | 161 | - dist/ `generated by building` 162 | 163 | ## License 164 | 165 | MIT 166 | 167 | [david-image]: https://img.shields.io/david/Jackong/generator-reactapp.svg?style=flat-square 168 | [david-url]: https://david-dm.org/Jackong/generator-reactapp 169 | [gittip-image]: https://img.shields.io/gratipay/Jackong.svg 170 | [gittip-url]: https://gratipay.com/~Jackong 171 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.11-green.svg?style=flat-square 172 | [node-url]: http://nodejs.org/download/ 173 | [npm-image]: https://img.shields.io/npm/v/generator-reactapp.svg?style=flat-square 174 | [npm-url]: https://npmjs.org/package/generator-reactapp 175 | [travis-image]: https://travis-ci.org/Jackong/generator-reactapp.svg?branch=master 176 | [travis-url]: https://travis-ci.org/Jackong/generator-reactapp 177 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | const yeoman = require('yeoman-generator'); 2 | const chalk = require('chalk'); 3 | const yosay = require('yosay'); 4 | 5 | module.exports = yeoman.Base.extend({ 6 | prompting() { 7 | this.log(yosay( 8 | `Welcome to the stellar ${chalk.red('generator-reactapp')} generator!` 9 | )); 10 | 11 | const prompts = [ 12 | { 13 | type: 'input', 14 | name: 'appname', 15 | message: 'Your project name', 16 | default: this.appname.replace(/\s/g, '-'), 17 | }, 18 | { 19 | type: 'list', 20 | name: 'sm', 21 | message: 'Which state manager you want to use', 22 | choices: [ 23 | 'redux', 24 | 'mobx', 25 | ], 26 | }, 27 | ]; 28 | 29 | return this.prompt(prompts).then(function (props) { 30 | this.props = props; 31 | }.bind(this)); 32 | }, 33 | 34 | writing() { 35 | this.fs.copyTpl( 36 | this.templatePath('_package.json'), 37 | this.destinationPath('package.json'), 38 | this.props 39 | ); 40 | 41 | this.fs.copyTpl( 42 | this.templatePath('webpack.config.js'), 43 | this.destinationPath('webpack.config.js'), 44 | this.props 45 | ); 46 | 47 | this.fs.copy( 48 | this.templatePath('server.js'), 49 | this.destinationPath('server.js') 50 | ); 51 | 52 | this.fs.copy( 53 | this.templatePath('api.js'), 54 | this.destinationPath('api.js') 55 | ); 56 | 57 | this.fs.copy( 58 | this.templatePath('gulpfile.js'), 59 | this.destinationPath('gulpfile.js') 60 | ); 61 | 62 | this.fs.copy( 63 | this.templatePath('db.json'), 64 | this.destinationPath('db.json') 65 | ); 66 | 67 | this.fs.copy( 68 | this.templatePath('editorconfig'), 69 | this.destinationPath('.editorconfig') 70 | ); 71 | 72 | this.fs.copy( 73 | this.templatePath('babelrc'), 74 | this.destinationPath('.babelrc') 75 | ); 76 | 77 | this.fs.copy( 78 | this.templatePath('gitignore'), 79 | this.destinationPath('.gitignore') 80 | ); 81 | 82 | this.fs.copy( 83 | this.templatePath('npmignore'), 84 | this.destinationPath('.npmignore') 85 | ); 86 | 87 | this.fs.copy( 88 | this.templatePath('eslintrc'), 89 | this.destinationPath('.eslintrc') 90 | ); 91 | 92 | this.fs.copy( 93 | this.templatePath('stylelintrc'), 94 | this.destinationPath('.stylelintrc') 95 | ); 96 | 97 | const ignore = { 98 | redux: ['**/test/stores/**', '**/src/stores/**'], 99 | mobx: [ 100 | '**/test/sagas/**', 101 | '**/src/actions/**', 102 | '**/src/store/**', 103 | '**/src/reducers/**', 104 | '**/src/selectors/**', 105 | '**/src/schemas/**', 106 | '**/src/sagas/**', 107 | ], 108 | }; 109 | 110 | this.fs.copyTpl( 111 | this.templatePath('test'), 112 | this.destinationPath('test'), 113 | this.props, 114 | null, 115 | { 116 | globOptions: { 117 | ignore: ignore[this.props.sm], 118 | }, 119 | } 120 | ); 121 | 122 | this.fs.copyTpl( 123 | this.templatePath('src'), 124 | this.destinationPath('src'), 125 | this.props, 126 | null, 127 | { 128 | globOptions: { 129 | ignore: ignore[this.props.sm], 130 | }, 131 | } 132 | ); 133 | 134 | this.fs.copy( 135 | this.templatePath('src/assets'), 136 | this.destinationPath('src/assets') 137 | ); 138 | }, 139 | 140 | install() { 141 | this.installDependencies(); 142 | }, 143 | }); 144 | -------------------------------------------------------------------------------- /generators/app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appname %>", 3 | "version": "0.0.1", 4 | "author": "jackong", 5 | "scripts": { 6 | "start": "cross-env NODE_ENV=development node server.js", 7 | "lint": "cross-env gulp lint", 8 | "test": "cross-env gulp lint && NODE_ENV=production mocha --compilers jsx:babel-core/register --require test/setup.js --recursive test/", 9 | "testing": "cross-env npm test -- --watch", 10 | "build": "cross-env NODE_ENV=production gulp build" 11 | }, 12 | "dependencies": { 13 | "react": "^15.3.1", 14 | "react-dom": "^15.3.1", 15 | "react-router": "^2.7.0",<% if (sm === 'mobx') { %> 16 | "mobx": "^2.6.0", 17 | "mobx-react": "^3.5.8",<% } else { %> 18 | "redux": "^3.6.0", 19 | "react-redux": "^4.4.5", 20 | "react-router-redux": "^4.0.6", 21 | "redux-saga": "^0.12.0", 22 | "reselect": "^2.5.4", 23 | "immutable": "^3.8.1", 24 | "normalizr": "^2.2.1",<% } %> 25 | "react-router": "^2.0.1", 26 | "history": "^2.0.1", 27 | "restful.js": "^0.9.6", 28 | "es6-promise": "^4.0.5", 29 | "qs": "^6.2.1", 30 | "isomorphic-fetch": "^2.2.1", 31 | "store": "^1.3.20", 32 | "react-css-modules": "^3.7.10", 33 | "whatwg-fetch": "^1.0.0" 34 | }, 35 | "devDependencies": { 36 | "babel-core": "^6.7.4", 37 | "babel-loader": "^6.2.4", 38 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 39 | "babel-preset-es2015": "^6.6.0", 40 | "babel-preset-react": "^6.5.0", 41 | "babel-preset-stage-1": "^6.5.0", 42 | "chai": "^3.5.0", 43 | "babel-eslint": "^7.0.0", 44 | "eslint": "^3.7.1", 45 | "gulp-eslint": "^3.0.1", 46 | "eslint-config-airbnb": "^12.0.0", 47 | "eslint-plugin-babel": "^3.3.0", 48 | "eslint-plugin-import": "^2.0.1", 49 | "eslint-plugin-jsx-a11y": "^2.2.3", 50 | "eslint-plugin-react": "^6.4.1", 51 | "gulp": "^3.9.1", 52 | "del": "^2.2.0", 53 | "html-webpack-plugin": "^2.15.0", 54 | "jsdom": "^8.3.1", 55 | "mocha": "^2.4.5", 56 | "nock": "^8.0.0", 57 | "enzyme": "^2.3.0", 58 | "react-addons-test-utils": "^15.0.1", 59 | "react-hot-loader": "^3.0.0-beta.4",<% if (sm === 'mobx') { %> 60 | "mobx-react-devtools": "^4.2.9",<% } else { %> 61 | "babel-polyfill": "^6.16.0", 62 | "redux-devtools": "^3.3.1", 63 | "redux-devtools-dock-monitor": "^1.1.1", 64 | "redux-devtools-log-monitor": "1.1.0", 65 | "redux-logger": "^2.7.0",<% } %> 66 | "gulp-postcss": "^6.1.1", 67 | "autoprefixer": "^6.3.6", 68 | "css-mqpacker": "^5.0.1", 69 | "cssnano": "^3.7.5", 70 | "style-loader": "^0.13.1", 71 | "css-loader": "^0.23.1", 72 | "file-loader": "^0.9.0", 73 | "postcss-loader": "^0.9.1", 74 | "precss": "^1.4.0", 75 | "stylefmt": "^4.3.1", 76 | "stylelint": "^7.4.2", 77 | "stylelint-config-standard": "^13.0.2", 78 | "gulp-stylelint": "^3.4.0", 79 | "cross-env": "^1.0.8", 80 | "request": "^2.71.0", 81 | "sinon": "^1.17.3", 82 | "webpack": "^1.12.14", 83 | "webpack-dev-server": "^1.16.1", 84 | "extract-text-webpack-plugin": "^1.0.1", 85 | "json-server": "^0.8.21", 86 | "webpack-stream": "^3.1.0" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /generators/app/templates/api.js: -------------------------------------------------------------------------------- 1 | const js = require('json-server'); 2 | 3 | const server = js.create(); 4 | const router = js.router('db.json'); 5 | const middlewares = js.defaults(); 6 | 7 | module.exports = (endpoint, port) => { 8 | server.use(middlewares); 9 | server.use(endpoint, router); 10 | server.listen(port); 11 | }; 12 | -------------------------------------------------------------------------------- /generators/app/templates/babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-1"], 3 | "plugins": [ 4 | "react-hot-loader/babel", 5 | "transform-decorators-legacy" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /generators/app/templates/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "id": 1, 5 | "content": "pharetra amet placerat pulvinar lacus aenean sed", 6 | "isDone": false 7 | }, 8 | { 9 | "id": 2, 10 | "content": "velit consectetur porttitor sed ac lacus aenean", 11 | "isDone": true 12 | }, 13 | { 14 | "id": 3, 15 | "content": "mattis donec massa elementum tincidunt odio pharetra", 16 | "isDone": true 17 | }, 18 | { 19 | "id": 4, 20 | "content": "risus dolor lectus libero libero id lacus", 21 | "isDone": true 22 | }, 23 | { 24 | "id": 5, 25 | "content": "risus sagittis malesuada orci risus morbi massa", 26 | "isDone": true 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /generators/app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /generators/app/templates/eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "arrow-body-style": 0, 6 | "prefer-arrow-callback": 0, 7 | "func-names": 0, 8 | "react/jsx-no-bind": 0, 9 | "react/jsx-uses-react": 1, 10 | "react/prefer-stateless-function": 0, 11 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 12 | "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/test/**/*.js", "**/containers/root.js"]}] 13 | }, 14 | "env": { 15 | "browser": true, 16 | "node": true, 17 | "mocha": true, 18 | "es6": true, 19 | "commonjs": true 20 | }, 21 | "parserOptions": { 22 | "ecmaVersion": 6, 23 | "ecmaFeatures": { 24 | "experimentalObjectRestSpread": true 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /generators/app/templates/gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | dist 30 | -------------------------------------------------------------------------------- /generators/app/templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const webpack = require('webpack-stream'); 3 | const del = require('del'); 4 | const config = require('./webpack.config'); 5 | const stylelint = require('gulp-stylelint'); 6 | const eslint = require('gulp-eslint'); 7 | 8 | const buildDir = './dist'; 9 | 10 | gulp.task('clean', () => del([ 11 | `${buildDir}/**/*`, 12 | ])); 13 | 14 | gulp.task('webpack', ['clean', 'lint'], () => { 15 | return gulp.src('./src/index.js') 16 | .pipe(webpack(config)) 17 | .pipe(gulp.dest(buildDir)); 18 | }); 19 | 20 | gulp.task('assets', ['webpack'], () => { 21 | return gulp.src(['./src/assets/**/*'], { base: 'src' }) 22 | .pipe(gulp.dest(buildDir)); 23 | }); 24 | 25 | gulp.task('eslint', () => { 26 | return gulp.src('src/**/*.js') 27 | .pipe(eslint()) 28 | .pipe(eslint.format()) 29 | .pipe(eslint.failAfterError()); 30 | }); 31 | 32 | gulp.task('stylelint', () => { 33 | return gulp.src('src/**/*.css') 34 | .pipe(stylelint({ 35 | failAfterError: true, 36 | reporters: [ 37 | { 38 | formatter: 'string', 39 | console: true, 40 | }, 41 | ], 42 | })); 43 | }); 44 | 45 | gulp.task('lint', ['stylelint', 'eslint']); 46 | gulp.task('build', ['clean', 'lint', 'webpack']); 47 | -------------------------------------------------------------------------------- /generators/app/templates/npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .eslintrc 3 | .editorconfig 4 | .stylelintrc 5 | .babelrc 6 | gulpfile.js 7 | webpack.config.js 8 | server.js 9 | -------------------------------------------------------------------------------- /generators/app/templates/server.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const WebpackDevServer = require('webpack-dev-server'); 3 | const api = require('./api'); 4 | 5 | const config = require('./webpack.config'); 6 | 7 | const IP = '0.0.0.0'; 8 | const PORT = parseInt(process.env.PORT || 3000, 10); 9 | const API_PORT = PORT + 1; 10 | 11 | config.entry.app = config.entry.app.concat([ 12 | `webpack-dev-server/client?http://${IP}:${PORT}`, 13 | 'webpack/hot/only-dev-server', 14 | ]); 15 | 16 | config.plugins.push(new webpack.HotModuleReplacementPlugin()); 17 | 18 | new WebpackDevServer(webpack(config), { 19 | contentBase: 'src', 20 | publicPath: config.output.publicPath, 21 | hot: true, 22 | historyApiFallback: true, 23 | headers: { 24 | 'Access-Control-Allow-Origin': '*', 25 | }, 26 | proxy: { 27 | '/': { 28 | secure: false, 29 | target: `http://localhost:${API_PORT}/`, 30 | }, 31 | }, 32 | }).listen(PORT, IP, (err) => { 33 | /* eslint no-console: ["error", { allow: ["info", "error"] }] */ 34 | if (err) { 35 | console.error(err); 36 | process.exit(1); 37 | } 38 | api('/api', API_PORT); 39 | console.info(`Listening at ${IP}:${PORT}`); 40 | }); 41 | -------------------------------------------------------------------------------- /generators/app/templates/src/actions/creator.js: -------------------------------------------------------------------------------- 1 | export const createRequestTypes = (resource, operations) => { 2 | const types = {}; 3 | for (let i = 0; i < operations.length; i += 1) { 4 | const operation = operations[i]; 5 | types[operation] = { 6 | REQUEST: `${resource}_${operation}_REQUEST`, 7 | SUCCESS: `${resource}_${operation}_SUCCESS`, 8 | FAILURE: `${resource}_${operation}_FAILURE`, 9 | }; 10 | } 11 | return types; 12 | }; 13 | 14 | export const createTypes = (resource, operations) => { 15 | const types = {}; 16 | for (let i = 0; i < operations.length; i += 1) { 17 | const operation = operations[i]; 18 | types[operation] = `${resource}_${operation}`; 19 | } 20 | return types; 21 | }; 22 | 23 | export default null; 24 | -------------------------------------------------------------------------------- /generators/app/templates/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import { createTypes } from './creator'; 2 | 3 | export const action = (type, payload, meta) => ({ 4 | type, 5 | payload, 6 | meta, 7 | error: payload instanceof Error, 8 | }); 9 | 10 | export const ERROR = createTypes('ERROR', ['CATCH']); 11 | -------------------------------------------------------------------------------- /generators/app/templates/src/actions/task.js: -------------------------------------------------------------------------------- 1 | import { createRequestTypes } from './creator'; 2 | 3 | export default createRequestTypes('TASK', ['GET_LIST', 'UPDATE', 'ADD', 'REMOVE']); 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/apis/index.js: -------------------------------------------------------------------------------- 1 | import restful, { fetchBackend } from 'restful.js'; 2 | import fetch from 'isomorphic-fetch'; 3 | 4 | import config from '../config'; 5 | 6 | const url = config.API; 7 | 8 | const api = restful(url, fetchBackend(fetch)); 9 | 10 | export default api; 11 | -------------------------------------------------------------------------------- /generators/app/templates/src/apis/task.js: -------------------------------------------------------------------------------- 1 | import api from '../apis'; 2 | 3 | export const getList = () => { 4 | return api 5 | .all('tasks') 6 | .getAll() 7 | .then((res) => { 8 | return res.body(); 9 | }) 10 | .then((entities) => { 11 | return entities.map((entity) => { 12 | return entity.data(); 13 | }); 14 | }); 15 | }; 16 | 17 | export const update = (task) => { 18 | return api 19 | .one('tasks', task.id) 20 | .put(task) 21 | .then(res => res.body().data()); 22 | }; 23 | 24 | export const add = (task) => { 25 | return api 26 | .all('tasks') 27 | .post(task) 28 | .then((res) => { 29 | return res.body().data(); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /generators/app/templates/src/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jackong/generator-reactapp/f735861139c8c7a9d765d8ce0df73fbefcb15af4/generators/app/templates/src/assets/images/icon.png -------------------------------------------------------------------------------- /generators/app/templates/src/components/hello.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import css from 'react-css-modules'; 3 | 4 | import styles from '../styles/hello.css'; 5 | 6 | @css(styles) 7 | class Hello extends React.PureComponent { 8 | static propTypes = { 9 | children: PropTypes.node, 10 | } 11 | render() { 12 | const { children } = this.props; 13 | return ( 14 |
15 |
16 | Hello, {children} 17 |
18 | ); 19 | } 20 | } 21 | 22 | export default Hello; 23 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/tasks.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | <% if (sm === 'mobx') { %> 3 | import { observer, PropTypes } from 'mobx-react'; 4 | 5 | @observer<% } else { %> 6 | import { List } from 'immutable'; 7 | <% } %> 8 | export default class Tasks extends React.PureComponent { 9 | static propTypes = {<% if (sm === 'redux') { %> 10 | tasks: React.PropTypes.instanceOf(List).isRequired,<% } else { %> 11 | tasks: PropTypes.observableArrayOf(React.PropTypes.shape({ 12 | id: React.PropTypes.number, 13 | content: React.PropTypes.string, 14 | isDone: React.PropTypes.bool, 15 | })).isRequired,<% } %> 16 | onToggle: React.PropTypes.func, 17 | } 18 | render() { 19 | const { tasks, onToggle } = this.props; 20 | return ( 21 | 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /generators/app/templates/src/config.js: -------------------------------------------------------------------------------- 1 | import qs from 'qs'; 2 | import { DEV, TEST, PROD } from './constants/env'; 3 | 4 | const config = { 5 | [DEV]: { 6 | API: '/api', 7 | }, 8 | [TEST]: { 9 | API: 'http://localhost/api', 10 | }, 11 | [PROD]: { 12 | API: '/api', 13 | }, 14 | }; 15 | 16 | const hash = qs.parse(window.location.hash.substr(window.location.hash.indexOf('?') + 1)); 17 | 18 | export default config[hash.env || PROD]; 19 | -------------------------------------------------------------------------------- /generators/app/templates/src/constants/code.js: -------------------------------------------------------------------------------- 1 | export const SUCCESS = 0; 2 | export const FAILURE = 1; 3 | -------------------------------------------------------------------------------- /generators/app/templates/src/constants/env.js: -------------------------------------------------------------------------------- 1 | export const DEV = 'DEV'; 2 | export const TEST = 'TEST'; 3 | export const PROD = 'PROD'; 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/containers/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class About extends React.PureComponent { 4 | render() { 5 | return ( 6 |
7 | about 8 |
9 | ); 10 | } 11 | } 12 | 13 | export default About; 14 | -------------------------------------------------------------------------------- /generators/app/templates/src/containers/app.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | class App extends React.PureComponent { 5 | static propTypes = { 6 | children: PropTypes.node, 7 | } 8 | render() { 9 | return ( 10 |
11 |

App

12 |
13 |

14 | home 15 |

16 |

17 | about 18 |

19 |
20 | {this.props.children} 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /generators/app/templates/src/containers/devtools.js: -------------------------------------------------------------------------------- 1 | <% if (sm === 'mobx') { %>export default from 'mobx-react-devtools'; 2 | <% } else { %>import React from 'react'; 3 | import { createDevTools } from 'redux-devtools'; 4 | import LogMonitor from 'redux-devtools-log-monitor'; 5 | import DockMonitor from 'redux-devtools-dock-monitor'; 6 | 7 | export default createDevTools( 8 | 9 | 10 | 11 | );<% } %> 12 | -------------------------------------------------------------------------------- /generators/app/templates/src/containers/home.css: -------------------------------------------------------------------------------- 1 | .hello { 2 | composes: hello from '../styles/hello.css'; 3 | border: 1px solid gray; 4 | } 5 | 6 | .icon { 7 | composes: icon from '../styles/hello.css'; 8 | width: 125px; 9 | height: @width; 10 | } 11 | -------------------------------------------------------------------------------- /generators/app/templates/src/containers/home.js: -------------------------------------------------------------------------------- 1 | <% if (sm === 'redux') { %> 2 | import React, { PropTypes } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { List } from 'immutable'; 5 | 6 | import { action } from '../actions'; 7 | import TASK from '../actions/task'; 8 | import selector from '../selectors/task';<% } else { %> 9 | import React from 'react'; 10 | import { observer, PropTypes } from 'mobx-react';<% } %> 11 | 12 | import Hello from '../components/hello'; 13 | import Tasks from '../components/tasks'; 14 | import styles from './home.css'; 15 | <% if (sm === 'redux') { %> 16 | @connect((state, props) => ({ 17 | tasks: selector(state, props), 18 | })) 19 | export default class Home extends React.PureComponent { 20 | static propTypes = { 21 | dispatch: PropTypes.func.isRequired, 22 | tasks: PropTypes.instanceOf(List).isRequired, 23 | } 24 | componentWillMount() { 25 | this.props.dispatch(action(TASK.GET_LIST.REQUEST)); 26 | } 27 | onToggle = (task) => { 28 | this.props.dispatch(action(TASK.UPDATE.REQUEST, task.set('isDone', !task.isDone))); 29 | } 30 | onAdd = () => { 31 | this.props.dispatch(action(TASK.ADD.REQUEST, { content: this.task.value })); 32 | } 33 | render() { 34 | return ( 35 |
36 | 37 | reactapp 38 | 39 | { this.task = ref; }} type="text" placeholder="Enter task" /> 40 | 41 | 42 |
43 | ); 44 | } 45 | } 46 | <% } else { %> 47 | @observer(['task']) 48 | export default class Home extends React.PureComponent { 49 | static propTypes = { 50 | task: PropTypes.observableObject.isRequired, 51 | } 52 | componentWillMount() { 53 | this.props.task.getList(); 54 | } 55 | onAdd = () => { 56 | this.props.task.add({ isDone: false, content: this.task.value }); 57 | this.task.value = ''; 58 | } 59 | onToggle = (task) => { 60 | this.props.task.update({ ...task, isDone: !task.isDone }); 61 | } 62 | render() { 63 | return ( 64 |
65 | 66 | reactapp 67 | 68 | { this.task = ref; }} type="text" placeholder="Enter task" /> 69 | 70 | 71 |
72 | ); 73 | } 74 | } 75 | <% } %> 76 | -------------------------------------------------------------------------------- /generators/app/templates/src/containers/root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, hashHistory } from 'react-router';<% if (sm === 'redux') { %> 3 | import { Provider } from 'react-redux'; 4 | import { syncHistoryWithStore } from 'react-router-redux'; 5 | 6 | import store from '../store'; 7 | import routes from './routes'; 8 | 9 | const stores = { store }; 10 | const history = syncHistoryWithStore(hashHistory, store);<% } else { %> 11 | import { Provider } from 'mobx-react'; 12 | 13 | import * as stores from '../stores'; 14 | import routes from './routes'; 15 | 16 | const history = hashHistory;<% } %> 17 | 18 | const DevTools = global.DEBUG ? require('./devtools').default : () => null; 19 | 20 | class Root extends React.PureComponent { 21 | render() { 22 | return ( 23 | 24 |
25 | 26 | 27 |
28 |
29 | ); 30 | } 31 | } 32 | 33 | export default Root; 34 | -------------------------------------------------------------------------------- /generators/app/templates/src/containers/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | import App from './app'; 5 | 6 | export default ( 7 | 8 | { 10 | require.ensure([], function (require) { 11 | cb(null, require('./home').default); 12 | }); 13 | }} 14 | /> 15 | { 18 | require.ensure([], function (require) { 19 | cb(null, require('./about').default); 20 | }); 21 | }} 22 | /> 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /generators/app/templates/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | 4 | import './libs'; 5 | import Root from './containers/root'; 6 | 7 | window.handleError = (e) => { 8 | /* eslint no-console: ["error", { allow: ["error"] }] */ 9 | console.error(e, e.stack); 10 | }; 11 | 12 | window.onunhandledrejection = ({ reason }) => { 13 | window.handleError(reason instanceof Error ? reason : new Error(reason)); 14 | }; 15 | 16 | window.onerror = (msg, url, line, column, e) => { 17 | window.handleError(e || new Error(msg, url, line)); 18 | }; 19 | 20 | if (module.hot) { 21 | module.hot.accept(); 22 | } 23 | 24 | try { 25 | render(, document.getElementById('root')); 26 | } catch (e) { 27 | window.handleError(e); 28 | } 29 | -------------------------------------------------------------------------------- /generators/app/templates/src/libs.js: -------------------------------------------------------------------------------- 1 | import 'react'; 2 | import 'react-dom'; 3 | import 'react-router'; 4 | import 'history'; 5 | import 'qs'; 6 | import 'isomorphic-fetch'; 7 | import 'whatwg-fetch'; 8 | import 'restful.js'; 9 | import 'store'; 10 | import 'react-css-modules';<% if (sm === 'mobx') { %> 11 | import 'es6-promise/auto'; 12 | import 'mobx'; 13 | import 'mobx-react';<% } else { %> 14 | import 'babel-polyfill'; 15 | import 'react-redux'; 16 | import 'redux'; 17 | import 'redux-saga'; 18 | import 'react-router-redux'; 19 | import 'reselect'; 20 | import 'normalizr'; 21 | import 'immutable';<% } %> 22 | -------------------------------------------------------------------------------- /generators/app/templates/src/reducers/entities.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { entities as tasks } from './task'; 4 | 5 | export default combineReducers({ 6 | tasks, 7 | }); 8 | -------------------------------------------------------------------------------- /generators/app/templates/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import entities from './entities'; 5 | import result from './result'; 6 | 7 | export default combineReducers({ 8 | routing: routerReducer, 9 | entities, 10 | result, 11 | }); 12 | -------------------------------------------------------------------------------- /generators/app/templates/src/reducers/reducer.js: -------------------------------------------------------------------------------- 1 | export default (handlers, initialState) => { 2 | return (state = initialState, action) => { 3 | if ({}.hasOwnProperty.call(handlers, action.type)) { 4 | return handlers[action.type](state, action); 5 | } 6 | return state; 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /generators/app/templates/src/reducers/result.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { result as tasks } from './task'; 4 | 5 | export default combineReducers({ 6 | tasks, 7 | }); 8 | -------------------------------------------------------------------------------- /generators/app/templates/src/reducers/task.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import reducer from './reducer'; 4 | import TASK from '../actions/task'; 5 | 6 | export const init = fromJS({ 7 | entities: {}, 8 | result: [], 9 | }); 10 | 11 | export const entities = reducer({ 12 | [TASK.GET_LIST.SUCCESS]: (state, { payload }) => state.merge(payload.entities.tasks), 13 | [TASK.ADD.SUCCESS]: (state, { payload }) => state.merge(payload.entities.tasks), 14 | [TASK.UPDATE.SUCCESS]: (state, { payload }) => state.merge(payload.entities.tasks), 15 | }, init.get('entities')); 16 | 17 | export const result = reducer({ 18 | [TASK.GET_LIST.SUCCESS]: (state, { payload }) => state.merge(payload.result.tasks), 19 | [TASK.ADD.SUCCESS]: (state, { payload }) => state.push(payload.result.task), 20 | }, init.get('result')); 21 | -------------------------------------------------------------------------------- /generators/app/templates/src/sagas/api.js: -------------------------------------------------------------------------------- 1 | import { normalize } from 'normalizr'; 2 | import { takeEvery, takeLatest } from 'redux-saga'; 3 | import { call, put } from 'redux-saga/effects'; 4 | 5 | import { action, ERROR } from '../actions'; 6 | import { SUCCESS } from '../constants/code'; 7 | 8 | export function* modelize(data, schemas, models) { 9 | const { result, entities } = normalize(data, schemas); 10 | const modelized = {}; 11 | Object.keys(result).forEach((key) => { 12 | const ids = result[key]; 13 | const modelizedByKey = {}; 14 | if (ids instanceof Array) { 15 | const entitiesByKey = entities[key]; 16 | const Model = models[key]; 17 | ids.forEach((id) => { 18 | modelizedByKey[id] = new Model(entitiesByKey[id]); 19 | }); 20 | modelized[key] = modelizedByKey; 21 | } else { 22 | const Model = models[key]; 23 | const schemaKey = schemas[key].getKey(); 24 | const entitiesByKey = entities[schemaKey]; 25 | modelizedByKey[ids] = new Model(entitiesByKey[ids]); 26 | modelized[schemaKey] = modelizedByKey; 27 | } 28 | }); 29 | yield; 30 | return { result, entities: modelized }; 31 | } 32 | 33 | export function* callAPI(api, { payload, meta: { types, schemas, models } }) { 34 | try { 35 | const res = yield call(api, payload); 36 | if (res.code && res.code !== SUCCESS) { 37 | yield put(action(types.FAILURE, res, payload)); 38 | return; 39 | } 40 | const keys = Object.keys(schemas); 41 | let data = res; 42 | if (keys.length === 1) { 43 | data = { 44 | [keys[0]]: res, 45 | }; 46 | } 47 | const modelized = yield call(modelize, data, schemas, models); 48 | yield put(action(types.SUCCESS, modelized)); 49 | } catch (error) { 50 | yield put(action(ERROR.CATCH, error)); 51 | } 52 | } 53 | 54 | export function* callEvery(types, api, schemas, models) { 55 | yield takeEvery(types.REQUEST, function* ({ payload }) { 56 | yield callAPI(api, { payload, meta: { types, schemas, models } }); 57 | }); 58 | } 59 | 60 | export function* callLatest(types, api, schemas, models) { 61 | yield takeLatest(types.REQUEST, function* ({ payload }) { 62 | yield callAPI(api, { payload, meta: { types, schemas, models } }); 63 | }); 64 | } 65 | 66 | 67 | export default null; 68 | -------------------------------------------------------------------------------- /generators/app/templates/src/sagas/index.js: -------------------------------------------------------------------------------- 1 | import taskFlow from './task'; 2 | 3 | export default function* sagas() { 4 | yield [ 5 | taskFlow(), 6 | ]; 7 | } 8 | -------------------------------------------------------------------------------- /generators/app/templates/src/sagas/task.js: -------------------------------------------------------------------------------- 1 | import { fork } from 'redux-saga/effects'; 2 | 3 | import TASK from '../actions/task'; 4 | import { add, getList, update } from '../apis/task'; 5 | import { Task, tasks, task } from '../schemas/task'; 6 | import { callEvery } from './api'; 7 | 8 | export default function* flow() { 9 | yield fork(callEvery, TASK.GET_LIST, getList, { tasks }, { tasks: Task }); 10 | yield fork(callEvery, TASK.ADD, add, { task }, { task: Task }); 11 | yield fork(callEvery, TASK.UPDATE, update, { task }, { task: Task }); 12 | } 13 | -------------------------------------------------------------------------------- /generators/app/templates/src/schemas/task.js: -------------------------------------------------------------------------------- 1 | import { Schema, arrayOf } from 'normalizr'; 2 | import { Record } from 'immutable'; 3 | 4 | export const Task = new Record({ 5 | id: undefined, 6 | content: '', 7 | isDone: false, 8 | }); 9 | 10 | export const task = new Schema('tasks'); 11 | 12 | export const tasks = arrayOf(task); 13 | -------------------------------------------------------------------------------- /generators/app/templates/src/selectors/task.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export default createSelector( 4 | state => state.entities.tasks, 5 | state => state.result.tasks, 6 | (tasks, ids) => ids 7 | .map(id => tasks.get(`${id}`)) 8 | .sortBy(task => task.isDone), 9 | ); 10 | -------------------------------------------------------------------------------- /generators/app/templates/src/store/devtools.js: -------------------------------------------------------------------------------- 1 | import { persistState } from 'redux-devtools'; 2 | 3 | import DevTools from '../containers/devtools'; 4 | 5 | export default [ 6 | DevTools.instrument(), 7 | persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)), 8 | ]; 9 | -------------------------------------------------------------------------------- /generators/app/templates/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { compose, createStore, applyMiddleware } from 'redux'; 2 | import { hashHistory } from 'react-router'; 3 | import { routerMiddleware } from 'react-router-redux'; 4 | import createSagaMiddleware from 'redux-saga'; 5 | 6 | import reducers from '../reducers'; 7 | import sagas from '../sagas'; 8 | 9 | const sagaMiddleware = createSagaMiddleware(); 10 | 11 | const crashReporter = () => (next) => { 12 | return (action) => { 13 | try { 14 | return next(action); 15 | } catch (err) { 16 | window.handleError(err); 17 | } 18 | return null; 19 | }; 20 | }; 21 | 22 | let middlewares = [crashReporter, sagaMiddleware, routerMiddleware(hashHistory)]; 23 | 24 | const tools = global.DEBUG ? require('./devtools').default : []; 25 | middlewares = middlewares.concat(global.DEBUG ? require('redux-logger')() : []); 26 | 27 | export default createStore( 28 | reducers, 29 | compose( 30 | applyMiddleware(...middlewares), 31 | ...tools 32 | ), 33 | ); 34 | 35 | sagaMiddleware.run(sagas); 36 | -------------------------------------------------------------------------------- /generators/app/templates/src/stores/index.js: -------------------------------------------------------------------------------- 1 | import Task from './task'; 2 | 3 | export const task = new Task(); 4 | 5 | export default null; 6 | -------------------------------------------------------------------------------- /generators/app/templates/src/stores/task.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | 3 | import { getList, add, update } from '../apis/task'; 4 | 5 | export default class Task { 6 | @observable tasks = []; 7 | 8 | @action getList = () => { 9 | getList() 10 | .then((entities) => { 11 | this.tasks = entities; 12 | }); 13 | } 14 | 15 | @action add = (task) => { 16 | add(task) 17 | .then((entity) => { 18 | this.tasks.push(entity); 19 | }); 20 | } 21 | 22 | @action update = (task) => { 23 | update(task) 24 | .then(() => { 25 | for (let i = 0, l = this.tasks.length; i < l; i += 1) { 26 | if (this.tasks[i].id === task.id) { 27 | this.tasks[i].isDone = task.isDone; 28 | break; 29 | } 30 | } 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /generators/app/templates/src/styles/hello.css: -------------------------------------------------------------------------------- 1 | .hello { 2 | color: gray; 3 | font-size: 24px; 4 | } 5 | 6 | .icon { 7 | background-image: url(../assets/images/icon.png); 8 | background-repeat: no-repeat; 9 | background-position: center; 10 | background-size: cover; 11 | width: 100px; 12 | height: @width; 13 | } 14 | -------------------------------------------------------------------------------- /generators/app/templates/src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%%= htmlWebpackPlugin.options.title %> 8 | 9 | 10 | 11 |
12 | loading... 13 |
14 | <%% for (var i=0; i< htmlWebpackPlugin.options.cdns.length; i++) { %> 15 | 16 | <%% } %> 17 | 18 | 19 | -------------------------------------------------------------------------------- /generators/app/templates/stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "property-no-unknown": [ true, { 5 | "ignoreProperties": ["composes"] 6 | }] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /generators/app/templates/test/components/tasks.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import sinon from 'sinon'; 4 | import { expect } from 'chai'; 5 | 6 | import Tasks from '../../src/components/tasks'; 7 | 8 | const { describe, it } = global; 9 | 10 | describe('components', () => { 11 | describe('', () => { 12 | it('should render tasks', () => { 13 | const toggle = sinon.spy(); 14 | const tasks = [{ 15 | id: 123, 16 | content: 'test', 17 | isDone: false, 18 | }, { 19 | id: 321, 20 | content: 'test2', 21 | isDone: true, 22 | }]; 23 | const wrapper = shallow( 24 | 25 | ); 26 | expect(wrapper.find('ul').children()).to.have.lengthOf(tasks.length); 27 | }); 28 | 29 | it('should call toggle', () => { 30 | const toggle = sinon.spy(); 31 | const tasks = [{ 32 | id: 123, 33 | content: 'test', 34 | isDone: false, 35 | }]; 36 | const wrapper = shallow( 37 | 38 | ); 39 | wrapper.find('input').simulate('change'); 40 | expect(toggle.calledOnce).to.equal(true); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /generators/app/templates/test/sagas/api.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { call, put } from 'redux-saga/effects'; 3 | import sinon from 'sinon'; 4 | const { describe, it } = global; 5 | 6 | import { callAPI, modelize } from '../../src/sagas/api'; 7 | import { action, ERROR } from '../../src/actions'; 8 | import { FAILURE } from '../../src/constants/code'; 9 | 10 | describe('sagas', () => { 11 | describe('callAPI', () => { 12 | describe('with error request', () => { 13 | it('should be caught', () => { 14 | const payload = null; 15 | const api = sinon.spy(); 16 | const error = new Error('oops'); 17 | const gen = callAPI(api, { payload, meta: {} }); 18 | expect(gen.next().value).to.be.eql( 19 | call(api, payload) 20 | ); 21 | 22 | expect(gen.next(error).value).to.be.eql( 23 | put(action(ERROR.CATCH, error)) 24 | ); 25 | 26 | expect(gen.next()).to.be.eql({ 27 | done: true, 28 | value: undefined, 29 | }); 30 | }); 31 | }); 32 | 33 | describe('with failure code', () => { 34 | it('should be fail', () => { 35 | const payload = null; 36 | const api = sinon.spy(); 37 | const types = { 38 | FAILURE: 'FAILURE', 39 | }; 40 | const res = { code: FAILURE }; 41 | const gen = callAPI(api, { payload, meta: { types } }); 42 | expect(gen.next().value).to.be.eql( 43 | call(api, payload) 44 | ); 45 | 46 | expect(gen.next(res).value).to.be.eql( 47 | put(action(types.FAILURE, res, payload)) 48 | ); 49 | 50 | expect(gen.next()).to.be.eql({ 51 | done: true, 52 | value: undefined, 53 | }); 54 | }); 55 | }); 56 | 57 | describe('with success code', () => { 58 | it('should be success', () => { 59 | const payload = null; 60 | const api = sinon.spy(); 61 | const types = { 62 | SUCCESS: 'SUCCESS', 63 | }; 64 | const schemas = {}; 65 | const models = {}; 66 | const res = {}; 67 | const gen = callAPI(api, { payload, meta: { types, schemas, models } }); 68 | const modelized = {}; 69 | expect(gen.next().value).to.be.eql( 70 | call(api, payload) 71 | ); 72 | 73 | expect(gen.next(res).value).to.be.eql( 74 | call(modelize, res, schemas, models) 75 | ); 76 | 77 | expect(gen.next(modelized).value).to.be.eql( 78 | put(action(types.SUCCESS, modelized)) 79 | ); 80 | 81 | expect(gen.next()).to.be.eql({ 82 | done: true, 83 | value: undefined, 84 | }); 85 | }); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /generators/app/templates/test/setup.js: -------------------------------------------------------------------------------- 1 | import { jsdom } from 'jsdom';<% if (sm === 'redux') { %> 2 | import 'babel-polyfill';<% } %> 3 | 4 | global.document = jsdom(); 5 | global.window = document.defaultView; 6 | global.navigator = global.window.navigator; 7 | global.window.location.hash = '?env=TEST'; 8 | -------------------------------------------------------------------------------- /generators/app/templates/test/stores/task.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import nock from 'nock'; 3 | import { observe } from 'mobx'; 4 | 5 | import Task from '../../src/stores/task'; 6 | 7 | const { beforeEach, describe, it } = global; 8 | 9 | describe('stores', () => { 10 | describe('task', () => { 11 | describe('add new task', () => { 12 | beforeEach(() => { 13 | nock(/.*/) 14 | .post('/api/tasks') 15 | .reply(200, { 16 | id: 123, 17 | content: 'test', 18 | isDone: false, 19 | }); 20 | }); 21 | 22 | it('should update tasks', () => { 23 | const store = new Task(); 24 | observe(store.tasks, () => { 25 | expect(store.tasks).to.have.lengthOf(0); 26 | }); 27 | store.add({ content: 'test' }); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /generators/app/templates/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const autoprefixer = require('autoprefixer'); 4 | const precss = require('precss'); 5 | const mqpacker = require('css-mqpacker'); 6 | const cssnano = require('cssnano'); 7 | 8 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 9 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 10 | 11 | const DEBUG = (process.env.NODE_ENV !== 'production'); 12 | 13 | const plugins = [ 14 | new HtmlWebpackPlugin({ 15 | template: 'src/templates/index.html', 16 | title: '<%= appname %>', 17 | inject: 'body', 18 | chunks: ['common', 'vendor', 'app'], 19 | filename: './index.html', 20 | cdns: DEBUG ? [] : [ 21 | '//cdn.bootcss.com/react/15.3.1/react.min.js', 22 | '//cdn.bootcss.com/react/15.3.1/react-dom.min.js', 23 | '//cdn.bootcss.com/react-router/2.7.0/ReactRouter.min.js', 24 | '//cdn.bootcss.com/history/3.0.0/History.min.js',<% if (sm === 'mobx') { %> 25 | '//cdn.bootcss.com/mobx/2.6.0/mobx.umd.min.js', 26 | '//cdn.bootcss.com/es6-promise/4.0.5/es6-promise.auto.min.js',<% } else { %> 27 | '//cdn.bootcss.com/babel-polyfill/6.16.0/polyfill.min.js', 28 | '//cdn.bootcss.com/redux/3.6.0/redux.min.js', 29 | '//cdn.bootcss.com/react-redux/4.4.5/react-redux.min.js', 30 | '//cdn.bootcss.com/react-router-redux/4.0.6/ReactRouterRedux.min.js', 31 | '//cdn.bootcss.com/immutable/3.8.1/immutable.min.js',<% } %> 32 | '//cdn.bootcss.com/fetch/1.0.0/fetch.min.js', 33 | '//cdn.bootcss.com/qs/6.2.1/qs.min.js', 34 | '//cdn.bootcss.com/store.js/1.3.20/store.min.js', 35 | ], 36 | }), 37 | new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', chunks: ['libs', 'app'] }), 38 | new webpack.optimize.CommonsChunkPlugin({ name: 'common', chunks: ['libs', 'vendor'] }), 39 | new webpack.DefinePlugin({ 40 | 'process.env.NODE_ENV': `'${process.env.NODE_ENV}'`, 41 | 'global.DEBUG': DEBUG, 42 | }), 43 | ]; 44 | 45 | const loaders = [ 46 | { 47 | test: /\.js?$/, 48 | loader: 'babel', 49 | exclude: /(node_modules|bower_components)/, 50 | }, 51 | { 52 | test: /\.(png|woff|woff2|eot|ttf|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 53 | loader: `file?name=[path][name]${DEBUG ? '' : '.[hash]'}.[ext]&context=src`, 54 | }, 55 | ]; 56 | 57 | if (DEBUG) { 58 | loaders.push({ 59 | test: /\.css?$/, 60 | loaders: [ 61 | 'style?sourceMap', 62 | 'css?modules&importLoaders=1&localIdentName=[path][name]-[local]-[hash:base64:5]', 63 | 'postcss?sourceMap', 64 | ], 65 | }); 66 | } else { 67 | plugins.push( 68 | new webpack.optimize.UglifyJsPlugin({ 69 | compress: { 70 | warnings: false, 71 | }, 72 | }) 73 | ); 74 | 75 | plugins.push(new ExtractTextPlugin('css/app.[contenthash].css', { 76 | allChunks: true, 77 | })); 78 | 79 | loaders.push({ 80 | test: /\.css?$/, 81 | loader: ExtractTextPlugin.extract('style', [ 82 | 'css?modules&importLoaders=1&localIdentName=[path][name]-[local]-[hash:base64:5]', 83 | 'postcss', 84 | ], { 85 | publicPath: '../', 86 | }), 87 | }); 88 | } 89 | 90 | module.exports = { 91 | entry: { 92 | app: [ 93 | './src/index.js', 94 | ], 95 | libs: [ 96 | './src/libs.js', 97 | ], 98 | }, 99 | output: { 100 | path: path.join(__dirname, 'src/'), 101 | publicPath: '', 102 | filename: `js/[name]${DEBUG ? '' : '.[chunkhash]'}.js`, 103 | chunkFilename: `js/bundle-[name]${DEBUG ? '' : '.[chunkhash]'}.js`, 104 | }, 105 | plugins, 106 | module: { 107 | loaders, 108 | }, 109 | postcss(wp) { 110 | return [ 111 | precss({ 112 | import: { 113 | addDependencyTo: wp, 114 | }, 115 | }), 116 | autoprefixer, 117 | mqpacker, 118 | cssnano, 119 | ]; 120 | }, 121 | externals: DEBUG ? {} : { 122 | react: 'React', 123 | 'react-dom': 'ReactDOM', 124 | 'react-router': 'ReactRouter', 125 | history: 'History', 126 | qs: 'Qs', 127 | 'whatwg-fetch': 'fetch', 128 | store: 'store',<% if (sm === 'mobx') { %> 129 | mobx: 'mobx', 130 | 'es6-promise/auto': 'ES6Promise',<% } else { %> 131 | 'babel-polyfill': '_babelPolyfill', 132 | redux: 'Redux', 133 | 'react-redux': 'ReactRedux', 134 | 'react-router-redux': 'ReactRouterRedux', 135 | immutable: 'Immutable',<% } %> 136 | }, 137 | devtool: DEBUG && '#source-map', 138 | debug: DEBUG, 139 | }; 140 | -------------------------------------------------------------------------------- /generators/feature/index.js: -------------------------------------------------------------------------------- 1 | const yeoman = require('yeoman-generator'); 2 | const plural = require('plural'); 3 | 4 | module.exports = yeoman.Base.extend({ 5 | initializing() { 6 | this.argument('name', { 7 | type: String, 8 | required: true, 9 | description: 'Your feature name:', 10 | }); 11 | }, 12 | prompting() { 13 | return this.prompt([ 14 | { 15 | type: 'checkbox', 16 | name: 'modules', 17 | message: 'Select modules:', 18 | choices: [ 19 | 'container', 20 | 'component', 21 | 'style', 22 | 'api', 23 | 'store', 24 | 'action', 25 | 'reducer', 26 | 'schema', 27 | 'saga', 28 | 'selector', 29 | ], 30 | }, 31 | ]).then((answers) => { 32 | this.modules = answers.modules; 33 | }); 34 | }, 35 | 36 | writing() { 37 | this.className = this.name.charAt(0).toUpperCase() + this.name.slice(1); 38 | this.plural = plural(this.name); 39 | this.upper = this.name.toUpperCase(); 40 | 41 | this.modules.forEach(module => { 42 | const suffix = module === 'style' ? 'css' : 'js'; 43 | this.fs.copyTpl( 44 | this.templatePath(`${module}.${suffix}`), 45 | this.destinationPath(`src/${plural(module)}/${this.name}.${suffix}`), 46 | this 47 | ); 48 | }); 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /generators/feature/templates/action.js: -------------------------------------------------------------------------------- 1 | import { createRequestTypes } from './creator'; 2 | 3 | export default createRequestTypes('<%= upper %>', ['GET_LIST', 'UPDATE', 'ADD', 'REMOVE']); 4 | -------------------------------------------------------------------------------- /generators/feature/templates/api.js: -------------------------------------------------------------------------------- 1 | import api from '../api'; 2 | 3 | export const get = (id) => { 4 | return api 5 | .one('<%= plural %>', id) 6 | .get() 7 | .then((res) => { 8 | return res.body().data(); 9 | }); 10 | }; 11 | 12 | export const getList = (query = {}) => { 13 | return api 14 | .all('<%= plural %>') 15 | .getAll(query) 16 | .then((res) => { 17 | return res.body(); 18 | }) 19 | .then((entities) => { 20 | return entities.map((entity) => { 21 | return entity.data(); 22 | }); 23 | }); 24 | }; 25 | 26 | export const update = (<%= name %>) => { 27 | return api 28 | .one('<%= plural %>', <%= name %>.id) 29 | .put(<%= name %>) 30 | .then((res) => { 31 | return res.body().data(); 32 | }); 33 | }; 34 | 35 | export const add = (<%= name %>) => { 36 | return api 37 | .all('<%= plural %>') 38 | .post(<%= name %>) 39 | .then((res) => { 40 | return res.body().data(); 41 | }); 42 | }; 43 | 44 | export const remove = (<%= name %>) => { 45 | return api 46 | .one('<%= plural %>', <%= name %>.id) 47 | .delete() 48 | .then((res) => { 49 | return res.body().data(); 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /generators/feature/templates/component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import css from 'react-css-modules'; 3 | 4 | import styles from '../styles/<%= name %>.css'; 5 | 6 | @css(styles) 7 | export default class <%= className %> extends React.PureComponent { 8 | render() { 9 | return ( 10 |
11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /generators/feature/templates/container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class <%= className %> extends React.PureComponent { 4 | componentWillMount() { 5 | } 6 | render() { 7 | return ( 8 |
9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /generators/feature/templates/reducer.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import reducer from './reducer'; 4 | import <%= upper %> from '../actions/<%= name %>'; 5 | 6 | export const init = fromJS({ 7 | entities: {}, 8 | result: [], 9 | }); 10 | 11 | export const entities = reducer({ 12 | [<%= upper %>.GET_LIST.SUCCESS]: (state, { payload }) => state.merge(payload.entities.<%= plural %>), 13 | [<%= upper %>.ADD.SUCCESS]: (state, { payload }) => state.merge(payload.entities.<%= plural %>), 14 | [<%= upper %>.UPDATE.SUCCESS]: (state, { payload }) => state.merge(payload.entities.<%= plural %>), 15 | }, init.get('entities')); 16 | 17 | export const result = reducer({ 18 | [<%= upper %>.GET_LIST.SUCCESS]: (state, { payload }) => state.merge(payload.result.<%= plural %>), 19 | [<%= upper %>.ADD.SUCCESS]: (state, { payload }) => state.push(payload.result.<%= name %>), 20 | [<%= upper %>.REMOVE.SUCCESS]: (state, { payload }) => state.remove(state.indexOf(payload)), 21 | }, init.get('result')); 22 | -------------------------------------------------------------------------------- /generators/feature/templates/saga.js: -------------------------------------------------------------------------------- 1 | import { fork } from 'redux-saga/effects'; 2 | 3 | import <%= upper %> from '../actions/<%= name %>'; 4 | import { add, getList, update, remove } from '../apis/<%= name %>'; 5 | import { <%= className %>, <%= plural %>, <%= name %> } from '../schemas/<%= name %>'; 6 | import { callEvery } from './api'; 7 | 8 | export default function* flow() { 9 | yield fork(callEvery, <%= upper %>.GET_LIST, getList, { <%= plural %> }, { <%= plural %>: <%= className %> }); 10 | yield fork(callEvery, <%= upper %>.ADD, add, { <%= name %> }, { <%= name %>: <%= className %> }); 11 | yield fork(callEvery, <%= upper %>.UPDATE, update, { <%= name %> }, { <%= name %>: <%= className %> }); 12 | yield fork(callEvery, <%= upper %>.REMOVE, remove, { <%= name %> }, { <%= name %>: <%= className %> }); 13 | } 14 | -------------------------------------------------------------------------------- /generators/feature/templates/schema.js: -------------------------------------------------------------------------------- 1 | import { Schema, arrayOf } from 'normalizr'; 2 | import { Record } from 'immutable'; 3 | 4 | export const <%= className %> = new Record({ 5 | id: undefined, 6 | createdAt: null, 7 | updatedAt: null, 8 | creator: null, 9 | }); 10 | 11 | export const <%= name %> = new Schema('<%= plural %>'); 12 | 13 | export const <%= plural %> = arrayOf(<%= name %>); 14 | -------------------------------------------------------------------------------- /generators/feature/templates/selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export default createSelector( 4 | state => state.entities.<%= plural %>, 5 | state => state.result.<%= plural %>, 6 | (<%= plural %>, ids) => ids 7 | .map(id => <%= plural %>.get(`${id}`)), 8 | ); 9 | -------------------------------------------------------------------------------- /generators/feature/templates/store.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | 3 | import { get, getList, add, update, remove } from '../apis/<%= name %>'; 4 | 5 | export default class <%= className %> { 6 | @observable <%= plural %> = []; 7 | 8 | @action get = (id) => { 9 | get(id) 10 | .then((entity) => { 11 | this.<%= plural %> = entity; 12 | }); 13 | } 14 | 15 | @action getList = (payload) => { 16 | getList(payload) 17 | .then((entities) => { 18 | this.<%= plural %> = entities; 19 | }); 20 | } 21 | 22 | @action add = (<%= name %>) => { 23 | add(<%= name %>) 24 | .then((entity) => { 25 | this.<%= plural %>.push(entity); 26 | }); 27 | } 28 | 29 | @action update = (origin, changed) => { 30 | const index = this.<%= plural %>.indexOf(origin); 31 | if (index < 0) { 32 | return; 33 | } 34 | update(changed) 35 | .then(() => { 36 | this.<%= plural %>[index] = { ...origin, ...changed }; 37 | }); 38 | } 39 | 40 | @action remove = (<%= name %>) => { 41 | const index = this.<%= plural %>.indexOf(<%= name %>); 42 | if (index < 0) { 43 | return; 44 | } 45 | remove(<%= name %>) 46 | .then(() => { 47 | this.<%= plural %>.splice(index, 1); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /generators/feature/templates/style.css: -------------------------------------------------------------------------------- 1 | .<%= name %> { 2 | font-size: 1em; 3 | } 4 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var gulp = require('gulp'); 4 | var eslint = require('gulp-eslint'); 5 | var excludeGitignore = require('gulp-exclude-gitignore'); 6 | var mocha = require('gulp-mocha'); 7 | var istanbul = require('gulp-istanbul'); 8 | var nsp = require('gulp-nsp'); 9 | var plumber = require('gulp-plumber'); 10 | var coveralls = require('gulp-coveralls'); 11 | 12 | gulp.task('static', function () { 13 | return gulp.src('**/*.js') 14 | .pipe(excludeGitignore()) 15 | .pipe(eslint()) 16 | .pipe(eslint.format()) 17 | .pipe(eslint.failAfterError()); 18 | }); 19 | 20 | gulp.task('nsp', function (cb) { 21 | nsp({package: path.resolve('package.json')}, cb); 22 | }); 23 | 24 | gulp.task('pre-test', function () { 25 | return gulp.src('generators/**/*.js') 26 | .pipe(excludeGitignore()) 27 | .pipe(istanbul({ 28 | includeUntested: true 29 | })) 30 | .pipe(istanbul.hookRequire()); 31 | }); 32 | 33 | gulp.task('test', ['pre-test'], function (cb) { 34 | var mochaErr; 35 | 36 | gulp.src('test/**/*.js') 37 | .pipe(plumber()) 38 | .pipe(mocha({reporter: 'spec'})) 39 | .on('error', function (err) { 40 | mochaErr = err; 41 | }) 42 | .pipe(istanbul.writeReports()) 43 | .on('end', function () { 44 | cb(mochaErr); 45 | }); 46 | }); 47 | 48 | gulp.task('watch', function () { 49 | gulp.watch(['generators/**/*.js', 'test/**'], ['test']); 50 | }); 51 | 52 | gulp.task('coveralls', ['test'], function () { 53 | if (!process.env.CI) { 54 | return; 55 | } 56 | 57 | return gulp.src(path.join(__dirname, 'coverage/lcov.info')) 58 | .pipe(coveralls()); 59 | }); 60 | 61 | gulp.task('prepublish', ['nsp']); 62 | gulp.task('default', ['static', 'test', 'coveralls']); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-reactapp", 3 | "version": "1.5.3", 4 | "description": "Yeoman generator", 5 | "homepage": "https://github.com/Jackong/generator-reactapp", 6 | "author": { 7 | "name": "Jackong", 8 | "email": "", 9 | "url": "https://github.com/Jackong" 10 | }, 11 | "files": [ 12 | "generators" 13 | ], 14 | "main": "app/index.js", 15 | "keywords": [ 16 | "yeoman-generator", 17 | "react", 18 | "app", 19 | "redux", 20 | "webpack", 21 | "babel", 22 | "radium", 23 | "SPA", 24 | "RESTful", 25 | "yeoman-generator" 26 | ], 27 | "license": "MIT", 28 | "repository": "Jackong/generator-reactapp", 29 | "scripts": { 30 | "test": "gulp", 31 | "prepublish": "gulp prepublish" 32 | }, 33 | "dependencies": { 34 | "chalk": "^1.0.0", 35 | "plural": "^0.2.0", 36 | "yeoman-generator": "^0.23.0", 37 | "yosay": "^1.0.0" 38 | }, 39 | "devDependencies": { 40 | "babel-eslint": "^6.0.4", 41 | "eslint": "^3.1.1", 42 | "eslint-config-airbnb": "^9.0.1", 43 | "eslint-plugin-babel": "^3.2.0", 44 | "eslint-plugin-import": "^1.8.1", 45 | "eslint-plugin-jsx-a11y": "^1.2.2", 46 | "eslint-plugin-react": "^5.1.1", 47 | "mocha": "*", 48 | "yeoman-test": "^1.0.0", 49 | "yeoman-assert": "^2.0.0", 50 | "eslint-config-xo-space": "^0.15.0", 51 | "gulp": "^3.9.0", 52 | "gulp-eslint": "^3.0.1", 53 | "gulp-exclude-gitignore": "^1.0.0", 54 | "gulp-line-ending-corrector": "^1.0.1", 55 | "gulp-istanbul": "^1.0.0", 56 | "gulp-mocha": "^3.0.1", 57 | "gulp-plumber": "^1.0.0", 58 | "gulp-nsp": "^2.1.0", 59 | "gulp-coveralls": "^0.1.0" 60 | }, 61 | "eslintConfig": { 62 | "extends": "xo-space", 63 | "env": { 64 | "mocha": true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var assert = require('yeoman-assert'); 4 | var helpers = require('yeoman-test'); 5 | 6 | describe('generator-reactapp:app', function () { 7 | before(function () { 8 | return helpers.run(path.join(__dirname, '../generators/app')) 9 | .withPrompts({someAnswer: true}) 10 | .toPromise(); 11 | }); 12 | 13 | it('creates files', function () { 14 | assert.file([ 15 | 'dummyfile.txt' 16 | ]); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/store.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var assert = require('yeoman-assert'); 4 | var helpers = require('yeoman-test'); 5 | 6 | describe('generator-reactapp:store', function () { 7 | before(function () { 8 | return helpers.run(path.join(__dirname, '../generators/store')) 9 | .withPrompts({someAnswer: true}) 10 | .toPromise(); 11 | }); 12 | 13 | it('creates files', function () { 14 | assert.file([ 15 | 'dummyfile.txt' 16 | ]); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/test-app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var assert = require('yeoman-generator').assert; 5 | var helpers = require('yeoman-generator').test; 6 | var os = require('os'); 7 | 8 | describe('reactapp:app', function() { 9 | before(function(done) { 10 | helpers.run(path.join(__dirname, '../generators/app')) 11 | .withOptions({ 12 | skipInstall: true 13 | }) 14 | .withPrompts({ 15 | someOption: true 16 | }) 17 | .on('end', done); 18 | }); 19 | 20 | it('creates files', function() { 21 | assert.file([ 22 | 'package.json', 23 | '.editorconfig', 24 | '.eslintrc' 25 | ]); 26 | }); 27 | }); 28 | --------------------------------------------------------------------------------