├── .babelrc ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .stylelintrc ├── .travis.yml ├── LICENSE.txt ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── components │ ├── Counter │ │ ├── Counter.css │ │ ├── Counter.js │ │ └── index.js │ ├── Header │ │ ├── Header.css │ │ ├── Header.js │ │ └── index.js │ ├── Layout │ │ ├── Layout.css │ │ ├── Layout.js │ │ └── index.js │ └── variables.css ├── containers │ └── AppContainer.js ├── index.ejs ├── main.js ├── routes │ ├── Counter │ │ ├── components │ │ │ └── CounterView.js │ │ ├── containers │ │ │ └── CounterContainer.js │ │ ├── index.js │ │ └── modules │ │ │ └── counter.js │ ├── Home │ │ ├── assets │ │ │ └── Duck.jpg │ │ ├── components │ │ │ ├── HomeView.css │ │ │ └── HomeView.js │ │ └── index.js │ ├── NotFound │ │ ├── NotFound.css │ │ ├── NotFound.js │ │ └── index.js │ └── index.js ├── static │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── crossdomain.xml │ ├── favicon.ico │ ├── robots.txt │ ├── tile-wide.png │ └── tile.png └── store │ ├── createStore.js │ └── reducers.js ├── test ├── .eslintrc └── spec.js ├── tools ├── build.js ├── bundle.js ├── clean.js ├── copy.js ├── start.js └── task.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015-loose", 5 | "stage-1" 6 | ], 7 | "plugins": [ 8 | "transform-runtime" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "rules": { 5 | "semi": ["error", "never"], 6 | "react/prefer-stateless-function": 0, 7 | "global-require": 0, 8 | "linebreak-style": 0, 9 | "no-console": 0, 10 | "no-param-reassign": [2, { 11 | "props": false 12 | }] 13 | }, 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true, 17 | "experimentalObjectRestSpread": true 18 | } 19 | }, 20 | "env": { 21 | "browser": true, 22 | "node": true 23 | }, 24 | "globals": { 25 | "document": true, 26 | "window": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | 5 | # For the following file types, normalize line endings to LF on 6 | # checkin and prevent conversion to CRLF when they are checked out 7 | # (this is required in order to prevent newline related issues like, 8 | # for example, after the build script is run) 9 | .* text eol=lf 10 | *.css text eol=lf 11 | *.html text eol=lf 12 | *.js text eol=lf 13 | *.json text eol=lf 14 | *.md text eol=lf 15 | *.txt text eol=lf 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | 4 | build/ 5 | node_modules/ 6 | npm-debug.log 7 | debug.log 8 | *.log 9 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "string-quotes": "single" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | env: 5 | - CXX=g++-4.8 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | - g++-4.8 12 | script: 13 | - npm run lint 14 | - npm run test 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Konstantin Tarkus, John Ma. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Static Boilerplate 2 | 3 | > Static website starter kit powered by [React.js](http://facebook.github.io/react/) and [Webpack](http://webpack.github.io/) 4 | 5 | 6 | ### Features 7 | 8 |     ✓ Modern JavaScript syntax ([ES2015](http://babeljs.io/docs/learn-es2015/)+) via [Babel](http://babeljs.io/)
9 |     ✓ Modern CSS/SCSS syntax (CSS3+) via [PostCSS](https://github.com/postcss/postcss)
10 |     ✓ Application state management via [Redux](http://redux.js.org/)
11 |     ✓ Routing and navigation via [React Router](https://github.com/reactjs/react-router), [React Router Redux](https://github.com/reactjs/react-router-redux), [History](https://github.com/mjackson/history)
12 |     ✓ Modular styles via [CSS Modules](https://github.com/css-modules/css-modules)
13 |     ✓ [Code-splitting](https://github.com/webpack/docs/wiki/code-splitting) and async chunk loading
14 |     ✓ Hot Module Replacement ([HMR](https://webpack.github.io/docs/hot-module-replacement.html)) /w [React Hot Loader](http://gaearon.github.io/react-hot-loader/)
15 |     ✓ Bundling and optimization with [Webpack](https://webpack.github.io/)
16 |     ✓ Cross-device testing with [Browsersync](https://browsersync.io/)
17 |     ✓ IE8 Support (Need to build after) 18 | ### Directory Layout 19 | 20 | ```shell 21 | . 22 | ├── /build/ # The folder for compiled output 23 | ├── /node_modules/ # 3rd-party libraries and utilities 24 | ├── /src/ 25 | ├── /components/ # Shared/generic UI components 26 | │ ├── /Layout/ # Layout component 27 | │ ├── /Button/ # Button component 28 | │ └── /... # etc. 29 | ├── /containers/ # containers 30 | ├── /routes/ # View/screen UI components + routing information 31 | │ ├── /About/ # About page 32 | │ ├── /NotFound/ # Error page 33 | │ ├── /Home/ # Home page 34 | │ └── /... # etc. 35 | ├── /static/ # Static files such as favicon.ico etc. 36 | ├── /store/ # redux store 37 | ├── /test/ # Unit and integration tests 38 | ├── /tools/ # Build automation scripts and utilities 39 | │── LICENSE.txt # Licensing information 40 | │── package.json # The list of project dependencies and NPM scripts 41 | └── README.md # Project overview / getting started guide 42 | ``` 43 | 44 | 45 | ### Getting Started 46 | 47 | Just clone the repo, install Node.js modules and run `npm start`: 48 | 49 | ```shell 50 | $ git clone -o react-static-boilerplate -b master --single-branch \ 51 | https://github.com/jun0205/react-static-boilerplate.git MyApp 52 | $ cd MyApp 53 | $ npm install # Install project dependencies listed in package.json 54 | $ npm start # Build and launch the app, same as "node tools/start.js" 55 | ``` 56 | 57 | **NODE**: Make sure that you have [Node.js](https://nodejs.org/) v6 installed on your local machine. 58 | 59 | ### IE8 Support Version 60 | 61 |     react <= 0.14.9
62 |     react-dom <= 0.14.9
63 |     react-router <= 2.3.0
64 |     webpack = 1.15.0 65 | 66 | ### How to Test 67 | 68 | The unit tests are powered by [chai](http://chaijs.com/) and [mocha](http://mochajs.org/). 69 | 70 | ```shell 71 | $ npm test 72 | ``` 73 | 74 | ### Histories 75 | 76 | React Router history [guides](https://github.com/ReactTraining/react-router/blob/v2.3.0/docs/guides/Histories.md). 77 | 78 | production web applications should use browserHistory for the cleaner URLs. 79 | 80 | ### How to Build 81 | 82 | ```shell 83 | $ npm run build # Build production release 84 | ``` 85 | 86 | 87 | ### How to Update 88 | 89 | You can always fetch and merge the recent changes from this repo back into your own project: 90 | 91 | ```shell 92 | $ git checkout master 93 | $ git fetch react-static-boilerplate 94 | $ git merge react-static-boilerplate/master 95 | $ npm install 96 | ``` 97 | 98 | 99 | ### Related Projects 100 | 101 | * [React Starter Kit](https://github.com/kriasoft/react-starter-kit) — Isomorphic web app boilerplate (Node.js, React, GraphQL, Webpack, CSS Modules) 102 | * [Babel Starter Kit](https://github.com/kriasoft/babel-starter-kit) — JavaScript library boilerplate (ES2015, Babel, Rollup, Mocha, Chai, Sinon, Rewire) 103 | * [React Redux Starter Kit](https://github.com/davezuko/react-redux-starter-kit) — Get started with React, Redux, and React-Router! 104 | * [Redux](http://redux.js.org/) — Redux is a predictable state container for JavaScript apps. 105 | * [React Router](https://github.com/reactjs/react-router) — Declarative routing for React 106 | * [React Router Redux](https://github.com/reactjs/react-router-redux) — Ruthlessly simple bindings to keep react-router and redux in sync 107 | * [History](https://github.com/mjackson/history) — HTML5 History API wrapper library 108 | 109 | ### Learn More 110 | 111 | * [Getting Started with React.js](http://facebook.github.io/react/) 112 | * [Getting Started with GraphQL and Relay](https://quip.com/oLxzA1gTsJsE) 113 | * [React.js Questions on StackOverflow](http://stackoverflow.com/questions/tagged/reactjs) 114 | * [React.js Discussion Board](https://discuss.reactjs.org/) 115 | * [Learn ES6](https://babeljs.io/docs/learn-es6/), [ES6 Features](https://github.com/lukehoban/es6features#readme) 116 | 117 | 118 | ### License 119 | 120 | This source code is licensed under the MIT license found in the 121 | [LICENSE.txt](https://github.com/jun0205/react-static-boilerplate/blob/master/LICENSE.txt) file. 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-static-boilerplate", 3 | "version": "0.0.0", 4 | "private": true, 5 | "engines": { 6 | "node": ">=6.5", 7 | "npm": ">=3.10" 8 | }, 9 | "dependencies": { 10 | "babel-polyfill": "^6.26.0", 11 | "classnames": "^2.2.5", 12 | "es5-shim": "^4.5.9", 13 | "es6-promise": "^4.1.1", 14 | "fetch-detector": "^1.0.1", 15 | "fetch-ie8": "^1.5.0", 16 | "fetch-jsonp": "^1.1.3", 17 | "history": "^2.1.2", 18 | "prop-types": "^15.6.0", 19 | "react": "^0.14.9", 20 | "react-dom": "^0.14.9", 21 | "react-redux": "^4.4.8", 22 | "react-router": "2.3.0", 23 | "react-router-redux": "^4.0.8", 24 | "redux": "^3.5.2", 25 | "redux-thunk": "^2.1.0" 26 | }, 27 | "scripts": { 28 | "eslint": "eslint src/**/*.js tools/**/*.js", 29 | "stylelint": "stylelint src/components/**/*.css src/routes/**/*.css", 30 | "lint": "npm run eslint && npm run stylelint", 31 | "test": "mocha --compilers js:babel-register", 32 | "test:watch": "mocha --compilers js:babel-register --reporter min --watch", 33 | "clean": "node tools/clean", 34 | "build": "node tools/build --release", 35 | "build:debug": "node tools/build", 36 | "start": "node tools/start" 37 | }, 38 | "devDependencies": { 39 | "autoprefixer": "^7.1.5", 40 | "babel-cli": "^6.26.0", 41 | "babel-core": "^6.26.0", 42 | "babel-eslint": "^8.0.1", 43 | "babel-loader": "^6.4.1", 44 | "babel-plugin-transform-es3-member-expression-literals": "^6.22.0", 45 | "babel-plugin-transform-es3-modules-literals": "0.0.3", 46 | "babel-plugin-transform-es3-property-literals": "^6.22.0", 47 | "babel-plugin-transform-react-constant-elements": "^6.23.0", 48 | "babel-plugin-transform-react-inline-elements": "^6.22.0", 49 | "babel-plugin-transform-react-remove-prop-types": "^0.4.10", 50 | "babel-plugin-transform-runtime": "^6.23.0", 51 | "babel-preset-es2015": "^6.24.1", 52 | "babel-preset-es2015-loose": "^7.0.0", 53 | "babel-preset-react": "^6.24.1", 54 | "babel-preset-stage-1": "^6.24.1", 55 | "babel-register": "^6.26.0", 56 | "babel-runtime": "^6.26.0", 57 | "browser-sync": "^2.18.13", 58 | "chai": "^4.1.2", 59 | "css-loader": "^0.28.7", 60 | "del": "^3.0.0", 61 | "eslint": "^4.9.0", 62 | "eslint-config-airbnb": "^16.1.0", 63 | "eslint-plugin-import": "^2.7.0", 64 | "eslint-plugin-jsx-a11y": "^6.0.2", 65 | "eslint-plugin-react": "^7.4.0", 66 | "extract-text-webpack-plugin": "^1.0.1", 67 | "file-loader": "^1.1.5", 68 | "fs-extra": "^4.0.1", 69 | "html-webpack-plugin": "^2.30.1", 70 | "json-loader": "^0.5.7", 71 | "mocha": "^4.0.1", 72 | "node-sass": "^4.5.3", 73 | "pixrem": "^4.0.1", 74 | "pleeease-filters": "^4.0.0", 75 | "postcss": "^6.0.13", 76 | "postcss-calc": "^6.0.1", 77 | "postcss-color-function": "^4.0.0", 78 | "postcss-custom-media": "^6.0.0", 79 | "postcss-custom-properties": "^6.2.0", 80 | "postcss-custom-selectors": "^4.0.1", 81 | "postcss-import": "^11.0.0", 82 | "postcss-loader": "^2.0.8", 83 | "postcss-media-minmax": "^3.0.0", 84 | "postcss-nesting": "^4.2.1", 85 | "postcss-selector-matches": "^3.0.1", 86 | "postcss-selector-not": "^3.0.1", 87 | "react-hot-loader": "^3.1.1", 88 | "redbox-react": "^1.5.0", 89 | "sass-loader": "^6.0.6", 90 | "style-loader": "^0.19.0", 91 | "stylelint": "^8.1.1", 92 | "stylelint-config-standard": "^17.0.0", 93 | "url-loader": "^0.6.2", 94 | "webpack": "^1.15.0", 95 | "webpack-dev-middleware": "^1.12.0", 96 | "webpack-hot-middleware": "^2.19.1" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const AUTOPREFIXER_BROWSERS = [ 2 | 'Android >= 4', 3 | 'Chrome >= 35', 4 | 'Firefox >= 31', 5 | 'Explorer >= 9', 6 | 'iOS >= 7', 7 | 'Opera >= 12', 8 | 'Safari >= 7.1', 9 | ] 10 | 11 | module.exports = { 12 | plugins: [ 13 | // Transfer @import rule by inlining content, e.g. @import 'normalize.css' 14 | // https://github.com/postcss/postcss-import 15 | require('postcss-import')(), 16 | // W3C variables, e.g. :root { --color: red; } div { background: var(--color); } 17 | // https://github.com/postcss/postcss-custom-properties 18 | require('postcss-custom-properties')(), 19 | // W3C CSS Custom Media Queries, e.g. @custom-media --small-viewport (max-width: 30em); 20 | // https://github.com/postcss/postcss-custom-media 21 | require('postcss-custom-media')(), 22 | // CSS4 Media Queries, e.g. @media screen and (width >= 500px) and (width <= 1200px) { } 23 | // https://github.com/postcss/postcss-media-minmax 24 | require('postcss-media-minmax')(), 25 | // W3C CSS Custom Selectors, e.g. @custom-selector :--heading h1, h2, h3, h4, h5, h6; 26 | // https://github.com/postcss/postcss-custom-selectors 27 | require('postcss-custom-selectors')(), 28 | // W3C calc() function, e.g. div { height: calc(100px - 2em); } 29 | // https://github.com/postcss/postcss-calc 30 | require('postcss-calc')(), 31 | // Allows you to nest one style rule inside another 32 | // https://github.com/jonathantneal/postcss-nesting 33 | require('postcss-nesting')(), 34 | // W3C color() function, e.g. div { background: color(red alpha(90%)); } 35 | // https://github.com/postcss/postcss-color-function 36 | require('postcss-color-function')(), 37 | // Convert CSS shorthand filters to SVG equivalent, e.g. .blur { filter: blur(4px); } 38 | // https://github.com/iamvdo/pleeease-filters 39 | require('pleeease-filters')(), 40 | // Generate pixel fallback for "rem" units, e.g. div { margin: 2.5rem 2px 3em 100%; } 41 | // https://github.com/robwierzbowski/node-pixrem 42 | require('pixrem')(), 43 | // W3C CSS Level4 :matches() pseudo class, e.g. p:matches(:first-child, .special) { } 44 | // https://github.com/postcss/postcss-selector-matches 45 | require('postcss-selector-matches')(), 46 | // Transforms :not() W3C CSS Level 4 pseudo class to :not() CSS Level 3 selectors 47 | // https://github.com/postcss/postcss-selector-not 48 | require('postcss-selector-not')(), 49 | // Parse CSS and add vendor prefixes to rules 50 | // https://github.com/postcss/autoprefixer 51 | require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }), 52 | ], 53 | } 54 | -------------------------------------------------------------------------------- /src/components/Counter/Counter.css: -------------------------------------------------------------------------------- 1 | @import '../variables.css'; 2 | 3 | .counter { 4 | font-weight: bold; 5 | } 6 | 7 | .counterGreen { 8 | color: rgb(15, 150, 15); 9 | } 10 | 11 | .counterContainer { 12 | margin: 1em auto; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Counter/Counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classes from './Counter.css' 4 | 5 | export const Counter = (props) => ( 6 |
7 |

8 | Counter: 9 | {' '} 10 | 11 | {props.counter} 12 | 13 |

14 | 17 | {' '} 18 | 21 |
22 | ) 23 | 24 | Counter.propTypes = { 25 | counter: PropTypes.number.isRequired, 26 | doubleAsync: PropTypes.func.isRequired, 27 | increment: PropTypes.func.isRequired, 28 | } 29 | 30 | export default Counter 31 | -------------------------------------------------------------------------------- /src/components/Counter/index.js: -------------------------------------------------------------------------------- 1 | import Counter from './Counter' 2 | 3 | export default Counter 4 | -------------------------------------------------------------------------------- /src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | @import '../variables.css'; 2 | 3 | .activeRoute { 4 | font-weight: bold; 5 | text-decoration: underline; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IndexLink, Link } from 'react-router' 3 | import classes from './Header.css' 4 | 5 | export const Header = () => ( 6 |
7 |

IE8 React Redux Starter Kit

8 | 9 | Home 10 | 11 | {' · '} 12 | 13 | Counter 14 | 15 |
16 | ) 17 | 18 | export default Header 19 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header' 2 | 3 | export default Header 4 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.css: -------------------------------------------------------------------------------- 1 | @import '../variables.css'; 2 | 3 | body { 4 | font-family: var(--font-base); 5 | } 6 | 7 | .mainContainer { 8 | padding-top: 20px; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Header from '../Header' 4 | import classes from './Layout.css' 5 | 6 | export const CoreLayout = ({ children }) => ( 7 |
8 |
9 |
10 | {children} 11 |
12 |
13 | ) 14 | 15 | CoreLayout.propTypes = { 16 | children: PropTypes.element.isRequired, 17 | } 18 | 19 | export default CoreLayout 20 | -------------------------------------------------------------------------------- /src/components/Layout/index.js: -------------------------------------------------------------------------------- 1 | import Layout from './Layout' 2 | 3 | export default Layout 4 | -------------------------------------------------------------------------------- /src/components/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --font-base: 'sans-serif'; 3 | } 4 | 5 | html, 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Router } from 'react-router' 4 | import { Provider } from 'react-redux' 5 | 6 | class AppContainer extends React.Component { 7 | static propTypes = { 8 | history: PropTypes.object.isRequired, 9 | routes: PropTypes.object.isRequired, 10 | routerKey: PropTypes.number, 11 | store: PropTypes.object.isRequired, 12 | } 13 | 14 | render() { 15 | const { history, routes, routerKey, store } = this.props 16 | 17 | return ( 18 | 19 | 20 | 21 | ) 22 | } 23 | } 24 | 25 | export default AppContainer 26 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | IE8 React Redux Starter Kit 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | /* eslint-disable no-undef */ 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import createHashHistory from 'history/lib/createHashHistory' 6 | import { useRouterHistory } from 'react-router' 7 | import syncHistoryWithStore from 'react-router-redux/lib/sync' 8 | import createStore from './store/createStore' 9 | import AppContainer from './containers/AppContainer' 10 | 11 | // ======================================================== 12 | // Browser History Setup 13 | // ======================================================== 14 | const browserHistory = useRouterHistory(createHashHistory)({ 15 | basename: __BASENAME__, 16 | }) 17 | 18 | // ======================================================== 19 | // Store and History Instantiation 20 | // ======================================================== 21 | // Create redux store and sync with react-router-redux. We have installed the 22 | // react-router-redux reducer under the routerKey "router" in src/routes/index.js, 23 | // so we need to provide a custom `selectLocationState` to inform 24 | // react-router-redux of its location. 25 | const initialState = window.___INITIAL_STATE__ 26 | const store = createStore(initialState, browserHistory) 27 | const history = syncHistoryWithStore(browserHistory, store, { 28 | selectLocationState: (state) => state.router, 29 | }) 30 | 31 | // ======================================================== 32 | // Developer Tools Setup 33 | // ======================================================== 34 | if (__DEV__) { 35 | if (window.devToolsExtension) { 36 | window.devToolsExtension.open() 37 | } 38 | } 39 | 40 | // ======================================================== 41 | // Render Setup 42 | // ======================================================== 43 | const MOUNT_NODE = document.getElementById('container') 44 | 45 | let render = (routerKey = null) => { 46 | const routes = require('./routes/index').default(store) 47 | 48 | ReactDOM.render( 49 | , 55 | MOUNT_NODE 56 | ) 57 | } 58 | 59 | // Enable HMR and catch runtime errors in RedBox 60 | // This code is excluded from production bundle 61 | if (__DEV__) { 62 | if (module.hot) { 63 | // Development render functions 64 | const renderApp = render 65 | const renderError = (error) => { 66 | const RedBox = require('redbox-react').default 67 | 68 | ReactDOM.render(, MOUNT_NODE) 69 | } 70 | 71 | // Wrap render in try/catch 72 | render = () => { 73 | try { 74 | renderApp() 75 | } catch (error) { 76 | renderError(error) 77 | } 78 | } 79 | 80 | // Setup hot module replacement 81 | module.hot.accept('./routes/index', () => 82 | setImmediate(() => { 83 | ReactDOM.unmountComponentAtNode(MOUNT_NODE) 84 | render() 85 | }) 86 | ) 87 | } 88 | } 89 | 90 | // ======================================================== 91 | // Go! 92 | // ======================================================== 93 | render() 94 | -------------------------------------------------------------------------------- /src/routes/Counter/components/CounterView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Counter from 'components/Counter' 4 | 5 | export const CounterView = (props) => ( 6 |
7 | 12 |
13 | ) 14 | 15 | CounterView.defaultProps = { 16 | counter: 0, 17 | } 18 | 19 | CounterView.propTypes = { 20 | counter: PropTypes.number, 21 | doubleAsync: PropTypes.func, 22 | increment: PropTypes.func, 23 | } 24 | 25 | export default CounterView 26 | -------------------------------------------------------------------------------- /src/routes/Counter/containers/CounterContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { increment, doubleAsync } from '../modules/counter' 3 | 4 | /* This is a container component. Notice it does not contain any JSX, 5 | nor does it import React. This component is **only** responsible for 6 | wiring in the actions and state necessary to render a presentational 7 | component - in this case, the counter: */ 8 | 9 | import CounterView from '../components/CounterView' 10 | 11 | /* Object of action creators (can also be function that returns object). 12 | Keys will be passed as props to presentational components. Here we are 13 | implementing our wrapper around increment; the component doesn't care */ 14 | 15 | const mapActionCreators = { 16 | increment: () => increment(1), 17 | doubleAsync, 18 | } 19 | 20 | const mapStateToProps = (state) => ({ 21 | counter: state.counter, 22 | }) 23 | 24 | /* Note: mapStateToProps is where you should use `reselect` to create selectors, ie: 25 | 26 | import { createSelector } from 'reselect' 27 | const counter = (state) => state.counter 28 | const tripleCount = createSelector(counter, (count) => count * 3) 29 | const mapStateToProps = (state) => ({ 30 | counter: tripleCount(state) 31 | }) 32 | 33 | Selectors can compute derived data, allowing Redux to store the minimal possible state. 34 | Selectors are efficient. A selector is not recomputed unless one of its arguments change. 35 | Selectors are composable. They can be used as input to other selectors. 36 | https://github.com/reactjs/reselect */ 37 | 38 | export default connect(mapStateToProps, mapActionCreators)(CounterView) 39 | -------------------------------------------------------------------------------- /src/routes/Counter/index.js: -------------------------------------------------------------------------------- 1 | import { injectReducer } from '../../store/reducers' 2 | 3 | export default (store) => ({ 4 | path: 'counter', 5 | /* Async getComponent is only invoked when route matches */ 6 | getComponent(nextState, cb) { 7 | /* Webpack - use 'require.ensure' to create a split point 8 | and embed an async module loader (jsonp) when bundling */ 9 | require.ensure([], (require) => { 10 | /* Webpack - use require callback to define 11 | dependencies for bundling */ 12 | const Counter = require('./containers/CounterContainer').default 13 | const reducer = require('./modules/counter').default 14 | 15 | /* Add the reducer to the store on key 'counter' */ 16 | injectReducer(store, { key: 'counter', reducer }) 17 | 18 | /* Return getComponent */ 19 | cb(null, Counter) 20 | 21 | /* Webpack named bundle */ 22 | }, 'counter') 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /src/routes/Counter/modules/counter.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const COUNTER_INCREMENT = 'COUNTER_INCREMENT' 5 | 6 | // ------------------------------------ 7 | // Actions 8 | // ------------------------------------ 9 | export function increment(value = 1) { 10 | return { 11 | type: COUNTER_INCREMENT, 12 | payload: value, 13 | } 14 | } 15 | 16 | /* This is a thunk, meaning it is a function that immediately 17 | returns a function for lazy evaluation. It is incredibly useful for 18 | creating async actions, especially when combined with redux-thunk! 19 | 20 | NOTE: This is solely for demonstration purposes. In a real application, 21 | you'd probably want to dispatch an action of COUNTER_DOUBLE and let the 22 | reducer take care of this logic. */ 23 | 24 | export const doubleAsync = () => ( 25 | (dispatch, getState) => ( 26 | new Promise((resolve) => { 27 | setTimeout(() => { 28 | dispatch(increment(getState().counter)) 29 | resolve() 30 | }, 200) 31 | }) 32 | ) 33 | ) 34 | 35 | export const actions = { 36 | increment, 37 | doubleAsync, 38 | } 39 | 40 | // ------------------------------------ 41 | // Action Handlers 42 | // ------------------------------------ 43 | const ACTION_HANDLERS = { 44 | [COUNTER_INCREMENT]: (state, action) => state + action.payload, 45 | } 46 | 47 | // ------------------------------------ 48 | // Reducer 49 | // ------------------------------------ 50 | const initialState = 0 51 | export default function counterReducer(state = initialState, action) { 52 | const handler = ACTION_HANDLERS[action.type] 53 | 54 | return handler ? handler(state, action) : state 55 | } 56 | -------------------------------------------------------------------------------- /src/routes/Home/assets/Duck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-is/react-static-boilerplate/4c38aeff1e173ddb249b379645e79d2a45c05aa7/src/routes/Home/assets/Duck.jpg -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.css: -------------------------------------------------------------------------------- 1 | .duck { 2 | display: block; 3 | width: 120px; 4 | margin: auto; 5 | } 6 | -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DuckImage from '../assets/Duck.jpg' 3 | import classes from './HomeView.css' 4 | 5 | export const HomeView = () => ( 6 |
7 |

Welcome!

8 | This is a duck, because Redux! 13 |
14 | ) 15 | 16 | export default HomeView 17 | -------------------------------------------------------------------------------- /src/routes/Home/index.js: -------------------------------------------------------------------------------- 1 | import HomeView from './components/HomeView' 2 | 3 | // Sync route definition 4 | export default { 5 | component: HomeView, 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/NotFound/NotFound.css: -------------------------------------------------------------------------------- 1 | .notFound { 2 | padding: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/routes/NotFound/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { browserHistory } from 'react-router' 3 | import classes from './NotFound.css' 4 | 5 | const goBack = (e) => { 6 | e.preventDefault() 7 | return browserHistory.goBack() 8 | } 9 | 10 | export const NotFound = () => ( 11 |
12 |

Page not found!

13 |

← Back

14 |
15 | ) 16 | 17 | export default NotFound 18 | -------------------------------------------------------------------------------- /src/routes/NotFound/index.js: -------------------------------------------------------------------------------- 1 | import NotFound from './NotFound' 2 | 3 | export default { 4 | path: '*', 5 | component: NotFound, 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | // We only need to import the modules necessary for initial render 2 | import Layout from '../components/Layout' 3 | import Home from './Home' 4 | import NotFound from './NotFound' 5 | import counterRoute from './Counter' 6 | 7 | /* Note: Instead of using JSX, we recommend using react-router 8 | PlainRoute objects to build route definitions. */ 9 | 10 | export const createRoutes = (store) => ({ 11 | path: '/', 12 | component: Layout, 13 | indexRoute: Home, 14 | childRoutes: [ 15 | counterRoute(store), 16 | NotFound, 17 | ], 18 | }) 19 | 20 | /* Note: childRoutes can be chunked or otherwise loaded programmatically 21 | using getChildRoutes with the following signature: 22 | 23 | getChildRoutes (location, cb) { 24 | require.ensure([], (require) => { 25 | cb(null, [ 26 | // Remove imports! 27 | require('./Counter').default(store) 28 | ]) 29 | }) 30 | } 31 | 32 | However, this is not necessary for code-splitting! It simply provides 33 | an API for async route definitions. Your code splitting should occur 34 | inside the route `getComponent` function, since it is only invoked 35 | when the route exists and matches. 36 | */ 37 | 38 | export default createRoutes 39 | -------------------------------------------------------------------------------- /src/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-is/react-static-boilerplate/4c38aeff1e173ddb249b379645e79d2a45c05aa7/src/static/apple-touch-icon.png -------------------------------------------------------------------------------- /src/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/static/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-is/react-static-boilerplate/4c38aeff1e173ddb249b379645e79d2a45c05aa7/src/static/favicon.ico -------------------------------------------------------------------------------- /src/static/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /src/static/tile-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-is/react-static-boilerplate/4c38aeff1e173ddb249b379645e79d2a45c05aa7/src/static/tile-wide.png -------------------------------------------------------------------------------- /src/static/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-is/react-static-boilerplate/4c38aeff1e173ddb249b379645e79d2a45c05aa7/src/static/tile.png -------------------------------------------------------------------------------- /src/store/createStore.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { applyMiddleware, compose, createStore } from 'redux' 3 | import routerMiddleware from 'react-router-redux/lib/middleware' 4 | import thunk from 'redux-thunk' 5 | import makeRootReducer from './reducers' 6 | 7 | export default (initialState = {}, history) => { 8 | // ====================================================== 9 | // Middleware Configuration 10 | // ====================================================== 11 | const middleware = [thunk, routerMiddleware(history)] 12 | 13 | // ====================================================== 14 | // Store Enhancers 15 | // ====================================================== 16 | const enhancers = [] 17 | if (__DEV__) { 18 | const devToolsExtension = window.devToolsExtension 19 | if (typeof devToolsExtension === 'function') { 20 | enhancers.push(devToolsExtension()) 21 | } 22 | } 23 | 24 | // ====================================================== 25 | // Store Instantiation and HMR Setup 26 | // ====================================================== 27 | const store = createStore( 28 | makeRootReducer(), 29 | initialState, 30 | compose( 31 | applyMiddleware(...middleware), 32 | ...enhancers 33 | ) 34 | ) 35 | store.asyncReducers = {} 36 | 37 | if (module.hot) { 38 | module.hot.accept('./reducers', () => { 39 | const reducers = require('./reducers').default 40 | store.replaceReducer(reducers) 41 | }) 42 | } 43 | 44 | return store 45 | } 46 | -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { routerReducer as router } from 'react-router-redux/lib/reducer' 3 | 4 | export const makeRootReducer = (asyncReducers) => ( 5 | combineReducers({ 6 | // Add sync reducers here 7 | router, 8 | ...asyncReducers, 9 | }) 10 | ) 11 | 12 | export const injectReducer = (store, { key, reducer }) => { 13 | store.asyncReducers[key] = reducer 14 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 15 | } 16 | 17 | export default makeRootReducer 18 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "no-console": 0, 7 | "no-unused-expressions": 0, 8 | "padded-blocks": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | 3 | describe('test suite', () => { 4 | 5 | it('test', () => { 6 | expect(true).to.be.equal.true 7 | }) 8 | 9 | }) 10 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | const task = require('./task') 12 | 13 | module.exports = task('build', () => Promise.resolve() 14 | .then(() => require('./clean')) 15 | .then(() => require('./copy')) 16 | .then(() => require('./bundle')) 17 | ) 18 | -------------------------------------------------------------------------------- /tools/bundle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | const webpack = require('webpack') 12 | const task = require('./task') 13 | const webpackConfig = require('../webpack.config') 14 | 15 | module.exports = task('bundle', new Promise((resolve, reject) => { 16 | const bundler = webpack(webpackConfig) 17 | const run = (err, stats) => { 18 | if (err) { 19 | reject(err) 20 | } else { 21 | console.log(stats.toString(webpackConfig.stats)) 22 | resolve() 23 | } 24 | } 25 | bundler.run(run) 26 | })) 27 | -------------------------------------------------------------------------------- /tools/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | const del = require('del') 12 | const task = require('./task') 13 | 14 | module.exports = task('clean', () => 15 | del(['./build/*', '!./build/.git'], { dot: true }) 16 | ) 17 | -------------------------------------------------------------------------------- /tools/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | const task = require('./task') 12 | const fs = require('fs-extra') 13 | 14 | /** 15 | * Copies static files such as robots.txt, favicon.ico to the 16 | * output (build) folder. 17 | */ 18 | module.exports = task('copy', fs.copy('./src/static', './build')) 19 | -------------------------------------------------------------------------------- /tools/start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | const path = require('path') 12 | const browserSync = require('browser-sync') 13 | const webpack = require('webpack') 14 | const webpackDevMiddleware = require('webpack-dev-middleware') 15 | const webpackHotMiddleware = require('webpack-hot-middleware') 16 | const task = require('./task') 17 | const webpackConfig = require('../webpack.config') 18 | 19 | task('start', () => new Promise(resolve => { 20 | // Hot Module Replacement (HMR) + React Hot Reload 21 | if (webpackConfig.debug) { 22 | webpackConfig.entry.vendor 23 | .unshift('react-hot-loader/patch', 'webpack-hot-middleware/client') 24 | webpackConfig.module.loaders 25 | .find(x => x.loader === 'babel-loader').query.plugins 26 | .unshift('react-hot-loader/babel') 27 | webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()) 28 | webpackConfig.plugins.push(new webpack.NoErrorsPlugin()) 29 | } 30 | 31 | const bundler = webpack(webpackConfig) 32 | 33 | browserSync({ 34 | port: Number(process.env.PORT || 3000), 35 | ui: { 36 | port: Number(process.env.PORT || 3000) + 1, 37 | }, 38 | server: { 39 | baseDir: './src', 40 | 41 | middleware: [ 42 | webpackDevMiddleware(bundler, { 43 | // IMPORTANT: dev middleware can't access webpackConfig, so we should 44 | // provide publicPath by ourselves 45 | publicPath: webpackConfig.output.publicPath, 46 | 47 | // pretty colored output 48 | stats: webpackConfig.stats, 49 | 50 | // for other settings see 51 | // http://webpack.github.io/docs/webpack-dev-middleware.html 52 | }), 53 | 54 | // bundler should be the same as above 55 | webpackHotMiddleware(bundler), 56 | 57 | // Serve index.html for all unknown requests 58 | (req, res, next) => { 59 | if (req.headers.accept.startsWith('text/html')) { 60 | const filename = path.join(bundler.outputPath, 'index.html') 61 | bundler.outputFileSystem.readFile(filename, (err, result) => { 62 | if (err) { 63 | next(err) 64 | return 65 | } 66 | res.setHeader('Content-Type', 'text/html') 67 | res.end(result) 68 | }) 69 | } 70 | next() 71 | }, 72 | ], 73 | }, 74 | 75 | // no need to watch '*.js' here, webpack will take care of it for us, 76 | // including full page reloads if HMR won't work 77 | files: [ 78 | 'build/**/*.css', 79 | 'build/**/*.html', 80 | ], 81 | }) 82 | 83 | resolve() 84 | })) 85 | -------------------------------------------------------------------------------- /tools/task.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | function format(time) { 12 | return time.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1') 13 | } 14 | 15 | function task(name, action) { 16 | const start = new Date() 17 | console.log(`[${format(start)}] Starting '${name}'...`) 18 | return Promise.resolve(action instanceof Function ? action() : action).then(() => { 19 | const end = new Date() 20 | const time = end.getTime() - start.getTime() 21 | console.log(`[${format(end)}] Finished '${name}' after ${time}ms`) 22 | }, err => console.error(err.stack)) 23 | } 24 | 25 | module.exports = task 26 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | 6 | const args = process.argv.slice(2) 7 | const DEBUG = !(args[0] === '--release') 8 | const VERBOSE = args[0] === '--verbose' 9 | 10 | /** 11 | * Webpack configuration (core/main.js => build/bundle.js) 12 | * http://webpack.github.io/docs/configuration.html 13 | */ 14 | const config = { 15 | 16 | // The base directory 17 | context: path.resolve(__dirname, './src'), 18 | 19 | // The entry point for the bundle 20 | entry: { 21 | app: [ 22 | './main.js', 23 | ], 24 | vendor: [ 25 | 'es5-shim', 26 | 'es5-shim/es5-sham', 27 | 'babel-polyfill', 28 | 'es6-promise', 29 | 'fetch-detector', 30 | 'fetch-ie8', 31 | 'fetch-jsonp', 32 | 'react', 33 | 'react-dom', 34 | 'react-redux', 35 | 'react-router', 36 | 'redux', 37 | 'redux-thunk', 38 | ], 39 | }, 40 | 41 | // Options affecting the output of the compilation 42 | output: { 43 | path: path.resolve(__dirname, 'build'), 44 | publicPath: '/', 45 | filename: 'assets/[name].js', 46 | chunkFilename: 'assets/[name].js', 47 | sourcePrefix: ' ', 48 | }, 49 | 50 | // Switch loaders to debug or release mode 51 | debug: DEBUG, 52 | cache: DEBUG, 53 | 54 | // Developer tool to enhance debugging, source maps 55 | // http://webpack.github.io/docs/configuration.html#devtool 56 | devtool: DEBUG ? 'source-map' : false, 57 | 58 | // What information should be printed to the console 59 | stats: { 60 | colors: true, 61 | reasons: DEBUG, 62 | hash: VERBOSE, 63 | version: VERBOSE, 64 | timings: true, 65 | chunks: VERBOSE, 66 | chunkModules: VERBOSE, 67 | cached: VERBOSE, 68 | cachedAssets: VERBOSE, 69 | children: false, 70 | }, 71 | 72 | // The list of plugins for Webpack compiler 73 | plugins: [ 74 | new webpack.optimize.OccurenceOrderPlugin(), 75 | new webpack.optimize.CommonsChunkPlugin({ 76 | name: 'vendor', 77 | minChunks: Infinity, 78 | }), 79 | new webpack.DefinePlugin({ 80 | 'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"', 81 | __DEV__: DEBUG, 82 | __BASENAME__: JSON.stringify(process.env.BASENAME || ''), 83 | }), 84 | new ExtractTextPlugin( 85 | 'assets/styles.css', 86 | { 87 | minimize: !DEBUG, 88 | allChunks: true, 89 | } 90 | ), 91 | new HtmlWebpackPlugin({ 92 | template: path.resolve(__dirname, './src/index.ejs'), 93 | filename: 'index.html', 94 | minify: !DEBUG ? { 95 | collapseWhitespace: true, 96 | } : null, 97 | hash: true, 98 | }), 99 | ], 100 | 101 | // Options affecting the normal modules 102 | module: { 103 | loaders: [ 104 | { 105 | test: /\.jsx?$/, 106 | include: [ 107 | path.resolve(__dirname, './src'), 108 | ], 109 | loader: 'babel-loader', 110 | query: { 111 | plugins: [], 112 | }, 113 | }, 114 | { 115 | test: /\.css/, 116 | loader: ExtractTextPlugin.extract( 117 | 'style-loader', 118 | 'css-loader?-autoprefixer&modules=true&localIdentName=[local]!postcss-loader' 119 | ), 120 | }, 121 | { 122 | test: /\.scss$/, 123 | loader: ExtractTextPlugin.extract( 124 | 'style-loader', 125 | 'css-loader?-autoprefixer!postcss-loader!sass-loader' 126 | ), 127 | }, 128 | { 129 | test: /\.json$/, 130 | loader: 'json-loader', 131 | }, 132 | { 133 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/, 134 | loader: 'url-loader', 135 | query: { 136 | name: 'assets/[path][name].[ext]', 137 | limit: 10000, 138 | }, 139 | }, 140 | { 141 | test: /\.(eot|ttf|wav|mp3|ogg)$/, 142 | loader: 'file-loader', 143 | query: { 144 | name: 'assets/[path][name].[ext]', 145 | }, 146 | }, 147 | ], 148 | }, 149 | 150 | 151 | 152 | // Alias 153 | resolve: { 154 | alias: { 155 | components: path.resolve(__dirname, './src/components/'), 156 | routes: path.resolve(__dirname, './src/routes/'), 157 | services: path.resolve(__dirname, './src/services/'), 158 | store: path.resolve(__dirname, './src/store/'), 159 | }, 160 | }, 161 | } 162 | 163 | // Optimize the bundle in release (production) mode 164 | if (!DEBUG) { 165 | config.plugins.push(new webpack.optimize.DedupePlugin()) 166 | } 167 | 168 | // https://github.com/jun0205/react-static-boilerplate/issues/14 169 | // Must always Uglify somehow or it won't work in IE8 170 | const uglyOptions = !DEBUG ? { 171 | compress: { 172 | warnings: VERBOSE, 173 | screw_ie8: false, 174 | }, 175 | mangle: { screw_ie8: false }, 176 | output: { screw_ie8: false }, 177 | } : { 178 | mangle: false, 179 | compress: { 180 | drop_debugger: false, 181 | warnings: VERBOSE, 182 | screw_ie8: false, 183 | }, 184 | output: { 185 | beautify: true, 186 | comments: true, 187 | bracketize: true, 188 | indent_level: 2, 189 | keep_quoted_props: true, 190 | screw_ie8: false, 191 | }, 192 | } 193 | 194 | config.plugins.push(new webpack.optimize.UglifyJsPlugin(uglyOptions)) 195 | 196 | if (!DEBUG) { 197 | config.plugins.push(new webpack.optimize.AggressiveMergingPlugin()) 198 | config.module.loaders 199 | .find(x => x.loader === 'babel-loader').query.plugins 200 | .unshift( 201 | 'transform-react-remove-prop-types', 202 | 'transform-react-constant-elements', 203 | 'transform-react-inline-elements', 204 | 'transform-es3-modules-literals', 205 | 'transform-es3-member-expression-literals', 206 | 'transform-es3-property-literals' 207 | ) 208 | } 209 | 210 | module.exports = config 211 | --------------------------------------------------------------------------------