├── src ├── assets │ └── images │ │ ├── .gitkeep │ │ └── react-logo.png ├── components │ ├── index.js │ └── Image.js ├── utils │ ├── Alt.js │ ├── index.js │ ├── Storage.js │ └── GenerateRoute.js ├── index.html ├── actions │ └── UserActions.js ├── pages │ ├── account.js │ ├── root.js │ └── home.js ├── routes.js ├── index.js └── stores │ └── UserStore.js ├── .gitignore ├── .babelrc ├── LICENSE ├── .eslintrc ├── webpack.config.js ├── package.json ├── webpack.production.config.js ├── server.js └── README.md /src/assets/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Image } from './Image'; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | build 5 | .idea 6 | -------------------------------------------------------------------------------- /src/utils/Alt.js: -------------------------------------------------------------------------------- 1 | import Alt from 'alt'; 2 | 3 | // Export a singleton instance of Alt 4 | export default new Alt(); -------------------------------------------------------------------------------- /src/assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ehesp/react-flux-webpack-boilerplate/HEAD/src/assets/images/react-logo.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["syntax-decorators", "transform-decorators-legacy", "add-module-exports"] 4 | } -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { default as Alt } from './Alt'; 2 | export { default as GenerateRoute } from './GenerateRoute'; 3 | export { default as Storage } from './Storage'; -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /src/actions/UserActions.js: -------------------------------------------------------------------------------- 1 | import { Alt } from 'utils'; 2 | 3 | class UsersActions { 4 | 5 | add(name) { 6 | return name; 7 | } 8 | 9 | clear() { 10 | return null; 11 | } 12 | 13 | } 14 | 15 | export default Alt.createActions(UsersActions); 16 | -------------------------------------------------------------------------------- /src/pages/account.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Account extends Component { 4 | 5 | render() { 6 | return ( 7 |
8 | Account Page 9 |
10 | ); 11 | } 12 | } 13 | 14 | export default Account; -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router'; 3 | import { GenerateRoute } from './utils'; 4 | 5 | export default ( 6 | 7 | { GenerateRoute({ 8 | paths: ['/', '/home'], 9 | component: require('./pages/Home') 10 | }) } 11 | { GenerateRoute({ 12 | paths: ['/account'], 13 | component: require('./pages/Account') 14 | }) } 15 | 16 | ); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Router from 'react-router'; 4 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 5 | import routes from './routes.js'; 6 | 7 | const router = { 8 | routes, 9 | history: createBrowserHistory(), 10 | createElement: (component, props) => { 11 | return React.createElement(component, { ...props }); 12 | } 13 | }; 14 | 15 | ReactDOM.render( 16 | React.createElement(Router, { ...router }), 17 | document.getElementById('root') 18 | ); -------------------------------------------------------------------------------- /src/pages/root.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | class Root extends Component { 4 | 5 | static propTypes = { 6 | children: PropTypes.node.isRequired 7 | }; 8 | 9 | /* 10 | This component wraps your entire application. 11 | 12 | You can pass in props, context etc here. 13 | */ 14 | 15 | render() { 16 | return ( 17 |
18 | { this.props.children } 19 |
20 | ); 21 | } 22 | } 23 | 24 | export default Root; -------------------------------------------------------------------------------- /src/stores/UserStore.js: -------------------------------------------------------------------------------- 1 | import UserActions from 'actions/UserActions'; 2 | import { Alt, Storage } from 'utils'; 3 | 4 | @Storage 5 | class UserStore { 6 | 7 | constructor() { 8 | this.users = this.get('users') || []; 9 | 10 | this.bindListeners({ 11 | onAdd: UserActions.ADD, 12 | onClear: UserActions.CLEAR 13 | }); 14 | } 15 | 16 | onAdd = (user) => { 17 | const users = [...this.users, user]; 18 | this.users = users; 19 | this.set('users', users); 20 | }; 21 | 22 | onClear() { 23 | this.users = []; 24 | this.del('users'); 25 | }; 26 | 27 | } 28 | 29 | export default Alt.createStore(UserStore, 'UserStore'); -------------------------------------------------------------------------------- /src/components/Image.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /** 4 | * A global Image component. Loads an image in by file name 5 | * from a given path, and requires an alt string for accessibility. 6 | */ 7 | class Image extends Component { 8 | 9 | static propTypes = { 10 | file: PropTypes.string.isRequired, 11 | alt: PropTypes.string.isRequired, 12 | style: PropTypes.object 13 | }; 14 | 15 | render() { 16 | const { file, style, alt } = this.props; 17 | 18 | const image = require(`assets/images/${file}`); 19 | return ( 20 | { 21 | ); 22 | } 23 | } 24 | 25 | export default Image; -------------------------------------------------------------------------------- /src/utils/Storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A decorator to assist with local storage 3 | * @param Class 4 | */ 5 | 6 | export default function (Class) { 7 | Object.defineProperties(Class.prototype, { 8 | 9 | 'get': { 10 | value: function(id) { 11 | let payload; 12 | try { 13 | payload = JSON.parse(localStorage.getItem(id)); 14 | } catch (e) { 15 | return localStorage.getItem(id); 16 | } 17 | 18 | return payload; 19 | } 20 | }, 21 | 22 | 'set': { 23 | value: function(id, payload) { 24 | if (typeof payload === 'object') { 25 | localStorage.setItem(id, JSON.stringify(payload)); 26 | } else { 27 | localStorage.setItem(id, payload); 28 | } 29 | } 30 | }, 31 | 32 | 'del': { 33 | value: function(id) { 34 | return localStorage.removeItem(id); 35 | } 36 | } 37 | 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Elliot Hesp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils/GenerateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | /** 5 | * Helper to generate a new react-router route, which supports multiple paths for a single 6 | * route 7 | * 8 | * See: https://github.com/rackt/react-router/blob/latest/docs/Introduction.md#with-react-router 9 | * 10 | * @param index 11 | * @param key 12 | * @param paths 13 | * @param component 14 | * @param children 15 | * @returns {*} 16 | */ 17 | export default function ({ index, key, paths, component, children }) { 18 | if (index) { 19 | if (!key) { 20 | console.warn('IndexRoute requires a custom key param'); 21 | } 22 | const props = { 23 | component, 24 | key 25 | }; 26 | 27 | return ( 28 | 29 | ); 30 | } 31 | 32 | return paths.map(function (path) { 33 | const props = { 34 | key: path, 35 | path, 36 | component 37 | }; 38 | 39 | if (component.onEnter) { 40 | props.onEnter = component.onEnter; 41 | } 42 | 43 | if (!children || !children.length) { 44 | return ; 45 | } 46 | 47 | return ( 48 | 49 | { children } 50 | 51 | ); 52 | }); 53 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint-config-airbnb", 4 | "plugins": ["react"], 5 | "env": { 6 | "browser": true, 7 | "node": true, 8 | "es6": true, 9 | "mocha": true 10 | }, 11 | "globals": { 12 | "chai": true 13 | }, 14 | "rules": { 15 | "react/display-name": [0, { "acceptTranspilerName": true }], 16 | "react/jsx-curly-spacing": [2, "always"], 17 | "react/jsx-no-duplicate-props": 2, 18 | "react/jsx-no-undef": 2, 19 | "react/jsx-quotes": 0, 20 | "react/jsx-uses-react": 2, 21 | "react/jsx-uses-vars": 2, 22 | "react/no-did-mount-set-state": 2, 23 | "react/no-did-update-set-state": 2, 24 | "react/no-multi-comp": 2, 25 | "react/no-unknown-property": 0, 26 | "react/prop-types": 2, 27 | "react/react-in-jsx-scope": 2, 28 | "react/require-extension": 2, 29 | "react/self-closing-comp": 2, 30 | "react/wrap-multilines": 2, 31 | "react/sort-comp": 0, 32 | "dot-notation": 0, 33 | 34 | "quotes": [2, "single", "avoid-escape"], 35 | "jsx-quotes": [2, "prefer-single"], 36 | "comma-dangle": [2, "never"], 37 | "indent": 0, 38 | "object-curly-spacing": [2, "always"], 39 | "no-undef": 2, 40 | "no-underscore-dangle": 0, 41 | "func-names": 0, 42 | "no-else-return": 0, 43 | "no-console": 0, 44 | "no-throw-literal": 0, 45 | "id-length": 0, 46 | "space-before-function-paren": 1, 47 | "new-cap": 0, 48 | "radix": 0, 49 | "no-trailing-spaces": 0 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const buildPath = path.resolve(__dirname, 'public', 'build'); 4 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | 7 | module.exports = { 8 | devtool: 'eval', 9 | entry: [ 10 | 'webpack-dev-server/client?http://localhost:8080', 11 | 'webpack/hot/only-dev-server', 12 | './src/index' 13 | ], 14 | output: { 15 | path: buildPath, 16 | filename: 'bundle.js', 17 | publicPath: '/build/' 18 | }, 19 | resolve: { 20 | root: path.resolve('./src'), 21 | extensions: ['', '.js'] 22 | }, 23 | module: { 24 | loaders: [{ 25 | test: /\.js$/, 26 | loaders: ['react-hot', 'babel'], 27 | include: path.join(__dirname, 'src') 28 | }, { 29 | test: /\.(jpe?g|png|gif|svg)$/i, 30 | loaders: [ 31 | 'file?hash=sha512&digest=hex&name=[hash].[ext]', 32 | 'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false' 33 | ] 34 | }, { test: /\.css$/, 35 | loader: "style-loader!css-loader" 36 | }] 37 | }, 38 | plugins: [ 39 | new webpack.HotModuleReplacementPlugin(), 40 | new webpack.DefinePlugin({ 41 | 'process.env': { 42 | 'NODE_ENV': '"development"' 43 | } 44 | }) 45 | ] 46 | }; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-flux-webpack-boilerplate", 3 | "version": "1.0.0", 4 | "description": "React, Flux (Alt) & Webpack boilerplate", 5 | "scripts": { 6 | "dev": "node server.js", 7 | "prod": "npm run build && NODE_ENV=production node server.js", 8 | "lint": "eslint src", 9 | "build": "rm -rf build/* && NODE_ENV=production webpack -p --config webpack.production.config.js" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "babel-core": "^6.4.0", 14 | "babel-eslint": "^5.0.0-beta6", 15 | "babel-loader": "^6.2.1", 16 | "babel-plugin-add-module-exports": "^0.1.3-alpha", 17 | "babel-plugin-syntax-decorators": "^6.3.13", 18 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 19 | "babel-preset-es2015": "^6.3.13", 20 | "babel-preset-react": "^6.3.13", 21 | "babel-preset-react-hmre": "^1.0.1", 22 | "babel-preset-stage-0": "^6.3.13", 23 | "css-loader": "^0.23.1", 24 | "eslint": "^1.8.0", 25 | "eslint-config-airbnb": "^2.0.0", 26 | "eslint-plugin-react": "^3.6.3", 27 | "html-webpack-plugin": "^1.7.0", 28 | "image-webpack-loader": "^1.6.3", 29 | "react-hot-loader": "^1.3.0", 30 | "style-loader": "^0.13.0", 31 | "webpack": "^1.12.11", 32 | "webpack-dev-server": "^1.14.1" 33 | }, 34 | "dependencies": { 35 | "alt": "^0.18.1", 36 | "extract-text-webpack-plugin": "^1.0.1", 37 | "history": "^1.13.0", 38 | "react": "^0.14.0", 39 | "react-dom": "^0.14.0", 40 | "react-router": "^1.0.0-rc4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import { Image } from 'components'; 5 | import UserActions from 'actions/UserActions'; 6 | import UserStore from 'stores/UserStore'; 7 | 8 | class Home extends Component { 9 | 10 | constructor() { 11 | super(); 12 | this.state = { 13 | users: UserStore.getState().users, 14 | input: '' 15 | }; 16 | } 17 | 18 | componentDidMount() { 19 | UserStore.listen(this.handleUserStore); 20 | } 21 | 22 | componentWillUnmount() { 23 | UserStore.unlisten(this.handleUserStore); 24 | } 25 | 26 | handleUserStore = (store) => { 27 | this.setState({ 28 | users: store.users 29 | }); 30 | }; 31 | 32 | handleInputChange = (e) => { 33 | this.setState({ 34 | input: e.target.value 35 | }); 36 | }; 37 | 38 | addUser = () => { 39 | UserActions.add(this.state.input); 40 | this.setState({ 41 | input: '' 42 | }); 43 | }; 44 | 45 | render() { 46 | const { users, input } = this.state; 47 | return ( 48 |
49 |

Logo Homepage

50 |
51 | Go to my Account 52 |
53 |
54 | 55 | 56 |

List of users:

57 |
    58 | { 59 | users.map((user, i) => { 60 | return ( 61 |
  1. { user }
  2. 62 | ); 63 | }) 64 | } 65 |
66 |
67 | ); 68 | } 69 | } 70 | 71 | export default Home; -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const nodeModulesPath = path.resolve(__dirname, 'node_modules'); 7 | const buildPath = path.resolve(__dirname, 'build'); 8 | const mainPath = path.resolve(__dirname, 'src', 'index.js'); 9 | 10 | const pathToReact = path.resolve(nodeModulesPath, 'react/dist/react.min.js'); 11 | const pathToReactDom = path.resolve(nodeModulesPath, 'react-dom/dist/react-dom.min.js'); 12 | const pathToReactObj = path.resolve(nodeModulesPath, 'react/lib/Object.assign'); 13 | 14 | const config = { 15 | 16 | // We change to normal source mapping 17 | devtool: 'source-map', 18 | entry: mainPath, 19 | output: { 20 | path: buildPath, 21 | filename: '/dist/bundle.js' 22 | }, 23 | resolve: { 24 | root: path.resolve('./src'), 25 | extensions: ['', '.js'], 26 | alias: { 27 | 'react/lib/Object.assign': pathToReactObj, 28 | 'react': pathToReact, 29 | 'react-dom': pathToReactDom 30 | }, 31 | }, 32 | module: { 33 | loaders: [{ 34 | test: /\.js$/, 35 | loader: 'babel', 36 | exclude: [nodeModulesPath] 37 | }, { 38 | test: /\.(jpe?g|png|gif|svg|ico)$/i, 39 | loaders: [ 40 | 'file?hash=sha512&digest=hex&name=dist/images/[name]-[hash].[ext]', 41 | 'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false' 42 | ] 43 | }, { 44 | test: /\.css$/, 45 | loader: ExtractTextPlugin.extract("style-loader", "css-loader") 46 | }], 47 | noParse: [pathToReact] 48 | }, 49 | plugins: [ 50 | new ExtractTextPlugin('/dist/styles.css', { 51 | allChunks: true 52 | }), 53 | new HtmlWebpackPlugin({ 54 | templateContent: function () { 55 | var htmlTemplate = path.join(__dirname, 'src', 'index.html'); 56 | var template = fs.readFileSync(htmlTemplate, 'utf8'); 57 | return template.replace('', ''); 58 | }, 59 | hash: true, 60 | filename: 'index.html', 61 | inject: 'body' // Inject all scripts into the body 62 | }), 63 | new webpack.DefinePlugin({ 64 | 'process.env': { 65 | 'NODE_ENV': '"production"' 66 | } 67 | }) 68 | ] 69 | }; 70 | 71 | module.exports = config; 72 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const path = require('path'); 4 | const httpProxy = require('http-proxy'); 5 | 6 | const isProduction = process.env.NODE_ENV === 'production'; 7 | const port = isProduction ? process.env.PORT || 4000 : 3000; 8 | const publicPath = isProduction ? path.resolve(__dirname, 'build') : path.resolve(__dirname, 'src'); 9 | 10 | app.set("view options", {layout: false}); 11 | app.use(express.static(publicPath)); 12 | 13 | // We only want to run the workflow when not in production 14 | if (!isProduction) { 15 | 16 | const Webpack = require('webpack'); 17 | const WebpackDevServer = require('webpack-dev-server'); 18 | const webpackConfig = require('./webpack.config.js'); 19 | const proxy = httpProxy.createProxyServer(); 20 | 21 | // First we fire up Webpack an pass in the configuration we 22 | // created 23 | var bundleStart = null; 24 | const compiler = Webpack(webpackConfig); 25 | 26 | // We give notice in the terminal when it starts bundling and 27 | // set the time it started 28 | compiler.plugin('compile', function() { 29 | console.log('Bundling...'); 30 | bundleStart = Date.now(); 31 | }); 32 | 33 | // We also give notice when it is done compiling, including the 34 | // time it took. Nice to have 35 | compiler.plugin('done', function() { 36 | console.log('Bundled in ' + (Date.now() - bundleStart) + 'ms!'); 37 | }); 38 | 39 | const bundler = new WebpackDevServer(compiler, { 40 | 41 | // We need to tell Webpack to serve our bundled application 42 | // from the build path. When proxying: 43 | // http://localhost:3000/build -> http://localhost:8080/build 44 | publicPath: '/build/', 45 | 46 | // Configure hot replacement 47 | hot: true, 48 | 49 | historyApiFallback: true, 50 | 51 | // The rest is terminal configurations 52 | quiet: false, 53 | noInfo: true, 54 | stats: { 55 | colors: true 56 | } 57 | }); 58 | 59 | // We fire up the development server and give notice in the terminal 60 | // that we are starting the initial bundle 61 | bundler.listen(8080, 'localhost', function () { 62 | console.log('Bundling project, please wait...'); 63 | }); 64 | 65 | 66 | // Any requests to localhost:3000/build is proxied 67 | // to webpack-dev-server 68 | app.all('/build/*', function (req, res) { 69 | proxy.web(req, res, { 70 | target: 'http://localhost:8080' 71 | }); 72 | }); 73 | 74 | // It is important to catch any errors from the proxy or the 75 | // server will crash. An example of this is connecting to the 76 | // server when webpack is bundling 77 | proxy.on('error', function (e) { 78 | console.log('Could not connect to proxy, please try again...'); 79 | }); 80 | } 81 | 82 | app.all('*', function (req, res) { 83 | res.sendFile(`${publicPath}/index.html`); 84 | }); 85 | 86 | app.listen(port, function () { 87 | console.log('Server running on port ' + port); 88 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webpack + ReactJS 2 | 3 | This repository is a basic example of a ReactJS application, built with [Webpack](https://webpack.github.io) and [ReactJS](https://facebook.github.io/react/index.html). It's aim is to be simple & highly configurable, with no crazy opinionated structure. This is not an isomorphic structure, it works purely on the client side with a simple [Express](http://expressjs.com) based server running on the backend (for development, or production if needed). 4 | 5 | ## Setup 6 | 7 | 1. Clone the repository. 8 | 2. Install the project dependencies: `npm install`. 9 | 3. Run the development setup: `npm run dev`. 10 | 4. View the application on `http://localhost:3000`. 11 | 12 | You'll see that a very basic application is in place, which allows you to navigate to another route and add text, which saves to local storage. 13 | 14 | ## How it works 15 | 16 | The project is built using ES6/7, over ES5... But don't worry, Webpack has been setup with Babel 6 to handle this for you. 17 | 18 | The files you'll mostly be concerned with are located within `./src`. Here you'll see a number of files: 19 | 20 | #### index.html 21 | 22 | This is the base entry point of your application, for both development and production. Change this to your will, but make sure you keep the `root` div, and the script which is importing your entire React application. 23 | 24 | #### index.js 25 | 26 | This is the base JavaScript entry point of your application. Here, we use [React Router](https://github.com/rackt/react-router) to load in our components/routes. 27 | 28 | #### routes.js 29 | 30 | Here we export our React Router routes config. A helper has been created called `GenerateRoute` (see below) which allows us to quickly pass in route names and the route component, along with any child routes. 31 | 32 | The `GenerateRoute` function takes arguments, as an object: 33 | - index (boolean): Whether the route is an [IndexRoute](https://github.com/rackt/react-router/blob/master/docs/guides/basics/IndexRoutes.md). Used when you can have multiple children of a route. 34 | - key (string): Required when `index` is true, used to give a unique identifier to the IndexRoute. 35 | - paths (array): An array of strings, which are your routing paths. For params, use `:`, e.g. `/user/:userId`, which can then be accessed via the component props, e.g. `this.props.params.userId`. 36 | - component (component): A ReactJS component. 37 | - children (array): An array of `GenerateRoute` functions, but the routes will be generated as sub-routes of the parent. 38 | 39 | ```javascript 40 | { GenerateRoute({ 41 | paths: ['/profile', '/account'], 42 | component: require('./pages/account/Index'), 43 | children: [ 44 | GenerateRoute({ 45 | key: 'account-index', 46 | index: true, 47 | component: require('./pages/account/Overview') 48 | }), 49 | GenerateRoute({ 50 | paths: ['billing'], 51 | component: require('./pages/account/Billing') 52 | }) 53 | ] 54 | }) } 55 | ``` 56 | 57 | ### Managing State (Flux) 58 | 59 | The suggested way of managing your applications state is [Flux](https://facebook.github.io/flux). Since Flux is pretty confusing, this application uses a Flux wrapper library called [Alt](http://alt.js.org), 60 | which makes Flux much simpler to understand and work with. 61 | 62 | Basically Alt requires `Actions` to send information (in our example text from the input box) into a specific method in our `Store`. The Store updates it's state, which triggers an event. 63 | Any components listening for a store event (e.g. `UserStore.listen(this.someMethod)`) will trigger the function. In our case updating the array of users on the components state. 64 | 65 | There's not much point in explaining more, visit the [documentation](http://alt.js.org/guide) for more information on Alt. 66 | 67 | > For users wanting [Redux](https://github.com/rackt/redux), it should be pretty straight forward to implement yourselves. 68 | 69 | ### Images/CSS 70 | 71 | Any images should be placed within the `src/assets/images` directory, and required so Webpack can minify and cache bust them for you. 72 | 73 | There's a handy `` component within `src/components/Image` which handles this for you, used like so: 74 | 75 | ```javascript 76 | import { Image } from 'components'; 77 | 78 | ... 79 | 80 | React Logo 81 | ``` 82 | 83 | Any CSS is loaded in using a `css-loader`. A good place to include it in your project would be in the `src/index.js` file, for example: 84 | 85 | ```javascript 86 | import 'bootstrap/css/bootstrap.css'; 87 | ``` 88 | 89 | ### Development vs Production 90 | 91 | Development: `npm run dev`. 92 | 93 | In development, component updates are changed on the fly (known as hot-reloading) using websockets. All files are stored 'in memory' too. 94 | 95 | Production: `npm run prod`. 96 | 97 | In production, Webpack bundles your files into a single file, compressed and without hot-reloading. The contents of which are within `build`. The bundle name is hashed on each build (if something has changed), 98 | to stop browsers caching the bundle even if it's been changed. This also applies for your images. 99 | 100 | The bundled, minified production ReactJS script will also be used, which does not produce any errors. 101 | 102 | ## Misc 103 | 104 | #### Changing ports 105 | 106 | Simply change the numbers in `server.js`, or with production add `PORT=1234` as an environment variable. 107 | 108 | #### LESS/SASS 109 | 110 | These aren't handled by default, you'll need to configure Webpack yourselves using a loader, e.g. [less-loader](https://github.com/webpack/less-loader). 111 | 112 | #### Environment Detection 113 | 114 | If you need to detect the working environment within your code, simply use the process as you would in a Node application: 115 | 116 | ``` 117 | if (process.env.NODE_ENV === 'development') 118 | // Developing 119 | if (process.env.NODE_ENV === 'production') 120 | // Production build 121 | ``` 122 | --------------------------------------------------------------------------------