├── .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 |

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 |
--------------------------------------------------------------------------------