├── .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 | [](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 |
--------------------------------------------------------------------------------