├── .gitignore ├── .npmignore ├── .yo-rc.json ├── README.md ├── app ├── index.js └── templates │ ├── .eslintignore │ ├── .eslintrc │ ├── _gitignore │ ├── app.json │ ├── app │ ├── actions │ │ └── index.js │ ├── api │ │ ├── fetchComponentDataBeforeRender.js │ │ └── promiseMiddleware.js │ ├── client.jsx │ ├── components │ │ └── Meta.jsx │ ├── constants │ │ └── index.js │ ├── containers │ │ ├── About.jsx │ │ ├── App.jsx │ │ ├── Home.jsx │ │ └── Navigation.jsx │ ├── createDevToolsWindow.js │ ├── css │ │ ├── common │ │ │ ├── colors.css │ │ │ ├── common.css │ │ │ ├── layout.css │ │ │ └── typography.css │ │ ├── components │ │ │ ├── home.css │ │ │ └── navigation.css │ │ ├── main.css │ │ └── settings.css │ ├── helmconfig.js │ ├── images │ │ ├── apple-ninja152-precomposed.png │ │ ├── chrome-ninja192-precomposed.png │ │ ├── favicon.png │ │ └── ms-ninja144-precomposed.png │ ├── reducers │ │ ├── index.js │ │ └── reducer.js │ ├── routes.jsx │ ├── server.jsx │ ├── store │ │ └── configureStore.js │ └── tests │ │ └── .gitkeep │ ├── karma.conf.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── config │ │ ├── express.js │ │ ├── routes.js │ │ └── secrets.js │ ├── controllers │ │ └── .gitkeep │ ├── index.js │ └── models │ │ └── .gitkeep │ ├── tests.webpack.js │ └── webpack │ ├── webpack.config.dev-client.js │ ├── webpack.config.dev-server.js │ └── webpack.config.prod.js ├── component ├── index.js └── templates │ ├── component.js │ └── component.scss ├── constant └── index.js ├── container ├── index.js └── templates │ └── container.js ├── package.json ├── reducer ├── index.js └── templates │ └── reducer.js └── tests └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | tests -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-generator": {} 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-react-webpack-node [![npm version](https://badge.fury.io/js/generator-react-webpack-node.svg)](http://badge.fury.io/js/generator-react-webpack-node) 2 | Yeoman generator for [react-webpack-node](https://github.com/choonkending/react-webpack-node). 3 | 4 | ## Getting Started 5 | 6 | ```bash 7 | npm install -g yo 8 | ``` 9 | 10 | To install generator-react-webpack-node from npm, run: 11 | 12 | ```bash 13 | npm install -g generator-react-webpack-node 14 | ``` 15 | 16 | Finally, initiate the generator: 17 | 18 | ```bash 19 | yo react-webpack-node 20 | ``` 21 | 22 | ## Generators 23 | Available generators: 24 | - [react-webpack-node:component](#component) 25 | - [react-webpack-node:container](#container) 26 | - [react-webpack-node:reducer](#reducer) 27 | - [react-webpack-node:constant](#constant) 28 | 29 | ### Component 30 | Generates a component in app/components with a stylesheet. 31 | 32 | Example: 33 | ``yo react-webpack-node:component myComponent`` 34 | 35 | ### Container 36 | Generates a container in app/containers. 37 | 38 | Example: 39 | ``yo react-webpack-node:container myContainer`` 40 | 41 | ### Reducer 42 | Generates a reducer in app/reducers. 43 | 44 | Example: 45 | ``yo react-webpack-node:reducer myReducer`` 46 | 47 | *Remember that you will need to combine this new one with the others already created in the index.js* 48 | 49 | ### Constant 50 | Creates a constant in the specified constants file located in app/constants. 51 | 52 | Example: 53 | ``yo react-webpack-node:constant myConstantsFile MY_NEW_CONST`` 54 | 55 | ## License 56 | MIT © [Iegor Azuaga](http://iegor.me) 57 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var yeoman = require('yeoman-generator'); 4 | var chalk = require('chalk'); 5 | var yosay = require('yosay'); 6 | var str = require('underscore.string'); 7 | 8 | module.exports = yeoman.generators.Base.extend({ 9 | constructor: function () { 10 | yeoman.generators.Base.apply(this, arguments); 11 | 12 | this.option('skip-install', { 13 | desc: 'Whether dependencies should be installed', 14 | defaults: false 15 | }); 16 | }, 17 | 18 | initializing: function () { 19 | this.pkg = require('../package.json'); 20 | }, 21 | 22 | prompting: function () { 23 | var done = this.async(); 24 | 25 | this.log(yosay( 26 | 'Welcome to the riveting ' + chalk.red('react-webpack-node') + ' generator!' 27 | )); 28 | 29 | var prompts = [{ 30 | type: 'input', 31 | name: 'name', 32 | default: this.appname, 33 | message: 'Project name:', 34 | validate: function(input) { 35 | return !!input; 36 | } 37 | }]; 38 | 39 | this.prompt(prompts, function (props) { 40 | this.displayName = props.name; 41 | this.name = str.slugify(props.name); 42 | this.buildSystem = str.slugify(props.buildSystem); 43 | 44 | done(); 45 | }.bind(this)); 46 | }, 47 | 48 | writing: { 49 | config: function () { 50 | this.template('.eslintignore', '.eslintignore', this.context); 51 | this.template('.eslintrc', '.eslintrc', this.context); 52 | this.template('karma.conf.js', 'karma.conf.js', this.context); 53 | // .gitignore is renamed by npm to .npmignore, so use underscore 54 | this.template('_gitignore', '.gitignore', this.context); 55 | this.template('package.json', 'package.json', this.context); 56 | this.template('nodemon.json', 'nodemon.json', this.context); 57 | }, 58 | 59 | projectfiles: function () { 60 | this.template('tests.webpack.js', 'tests.webpack.js', this.context); 61 | this.template('app.json', 'app.json', this.context); 62 | this.template('app', 'app', this.context); 63 | this.template('server', 'server', this.context); 64 | this.template('server/models/.gitkeep', 'server/models/.gitkeep', this.context); 65 | this.template('server/controllers/.gitkeep', 'server/controllers/.gitkeep', this.context); 66 | this.template('webpack', 'webpack', this.context); 67 | } 68 | }, 69 | 70 | install: function () { 71 | this.installDependencies({ 72 | npm: true, 73 | bower: false, 74 | skipInstall: this.options['skip-install'] 75 | }); 76 | } 77 | }); -------------------------------------------------------------------------------- /app/templates/.eslintignore: -------------------------------------------------------------------------------- 1 | /server/** 2 | /webpack/** 3 | karma.conf.js 4 | tests.webpack.js 5 | /api/promiseMiddleware.js -------------------------------------------------------------------------------- /app/templates/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": 6, 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "forOf": true, 13 | "jsx": true, 14 | "es6": true, 15 | "experimentalObjectRestSpread" : true 16 | }, 17 | }, 18 | "rules": { 19 | "comma-dangle": 0, 20 | "indent": 0, 21 | "react/prop-types": 0, 22 | "react/jsx-indent-props" : 0, 23 | "react/jsx-closing-bracket-location" : 0, 24 | "object-curly-spacing" : 0, 25 | "arrow-body-style": 0, 26 | "no-console": 0, 27 | "max-len": 0, 28 | "prefer-template": 0 29 | }, 30 | "plugins": [ 31 | "react" 32 | ], 33 | "globals": { 34 | "__DEVSERVER__": true, 35 | "__DEVCLIENT__": true 36 | } 37 | } -------------------------------------------------------------------------------- /app/templates/_gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .tmp 4 | .idea 5 | public 6 | .swp -------------------------------------------------------------------------------- /app/templates/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= name %>", 3 | "description": "A barebones Node.js app using Express 4 with MongoDB, and ReactJS in Flux, using Webpack as a module bundler", 4 | "repository": "https://github.com/choonkending/react-webpack-node", 5 | "logo": "https://node-js-sample.herokuapp.com/node.svg", 6 | "keywords": ["node", "express", "static", "react", "webpack", "mongodb"], 7 | "addons" : [ 8 | "mongohq" 9 | ] 10 | } -------------------------------------------------------------------------------- /app/templates/app/actions/index.js: -------------------------------------------------------------------------------- 1 | // .. -------------------------------------------------------------------------------- /app/templates/app/api/fetchComponentDataBeforeRender.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This looks at static needs parameter in components and waits for the promise to be fullfilled 3 | * It is used to make sure server side rendered pages wait for APIs to resolve before returning res.end() 4 | * As seen in: https://github.com/caljrimmer/isomorphic-redux-app 5 | */ 6 | 7 | export function fetchComponentDataBeforeRender(dispatch, components, params) { 8 | const needs = components.reduce((prev, current) => { 9 | return (current.need || []) 10 | .concat((current.WrappedComponent ? current.WrappedComponent.need : []) || []) 11 | .concat(prev); 12 | }, []); 13 | const promises = needs.map(need => dispatch(need(params))); 14 | return Promise.all(promises); 15 | } -------------------------------------------------------------------------------- /app/templates/app/api/promiseMiddleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Redux middleware to handle promises 3 | * As seen in: https://github.com/caljrimmer/isomorphic-redux-app 4 | */ 5 | 6 | export default function promiseMiddleware() { 7 | return next => action => { 8 | const { promise, type, ...rest } = action; 9 | 10 | if (!promise) return next(action); 11 | 12 | const SUCCESS = type + '_SUCCESS'; 13 | const REQUEST = type + '_REQUEST'; 14 | const FAILURE = type + '_FAILURE'; 15 | next({ ...rest, type: REQUEST }); 16 | return promise 17 | .then(req => { 18 | next({ ...rest, req, type: SUCCESS }); 19 | return true; 20 | }) 21 | .catch(error => { 22 | next({ ...rest, error, type: FAILURE }); 23 | return false; 24 | }); 25 | }; 26 | } -------------------------------------------------------------------------------- /app/templates/app/client.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { Router, browserHistory } from 'react-router'; 5 | import { syncHistoryWithStore } from 'react-router-redux'; 6 | import createRoutes from 'routes.jsx'; 7 | import configureStore from 'store/configureStore'; 8 | 9 | // Grab the state from a global injected into 10 | // server-generated HTML 11 | const initialState = window.__INITIAL_STATE__; 12 | 13 | const store = configureStore(initialState, browserHistory); 14 | const history = syncHistoryWithStore(browserHistory, store); 15 | const routes = createRoutes(store); 16 | 17 | // Router converts element hierarchy to a route config: 18 | // Read more https://github.com/rackt/react-router/blob/latest/docs/Glossary.md#routeconfig 19 | render( 20 | 21 | 22 | {routes} 23 | 24 | , document.getElementById('app')); 25 | -------------------------------------------------------------------------------- /app/templates/app/components/Meta.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOMServer from 'react-dom/server'; 3 | import Helmet from 'react-helmet'; 4 | 5 | import config from 'helmconfig.js'; 6 | 7 | // Remove stylesheets because we do not extract them into a css file 8 | // in development mode 9 | if (__DEVSERVER__) { 10 | config.link = config.link.filter(l => l.rel !== 'stylesheet'); 11 | } 12 | 13 | const Meta = () => ; 14 | 15 | 16 | ReactDOMServer.renderToString(); 17 | const header = Helmet.rewind(); 18 | 19 | export default header; -------------------------------------------------------------------------------- /app/templates/app/constants/index.js: -------------------------------------------------------------------------------- 1 | export const CREATE_TOPIC_REQUEST = 'CREATE_TOPIC_REQUEST'; 2 | export const CREATE_TOPIC_FAILURE = 'CREATE_TOPIC_FAILURE'; 3 | export const CREATE_TOPIC_SUCCESS = 'CREATE_TOPIC_SUCCESS'; 4 | export const CREATE_TOPIC_DUPLICATE = 'CREATE_TOPIC_DUPLICATE'; 5 | export const INCREMENT_COUNT = 'INCREMENT_COUNT'; 6 | export const DECREMENT_COUNT = 'DECREMENT_COUNT'; 7 | export const DESTROY_TOPIC = 'DESTROY_TOPIC'; 8 | export const TYPING = 'TYPING'; 9 | 10 | export const MANUAL_LOGIN_USER = 'MANUAL_LOGIN_USER'; 11 | export const LOGIN_SUCCESS_USER = 'LOGIN_SUCCESS_USER'; 12 | export const LOGIN_ERROR_USER = 'LOGIN_ERROR_USER'; 13 | export const SIGNUP_USER = 'SIGNUP_USER'; 14 | export const SIGNUP_SUCCESS_USER = 'SIGNUP_SUCCESS_USER'; 15 | export const SIGNUP_ERROR_USER = 'SIGNUP_ERROR_USER'; 16 | export const LOGOUT_USER = 'LOGOUT_USER'; 17 | export const LOGOUT_SUCCESS_USER = 'LOGOUT_SUCCESS_USER'; 18 | export const LOGOUT_ERROR_USER = 'LOGOUT_ERROR_USER'; 19 | -------------------------------------------------------------------------------- /app/templates/app/containers/About.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /* 4 | * Note: This is kept as a container-level component, 5 | * i.e. We should keep this as the container that does the data-fetching 6 | * and dispatching of actions if you decide to have any sub-components. 7 | */ 8 | export default class About extends React.Component { 9 | 10 | render() { 11 | return ( 12 |
13 |

About page

14 |
15 | ); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /app/templates/app/containers/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Navigation from 'containers/Navigation'; 3 | 4 | import classNames from 'classnames/bind'; 5 | import styles from 'css/main'; 6 | 7 | const cx = classNames.bind(styles); 8 | 9 | /* 10 | * React-router's component renders 's 11 | * and replaces `this.props.children` with the proper React Component. 12 | * 13 | * Please refer to `routes.jsx` for the route config. 14 | * 15 | * A better explanation of react-router is available here: 16 | * https://github.com/rackt/react-router/blob/latest/docs/Introduction.md 17 | */ 18 | export default class App extends Component { 19 | render() { 20 | return ( 21 |
22 | 23 | {this.props.children} 24 |
25 | ); 26 | } 27 | }; 28 | 29 | App.propTypes = { 30 | children: PropTypes.object 31 | }; 32 | -------------------------------------------------------------------------------- /app/templates/app/containers/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classNames from 'classnames/bind'; 4 | import styles from 'css/components/home'; 5 | 6 | const cx = classNames.bind(styles); 7 | 8 | /* 9 | * Note: This is kept as a container-level component, 10 | * i.e. We should keep this as the container that does the data-fetching 11 | * and dispatching of actions if you decide to have any sub-components. 12 | */ 13 | export default class Home extends React.Component { 14 | 15 | render() { 16 | return ( 17 |
18 |

Welcome to react-webpack-node!

19 |
20 | ); 21 | } 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /app/templates/app/containers/Navigation.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | 5 | import classNames from 'classnames/bind'; 6 | import styles from 'css/components/navigation'; 7 | 8 | const cx = classNames.bind(styles); 9 | 10 | class Navigation extends Component { 11 | 12 | render() { 13 | return ( 14 | 18 | ); 19 | } 20 | 21 | } 22 | 23 | export default Navigation; -------------------------------------------------------------------------------- /app/templates/app/createDevToolsWindow.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react'; 4 | 5 | /* 6 | * Puts Redux DevTools into a separate window. 7 | * Based on https://gist.github.com/tlrobinson/1e63d15d3e5f33410ef7#gistcomment-1560218. 8 | */ 9 | export default function createDevToolsWindow(store) { 10 | // Window name. 11 | const name = 'Redux DevTools'; 12 | 13 | // Give it a name so it reuses the same window. 14 | const win = window.open( 15 | null, 16 | name, 17 | 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no,width=450,height=5000' 18 | ); 19 | 20 | // Reload in case it's reusing the same window with the old content. 21 | win.location.reload(); 22 | 23 | // Set visible Window title. 24 | win.document.title = name; 25 | 26 | // Wait a little bit for it to reload, then render. 27 | setTimeout(() => ReactDOM.render( 28 | 29 | 30 | , 31 | win.document.body.appendChild(document.createElement('div')) 32 | ), 10); 33 | } 34 | -------------------------------------------------------------------------------- /app/templates/app/css/common/colors.css: -------------------------------------------------------------------------------- 1 | @value color-black: #000; 2 | @value color-white: #fff; 3 | @value color-dodger-blue: #2196F3; 4 | @value color-salem: #0F9D58; 5 | @value color-darker-salem: #0b6e3e; 6 | @value color-punch: #db4437; 7 | @value color-loblolly: #c3c8ce; 8 | @value color-limed-spruce: #333f48; 9 | @value color-athens-gray: #f5f5f6; 10 | @value color-mercury: #e3e3e3; 11 | @value color-bombay: #b4bac1; 12 | @value color-crimson: #ed193a; -------------------------------------------------------------------------------- /app/templates/app/css/common/common.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiegor/generator-react-webpack-node/b0b30e095daaf047f56be89e68463c5ed403c057/app/templates/app/css/common/common.css -------------------------------------------------------------------------------- /app/templates/app/css/common/layout.css: -------------------------------------------------------------------------------- 1 | @value color-mercury from './colors.css'; 2 | 3 | .box { 4 | padding: 8px 24px; 5 | } 6 | 7 | .flexNotMoreThan200 { 8 | flex: 0 1 200px; 9 | } -------------------------------------------------------------------------------- /app/templates/app/css/common/typography.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Montserrat:400,700); 2 | 3 | @value montserrat: 'Montserrat', Helvetica, Arial, sans-serif; 4 | -------------------------------------------------------------------------------- /app/templates/app/css/components/home.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiegor/generator-react-webpack-node/b0b30e095daaf047f56be89e68463c5ed403c057/app/templates/app/css/components/home.css -------------------------------------------------------------------------------- /app/templates/app/css/components/navigation.css: -------------------------------------------------------------------------------- 1 | @value font-x-large from '../settings.css'; 2 | @value color-black, color-dodger-blue from '../common/colors.css'; 3 | 4 | .navigation { 5 | padding: 0 28px; 6 | } 7 | 8 | .item { 9 | display: inline-block; 10 | text-decoration: none; 11 | padding: 16px 32px; 12 | color: color-black; 13 | } 14 | 15 | .logo { 16 | font-size: font-x-large; 17 | } 18 | 19 | .active { 20 | color: color-dodger-blue; 21 | } -------------------------------------------------------------------------------- /app/templates/app/css/main.css: -------------------------------------------------------------------------------- 1 | @value montserrat from './common/typography.css'; 2 | 3 | /* 4 | * Add any global styles here 5 | */ 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | body { 11 | margin: 0; 12 | } 13 | 14 | .app { 15 | composes: box from './common/layout.css'; 16 | 17 | font-family: montserrat; 18 | font-weight: normal; 19 | font-smoothing: antialiased; 20 | } 21 | -------------------------------------------------------------------------------- /app/templates/app/css/settings.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Montserrat:400,700); 2 | @value font-family: 'Montserrat', Helvetica, Arial, sans-serif; 3 | 4 | @value font-small: 12px; 5 | @value font-medium: 16px; 6 | @value font-large: 21px; 7 | @value font-x-large: 28px; 8 | 9 | @value global-radius: 4px; 10 | -------------------------------------------------------------------------------- /app/templates/app/helmconfig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on the template in Web Starter Kit : https://github.com/google/web-starter-kit/blob/master/app/index.html 3 | * To add to the config, add an object: 4 | * { 5 | * type: 'link' | 'meta', 6 | * sizes: 'widthxheight', 7 | * rel: 'rel value' 8 | * filename: ����Դ���������TEAJBA:12��П�{LEDM@7����ۮ�~54--.��š���Ǔ��t{{��n�qnakj�w3�\*ϴ�������z�N[Y) 9&���������������麁lrsfpoi[M<.,{O'����Μ���|�m[XZ: %�⸰�����������owwytv[feۙS�q12('���������uop�xm��eYQPAKIH97�U)pH$������é����jcc`QNْI3310&%eA"C+ڂvyfcb[\9=;O2-�ե�����woilFRP�c-����������lbRJJ}LE/+)�XP ��tRNS@���`��0ϏP �pPP$\ YIDATx���1�0D�l'���Y��5 S�;�/vG�yj�a�kX1 �4Y�Xd��,K_��0���Q~~���O �l�P��TL�*�eg��h\a�V/P��MO��9��M�x���A7< Z�OڈBh��*�҈�Q��iӥC�vA�Ȑ.��#![��p�"{@DBH &"6�������������޽�L ������_�J��B;���^�/5�m{�e/?d�X̓�SC RU!f\�?�2�^ 11 | ��+$Z`W먘�4fd��CV��nD�H�.6� 1?�`G1k�!�D�C� P��#5��"�.��5�i�ŐA\~.�O�V��,b�1D|k��� m�JȬ��s�; ��Zyd�=�[��$c���.�|��}ϢUw0��:2���x��Vc��U��#M3���̔�B�� M�NZr!�7w2Q2B���,��Z��<��:OJ�����dHK�Q�%�~)S �)f�U?n>Zh���V/nԩ�/g����`�����bG�H�~�R�ju�T��b���~�D�K� 12 | "0oHv�� uk�-:\���ؽ:��+�郎���[ea����;��w�! 13 | 8i�׍�d��')��� �v�,�ԣ<��\|���1�v�� ny��~b3�-��֥ۤ?�I� u�&�lF},�O�I���w*��F6#/� �k�@z�R�w/���6kr��,��V����fsF�"�� ���ξ��[�|fy(�ό�2�U&�hPk2��P��I�#�x�t$OX����V��Z/S��R��P6����I�����Zi��i��5��u���?k�@�i;�K�z�'p�얏�| �q 14 | �A$�t ���Rti�� (T ����C4�xy$܅��Ϟ������qU\ ��a�Ys@�e9�/`g�s�qI� m�${>F%� 15 | /-х�t�i����u��Q��ݛzJ�M�m@{kb�����v��Cvǻ����l{��!���ӫ��$v�K�1jz:~�6"�m�D��W������x�-��H��L��1nju��5dI)��!T%Ǚx�D\�T� �27��/����b/�Ȁ�'$�3�<Y�da�G�/��:s�4s�$ɤ6[ݦr�qL� ���nB?֔�֟�e�cMh��ɼ���Fu���Qd�SJ[�t�t��|YZ�'nn��Z�p���i 16 | ���&��y���b�J� fF\x�:gB�f$�r���U ����`]���L 17 | ��DB���\~b2C�ȗ0��)ٳZO�fLV�'ž���coR���`4Lր'�y�#�| 18 | ��J�^��PIZ��.�#��# 19 | ����ՌS&�'����3��瀽��^v�I�q7�:��|��|�lK�taM)�.��¦�T�d]4� w�6Y8���*���ƛ]��`݄A�jPA�E��9�s�g��j�s�����<���嶦S��o�G�:���0�{r��=ux���WXvT��;�_Z�};�ߥa��ktk�$M��Z�m��/�����B� ��(��&J:�D�!��,��)�/����<==���G��n߾�k��8 o��^o ԍ�z�hO�\���9��������UÝ���E�æ�/�s5�r��e{u�:Z���z} _1�o[�V�%�լ'���M,fF�������"���� �2�,�'�b�F*�V����R-�'��+��C�m-X|-g62���20��&�6l;d6�ۘ��e�,�&�1���%�-f���`�e���,ɂڲ��U�V{ٕpf���n��gY�I�a�u�.����e�*�R�d��ڲ�%Kb�<)��,XOmX=oS� �l�Y: ˅��s9K*مh���e��YF���*+��,#0,�K��d�bT�w�J^��/��\�b�X2�r��,�R�X���,��r�Y�J ,�a��`���w�a{2���E�%�����%+��e4dݼџ�J���P �%�>�� �+,K�,k �Is0d�� ���-��x����i ,��ž,?s�~�G���y#���J�Y�b0��`�.�-���/��zmu� F<`4KKW&9��{����u^̒�|R�]�E*f������z��~Ȗ�K� ���1.k��` �d�%��v0�dŽ.G������4&gɮ�]���,��~KvЭ�=��`�,H������6l�my��uy0�J7n�k7���+��UȒJ�  �˒J�ʒn%�t+��m��`kv�O���!�B o%�� d߫�+ �'����n%�`���`�o%:ݦ�D0= �/�6"�́�IEND�B`� -------------------------------------------------------------------------------- /app/templates/app/images/chrome-ninja192-precomposed.png: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR��e�5�PLTE###### #"####### #���Ӆ7���HVT ��ե�������򬪮���������PE;������?GE��옔������Ϻ����������������̱����������Ɨ�������������¼�������������������Ĉ��0%"��ޞ����㜙������ή������~���������Я�������������ۺ����������崯�����⿧�����E*��ȭ�������׾����������������p�����������������â��viZ���nvu���������D:9�����̂������Դ675��ʙ��A88�Ǩȹ�-#!���¾����E>=>55?3-��ː��LDDZNC�~5eon֌@911�j/������qzzޠ\IAAL@73,,���Ŀ��Ǔ'�������qn{O'���m[Xϴ��ۮ꼄���z~�irrQ]\h[NeA"9&���ܿ��⸌�����`ji<.,Y: O3����ե�����{[eeْI.������}���}���w��nTEB�������Μ�w3�c-pH$v{}lst�veRIJC+��凎��|xytvqii\UVۙS��������eVb`aPML0,�U)�������xmibbMZXYQQFRP9=;Z83�\+é����yfcCNL!rlmb[\�`WzJDh@:331�����|vom�lbj*!oe��=��+07�vb�υ��L�]4�����;��5�9]G/� �X�0*a�[��*���S��w��*v�*���9H32�׭�,�K��� �3 F���;�f9t�XT���S��{��2*aU'�s����P�K����0�dq[�0����O̓6p�Ɖor � 15 | �H�c�9$�ElPu��� �t��3>�u>�0�����%�]��g�@ia��z�{����[ ��|�b`���u��`KM<�w��0���9� Vі��5/[�1`W� �������4n�9�:��������k-p���t0�P� � ���L‚��I���#�B��0n�2p��0%���3��h��DY�2��m�.;����oA� �t�4'P� 16 | ��dʡ?������o n�X�ʢL� Qkmœ���-�t�_��ߵm ���.�����=�3�X���->O䩃��bC�4��Ji0�@i�@��`29!�B�D��ʗ;^��� ������q�<���fA�C/_�Y�����ʖ��e<t<Ё�uޢ{i�Ӓ�_����Zg"t2�<��� �tY,�U�b�ȏ��������{َ�qe@/1���y����4j�Ҁ+�X}��V���!'!��hV9�2���X ��JBZ'@�!�>h����b��~!DϤ�=��WG��U^|�m�����X�`�No�A�b�=y=D%M�X�sB��mO�E\�2��� � 7f ���c��qL9����Η�������S�g)$0�:�߉9��oB{1G� l+��|c�\3�<��S�%��q`|ݎP}��m��n�$`�=D0��9�?x���%�<dc,%�K�c��j����ܿQ�`�d˞��nb��=���3��b��h�sd�k9������"�,�Gx��(�yZ�%<�ga���y��jc��t2��Wd%X+5N�g*�^��������ݻ�����˱]D�FB�+j���:�y�mM8���``�Ҟ�q���n�u��<�H� H�g�� y��MQ��9.$����2��(� _f?� "��1��d)1���!���{J���H�3��qR���%����?8O)�"d&� ���\��-�đ ��� ģv8�׋(ɈQ�5�x�q} ����f������(ʅB��0*U��mj��$��`TYε[(���{������鏇�������������ړ�g��*� 4 | I�j"�{P�<Ņ�p�`"Z�I_����� 5 | �ەF��ҳ�WW�w'z�l,���' ��<�%���_�@e�ꃟ�"\LmnL�g���pO��P ��i��}�s?v2tjg�]7C�����?����~���XHÄ\�P�[�gꀃ�W(;W�b�X�1=��/F*W����q)&��W ۩c �A �F8�A���jn�Ҹ��0! 6 | #O)N)�IP�N}d�}�V�D�pM���hh��f�Y<���Q��>!�S�I���T���L3��m�!��poC� 7 | }nۡ�d��>�2��1vIN����p��Au���? 8 | �H��Ҡ=�W6����|J�B$Eװq!Nm!w�B> V�V�� ���]�!��=!��4��5b�b�yȴ��Py4��қ��y���Opb<���*�`L�a��A��wl�^3ܭC�fj>�,�T[�5� ��k�a��p��j�͕�c�0�ݞ��D���C���� q\�i��鏻�7�R�K�=�t`3Bl,&$�T�� �:�( �$E��0v������߻gO��H����wg,8�[���'��9�KЫ�1� ��Ɵ������h��F31%� �I ��.fbH- �g,� L413��>?� ��F���+�#Sq�Z �1� V��X��,�Ew��� 9 | =BL鑶̫R����D�!nQ� 恶�9DB���P�lJ�q��s��Ju�2�B��!��M�J�(���7�i�,M��b�[Y�6�!��y�x�����,S��JJ������;�F�i�)$(i>њl� 10 | �hPLU�e�_ �Qr��:��ϿE��m��?^6!�e�� 11 | ��&�d�6�(���� 12 | :��W ��ؓ��@nzC뷥���;ڡʍ��/V,�$���V+�\cEJ[���G�IXcZs��Hž]��c�P��|D�%0R"�E��⠊�:ϴ1Z��<�<����J�a��n{�ʦ�Ñ.WO�\����aWh��`��ꌆӝu+��qgS�>���|���=$�'�9;iPeC�� ���D�� bF�h���e�$F��V�jv &��o��.0��)��K5���� fW�ا�)��|+ڊi�tOI��l䲏�#.�g~k�V�a^����7 g��W��I��k�'�IwHW�g����f������{�Ǔ����6IE�+�-��m�|���E�<"s����ص8VH�T�ph��Y\�i���Of`ecp�or.G ���U�i�?1ܰ ,e<�����7�r��� �)�U��лZ���Y�҄�}��^�v�����#zQ�dD��@EH�Z\��s�����٬'��b "����`�.o;�&��V2I4D.K��BaͰOW��H\q��*kj!��O# �ѻ=*��?%�w8"i����9�i �~n&��\��c��)á!:�͍����7��/n\ �JG�}�\j���^�w�8 �$ym5Y �̌F� �X���l�٘iu�5�, �i͆}����"^`5�I����i ��(�L�fL��M�Mq�y�X ��П.;nN�&R��4!�-z�HG�D�A�HǛȒ�5�0��MDR�f��f>�dD��S��,�w��{L�e؅U�D��l�7Q�8�v�4�`�����N��h��`��u`g]���Z�NkyH� ����\��T>��LzM`�7��4[�M���\k'H|0 17 | ����8�& $|d�����_s����\��G��K%:p��Yz��v�H��/�H��?Q { 14 | return ( 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /app/templates/app/server.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderToString } from 'react-dom/server'; 3 | import { RouterContext, match, createMemoryHistory } from 'react-router' 4 | import axios from 'axios'; 5 | import { Provider } from 'react-redux'; 6 | import createRoutes from 'routes.jsx'; 7 | import configureStore from 'store/configureStore'; 8 | import headconfig from 'components/Meta'; 9 | import { fetchComponentDataBeforeRender } from 'api/fetchComponentDataBeforeRender'; 10 | 11 | const clientConfig = { 12 | host: process.env.HOSTNAME || 'localhost', 13 | port: process.env.PORT || '3000' 14 | }; 15 | 16 | // configure baseURL for axios requests (for serverside API calls) 17 | axios.defaults.baseURL = `http://${clientConfig.host}:${clientConfig.port}`; 18 | 19 | /* 20 | * Our html template file 21 | * @param {String} renderedContent 22 | * @param initial state of the store, so that the client can be hydrated with the same state as the server 23 | * @param head - optional arguments to be placed into the head 24 | */ 25 | function renderFullPage(renderedContent, initialState, head={ 26 | title: '<%= name %>', 27 | meta: '', 28 | link: '' 29 | }) { 30 | return ` 31 | 32 | 33 | 34 | ${head.title} 35 | ${head.meta} 36 | ${head.link} 37 | 38 | 39 |
${renderedContent}
40 | 41 | 42 | 43 | 44 | 45 | `; 46 | } 47 | 48 | /* 49 | * Export render function to be used in server/config/routes.js 50 | * We grab the state passed in from the server and the req object from Express/Koa 51 | * and pass it into the Router.run function. 52 | */ 53 | export default function render(req, res) { 54 | const history = createMemoryHistory(); 55 | const store = configureStore({ 56 | reducer: {}, 57 | }, history); 58 | 59 | const routes = createRoutes(store); 60 | 61 | /* 62 | * From the react-router docs: 63 | * 64 | * This function is to be used for server-side rendering. It matches a set of routes to 65 | * a location, without rendering, and calls a callback(error, redirectLocation, renderProps) 66 | * when it's done. 67 | * 68 | * The function will create a `history` for you, passing additional `options` to create it. 69 | * These options can include `basename` to control the base name for URLs, as well as the pair 70 | * of `parseQueryString` and `stringifyQuery` to control query string parsing and serializing. 71 | * You can also pass in an already instantiated `history` object, which can be constructured 72 | * however you like. 73 | * 74 | * The three arguments to the callback function you pass to `match` are: 75 | * - error: A javascript Error object if an error occured, `undefined` otherwise. 76 | * - redirectLocation: A `Location` object if the route is a redirect, `undefined` otherwise 77 | * - renderProps: The props you should pass to the routing context if the route matched, `undefined` 78 | * otherwise. 79 | * If all three parameters are `undefined`, this means that there was no route found matching the 80 | * given location. 81 | */ 82 | match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { 83 | if (error) { 84 | res.status(500).send(error.message); 85 | } else if (redirectLocation) { 86 | res.redirect(302, redirectLocation.pathname + redirectLocation.search); 87 | } else if (renderProps) { 88 | const InitialView = ( 89 | 90 | 91 | 92 | ); 93 | 94 | // This method waits for all render component promises to resolve before returning to browser 95 | fetchComponentDataBeforeRender(store.dispatch, renderProps.components, renderProps.params) 96 | .then(() => { 97 | const componentHTML = renderToString(InitialView); 98 | const initialState = store.getState(); 99 | res.status(200).end(renderFullPage(componentHTML, initialState, { 100 | title: headconfig.title, 101 | meta: headconfig.meta, 102 | link: headconfig.link 103 | })); 104 | }) 105 | .catch(() => { 106 | res.end(renderFullPage('', {})); 107 | }); 108 | } else { 109 | res.status(404).send('Not Found'); 110 | } 111 | }); 112 | } -------------------------------------------------------------------------------- /app/templates/app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import { routerMiddleware } from 'react-router-redux'; 3 | import thunk from 'redux-thunk'; 4 | import rootReducer from 'reducers'; 5 | import promiseMiddleware from 'api/promiseMiddleware'; 6 | import createLogger from 'redux-logger'; 7 | 8 | /* 9 | * @param {Object} initial state to bootstrap our stores with for server-side rendering 10 | * @param {History Object} a history object. We use `createMemoryHistory` for server-side rendering, 11 | * while using browserHistory for client-side 12 | * rendering. 13 | */ 14 | export default function configureStore(initialState, history) { 15 | const middleware = [thunk, promiseMiddleware]; 16 | // Installs hooks that always keep react-router and redux 17 | // store in sync 18 | const reactRouterReduxMiddleware = routerMiddleware(history); 19 | if (__DEVCLIENT__) { 20 | middleware.push(reactRouterReduxMiddleware, createLogger()); 21 | } else { 22 | middleware.push(reactRouterReduxMiddleware); 23 | } 24 | 25 | const finalCreateStore = applyMiddleware(...middleware)(createStore); 26 | 27 | const store = finalCreateStore(rootReducer, initialState); 28 | 29 | if (module.hot) { 30 | // Enable Webpack hot module replacement for reducers 31 | module.hot.accept('reducers', () => { 32 | const nextReducer = require('reducers'); 33 | store.replaceReducer(nextReducer); 34 | }); 35 | } 36 | 37 | return store; 38 | } -------------------------------------------------------------------------------- /app/templates/app/tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiegor/generator-react-webpack-node/b0b30e095daaf047f56be89e68463c5ed403c057/app/templates/app/tests/.gitkeep -------------------------------------------------------------------------------- /app/templates/karma.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // Start these browsers, currently available: 7 | // - Chrome 8 | // - ChromeCanary 9 | // - Firefox 10 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 11 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 12 | // - PhantomJS 13 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 14 | browsers: ['jsdom'], 15 | 16 | frameworks: ['mocha', 'sinon'], 17 | 18 | // Point karma at the tests.webpack.js 19 | files: [ 20 | 'tests.webpack.js' 21 | ], 22 | 23 | // Run karma through preprocessor plugins 24 | preprocessors: { 25 | 'tests.webpack.js': [ 'webpack', 'sourcemap' ] 26 | }, 27 | 28 | // Continuous Integration mode 29 | // if true, it capture browsers, run tests and exit 30 | singleRun: true, 31 | 32 | // How long will Karma wait for a message from a browser before disconnecting 33 | // from it (in ms). 34 | browserNoActivityTimeout: 30000, 35 | 36 | webpack: { 37 | devtool: 'inline-source-map', 38 | context: path.join(__dirname, "app"), 39 | module: { 40 | loaders: [ 41 | { 42 | test: /\.js$|\.jsx$/, 43 | loader: 'babel', 44 | // Reason why we put this here instead of babelrc 45 | // https://github.com/gaearon/react-transform-hmr/issues/5#issuecomment-142313637 46 | query: { 47 | "presets": ["es2015", "react", "stage-0"], 48 | "plugins": [ 49 | "transform-react-remove-prop-types", 50 | "transform-react-constant-elements", 51 | "transform-react-inline-elements" 52 | ] 53 | }, 54 | include: path.join(__dirname, 'app'), 55 | exclude: path.join(__dirname, '/node_modules/') 56 | }, 57 | { test: /\.json$/, loader: "json-loader" }, 58 | { test: /\.css$/, loader: "null-loader" } 59 | ], 60 | }, 61 | resolve: { 62 | extensions: ['', '.js', '.jsx', '.css'], 63 | modulesDirectories: [ 64 | 'app', 'node_modules' 65 | ] 66 | }, 67 | node: { 68 | fs: "empty" 69 | }, 70 | watch: true 71 | }, 72 | 73 | webpackMiddleware: { 74 | // webpack-dev-middleware configuration 75 | noInfo: true 76 | }, 77 | 78 | webpackServer: { 79 | noInfo: true // Do not spam the console when running in karma 80 | }, 81 | 82 | plugins: [ 83 | 'karma-jsdom-launcher', 84 | 'karma-mocha', 85 | 'karma-sinon', 86 | 'karma-mocha-reporter', 87 | 'karma-sourcemap-loader', 88 | 'karma-webpack', 89 | ], 90 | 91 | // test results reporter to use 92 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage', 93 | // 'mocha' (added in plugins) 94 | reporters: ['mocha'], 95 | 96 | // level of logging 97 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 98 | logLevel: config.LOG_INFO, 99 | }); 100 | }; -------------------------------------------------------------------------------- /app/templates/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "app/server.jsx", 5 | "app/routes.jsx", 6 | "app/helmconfig.js", 7 | "app/components/Meta.jsx", 8 | "server", 9 | "webpack" 10 | ], 11 | "exec": "npm run build:dev && node server/index.js" 12 | } -------------------------------------------------------------------------------- /app/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "<%= name %>", 4 | "version": "0.0.1", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint app", 8 | "clean": "rimraf public", 9 | "start": "cross-env NODE_ENV=production node server/index.js", 10 | "dev": "cross-env NODE_ENV=development nodemon", 11 | "build:dev": "cross-env NODE_ENV=development webpack --colors --config ./webpack/webpack.config.dev-server.js", 12 | "build:prod": "cross-env NODE_ENV=production webpack --colors --config ./webpack/webpack.config.prod.js", 13 | "build": "npm run clean && npm run build:prod", 14 | "test": "cross-env NODE_ENV=test karma start", 15 | "test:watch": "cross-env NODE_ENV=test npm test -- --watch --no-single-run", 16 | "postinstall": "npm run build" 17 | }, 18 | "devDependencies": { 19 | "expect": "^1.15.2", 20 | "karma": "^0.13.22", 21 | "karma-jsdom-launcher": "^3.0.0", 22 | "karma-mocha": "^0.2.2", 23 | "karma-mocha-reporter": "^2.0.0", 24 | "karma-sinon": "^1.0.4", 25 | "karma-sourcemap-loader": "^0.3.7", 26 | "karma-webpack": "^1.7.0", 27 | "mocha": "^2.4.5", 28 | "nock": "^7.2.2", 29 | "nodemon": "^1.9.1", 30 | "null-loader": "^0.1.1", 31 | "react-addons-test-utils": "^0.14.7", 32 | "react-stateless-wrapper": "^1.0.2", 33 | "react-transform-catch-errors": "^1.0.2", 34 | "redux-mock-store": "1.0.2", 35 | "sinon": "^1.17.3" 36 | }, 37 | "dependencies": { 38 | "autoprefixer": "^6.3.3", 39 | "axios": "^0.9.1", 40 | "babel": "^6.5.2", 41 | "babel-core": "^6.7.2", 42 | "babel-eslint": "^5.0.0", 43 | "babel-loader": "^6.2.4", 44 | "babel-plugin-react-transform": "^2.0.2", 45 | "babel-plugin-transform-react-constant-elements": "^6.5.0", 46 | "babel-plugin-transform-react-inline-elements": "^6.6.5", 47 | "babel-plugin-transform-react-remove-prop-types": "^0.2.3", 48 | "babel-preset-es2015": "^6.6.0", 49 | "babel-preset-react": "^6.5.0", 50 | "babel-preset-react-hmre": "^1.1.1", 51 | "babel-preset-stage-0": "^6.5.0", 52 | "bcrypt-nodejs": "0.0.3", 53 | "body-parser": "^1.15.0", 54 | "classnames": "^2.2.3", 55 | "connect-mongo": "^1.1.0", 56 | "cross-env": "^1.0.7", 57 | "css-loader": "^0.23.1", 58 | "deep-equal": "^1.0.1", 59 | "es6-promise": "^3.1.2", 60 | "eslint": "^2.4.0", 61 | "eslint-config-airbnb": "^6.1.0", 62 | "eslint-plugin-react": "^4.2.1", 63 | "express": "^4.13.4", 64 | "express-flash": "0.0.2", 65 | "express-session": "^1.13.0", 66 | "extract-text-webpack-plugin": "^1.0.1", 67 | "file-loader": "^0.8.5", 68 | "helmet": "^1.3.0", 69 | "immutable": "^3.7.6", 70 | "inline-environment-variables-webpack-plugin": "0.0.2", 71 | "invariant": "^2.2.1", 72 | "isomorphic-fetch": "^2.2.1", 73 | "json-loader": "^0.5.4", 74 | "kerberos": "0.0.19", 75 | "lodash": "^4.6.1", 76 | "method-override": "^2.3.5", 77 | "mongoose": "^4.4.7", 78 | "node-libs-browser": "^1.0.0", 79 | "passport": "^0.3.2", 80 | "passport-google-oauth": "^1.0.0", 81 | "passport-local": "^1.0.0", 82 | "postcss-import": "^8.0.2", 83 | "postcss-loader": "^0.8.2", 84 | "postcss-mixins": "^4.0.1", 85 | "postcss-nested": "^1.0.0", 86 | "postcss-reporter": "^1.3.3", 87 | "postcss-simple-vars": "^1.2.0", 88 | "react": "^0.14.7", 89 | "react-dom": "^0.14.7", 90 | "react-helmet": "2.3.1", 91 | "react-redux": "^4.4.1", 92 | "react-router": "^2.0.1", 93 | "react-router-redux": "^4.0.0", 94 | "react-transform-hmr": "^1.0.4", 95 | "redux": "^3.3.1", 96 | "redux-devtools": "^3.1.1", 97 | "redux-logger": "^2.6.1", 98 | "redux-thunk": "^2.0.1", 99 | "rimraf": "^2.5.2", 100 | "spark-md5": "^2.0.2", 101 | "style-loader": "^0.13.0", 102 | "url-loader": "^0.5.7", 103 | "warning": "^2.1.0", 104 | "webpack": "^1.12.14", 105 | "webpack-dev-middleware": "^1.5.1", 106 | "webpack-hot-middleware": "^2.10.0" 107 | }, 108 | "engines": { 109 | "node": "5.x" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/templates/server/config/express.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var session = require('express-session'); 3 | var bodyParser = require('body-parser'); 4 | var path = require('path'); 5 | var secrets = require('./secrets'); 6 | var flash = require('express-flash'); 7 | var methodOverride = require('method-override'); 8 | 9 | module.exports = function (app) { 10 | app.set('port', (process.env.PORT || 3000)); 11 | 12 | // X-Powered-By header has no functional value. 13 | // Keeping it makes it easier for an attacker to build the site's profile 14 | // It can be removed safely 15 | app.disable('x-powered-by'); 16 | app.set('views', path.join(__dirname, '..', 'views')); 17 | 18 | app.set('view cache', false); 19 | 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-form-urlencoded 22 | app.use(methodOverride()); 23 | app.use(express.static(path.join(__dirname, '../..', 'public'))); 24 | 25 | // I am adding this here so that the Heroku deploy will work 26 | // Indicates the app is behind a front-facing proxy, 27 | // and to use the X-Forwarded-* headers to determine the connection and the IP address of the client. 28 | // NOTE: X-Forwarded-* headers are easily spoofed and the detected IP addresses are unreliable. 29 | // trust proxy is disabled by default. 30 | // When enabled, Express attempts to determine the IP address of the client connected through the front-facing proxy, or series of proxies. 31 | // The req.ips property, then, contains an array of IP addresses the client is connected through. 32 | // To enable it, use the values described in the trust proxy options table. 33 | // The trust proxy setting is implemented using the proxy-addr package. For more information, see its documentation. 34 | // loopback - 127.0.0.1/8, ::1/128 35 | app.set('trust proxy', 'loopback'); 36 | // Create a session middleware with the given options 37 | // Note session data is not saved in the cookie itself, just the session ID. Session data is stored server-side. 38 | // Options: resave: forces the session to be saved back to the session store, even if the session was never 39 | // modified during the request. Depending on your store this may be necessary, but it can also 40 | // create race conditions where a client has two parallel requests to your server and changes made 41 | // to the session in one request may get overwritten when the other request ends, even if it made no 42 | // changes(this behavior also depends on what store you're using). 43 | // saveUnitialized: Forces a session that is uninitialized to be saved to the store. A session is uninitialized when 44 | // it is new but not modified. Choosing false is useful for implementing login sessions, reducing server storage 45 | // usage, or complying with laws that require permission before setting a cookie. Choosing false will also help with 46 | // race conditions where a client makes multiple parallel requests without a session 47 | // secret: This is the secret used to sign the session ID cookie. 48 | // name: The name of the session ID cookie to set in the response (and read from in the request). 49 | // cookie: Please note that secure: true is a recommended option. 50 | // However, it requires an https-enabled website, i.e., HTTPS is necessary for secure cookies. 51 | // If secure is set, and you access your site over HTTP, the cookie will not be set. 52 | var sess = { 53 | resave: true, 54 | saveUninitialized: false, 55 | secret: secrets.sessionSecret, 56 | proxy: true, // The "X-Forwarded-Proto" header will be used. 57 | name: 'sessionId', 58 | // Add HTTPOnly, Secure attributes on Session Cookie 59 | // If secure is set, and you access your site over HTTP, the cookie will not be set 60 | cookie: { 61 | httpOnly: true, 62 | secure: false, 63 | } 64 | }; 65 | 66 | var node_env = process.env.NODE_ENV; 67 | console.log('--------------------------'); 68 | console.log('===> 😊 Starting Server . . .'); 69 | console.log('===> Environment: ' + node_env); 70 | if(node_env === 'production') { 71 | console.log('===> 🚦 Note: In order for authentication to work in production'); 72 | console.log('===> you will need a secure HTTPS connection'); 73 | sess.cookie.secure = true; // Serve secure cookies 74 | } 75 | 76 | app.use(session(sess)); 77 | 78 | app.use(flash()); 79 | 80 | }; -------------------------------------------------------------------------------- /app/templates/server/config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Routes for express app 3 | */ 4 | var express = require('express'); 5 | var _ = require('lodash'); 6 | var path = require('path'); 7 | 8 | var App = require(path.resolve(__dirname, '../../', 'public', 'assets', 'server.js'))['default']; 9 | 10 | module.exports = function(app) { 11 | // app.put('/myRoute', myController.handlerMethod); 12 | // app.delete('/otherRoute', routeController.handlerMethod); 13 | 14 | // This is where the magic happens. We take the locals data we have already 15 | // fetched and seed our stores with data. 16 | // App is a function that requires store data and url to initialize and return the React-rendered html string 17 | app.get('*', function (req, res, next) { 18 | App(req, res); 19 | }); 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /app/templates/server/config/secrets.js: -------------------------------------------------------------------------------- 1 | /** Important **/ 2 | /** You should not be committing this file to GitHub **/ 3 | /** Repeat: DO! NOT! COMMIT! THIS! FILE! TO! YOUR! REPO! **/ 4 | 5 | module.exports = { 6 | sessionSecret: process.env.SESSION_SECRET || 'Your Session Secret goes here', 7 | // ... 8 | // db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/ReactWebpackNode' 9 | }; 10 | -------------------------------------------------------------------------------- /app/templates/server/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiegor/generator-react-webpack-node/b0b30e095daaf047f56be89e68463c5ed403c057/app/templates/server/controllers/.gitkeep -------------------------------------------------------------------------------- /app/templates/server/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var fs = require('fs'); 3 | var secrets = require('./config/secrets'); 4 | var webpack = require('webpack'); 5 | var app = express(); 6 | 7 | // Bootstrap models 8 | fs.readdirSync(__dirname + '/models').forEach(function(file) { 9 | if(~file.indexOf('.js')) require(__dirname + '/models/' + file); 10 | }); 11 | 12 | var isDev = process.env.NODE_ENV === 'development'; 13 | 14 | if (isDev) { 15 | var config = require('../webpack/webpack.config.dev-client.js'); 16 | var compiler = webpack(config); 17 | 18 | app.use(require('webpack-dev-middleware')(compiler, { 19 | noInfo: true, 20 | publicPath: config.output.publicPath 21 | })); 22 | 23 | app.use(require('webpack-hot-middleware')(compiler)); 24 | } 25 | 26 | // Bootstrap application settings 27 | require('./config/express')(app); 28 | 29 | // Bootstrap routes 30 | require('./config/routes')(app); 31 | 32 | app.listen(app.get('port')); 33 | -------------------------------------------------------------------------------- /app/templates/server/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiegor/generator-react-webpack-node/b0b30e095daaf047f56be89e68463c5ed403c057/app/templates/server/models/.gitkeep -------------------------------------------------------------------------------- /app/templates/tests.webpack.js: -------------------------------------------------------------------------------- 1 | // require.context(directory, useSubdirectories = false, regExp = /^\.\//) 2 | var context = require.context('./app', true, /-test.js$/); 3 | context.keys().forEach(context); 4 | -------------------------------------------------------------------------------- /app/templates/webpack/webpack.config.dev-client.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var assetsPath = path.join(__dirname, '..', 'public', 'assets'); 4 | var hotMiddlewareScript = 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true'; 5 | 6 | var commonLoaders = [ 7 | { 8 | /* 9 | * TC39 categorises proposals for babel in 4 stages 10 | * Read more http://babeljs.io/docs/usage/experimental/ 11 | */ 12 | test: /\.js$|\.jsx$/, 13 | loader: 'babel', 14 | // Reason why we put this here instead of babelrc 15 | // https://github.com/gaearon/react-transform-hmr/issues/5#issuecomment-142313637 16 | query: { 17 | "presets": ["react-hmre", "es2015", "react", "stage-0"] 18 | }, 19 | include: path.join(__dirname, '..', 'app'), 20 | exclude: path.join(__dirname, '/node_modules/') 21 | }, 22 | { 23 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/, 24 | loader: 'url', 25 | query: { 26 | name: '[hash].[ext]', 27 | limit: 10000, 28 | } 29 | }, 30 | { test: /\.html$/, loader: 'html-loader' } 31 | ]; 32 | 33 | var postCSSConfig = function() { 34 | return [ 35 | require('postcss-import')({ 36 | path: path.join(__dirname, '..', 'app', 'css'), 37 | // addDependencyTo is used for hot-reloading in webpack 38 | addDependencyTo: webpack 39 | }), 40 | require('postcss-simple-vars')(), 41 | // Unwrap nested rules like how Sass does it 42 | require('postcss-nested')(), 43 | // parse CSS and add vendor prefixes to CSS rules 44 | require('autoprefixer')({ 45 | browsers: ['last 2 versions', 'IE > 8'] 46 | }), 47 | // A PostCSS plugin to console.log() the messages registered by other 48 | // PostCSS plugins 49 | require('postcss-reporter')({ 50 | clearMessages: true 51 | }) 52 | ]; 53 | }; 54 | 55 | module.exports = { 56 | // eval - Each module is executed with eval and //@ sourceURL. 57 | devtool: 'eval', 58 | // The configuration for the client 59 | name: 'browser', 60 | /* The entry point of the bundle 61 | * Entry points for multi page app could be more complex 62 | * A good example of entry points would be: 63 | * entry: { 64 | * pageA: "./pageA", 65 | * pageB: "./pageB", 66 | * pageC: "./pageC", 67 | * adminPageA: "./adminPageA", 68 | * adminPageB: "./adminPageB", 69 | * adminPageC: "./adminPageC" 70 | * } 71 | * 72 | * We can then proceed to optimize what are the common chunks 73 | * plugins: [ 74 | * new CommonsChunkPlugin("admin-commons.js", ["adminPageA", "adminPageB"]), 75 | * new CommonsChunkPlugin("common.js", ["pageA", "pageB", "admin-commons.js"], 2), 76 | * new CommonsChunkPlugin("c-commons.js", ["pageC", "adminPageC"]); 77 | * ] 78 | */ 79 | context: path.join(__dirname, '..', 'app'), 80 | // Multiple entry with hot loader 81 | // https://github.com/glenjamin/webpack-hot-middleware/blob/master/example/webpack.config.multientry.js 82 | entry: { 83 | app: ['./client', hotMiddlewareScript] 84 | }, 85 | output: { 86 | // The output directory as absolute path 87 | path: assetsPath, 88 | // The filename of the entry chunk as relative path inside the output.path directory 89 | filename: '[name].js', 90 | // The output path from the view of the Javascript 91 | publicPath: '/assets/' 92 | }, 93 | module: { 94 | loaders: commonLoaders.concat([ 95 | { test: /\.css$/, 96 | loader: 'style!css?module&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader' 97 | } 98 | ]) 99 | }, 100 | resolve: { 101 | extensions: ['', '.js', '.jsx', '.css'], 102 | modulesDirectories: [ 103 | 'app', 'node_modules' 104 | ] 105 | }, 106 | plugins: [ 107 | new webpack.HotModuleReplacementPlugin(), 108 | new webpack.NoErrorsPlugin(), 109 | new webpack.DefinePlugin({ 110 | __DEVCLIENT__: true, 111 | __DEVSERVER__: false 112 | }) 113 | ], 114 | postcss: postCSSConfig 115 | }; -------------------------------------------------------------------------------- /app/templates/webpack/webpack.config.dev-server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var assetsPath = path.join(__dirname, '..', 'public', 'assets'); 4 | 5 | var commonLoaders = [ 6 | { 7 | /* 8 | * TC39 categorises proposals for babel in 4 stages 9 | * Read more http://babeljs.io/docs/usage/experimental/ 10 | */ 11 | test: /\.js$|\.jsx$/, 12 | loader: 'babel', 13 | // Reason why we put this here instead of babelrc 14 | // https://github.com/gaearon/react-transform-hmr/issues/5#issuecomment-142313637 15 | query: { 16 | "presets": ["es2015", "react", "stage-0"] 17 | }, 18 | include: path.join(__dirname, '..', 'app'), 19 | exclude: path.join(__dirname, '/node_modules/') 20 | }, 21 | { test: /\.json$/, loader: "json-loader" }, 22 | { 23 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/, 24 | loader: 'url', 25 | query: { 26 | name: '[hash].[ext]', 27 | limit: 10000, 28 | } 29 | }, 30 | { test: /\.html$/, loader: 'html-loader' } 31 | ]; 32 | 33 | module.exports = { 34 | // The configuration for the server-side rendering 35 | name: "server-side rendering", 36 | context: path.join(__dirname, "..", "app"), 37 | entry: { 38 | server: "./server" 39 | }, 40 | target: "node", 41 | output: { 42 | // The output directory as absolute path 43 | path: assetsPath, 44 | // The filename of the entry chunk as relative path inside the output.path directory 45 | filename: "server.js", 46 | // The output path from the view of the Javascript 47 | publicPath: "/assets/", 48 | libraryTarget: "commonjs2" 49 | }, 50 | module: { 51 | loaders: commonLoaders.concat([ 52 | { 53 | test: /\.css$/, 54 | loader: 'css/locals?module&localIdentName=[name]__[local]___[hash:base64:5]' 55 | } 56 | ]) 57 | }, 58 | resolve: { 59 | extensions: ['', '.js', '.jsx', '.css'], 60 | modulesDirectories: [ 61 | "app", "node_modules" 62 | ] 63 | }, 64 | plugins: [ 65 | new webpack.DefinePlugin({ 66 | __DEVCLIENT__: false, 67 | __DEVSERVER__: true 68 | }) 69 | ] 70 | }; -------------------------------------------------------------------------------- /app/templates/webpack/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 3 | var InlineEnviromentVariablesPlugin = require('inline-environment-variables-webpack-plugin'); 4 | var webpack = require("webpack"); 5 | 6 | var assetsPath = path.join(__dirname, "..", "public", "assets"); 7 | var publicPath = "/assets/"; 8 | 9 | var commonLoaders = [ 10 | { 11 | /* 12 | * TC39 categorises proposals for babel in 4 stages 13 | * Read more http://babeljs.io/docs/usage/experimental/ 14 | */ 15 | test: /\.js$|\.jsx$/, 16 | loader: 'babel', 17 | // Reason why we put this here instead of babelrc 18 | // https://github.com/gaearon/react-transform-hmr/issues/5#issuecomment-142313637 19 | query: { 20 | "presets": ["es2015", "react", "stage-0"], 21 | "plugins": [ 22 | "transform-react-remove-prop-types", 23 | "transform-react-constant-elements", 24 | "transform-react-inline-elements" 25 | ] 26 | }, 27 | include: path.join(__dirname, '..', 'app'), 28 | exclude: path.join(__dirname, '/node_modules/') 29 | }, 30 | { test: /\.json$/, loader: "json-loader" }, 31 | { 32 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/, 33 | loader: 'url', 34 | query: { 35 | name: '[hash].[ext]', 36 | limit: 10000, 37 | } 38 | }, 39 | { test: /\.css$/, 40 | loader: ExtractTextPlugin.extract('style-loader', 'css-loader?module!postcss-loader') 41 | } 42 | ]; 43 | 44 | var postCSSConfig = function() { 45 | return [ 46 | require('postcss-import')(), 47 | // Note: you must set postcss-mixins before simple-vars and nested 48 | require('postcss-mixins')(), 49 | require('postcss-simple-vars')(), 50 | // Unwrap nested rules like how Sass does it 51 | require('postcss-nested')(), 52 | // parse CSS and add vendor prefixes to CSS rules 53 | require('autoprefixer')({ 54 | browsers: ['last 2 versions', 'IE > 8'] 55 | }), 56 | // A PostCSS plugin to console.log() the messages registered by other 57 | // PostCSS plugins 58 | require('postcss-reporter')({ 59 | clearMessages: true 60 | }) 61 | ]; 62 | }; 63 | 64 | module.exports = [ 65 | { 66 | // The configuration for the client 67 | name: "browser", 68 | /* The entry point of the bundle 69 | * Entry points for multi page app could be more complex 70 | * A good example of entry points would be: 71 | * entry: { 72 | * pageA: "./pageA", 73 | * pageB: "./pageB", 74 | * pageC: "./pageC", 75 | * adminPageA: "./adminPageA", 76 | * adminPageB: "./adminPageB", 77 | * adminPageC: "./adminPageC" 78 | * } 79 | * 80 | * We can then proceed to optimize what are the common chunks 81 | * plugins: [ 82 | * new CommonsChunkPlugin("admin-commons.js", ["adminPageA", "adminPageB"]), 83 | * new CommonsChunkPlugin("common.js", ["pageA", "pageB", "admin-commons.js"], 2), 84 | * new CommonsChunkPlugin("c-commons.js", ["pageC", "adminPageC"]); 85 | * ] 86 | */ 87 | // A SourceMap is emitted. 88 | devtool: "source-map", 89 | context: path.join(__dirname, "..", "app"), 90 | entry: { 91 | app: "./client" 92 | }, 93 | output: { 94 | // The output directory as absolute path 95 | path: assetsPath, 96 | // The filename of the entry chunk as relative path inside the output.path directory 97 | filename: "[name].js", 98 | // The output path from the view of the Javascript 99 | publicPath: publicPath 100 | 101 | }, 102 | 103 | module: { 104 | loaders: commonLoaders 105 | }, 106 | resolve: { 107 | extensions: ['', '.js', '.jsx', '.css'], 108 | modulesDirectories: [ 109 | "app", "node_modules" 110 | ] 111 | }, 112 | plugins: [ 113 | // extract inline css from modules into separate files 114 | new ExtractTextPlugin("styles/main.css"), 115 | new webpack.optimize.UglifyJsPlugin({ 116 | compressor: { 117 | warnings: false 118 | } 119 | }), 120 | new webpack.DefinePlugin({ 121 | __DEVCLIENT__: false, 122 | __DEVSERVER__: false 123 | }), 124 | new InlineEnviromentVariablesPlugin({ NODE_ENV: 'production' }) 125 | ], 126 | postcss: postCSSConfig 127 | }, { 128 | // The configuration for the server-side rendering 129 | name: "server-side rendering", 130 | context: path.join(__dirname, "..", "app"), 131 | entry: { 132 | server: "./server" 133 | }, 134 | target: "node", 135 | output: { 136 | // The output directory as absolute path 137 | path: assetsPath, 138 | // The filename of the entry chunk as relative path inside the output.path directory 139 | filename: "server.js", 140 | // The output path from the view of the Javascript 141 | publicPath: publicPath, 142 | libraryTarget: "commonjs2" 143 | }, 144 | module: { 145 | loaders: commonLoaders 146 | }, 147 | resolve: { 148 | extensions: ['', '.js', '.jsx', '.css'], 149 | modulesDirectories: [ 150 | "app", "node_modules" 151 | ] 152 | }, 153 | plugins: [ 154 | // Order the modules and chunks by occurrence. 155 | // This saves space, because often referenced modules 156 | // and chunks get smaller ids. 157 | new webpack.optimize.OccurenceOrderPlugin(), 158 | new ExtractTextPlugin("styles/main.css"), 159 | new webpack.optimize.UglifyJsPlugin({ 160 | compressor: { 161 | warnings: false 162 | } 163 | }), 164 | new webpack.DefinePlugin({ 165 | __DEVCLIENT__: false, 166 | __DEVSERVER__: false 167 | }), 168 | new InlineEnviromentVariablesPlugin({ NODE_ENV: 'production' }) 169 | ], 170 | postcss: postCSSConfig 171 | } 172 | ]; -------------------------------------------------------------------------------- /component/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var camelcase = require('camelcase'); 4 | var generators = require('yeoman-generator'); 5 | 6 | module.exports = generators.Base.extend({ 7 | constructor: function () { 8 | generators.Base.apply(this, arguments); 9 | 10 | this.argument('name', { 11 | type: String, 12 | required: true, 13 | description: 'Name of the component' 14 | }); 15 | }, 16 | 17 | writing: function () { 18 | this.template( 19 | 'component.js', 20 | path.join('app', 'components', camelcase(this.name) + '.js'), 21 | this.context 22 | ); 23 | 24 | this.template( 25 | 'component.scss', 26 | path.join('app', 'scss', 'components', '_' + this.name + '.scss'), 27 | this.context 28 | ); 29 | } 30 | }); -------------------------------------------------------------------------------- /component/templates/component.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import styles from 'scss/components/_<%= name %>'; 3 | 4 | const cx = classNames.bind(styles); 5 | 6 | export default class <%= name %> extends Component { 7 | render() { 8 | return ( 9 |
<%= name %> component!
10 | ); 11 | } 12 | } -------------------------------------------------------------------------------- /component/templates/component.scss: -------------------------------------------------------------------------------- 1 | .<%= name %> { 2 | // .. 3 | } -------------------------------------------------------------------------------- /constant/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var camelcase = require('camelcase'); 4 | var generators = require('yeoman-generator'); 5 | var htmlWiring = require('html-wiring'); 6 | var readFileAsString = htmlWiring.readFileAsString; 7 | var writeFileFromString = htmlWiring.writeFileFromString; 8 | 9 | module.exports = generators.Base.extend({ 10 | constructor: function () { 11 | generators.Base.apply(this, arguments); 12 | 13 | this.argument('namespace', { 14 | type: String, 15 | required: true, 16 | description: 'Name of the namespace' 17 | }); 18 | 19 | this.argument('name', { 20 | type: String, 21 | required: true, 22 | description: 'Name of the constant' 23 | }); 24 | }, 25 | 26 | writing: function () { 27 | var constantsFilePath = path.join('app', 'constants', this.namespace + '.js'); 28 | var file = readFileAsString(constantsFilePath); 29 | 30 | file += "\n export const " + this.name + " = '" + this.name + "';"; 31 | 32 | writeFileFromString(file,constantsFilePath); 33 | } 34 | }); -------------------------------------------------------------------------------- /container/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var camelcase = require('camelcase'); 4 | var generators = require('yeoman-generator'); 5 | 6 | module.exports = generators.Base.extend({ 7 | constructor: function () { 8 | generators.Base.apply(this, arguments); 9 | 10 | this.argument('name', { 11 | type: String, 12 | required: true, 13 | description: 'Name of the container' 14 | }); 15 | }, 16 | 17 | writing: function () { 18 | this.template( 19 | 'container.js', 20 | path.join('app', 'containers', camelcase(this.name) + '.js'), 21 | this.context 22 | ); 23 | } 24 | }); -------------------------------------------------------------------------------- /container/templates/container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /* 4 | * Note: This is kept as a container-level component, 5 | * i.e. We should keep this as the container that does the data-fetching 6 | * and dispatching of actions if you decide to have any sub-components. 7 | */ 8 | export default class <%= name %> extends React.Component { 9 | render() { 10 | return ( 11 |
<%= name %> container!
12 | ); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-react-webpack-node", 3 | "version": "0.1.3", 4 | "description": "Yeoman generator for react-webpack-node", 5 | "main": "app/index.js", 6 | "repository": "iiegor/generator-react-webpack-node", 7 | "author": { 8 | "name": "Iegor Azuaga", 9 | "email": "dextrackmedia@gmail.com" 10 | }, 11 | "engines": { 12 | "node": ">=0.10.0" 13 | }, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "keywords": [ 18 | "yeoman-generator", 19 | "react", 20 | "webpack", 21 | "node", 22 | "flux", 23 | "redux", 24 | "alt" 25 | ], 26 | "files": [ 27 | "app", 28 | "component", 29 | "constant", 30 | "container", 31 | "reducer" 32 | ], 33 | "dependencies": { 34 | "camelcase": "^2.0.1", 35 | "chalk": "^1.0.0", 36 | "html-wiring": "^1.2.0", 37 | "underscore.string": "^3.0.2", 38 | "yeoman-generator": "^0.20.1", 39 | "yo": ">=1.0.0", 40 | "yosay": "^1.0.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /reducer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var chalk = require('chalk'); 4 | var camelcase = require('camelcase'); 5 | var generators = require('yeoman-generator'); 6 | 7 | module.exports = generators.Base.extend({ 8 | constructor: function () { 9 | generators.Base.apply(this, arguments); 10 | 11 | this.argument('name', { 12 | type: String, 13 | required: true, 14 | description: 'Name of the reducer' 15 | }); 16 | }, 17 | 18 | writing: function () { 19 | this.log(chalk.yellow(' (!) Remember to add the reducer to the reducers index file')); 20 | 21 | this.template( 22 | 'reducer.js', 23 | path.join('app', 'reducers', camelcase(this.name) + '.js'), 24 | this.context 25 | ); 26 | } 27 | }); -------------------------------------------------------------------------------- /reducer/templates/reducer.js: -------------------------------------------------------------------------------- 1 | // import { MY_CONSTANT } from 'constants'; 2 | 3 | export default function <%= name %>(state = {}, action) { 4 | switch (action.type) { 5 | /* .. */ 6 | 7 | default: 8 | return state; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiegor/generator-react-webpack-node/b0b30e095daaf047f56be89e68463c5ed403c057/tests/.gitkeep --------------------------------------------------------------------------------