├── .gitattributes
├── .gitignore
├── generators
├── app
│ ├── templates
│ │ ├── config
│ │ │ ├── production.config.js
│ │ │ ├── test.config.js
│ │ │ ├── development.config.js
│ │ │ └── README.md
│ │ ├── env
│ │ ├── features
│ │ │ └── redux
│ │ │ │ ├── redux
│ │ │ │ ├── modules
│ │ │ │ │ └── README.md
│ │ │ │ ├── rootReducer.js
│ │ │ │ └── configureStore.js
│ │ │ │ ├── containers
│ │ │ │ ├── DevTools
│ │ │ │ │ └── DevTools.js
│ │ │ │ └── App
│ │ │ │ │ └── App.js
│ │ │ │ └── app.js
│ │ ├── src
│ │ │ ├── containers
│ │ │ │ └── App
│ │ │ │ │ ├── styles.module.css
│ │ │ │ │ ├── App.spec.js
│ │ │ │ │ └── App.js
│ │ │ ├── styles
│ │ │ │ ├── base.css
│ │ │ │ ├── colors.css
│ │ │ │ └── queries.css
│ │ │ ├── views
│ │ │ │ └── main
│ │ │ │ │ ├── indexPage
│ │ │ │ │ ├── styles.module.css
│ │ │ │ │ └── IndexPage.js
│ │ │ │ │ ├── about
│ │ │ │ │ └── About.js
│ │ │ │ │ ├── styles.module.css
│ │ │ │ │ ├── routes.js
│ │ │ │ │ └── Container.js
│ │ │ ├── routes.js
│ │ │ ├── app.js
│ │ │ ├── app.css
│ │ │ └── components
│ │ │ │ └── Header
│ │ │ │ ├── styles.module.css
│ │ │ │ ├── Header.js
│ │ │ │ └── Header.spec.js
│ │ ├── gitignore
│ │ ├── .babelrc
│ │ ├── tests.webpack.js
│ │ ├── package.json
│ │ ├── karma.conf.js
│ │ └── webpack.config.js
│ └── index.js
└── redux-module
│ ├── templates
│ └── module-template.js
│ └── index.js
├── .travis.yml
├── .editorconfig
├── test
└── app.js
├── LICENSE
├── gulpfile.js
├── package.json
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/generators/app/templates/config/production.config.js:
--------------------------------------------------------------------------------
1 | APP_NAME=<%= title %>
2 |
--------------------------------------------------------------------------------
/generators/app/templates/config/test.config.js:
--------------------------------------------------------------------------------
1 | APP_NAME=<%= title %>.test
2 |
--------------------------------------------------------------------------------
/generators/app/templates/config/development.config.js:
--------------------------------------------------------------------------------
1 | APP_NAME=<%= title %>.dev
2 |
--------------------------------------------------------------------------------
/generators/app/templates/env:
--------------------------------------------------------------------------------
1 | APP_NAME=<%= title %>
2 | ROOT_URL='https://api.<%= title %>.com'
3 |
--------------------------------------------------------------------------------
/generators/app/templates/features/redux/redux/modules/README.md:
--------------------------------------------------------------------------------
1 | ## Store your redux modules here
2 |
--------------------------------------------------------------------------------
/generators/app/templates/src/containers/App/styles.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | }
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - v6
4 | - v5
5 | - v4
6 | - '0.12'
7 | - '0.10'
8 |
--------------------------------------------------------------------------------
/generators/app/templates/gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | config/
3 | npm-debug.log
4 | .env
5 | .awsDetails
6 | dist/
7 |
--------------------------------------------------------------------------------
/generators/app/templates/src/styles/base.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --topbar-height: 80px;
3 | --padding: 25px;
4 | }
5 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/main/indexPage/styles.module.css:
--------------------------------------------------------------------------------
1 | @import url("../../../styles/base.css");
2 |
3 | .content {
4 | padding: var(--padding);
5 | }
6 |
--------------------------------------------------------------------------------
/generators/app/templates/config/README.md:
--------------------------------------------------------------------------------
1 | ## Configuration
2 |
3 | These configuration files will automatically be loaded depending upon the `NODE_ENV` variable. These take precedence over the `/.env` file.
4 |
--------------------------------------------------------------------------------
/generators/app/templates/src/styles/colors.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --dark: #404040;
3 | --light-gray: #a2a2a2;
4 | --white: #ffffff;
5 | --highlight: #48b5e9;
6 | --heading-color: var(--highlight);
7 | }
8 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/main/about/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const About = (props) => (
4 |
5 | The about page
6 |
7 | )
8 |
9 | export default About;
10 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/generators/app/templates/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"],
3 | "env": {
4 | "development": {
5 | "presets": ["react-hmre"]
6 | },
7 | "production": {
8 | "presets": []
9 | },
10 | "test": {
11 | "presets": []
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/generators/app/templates/tests.webpack.js:
--------------------------------------------------------------------------------
1 | require('babel-polyfill');
2 | // some setup first
3 |
4 | var chai = require('chai');
5 | var chaiEnzyme = require('chai-enzyme');
6 |
7 | chai.use(chaiEnzyme());
8 |
9 | var context = require.context('./src', true, /\.spec\.js$/);
10 | context.keys().forEach(context);
11 |
--------------------------------------------------------------------------------
/generators/app/templates/src/styles/queries.css:
--------------------------------------------------------------------------------
1 | @custom-media --screen-phone (width <= 35.5em);
2 | @custom-media --screen-phone-lg (width > 35.5em);
3 |
4 | @custom-media --screen-sm var(--screen-phone) and (width < 48em);
5 | @custom-media --screen-md (width >= 48em) and (width < 64em);
6 | @custom-media --screen-lg (width >= 64em) and (width < 80em);
7 | @custom-media --screen-xl (width >= 80em);
8 |
--------------------------------------------------------------------------------
/generators/app/templates/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Route} from 'react-router'
3 |
4 | import makeMainRoutes from 'views/main/routes';
5 |
6 | export const makeRoutes = (store) => {
7 | const mainRoutes = makeMainRoutes(store);
8 |
9 | return (
10 |
11 | {mainRoutes}
12 |
13 | )
14 | }
15 |
16 | export default makeRoutes
17 |
--------------------------------------------------------------------------------
/generators/app/templates/features/redux/containers/DevTools/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createDevTools } from 'redux-devtools'
3 | import LogMonitor from 'redux-devtools-log-monitor'
4 | import DockMonitor from 'redux-devtools-dock-monitor'
5 |
6 | export default createDevTools(
7 |
11 |
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/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-react-gen: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 |
--------------------------------------------------------------------------------
/generators/app/templates/src/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import 'font-awesome/css/font-awesome.css'
5 | import './app.css'
6 |
7 | import App from 'containers/App/App'
8 |
9 | import {hashHistory} from 'react-router'
10 | import makeRoutes from './routes'
11 |
12 | const routes = makeRoutes()
13 |
14 | const mountNode = document.querySelector('#root');
15 | ReactDOM.render(
16 | ,
18 | mountNode);
19 |
--------------------------------------------------------------------------------
/generators/app/templates/src/app.css:
--------------------------------------------------------------------------------
1 | @import url("styles/colors.css");
2 |
3 | *,
4 | *:after,
5 | *:before {
6 | box-sizing: border-box;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | font-smoothing: antialiased;
10 | text-rendering: optimizeLegibility;
11 |
12 | font-size: 16px;
13 | }
14 |
15 | body {
16 | color: var(--dark);
17 | font-weight: lighter;
18 | font: 400 15px/22px 'Open Sans', 'Helvetica Neue', Sans-serif;
19 | font-smoothing: antialiased;
20 | padding: 0;
21 | margin: 0;
22 | }
23 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/main/styles.module.css:
--------------------------------------------------------------------------------
1 | @import url("../../styles/base.css");
2 | @import url("../../styles/queries.css");
3 |
4 | .wrapper {
5 | overflow-y: scroll;
6 | display: flex;
7 | margin: 0;
8 |
9 | height: 100vh;
10 | -webkit-box-orient: horizontal;
11 | -o-box-orient: horizontal;
12 |
13 | flex-direction: column;
14 |
15 | @media (--screen-phone-lg) {
16 | flex-direction: row;
17 | }
18 |
19 | }
20 |
21 | .content {
22 | position: relative;
23 | top: var(--topbar-height);
24 | left: 0;
25 |
26 | flex: 1;
27 | order: 1;
28 |
29 | @media (--screen-phone-lg) {
30 | flex: 2;
31 | order: 2;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/Header/styles.module.css:
--------------------------------------------------------------------------------
1 | @import url("../../styles/base.css");
2 |
3 | .topbar {
4 | position: fixed;
5 | z-index: 10;
6 | top: 0;
7 | left: 0;
8 |
9 | background: #48b5e9;
10 | width: 100%;
11 | padding: 0 25px;
12 |
13 | height: var(--topbar-height);
14 | line-height: var(--topbar-height);
15 | color: #fff;
16 |
17 | a {
18 | color: #fff;
19 | text-transform: uppercase;
20 | text-decoration: none;
21 | letter-spacing: 1px;
22 |
23 | line-height: 40px;
24 | h1 {
25 | font-size: 28px;
26 | }
27 | }
28 | section {
29 | position: absolute;
30 | top: 0px;
31 | right: 25px;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/generators/app/templates/features/redux/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerReducer as routing, push } from 'react-router-redux';
3 |
4 | // Require your modules here
5 | const modules = {
6 | }
7 | export let actions = {
8 | routing: {
9 | navigateTo: path => dispatch => dispatch(push(path))
10 | }
11 | }
12 |
13 | export let initialState = {}
14 | export let reducers = {routing};
15 |
16 | Object.keys(modules).forEach(key => {
17 | const module = modules[key];
18 | initialState[key] = module.initialState || {};
19 | actions[key] = module.actions;
20 | reducers[key] = module.reducer;
21 | });
22 |
23 | export const rootReducer = combineReducers(reducers);
24 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 | import {Link} from 'react-router'
3 |
4 | import styles from './styles.module.css';
5 |
6 | export class Header extends React.Component {
7 | render() {
8 | const {title} = this.props;
9 |
10 | return (
11 |
12 |
{title}
13 |
16 |
17 | )
18 | }
19 | }
20 |
21 | Header.propTypes = {
22 | title: T.string
23 | }
24 |
25 | Header.defaultProps = {
26 | title: '<%= title %>'
27 | }
28 |
29 | export default Header
30 |
--------------------------------------------------------------------------------
/generators/app/templates/src/containers/App/App.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { expect } from 'chai'
3 | import { shallow } from 'enzyme'
4 |
5 | import App from './App'
6 | import styles from './styles.module.css'
7 |
8 | describe('', () => {
9 | let wrapper;
10 | let history = {};
11 | beforeEach(() => {
12 | wrapper =
13 | shallow()
14 | })
15 |
16 | it('has a Router component', () => {
17 | expect(wrapper.find('Router'))
18 | .to.have.length(1);
19 | });
20 |
21 | it('passes a history prop', () => {
22 | const props = wrapper.find('Router').props();
23 |
24 | expect(props.history)
25 | .to.be.defined;
26 | })
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/generators/app/templates/src/containers/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Router } from 'react-router';
3 |
4 | class App extends React.Component {
5 | static contextTypes = {
6 | router: PropTypes.object
7 | }
8 |
9 | static propTypes = {
10 | history: PropTypes.object.isRequired,
11 | routes: PropTypes.element.isRequired
12 | };
13 |
14 | get content() {
15 | return (
16 |
19 | )
20 | }
21 |
22 | render () {
23 | return (
24 |
25 | {this.content}
26 |
27 | )
28 | }
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/generators/app/templates/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "<%= title %>",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "start": "./node_modules/.bin/cross-env NODE_ENV=development ./node_modules/.bin/hjs-dev-server",
7 | "clean": "rimraf dist",
8 | "build": "npm run clean && ./node_modules/.bin/cross-env NODE_ENV=production webpack",
9 | "publish_pages": "./node_modules/.bin/gh-pages -d dist",
10 | "ghpages": "npm run build && npm run publish_pages",
11 | "test": "./node_modules/.bin/cross-env NODE_ENV=test ./node_modules/karma/bin/karma start karma.conf.js",
12 | "test:watch": "npm run test -- --watch"
13 | },
14 | "author": "<%= authorName %> <<%= authorEmail %>>",
15 | "license": "ISC"
16 | }
17 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/main/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Route, IndexRoute} from 'react-router'
3 |
4 | import Container from './Container'
5 | import IndexPage from './indexPage/IndexPage'
6 |
7 | export const makeMainRoutes = () => {
8 | return (
9 |
10 | {/* Lazy-loading */}
11 | {
12 | require.ensure([], (require) => {
13 | const mod = require('./about/About');
14 | cb(null, mod.default);
15 | });
16 | }} />
17 | {/* inline loading */}
18 |
19 |
20 | )
21 | }
22 |
23 | export default makeMainRoutes
24 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/main/indexPage/IndexPage.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react';
2 | import {Link} from 'react-router';
3 |
4 | import styles from './styles.module.css';
5 |
6 | export class IndexPage extends React.Component {
7 | render() {
8 | return (
9 |
18 | )
19 | }
20 | }
21 |
22 | export default IndexPage;
23 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/Header/Header.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { expect } from 'chai'
3 | import { shallow } from 'enzyme'
4 |
5 | import Header from './Header'
6 | import styles from './styles.module.css'
7 |
8 | describe('', () => {
9 | let wrapper;
10 | beforeEach(() => {
11 | wrapper = shallow()
12 | });
13 |
14 | it('contains a title component with yelp', () => {
15 | expect(wrapper.find('h1').first().text())
16 | .to.equal('<%= title %>')
17 | });
18 |
19 | it('contains topbar styling', () => {
20 | expect(wrapper.find(`.${styles.topbar}`))
21 | .to.have.length(1);
22 | })
23 |
24 | it('contains a section menu with the title', () => {
25 | expect(wrapper.find('section').first().text())
26 | .to.equal('Fullstack.io')
27 | });
28 |
29 | })
30 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/main/Container.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 |
3 | import Header from 'components/Header/Header'
4 | import styles from './styles.module.css'
5 |
6 | export class Container extends React.Component {
7 | renderChildren() {
8 | const childProps = {
9 | ...this.props
10 | };
11 | const {children} = this.props;
12 | return React.Children.map(children,
13 | c => React.cloneElement(c, childProps));
14 | }
15 | render() {
16 | return (
17 |
18 |
19 |
20 | {this.renderChildren()}
21 |
22 |
23 | )
24 | }
25 | }
26 |
27 | Container.contextTypes = {
28 | router: T.object
29 | }
30 |
31 | export default Container
32 |
--------------------------------------------------------------------------------
/generators/app/templates/karma.conf.js:
--------------------------------------------------------------------------------
1 | var argv = require('yargs').argv;
2 |
3 | var webpackConfig = require('./webpack.config');
4 |
5 | module.exports = function (config) {
6 | config.set({
7 | basePath: '',
8 | frameworks: ['mocha', 'chai'],
9 | files: [
10 | 'tests.webpack.js'
11 | ],
12 |
13 | preprocessors: {
14 | // add webpack as preprocessor
15 | 'tests.webpack.js': ['webpack', 'sourcemap']
16 | },
17 |
18 | webpack: webpackConfig,
19 | webpackServer: {
20 | noInfo: true
21 | },
22 |
23 | plugins: [
24 | 'karma-mocha',
25 | 'karma-chai',
26 | 'karma-webpack',
27 | 'karma-phantomjs-launcher',
28 | 'karma-spec-reporter',
29 | 'karma-sourcemap-loader'
30 | ],
31 |
32 | reporters: ['spec'],
33 | port: 9876,
34 | colors: true,
35 | logLevel: config.LOG_INFO,
36 | browsers: ['PhantomJS'],
37 | singleRun: !argv.watch
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/generators/app/templates/features/redux/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import 'font-awesome/css/font-awesome.css'
5 | import './app.css'
6 |
7 | import App from 'containers/App/App'
8 |
9 | import {hashHistory} from 'react-router'
10 | import makeRoutes from './routes'
11 |
12 | const initialState = {}
13 | import {configureStore} from './redux/configureStore'
14 | const {store, actions, history} = configureStore({initialState, historyType: hashHistory});
15 |
16 | let render = (routerKey = null) => {
17 | const makeRoutes = require('./routes').default;
18 | const routes = makeRoutes(store)
19 |
20 | const mountNode = document.querySelector('#root');
21 | ReactDOM.render(
22 | , mountNode);
27 | }
28 |
29 | if (__DEBUG__ && module.hot) {
30 | const renderApp = render;
31 | render = () => renderApp(Math.random())
32 |
33 | module.hot.accept('./routes', () => render());
34 | }
35 |
36 | render();
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Ari Lerner (http://fullstackreact.com)
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/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 |
11 | gulp.task('static', function () {
12 | return gulp.src('**/*.js')
13 | .pipe(excludeGitignore())
14 | .pipe(eslint())
15 | .pipe(eslint.format())
16 | .pipe(eslint.failAfterError());
17 | });
18 |
19 | gulp.task('nsp', function (cb) {
20 | nsp({package: path.resolve('package.json')}, cb);
21 | });
22 |
23 | gulp.task('pre-test', function () {
24 | return gulp.src('generators/**/*.js')
25 | .pipe(excludeGitignore())
26 | .pipe(istanbul({
27 | includeUntested: true
28 | }))
29 | .pipe(istanbul.hookRequire());
30 | });
31 |
32 | gulp.task('test', ['pre-test'], function (cb) {
33 | var mochaErr;
34 |
35 | gulp.src('test/**/*.js')
36 | .pipe(plumber())
37 | .pipe(mocha({reporter: 'spec'}))
38 | .on('error', function (err) {
39 | mochaErr = err;
40 | })
41 | .pipe(istanbul.writeReports())
42 | .on('end', function () {
43 | cb(mochaErr);
44 | });
45 | });
46 |
47 | gulp.task('watch', function () {
48 | gulp.watch(['generators/**/*.js', 'test/**'], ['test']);
49 | });
50 |
51 | gulp.task('prepublish', ['nsp']);
52 | gulp.task('default', ['static', 'test']);
53 |
--------------------------------------------------------------------------------
/generators/app/templates/features/redux/containers/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react';
2 | import { Router } from 'react-router';
3 | import { Provider } from 'react-redux';
4 |
5 | class App extends React.Component {
6 | static contextTypes = {
7 | router: T.object
8 | }
9 |
10 | static propTypes = {
11 | history: T.object.isRequired,
12 | routes: T.element.isRequired,
13 | routerKey: T.number,
14 | actions: T.object
15 | };
16 |
17 | get content() {
18 | const { history, routes, routerKey, store, actions } = this.props;
19 | let newProps = {
20 | actions,
21 | ...this.props
22 | }
23 |
24 | const createElement = (Component, props) => {
25 | return
26 | }
27 |
28 | return (
29 |
30 |
35 |
36 | )
37 | }
38 |
39 | get devTools () {
40 | if (__DEBUG__) {
41 | if (!window.devToolsExtension) {
42 | const DevTools = require('containers/DevTools/DevTools').default
43 | return
44 | }
45 | }
46 | }
47 |
48 | render () {
49 | return (
50 |
51 |
52 | {this.content}
53 | {this.devTools}
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | export default App;
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generator-react-gen",
3 | "version": "1.1.0",
4 | "description": "Generate a react project in the fullstack way",
5 | "homepage": "",
6 | "author": {
7 | "name": "Ari Lerner",
8 | "email": "ari@fullstack.io",
9 | "url": "http://fullstackreact.com"
10 | },
11 | "files": [
12 | "generators/app",
13 | "generators/redux-module"
14 | ],
15 | "main": "generators/index.js",
16 | "keywords": [
17 | "fullstackio",
18 | "react",
19 | "redux",
20 | "react-router",
21 | "hjs-webpack",
22 | "yeoman-generator"
23 | ],
24 | "dependencies": {
25 | "chalk": "^1.1.3",
26 | "escodegen": "^1.8.0",
27 | "esprima": "^2.7.2",
28 | "estraverse": "^4.2.0",
29 | "react": "^15.1.0",
30 | "react-dom": "^15.1.0",
31 | "react-router-redux": "^4.0.5",
32 | "redux": "^3.5.2",
33 | "redux-module-builder": "0.0.7",
34 | "redux-thunk": "^2.1.0",
35 | "yeoman-generator": "^0.23.3",
36 | "yosay": "^1.1.1"
37 | },
38 | "devDependencies": {
39 | "cheerio": "^0.20.0",
40 | "cross-env": "^1.0.8",
41 | "eslint": "^2.12.0",
42 | "eslint-config-xo-space": "^0.13.0",
43 | "gulp": "^3.9.1",
44 | "gulp-eslint": "^2.0.0",
45 | "gulp-exclude-gitignore": "^1.0.0",
46 | "gulp-istanbul": "^1.0.0",
47 | "gulp-line-ending-corrector": "^1.0.1",
48 | "gulp-mocha": "^2.2.0",
49 | "gulp-nsp": "^2.4.1",
50 | "gulp-plumber": "^1.1.0",
51 | "yargs": "^4.7.1",
52 | "yeoman-assert": "^2.2.1",
53 | "yeoman-test": "^1.4.0"
54 | },
55 | "eslintConfig": {
56 | "extends": "xo-space",
57 | "env": {
58 | "mocha": true
59 | }
60 | },
61 | "repository": {
62 | "type": "git",
63 | "url": "https://github.com/fullstackreact/react-gen-generator"
64 | },
65 | "scripts": {
66 | "prepublish": "gulp prepublish",
67 | "test": "gulp"
68 | },
69 | "license": "MIT"
70 | }
71 |
--------------------------------------------------------------------------------
/generators/app/templates/features/redux/redux/configureStore.js:
--------------------------------------------------------------------------------
1 | import { browserHistory } from 'react-router';
2 | import { bindActionCreatorsToStore } from 'redux-module-builder';
3 | import { createApiMiddleware } from 'redux-module-builder/api';
4 | import { routerMiddleware, syncHistoryWithStore } from 'react-router-redux';
5 | import thunkMiddleware from 'redux-thunk';
6 | import { createStore, compose, applyMiddleware } from 'redux';
7 | import { rootReducer, actions, initialState } from './rootReducer';
8 |
9 | export const configureStore = ({
10 | historyType = browserHistory,
11 | userInitialState = {}}) => {
12 |
13 | let middleware = [
14 | createApiMiddleware({
15 | baseUrl: __ROOT_URL__,
16 | headers: {
17 | 'X-Requested-By': '<%= title %> client'
18 | }
19 | }),
20 | thunkMiddleware,
21 | routerMiddleware(historyType)
22 | ]
23 |
24 | let tools = [];
25 | if (__DEBUG__) {
26 | const DevTools = require('containers/DevTools/DevTools').default;
27 | let devTools = window.devToolsExtension ? window.devToolsExtension : DevTools.instrument;
28 | if (typeof devTools === 'function') {
29 | tools.push(devTools())
30 | }
31 | }
32 |
33 | let finalCreateStore;
34 | finalCreateStore = compose(
35 | applyMiddleware(...middleware),
36 | ...tools
37 | )(createStore);
38 |
39 | const store = finalCreateStore(
40 | rootReducer,
41 | Object.assign({}, initialState, userInitialState)
42 | );
43 |
44 | const history = syncHistoryWithStore(historyType, store, {
45 | adjustUrlOnReplay: true
46 | })
47 |
48 | if (module.hot) {
49 | module.hot.accept('./rootReducer', () => {
50 | const {rootReducer} = require('./rootReducer');
51 | store.replaceReducer(rootReducer);
52 | });
53 | }
54 |
55 | const boundActions = bindActionCreatorsToStore(actions, store);
56 | return {store, actions: boundActions, history}
57 | }
58 |
--------------------------------------------------------------------------------
/generators/redux-module/templates/module-template.js:
--------------------------------------------------------------------------------
1 | import {createConstants, createReducer} from 'redux-module-builder'
2 | import {createApiHandler, createApiAction} from 'redux-module-builder/api'
3 |
4 | /*
5 | * Constants
6 | *
7 | * Basic constants can just be a name
8 | * i.e. 'GET_ME'
9 | * and api constants can be an Object
10 | * i.e. { 'GET_UPCOMING': { api: true }}
11 | */
12 | export const types = createConstants('<%= moduleName %>')(
13 | );
14 |
15 | /*
16 | * actions
17 | *
18 | * The actions object can be a simple function
19 | * i.e. getMe: () => (dispatch, getState) => {
20 | * dispatch({type: types.GET_ME})
21 | * }
22 | * or using the `createApiAction()` function, we can create an
23 | * action that calls out to the api helpers of redux-module-builder
24 | * For instance:
25 | * getUpcomingEvents: createApiAction(types.GET_UPCOMING)((client, opts) => {
26 | * const {count} = opts;
27 | * return client.get({
28 | * path: '/events/upcoming',
29 | * params: {count}
30 | * }).then(res => res.events)
31 | * })
32 | */
33 | export const actions = {
34 |
35 | }
36 |
37 | /*
38 | * reducers
39 | *
40 | * The reducers go here where we can either create a simple reducer
41 | * using our types object from above
42 | * i.e. [types.GET_ME]: (state, action) => ({
43 | * ...state,
44 | * me: action.payload
45 | * })
46 | * or it can be a a complex object, such as using the `createApiHandler`
47 | * method provided by `redux-module-builder`
48 | *
49 | * ...createApiHandler(types.GET_UPCOMING, (apiTypes) => {
50 | * // optional argument to handle apiTypes (i.e. loading and error)
51 | * return {
52 | * [apiTypes.LOADING]: (state) => ({...state, loading: true}),
53 | * [apiTypes.ERROR]: (state, {payload}) => {
54 | * return {...state, loading: false, errors: payload};
55 | * }
56 | * })((state, {payload}) => {
57 | * return {
58 | * ...state,
59 | * loading: false,
60 | * errors: null,
61 | * events: payload
62 | * }
63 | * })
64 | *
65 | */
66 | export const reducer = createReducer({
67 |
68 | });
69 |
70 | /*
71 | * The initial state for this part of the component tree
72 | */
73 | export const initialState = {
74 | loading: false,
75 | errors: null,
76 | };
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Generate a starting point for your react app
2 |
3 | The `react-gen` generator is a yeoman generator that creates an app using the same structure detailed in the the [blog post detailing cloning yelp with React](http://fullstackreact.com/articles/react-tutorial-cloning-yelp/).
4 |
5 | Using this generator gives you a sane react structure with the following technologies:
6 |
7 | * Webpack with hot module reloading (hjs-webpack)
8 | * PostCSS and CSS modules, including autoprefixer, precss
9 | * Global CSS loading
10 | * React / ReactDOM
11 | * react-router
12 | * Nested routing with multiple views
13 | * Testing with karma, mocha, chai
14 | * React component testing helper enzyme
15 | * Multiple deployment environments with dotenv configuration
16 | * Babel with react, stage-0, and es2015
17 | * font-awesome
18 | * and more
19 |
20 | ## Installation
21 |
22 | First, install [Yeoman](http://yeoman.io) and generator-react-gen using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
23 |
24 | ```bash
25 | npm install -g yo
26 | npm install -g generator-react-gen
27 | ```
28 |
29 | Then generate your new project, answer some questions about your project, and go!
30 |
31 | ```bash
32 | yo react-gen
33 | ```
34 |
35 | ## Workflow
36 |
37 | Open your app in a text editor and start to work. To run the application, use the `npm start` script. This will boot a server with hot module reloading:
38 |
39 | ```bash
40 | npm run start
41 | ```
42 |
43 | To run the tests in our app, we can use the `npm run test` script. This sets up enzyme, boots the tests with karma, and executes them:
44 |
45 | ```bash
46 | npm run test
47 | ```
48 |
49 | As we're writing tests, sometimes it's just easier to run the tests as we update and edit files. The generator makes this easy using the `npm run test:watch` script. Run this command and then any changes to the files in our project will cause the tests to be run:
50 |
51 | ```bash
52 | npm run test:watch
53 | ```
54 |
55 | To build the app for distribution, we can use the `npm run build` command:
56 |
57 | ```bash
58 | npm run build
59 | ```
60 |
61 | ## Features
62 |
63 | Wanna add redux to the mix? No problem, pass the feature flag of `--redux` when calling generate, i.e.:
64 |
65 | ```bash
66 | yo react-gen --redux
67 | ```
68 |
69 | The `--redux` flag will also install the [redux-devtools](https://github.com/gaearon/redux-devtools), hidden by default. Pressing the combination of `Ctrl+h` will make them visible.
70 |
71 | ## Contributing
72 |
73 | ```shell
74 | git clone https://github.com/fullstackreact/redux-modules.git
75 | cd redux-modules
76 | npm install
77 | npm start
78 | ```
79 | ___
80 |
81 | # Fullstack React Book
82 |
83 |
84 |
85 |
86 |
87 | This generator was built alongside the blog post [React Tutorial: Cloning Yelp](https://www.fullstackreact.com/articles/react-tutorial-cloning-yelp/).
88 |
89 | This repo was written and is maintained by the [Fullstack React](https://fullstackreact.com) team. In the book, we cover many more projects like this. We walk through each line of code, explain why it's there and how it works.
90 |
91 |
92 |
93 | ## License
94 | [MIT](/LICENSE)
95 |
--------------------------------------------------------------------------------
/generators/app/templates/webpack.config.js:
--------------------------------------------------------------------------------
1 | const NODE_ENV = process.env.NODE_ENV || 'development';
2 | const dotenv = require('dotenv');
3 |
4 | const webpack = require('webpack');
5 | const path = require('path');
6 |
7 | const join = path.join;
8 | const resolve = path.resolve;
9 |
10 | const getConfig = require('hjs-webpack');
11 |
12 | const isDev = NODE_ENV === 'development';
13 | const isTest = NODE_ENV === 'test';
14 |
15 | // devServer config
16 | const devHost = process.env.HOST || 'localhost';
17 | const devPort = process.env.PORT || 3000;
18 |
19 | const setPublicPath = process.env.SET_PUBLIC_PATH !== 'false';
20 | const publicPath = (isDev && setPublicPath) ? `//${devHost}:${devPort}/` : '';
21 |
22 | const root = resolve(__dirname);
23 | const src = join(root, 'src');
24 | const modules = join(root, 'node_modules');
25 | const dest = join(root, 'dist');
26 | const css = join(src, 'styles');
27 |
28 | var config = getConfig({
29 | isDev: isDev || isTest,
30 | in: join(src, 'app.js'),
31 | out: dest,
32 | html: function (context) {
33 | return {
34 | 'index.html': context.defaultTemplate({
35 | title: '<%= title %>',
36 | publicPath,
37 | meta: {}
38 | })
39 | };
40 | }
41 | });
42 |
43 | // ENV variables
44 | const dotEnvVars = dotenv.config();
45 | const environmentEnv = dotenv.config({
46 | path: join(root, 'config', `${NODE_ENV}.config.js`),
47 | silent: true
48 | });
49 | const envVariables =
50 | Object.assign({}, dotEnvVars, environmentEnv);
51 |
52 | const defines =
53 | Object.keys(envVariables)
54 | .reduce((memo, key) => {
55 | const val = JSON.stringify(envVariables[key]);
56 | memo[`__${key.toUpperCase()}__`] = val;
57 | return memo;
58 | }, {
59 | __NODE_ENV__: JSON.stringify(NODE_ENV),
60 | __DEBUG__: isDev
61 | });
62 |
63 | config.plugins = [
64 | new webpack.DefinePlugin(defines)
65 | ].concat(config.plugins);
66 | // END ENV variables
67 |
68 | // CSS modules
69 | const cssModulesNames = `${isDev ? '[path][name]__[local]__' : ''}[hash:base64:5]`;
70 |
71 | const matchCssLoaders = /(^|!)(css-loader)($|!)/;
72 |
73 | const findLoader = (loaders, match) => {
74 | const found = loaders.filter(l => l && l.loader && l.loader.match(match));
75 | return found ? found[0] : null;
76 | };
77 |
78 | // existing css loader
79 | const cssloader =
80 | findLoader(config.module.loaders, matchCssLoaders);
81 |
82 | const newloader = Object.assign({}, cssloader, {
83 | test: /\.module\.css$/,
84 | include: [src],
85 | loader: cssloader.loader.replace(matchCssLoaders, `$1$2?modules&localIdentName=${cssModulesNames}$3`)
86 | });
87 | config.module.loaders.push(newloader);
88 | cssloader.test = new RegExp(`[^module]${cssloader.test.source}`);
89 | cssloader.loader = newloader.loader;
90 |
91 | config.module.loaders.push({
92 | test: /\.css$/,
93 | include: [modules],
94 | loader: 'style!css'
95 | });
96 |
97 | // CSS modules
98 |
99 | // postcss
100 | config.postcss = [].concat([
101 | require('precss')({}),
102 | require('autoprefixer')({}),
103 | require('cssnano')({})
104 | ]);
105 |
106 | // END postcss
107 |
108 | // Roots
109 | config.resolve.root = [src, modules];
110 | config.resolve.alias = {
111 | css: join(src, 'styles'),
112 | containers: join(src, 'containers'),
113 | components: join(src, 'components'),
114 | utils: join(src, 'utils'),
115 | styles: join(src, 'styles')
116 | };
117 | // end Roots
118 |
119 | // Dev
120 | if (isDev) {
121 | config.devServer.port = devPort;
122 | config.devServer.hostname = devHost;
123 | }
124 |
125 | // Testing
126 | if (isTest) {
127 | config.externals = {
128 | 'react/addons': true,
129 | 'react/lib/ReactContext': true,
130 | 'react/lib/ExecutionEnvironment': true
131 | };
132 |
133 | config.module.noParse = /[/\\]sinon\.js/;
134 | config.resolve.alias.sinon = 'sinon/pkg/sinon';
135 |
136 | config.plugins = config.plugins.filter(p => {
137 | const name = p.constructor.toString();
138 | const fnName = name.match(/^function (.*)\((.*\))/);
139 |
140 | const idx = [
141 | 'DedupePlugin',
142 | 'UglifyJsPlugin'
143 | ].indexOf(fnName[1]);
144 | return idx < 0;
145 | });
146 | }
147 | // End Testing
148 |
149 | module.exports = config;
150 |
--------------------------------------------------------------------------------
/generators/app/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var yeoman = require('yeoman-generator');
3 | var chalk = require('chalk');
4 | var yosay = require('yosay');
5 |
6 | const tplCopy = function (name, to, props) {
7 | this.fs.copyTpl(
8 | this.templatePath(name),
9 | this.destinationPath(to || name),
10 | (props || this.props)
11 | );
12 | };
13 |
14 | const fileListCopy = function (files, dest) {
15 | const cpy = tplCopy.bind(this);
16 | files.forEach(filename => {
17 | cpy(filename, dest, this.props);
18 | });
19 | };
20 |
21 | const availableFeatures = [
22 | 'redux'
23 | ];
24 |
25 | module.exports = yeoman.Base.extend({
26 | prompting: function () {
27 | // Have Yeoman greet the user.
28 | this.log(yosay(
29 | 'Welcome to the ' + chalk.red('generator-react-gen') + ' generator!'
30 | ));
31 |
32 | var prompts = [{
33 | type: 'input',
34 | name: 'title',
35 | message: 'App name',
36 | default: this.appname
37 | }, {
38 | type: 'input',
39 | name: 'authorName',
40 | message: 'Author name',
41 | default: 'Ari Lerner'
42 | }, {
43 | type: 'input',
44 | name: 'authorEmail',
45 | message: 'Author email',
46 | default: 'ari@fullstack.io'
47 | }];
48 |
49 | // feature options
50 | availableFeatures.forEach(feature => this.option(feature));
51 |
52 | return this.prompt(prompts)
53 | .then(function (props) {
54 | // To access props later use this.props.someAnswer;
55 | this.props = props;
56 |
57 | this.features = [];
58 | availableFeatures.forEach(feature => {
59 | if (this.options[feature]) {
60 | this.features.push(feature);
61 | }
62 | });
63 |
64 | }.bind(this));
65 | },
66 |
67 | writing: function () {
68 | // root files
69 | var copyFiles = fileListCopy.bind(this);
70 | var baseFiles = ['package.json',
71 | 'tests.webpack.js',
72 | 'webpack.config.js',
73 | 'karma.conf.js',
74 | '.babelrc',
75 | 'src/',
76 | 'config/'
77 | ];
78 | var dependencies = [
79 | 'react', 'react-dom', 'classnames', 'font-awesome',
80 | 'react-router', 'react-router-redux'
81 | ];
82 | var devDependencies = [
83 | 'autoprefixer',
84 | 'babel-core',
85 | 'babel-loader',
86 | 'babel-plugin-transform-es2015-modules-umd',
87 | 'babel-polyfill',
88 | 'babel-preset-es2015',
89 | 'babel-preset-react',
90 | 'babel-preset-react-hmre',
91 | 'babel-preset-stage-0',
92 | 'babel-register',
93 | 'chai',
94 | 'chai-enzyme',
95 | 'cross-env',
96 | 'css-loader',
97 | 'cssnano',
98 | 'dotenv',
99 | 'enzyme',
100 | 'expect',
101 | 'file-loader',
102 | 'hjs-webpack',
103 | 'jasmine-core',
104 | 'json-loader',
105 | 'karma',
106 | 'karma-chai',
107 | 'karma-jasmine',
108 | 'karma-mocha',
109 | 'karma-phantomjs-launcher',
110 | 'karma-sourcemap-loader',
111 | 'karma-spec-reporter',
112 | 'karma-webpack',
113 | 'mocha',
114 | 'phantomjs-polyfill',
115 | 'phantomjs-prebuilt',
116 | 'postcss-loader',
117 | 'precss',
118 | 'react-addons-test-utils',
119 | 'sinon',
120 | 'style-loader',
121 | 'url-loader',
122 | 'webpack',
123 | 'yargs'
124 | ];
125 |
126 | var featureFiles = {
127 | redux: {
128 | 'src/': {
129 | dependencies: ['react-redux',
130 | 'redux-module-builder', 'redux-thunk'],
131 | devDependencies: ['redux-devtools',
132 | 'redux-devtools-log-monitor', 'redux-devtools-dock-monitor'],
133 | files: ['features/redux/']
134 | }
135 | }
136 | };
137 |
138 | copyFiles(baseFiles);
139 | const cp = tplCopy.bind(this);
140 | cp("env", ".env", this.props);
141 | cp("gitignore", ".gitignore", this.props);
142 |
143 | this.features.forEach(featureKey => {
144 | this.log(`======> Enabling ` + chalk.blue(featureKey));
145 | const feat = featureFiles[featureKey];
146 | Object.keys(feat)
147 | .forEach(key => {
148 | const dest = key;
149 | const feature = feat[key];
150 | const files = feature.files;
151 | copyFiles(files, dest);
152 |
153 | if (feature.dependencies) {
154 | dependencies = dependencies.concat(feature.dependencies);
155 | }
156 | if (feature.devDependencies) {
157 | devDependencies =
158 | devDependencies.concat(feature.devDependencies);
159 | }
160 | });
161 | });
162 |
163 | this.npmInstall(dependencies, {save: true});
164 | this.npmInstall(devDependencies, {saveDev: true});
165 | },
166 |
167 | install: function () {
168 | this.installDependencies();
169 | }
170 | });
171 |
--------------------------------------------------------------------------------
/generators/redux-module/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var yeoman = require('yeoman-generator');
3 | var chalk = require('chalk');
4 | var yosay = require('yosay');
5 |
6 | var fs = require('fs');
7 | var path = require('path');
8 | var process = require('process')
9 | var esprima = require('esprima');
10 | var escodegen = require('escodegen');
11 | var estraverse = require('estraverse');
12 |
13 | const tplCopy = function (name, to, props) {
14 | this.fs.copyTpl(
15 | this.templatePath(name),
16 | this.destinationPath(to || name),
17 | (props || this.props)
18 | );
19 | };
20 |
21 | const fileListCopy = function (files, dest) {
22 | const cpy = tplCopy.bind(this);
23 | files.forEach(filename => {
24 | cpy(filename, dest, this.props);
25 | });
26 | };
27 |
28 | const updateRootReducer = function(props) {
29 | const moduleName = props.moduleName;
30 | const rootReducerPath = path.join(process.cwd(), 'src/redux/rootReducer.js');
31 | const done = this.async();
32 |
33 | // don't do any traversing yet
34 | this.log(`Make sure you update your rootReducer with the property:
35 |
36 | ${chalk.blue(`${props.moduleName}: require('./modules/${props.moduleName}')`)}
37 |
38 | const modules = {
39 | ${props.moduleName}: require('./modules/${props.moduleName}')
40 | }
41 | `);
42 |
43 | return done();
44 |
45 |
46 | try {
47 | fs.stat(rootReducerPath, (err, stats) => {
48 | if (err) return done(err);
49 |
50 | let needsUpdate = false;
51 | if (stats.isFile()) {
52 | const data = fs.readFileSync(rootReducerPath, 'utf8');
53 | const AST = esprima.parse(data, {
54 | comment: true,
55 | sourceType: 'module'
56 | });
57 | estraverse.traverse(AST, {
58 | enter: (node, parent) => {
59 | if (node.type === 'VariableDeclarator' &&
60 | node.id.type === 'Identifier' &&
61 | node.id.name === 'modules') {
62 |
63 | const newKV = {
64 | 'type': 'Property',
65 | 'key': {
66 | 'type': 'Identifier',
67 | 'name': this.props.moduleName
68 | },
69 | 'computed': false,
70 | 'value': {
71 | 'type': 'Identifier',
72 | 'name': `require('./modules/${this.props.moduleName}')`
73 | },
74 | 'kind': 'init',
75 | 'method': false,
76 | 'shorthand': false
77 | }
78 |
79 | const alreadyExists = false;
80 | node.init.properties.forEach(prop => {
81 | if (prop.type === 'Property' &&
82 | prop.key.type === 'Identifier' &&
83 | prop.key.name === this.props.moduleName) {
84 | // Update this thing
85 | }
86 | })
87 | // We found the const modules = {};
88 | // let props = node.init.properties;
89 | // props.push(newKV);
90 | // needsUpdate = true;
91 | // return node;
92 | }
93 | }
94 | });
95 | if (needsUpdate) {
96 | const finalCode = escodegen.generate(AST, {
97 | comment: true,
98 | format: {
99 | indent: {
100 | style: ' '
101 | }
102 | },
103 | comment: true
104 | });
105 | fs.writeFile(rootReducerPath, finalCode, done);
106 | } else {
107 | done();
108 | }
109 | }
110 | });
111 | } catch (e) {
112 | this.log(`${chalk.red('Cannot update rootReducer')}
113 |
114 | Make sure you import this module into your root reducer.`);
115 | done('Cannot update rootReducer');
116 | }
117 | }
118 |
119 | module.exports = yeoman.Base.extend({
120 | prompting: function () {
121 | this.log('Create a redux module');
122 | const done = this.async();
123 |
124 | var prompts = [{
125 | type: 'input',
126 | name: 'moduleName',
127 | message: 'What do you want your module to be called?',
128 | default: this.appname
129 | }];
130 |
131 | return this.prompt(prompts)
132 | .then(function (props) {
133 | this.props = props;
134 |
135 | done();
136 | }.bind(this));
137 | },
138 |
139 | writing: function () {
140 | // root files
141 | var copyFiles = fileListCopy.bind(this);
142 | const cp = tplCopy.bind(this);
143 |
144 | const modPath = `src/redux/modules/${this.props.moduleName}.js`
145 | cp('module-template.js', modPath, this.props);
146 | var dependencies = [
147 | 'redux-module-builder'
148 | ];
149 |
150 | updateRootReducer.call(this, this.props);
151 | this.npmInstall(dependencies, {save: true});
152 | },
153 |
154 |
155 | // Currently not used
156 |
157 | install: function () {
158 | this.installDependencies();
159 | }
160 | });
161 |
--------------------------------------------------------------------------------