Parcourir la source

upgrade react and react-dom to v15.0.2
change webpack config
change npm package
add eslint and stylelint config
index.html to index.ejs

John il y a 7 ans
Parent
commit
d3c22b227c

+ 0 - 20
.editorconfig

@@ -1,20 +0,0 @@
-# EditorConfig helps developers define and maintain consistent
-# coding styles between different editors and IDEs
-# http://editorconfig.org
-
-root = true
-
-[*]
-
-# Change these settings to your own preference
-indent_style = space
-indent_size = 2
-
-# We recommend you to keep these unchanged
-end_of_line = lf
-charset = utf-8
-trim_trailing_whitespace = true
-insert_final_newline = true
-
-[*.md]
-trim_trailing_whitespace = false

+ 28 - 0
.eslintrc

@@ -0,0 +1,28 @@
+{
+  "parser": "babel-eslint",
+  "extends": "airbnb",
+  "rules": {
+    "semi": ["error", "never"],
+    "react/prefer-stateless-function": 0,
+    "global-require": 0,
+    "linebreak-style": 0,
+    "no-console": 0,
+    "no-param-reassign": [2, { 
+      "props": false
+    }]
+  },
+  "parserOptions": {
+    "ecmaFeatures": {
+      "jsx": true, 
+      "experimentalObjectRestSpread": true
+    }
+  },
+  "env": {
+    "browser": true,
+    "node": true
+  },
+  "globals": {
+    "document": true,
+    "window": true
+  }
+}

+ 6 - 0
.stylelintrc

@@ -0,0 +1,6 @@
+{
+  "extends": "stylelint-config-standard",
+  "rules": {
+    "string-quotes": "single"
+  }
+}

+ 1 - 1
LICENSE.txt

@@ -1,6 +1,6 @@
                             The MIT License
 
-Copyright (c) 2016 jun0205. All rights reserved.
+Copyright (c) 2016 Konstantin Tarkus, John Ma. All rights reserved.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 2 - 2
README.md

@@ -58,8 +58,8 @@ $ npm start             # Build and launch the app, same as "node tools/start.js
 
 ### IE8 Support Version
 
-&nbsp; &nbsp; react <= 0.14.8<br>
-&nbsp; &nbsp; react-dom <= 0.14.8<br>
+&nbsp; &nbsp; react <= 0.14.8 or <= 15.0.2<br>
+&nbsp; &nbsp; react-dom <= 0.14.8 or <= 15.0.2<br>
 &nbsp; &nbsp; react-router <= 2.3.0<br>
 &nbsp; &nbsp; webpack = 1.15.0
 

+ 63 - 71
package.json

@@ -1,52 +1,72 @@
 {
   "name": "react-static-boilerplate",
+  "version": "0.0.0",
+  "private": true,
   "engines": {
-    "node": ">=5.0",
-    "npm": ">=3.3"
+    "node": ">=6.5",
+    "npm": ">=3.10"
   },
   "dependencies": {
-    "autoprefixer": "^6.3.6",
-    "babel-cli": "^6.9.0",
-    "babel-core": "^6.9.0",
-    "babel-eslint": "^6.0.4",
-    "babel-loader": "^6.2.4",
+    "babel-polyfill": "^6.26.0",
+    "classnames": "^2.2.5",
+    "es5-shim": "^4.5.9",
+    "es6-promise": "^4.1.1",
+    "fetch-detector": "^1.0.1",
+    "fetch-ie8": "^1.5.0",
+    "fetch-jsonp": "^1.1.3",
+    "react": "15.0.2",
+    "react-dom": "15.0.2",
+    "react-redux": "^4.4.5",
+    "react-router": "2.3.0",
+    "react-router-redux": "^4.0.5",
+    "redux": "^3.5.2",
+    "redux-thunk": "^2.1.0"
+  },
+  "scripts": {
+    "eslint": "eslint src/**/*.js tools/**/*.js",
+    "stylelint": "stylelint src/components/**/*.css src/routes/**/*.css",
+    "lint": "npm run eslint && npm run stylelint",
+    "test": "mocha --compilers js:babel-register",
+    "test:watch": "mocha --compilers js:babel-register --reporter min --watch",
+    "clean": "node tools/clean",
+    "build": "node tools/build --release",
+    "build:debug": "node tools/build",
+    "start": "node tools/start"
+  },
+  "devDependencies": {
+    "autoprefixer": "^6.7.7",
+    "babel-cli": "^6.26.0",
+    "babel-core": "^6.26.0",
+    "babel-eslint": "^6.1.2",
+    "babel-loader": "^6.4.1",
     "babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
     "babel-plugin-transform-es3-modules-literals": "0.0.3",
     "babel-plugin-transform-es3-property-literals": "^6.22.0",
-    "babel-plugin-transform-react-constant-elements": "^6.8.0",
-    "babel-plugin-transform-react-inline-elements": "^6.8.0",
-    "babel-plugin-transform-react-remove-prop-types": "^0.2.7",
-    "babel-plugin-transform-runtime": "^6.9.0",
-    "babel-polyfill": "^6.9.0",
-    "babel-preset-es2015": "^6.9.0",
+    "babel-plugin-transform-react-constant-elements": "^6.23.0",
+    "babel-plugin-transform-react-inline-elements": "^6.22.0",
+    "babel-plugin-transform-react-remove-prop-types": "^0.2.12",
+    "babel-plugin-transform-runtime": "^6.23.0",
+    "babel-preset-es2015": "^6.24.1",
     "babel-preset-es2015-loose": "^7.0.0",
-    "babel-preset-react": "^6.5.0",
-    "babel-preset-stage-1": "^6.5.0",
-    "babel-register": "^6.9.0",
-    "babel-runtime": "^6.9.0",
-    "browser-sync": "^2.12.8",
+    "babel-preset-react": "^6.24.1",
+    "babel-preset-stage-1": "^6.24.1",
+    "babel-register": "^6.26.0",
+    "babel-runtime": "^6.26.0",
+    "browser-sync": "^2.18.13",
     "chai": "^3.5.0",
-    "classnames": "^2.2.5",
     "css-loader": "^0.23.1",
-    "del": "^2.2.0",
-    "es5-shim": "^4.5.8",
-    "es6-promise": "^3.2.1",
-    "eslint": "^2.10.2",
+    "del": "^2.2.2",
+    "eslint": "^2.13.1",
     "eslint-config-airbnb": "^9.0.1",
-    "eslint-plugin-import": "^1.8.0",
-    "eslint-plugin-jsx-a11y": "^1.2.0",
-    "eslint-plugin-react": "^5.1.1",
-    "extend": "^3.0.0",
-    "fastclick": "^1.0.6",
-    "fbjs": "^0.8.2",
-    "fetch-detector": "^1.0.0",
-    "fetch-ie8": "^1.4.3",
-    "fetch-jsonp": "^1.0.0",
+    "eslint-plugin-import": "^1.16.0",
+    "eslint-plugin-jsx-a11y": "^1.5.5",
+    "eslint-plugin-react": "^5.2.2",
+    "extract-text-webpack-plugin": "^1.0.1",
     "file-loader": "^0.8.5",
     "fs-extra": "^4.0.1",
-    "history": "^2.1.1",
-    "json-loader": "^0.5.4",
-    "mocha": "^2.4.5",
+    "html-webpack-plugin": "^2.30.1",
+    "json-loader": "^0.5.7",
+    "mocha": "^2.5.3",
     "pixrem": "^3.0.0",
     "pleeease-filters": "^3.0.0",
     "postcss": "^5.0.21",
@@ -61,42 +81,14 @@
     "postcss-nesting": "^2.3.1",
     "postcss-selector-matches": "^2.0.1",
     "postcss-selector-not": "^2.0.0",
-    "react": "^0.14.8",
-    "react-dom": "^0.14.8",
-    "react-hot-loader": "^3.0.0-beta.6",
-    "react-redux": "^4.4.5",
-    "react-router": "2.3.0",
-    "react-router-redux": "^4.0.5",
-    "redbox-react": "^1.2.6",
-    "redux": "^3.5.2",
-    "redux-thunk": "^2.1.0",
-    "style-loader": "^0.13.1",
-    "stylelint": "^6.4.1",
-    "stylelint-config-standard": "^7.0.0",
-    "url-loader": "^0.5.7",
+    "react-hot-loader": "^3.0.0-beta.7",
+    "redbox-react": "^1.5.0",
+    "style-loader": "^0.13.2",
+    "stylelint": "^8.1.1",
+    "stylelint-config-standard": "^17.0.0",
+    "url-loader": "^0.5.9",
     "webpack": "1.15.0",
-    "webpack-dev-middleware": "^1.6.1",
-    "webpack-hot-middleware": "^2.10.0"
-  },
-  "eslintConfig": {
-    "parser": "babel-eslint",
-    "extends": "airbnb"
-  },
-  "stylelint": {
-    "extends": "stylelint-config-standard",
-    "rules": {
-      "string-quotes": "single"
-    }
-  },
-  "scripts": {
-    "eslint": "eslint components core routes test tools",
-    "stylelint": "stylelint \"src/components/**/*.css\" \"src/routes/**/*.css\"",
-    "lint": "npm run eslint && npm run stylelint",
-    "test": "mocha --compilers js:babel-register",
-    "test:watch": "mocha --compilers js:babel-register --reporter min --watch",
-    "clean": "node tools/clean",
-    "build": "node tools/build --release",
-    "build:debug": "node tools/build",
-    "start": "node tools/start"
+    "webpack-dev-middleware": "^1.12.0",
+    "webpack-hot-middleware": "^2.19.1"
   }
 }

+ 53 - 0
postcss.config.js

@@ -0,0 +1,53 @@
+const AUTOPREFIXER_BROWSERS = [
+  'Android >= 4',
+  'Chrome >= 35',
+  'Firefox >= 31',
+  'Explorer >= 9',
+  'iOS >= 7',
+  'Opera >= 12',
+  'Safari >= 7.1',
+]
+
+module.exports = {
+  plugins: [
+    // Transfer @import rule by inlining content, e.g. @import 'normalize.css'
+    // https://github.com/postcss/postcss-import
+    require('postcss-import')(),
+    // W3C variables, e.g. :root { --color: red; } div { background: var(--color); }
+    // https://github.com/postcss/postcss-custom-properties
+    require('postcss-custom-properties')(),
+    // W3C CSS Custom Media Queries, e.g. @custom-media --small-viewport (max-width: 30em);
+    // https://github.com/postcss/postcss-custom-media
+    require('postcss-custom-media')(),
+    // CSS4 Media Queries, e.g. @media screen and (width >= 500px) and (width <= 1200px) { }
+    // https://github.com/postcss/postcss-media-minmax
+    require('postcss-media-minmax')(),
+    // W3C CSS Custom Selectors, e.g. @custom-selector :--heading h1, h2, h3, h4, h5, h6;
+    // https://github.com/postcss/postcss-custom-selectors
+    require('postcss-custom-selectors')(),
+    // W3C calc() function, e.g. div { height: calc(100px - 2em); }
+    // https://github.com/postcss/postcss-calc
+    require('postcss-calc')(),
+    // Allows you to nest one style rule inside another
+    // https://github.com/jonathantneal/postcss-nesting
+    require('postcss-nesting')(),
+    // W3C color() function, e.g. div { background: color(red alpha(90%)); }
+    // https://github.com/postcss/postcss-color-function
+    require('postcss-color-function')(),
+    // Convert CSS shorthand filters to SVG equivalent, e.g. .blur { filter: blur(4px); }
+    // https://github.com/iamvdo/pleeease-filters
+    require('pleeease-filters')(),
+    // Generate pixel fallback for "rem" units, e.g. div { margin: 2.5rem 2px 3em 100%; }
+    // https://github.com/robwierzbowski/node-pixrem
+    require('pixrem')(),
+    // W3C CSS Level4 :matches() pseudo class, e.g. p:matches(:first-child, .special) { }
+    // https://github.com/postcss/postcss-selector-matches
+    require('postcss-selector-matches')(),
+    // Transforms :not() W3C CSS Level 4 pseudo class to :not() CSS Level 3 selectors
+    // https://github.com/postcss/postcss-selector-not
+    require('postcss-selector-not')(),
+    // Add vendor prefixes to CSS rules using values from caniuse.com
+    // https://github.com/postcss/autoprefixer
+    require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }),
+  ],
+}

+ 1 - 1
src/components/Counter/Counter.css

@@ -5,7 +5,7 @@
 }
 
 .counterGreen {
-  color: rgb(25,200,25);
+  color: rgb(25, 200, 25);
 }
 
 .counterContainer {

+ 4 - 4
src/components/Counter/Counter.js

@@ -6,15 +6,15 @@ export const Counter = (props) => (
     <h2 className={classes.counterContainer}>
       Counter:
       {' '}
-      <span className={classes['counterGreen']}>
+      <span className={classes.counterGreen}>
         {props.counter}
       </span>
     </h2>
-    <button className='btn btn-default' onClick={props.increment}>
+    <button className="btn btn-default" onClick={props.increment}>
       Increment
     </button>
     {' '}
-    <button className='btn btn-default' onClick={props.doubleAsync}>
+    <button className="btn btn-default" onClick={props.doubleAsync}>
       Double (Async)
     </button>
   </div>
@@ -23,7 +23,7 @@ export const Counter = (props) => (
 Counter.propTypes = {
   counter: React.PropTypes.number.isRequired,
   doubleAsync: React.PropTypes.func.isRequired,
-  increment: React.PropTypes.func.isRequired
+  increment: React.PropTypes.func.isRequired,
 }
 
 export default Counter

+ 2 - 2
src/components/Header/Header.js

@@ -5,11 +5,11 @@ import classes from './Header.css'
 export const Header = () => (
   <div>
     <h1>React Redux Starter Kit</h1>
-    <IndexLink to='/' activeClassName={classes.activeRoute}>
+    <IndexLink to="/" activeClassName={classes.activeRoute}>
       Home
     </IndexLink>
     {' · '}
-    <Link to='/counter' activeClassName={classes.activeRoute}>
+    <Link to="/counter" activeClassName={classes.activeRoute}>
       Counter
     </Link>
   </div>

+ 1 - 1
src/components/Layout/Layout.css

@@ -5,5 +5,5 @@ body {
 }
 
 .mainContainer {
-  padding-top:20px;
+  padding-top: 20px;
 }

+ 2 - 2
src/components/Layout/Layout.js

@@ -3,7 +3,7 @@ import Header from '../Header'
 import classes from './Layout.css'
 
 export const CoreLayout = ({ children }) => (
-  <div className='container text-center'>
+  <div className="container text-center">
     <Header />
     <div className={classes.mainContainer}>
       {children}
@@ -12,7 +12,7 @@ export const CoreLayout = ({ children }) => (
 )
 
 CoreLayout.propTypes = {
-  children: React.PropTypes.element.isRequired
+  children: React.PropTypes.element.isRequired,
 }
 
 export default CoreLayout

+ 5 - 4
src/components/variables.css

@@ -1,8 +1,9 @@
 :root {
-  --font-base : 'sans-serif';
+  --font-base: 'sans-serif';
 }
 
-html, body {
-	margin: 0px;
-	padding: 0px;
+html,
+body {
+  margin: 0;
+  padding: 0;
 }

+ 2 - 2
src/containers/AppContainer.js

@@ -7,10 +7,10 @@ class AppContainer extends React.Component {
     history: PropTypes.object.isRequired,
     routes: PropTypes.object.isRequired,
     routerKey: PropTypes.number,
-    store: PropTypes.object.isRequired
+    store: PropTypes.object.isRequired,
   }
 
-  render () {
+  render() {
     const { history, routes, routerKey, store } = this.props
 
     return (

+ 18 - 0
src/index.ejs

@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="no-js">
+
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="x-ua-compatible" content="ie=edge">
+  <title>React Redux Starter Kit</title>
+  <meta name="description" content="">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="apple-touch-icon" href="apple-touch-icon.png">
+  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
+</head>
+
+<body>
+  <div id="container"></div>
+</body>
+
+</html>

+ 5 - 2
src/main.js

@@ -1,3 +1,6 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable no-undef */
+
 import React from 'react'
 import ReactDOM from 'react-dom'
 import createBrowserHistory from 'history/lib/createBrowserHistory'
@@ -10,7 +13,7 @@ import AppContainer from './containers/AppContainer'
 // Browser History Setup
 // ========================================================
 const browserHistory = useRouterHistory(createBrowserHistory)({
-  basename: __BASENAME__
+  basename: __BASENAME__,
 })
 
 // ========================================================
@@ -23,7 +26,7 @@ const browserHistory = useRouterHistory(createBrowserHistory)({
 const initialState = window.___INITIAL_STATE__
 const store = createStore(initialState, browserHistory)
 const history = syncHistoryWithStore(browserHistory, store, {
-  selectLocationState: (state) => state.router
+  selectLocationState: (state) => state.router,
 })
 
 // ========================================================

+ 12 - 1
src/routes/Counter/components/CounterView.js

@@ -6,8 +6,19 @@ export const CounterView = (props) => (
     <Counter
       counter={props.counter}
       doubleAsync={props.doubleAsync}
-      increment={props.increment} />
+      increment={props.increment}
+    />
   </div>
 )
 
+CounterView.defaultProps = {
+  counter: 0,
+}
+
+CounterView.propTypes = {
+  counter: React.PropTypes.number,
+  doubleAsync: React.PropTypes.func,
+  increment: React.PropTypes.func,
+}
+
 export default CounterView

+ 2 - 2
src/routes/Counter/containers/CounterContainer.js

@@ -14,11 +14,11 @@ import CounterView from '../components/CounterView'
 
 const mapActionCreators = {
   increment: () => increment(1),
-  doubleAsync
+  doubleAsync,
 }
 
 const mapStateToProps = (state) => ({
-  counter: state.counter
+  counter: state.counter,
 })
 
 /*  Note: mapStateToProps is where you should use `reselect` to create selectors, ie:

+ 2 - 2
src/routes/Counter/index.js

@@ -3,7 +3,7 @@ import { injectReducer } from '../../store/reducers'
 export default (store) => ({
   path: 'counter',
   /*  Async getComponent is only invoked when route matches   */
-  getComponent (nextState, cb) {
+  getComponent(nextState, cb) {
     /*  Webpack - use 'require.ensure' to create a split point
         and embed an async module loader (jsonp) when bundling   */
     require.ensure([], (require) => {
@@ -20,5 +20,5 @@ export default (store) => ({
 
     /* Webpack named bundle   */
     }, 'counter')
-  }
+  },
 })

+ 10 - 10
src/routes/Counter/modules/counter.js

@@ -6,10 +6,10 @@ export const COUNTER_INCREMENT = 'COUNTER_INCREMENT'
 // ------------------------------------
 // Actions
 // ------------------------------------
-export function increment (value = 1) {
+export function increment(value = 1) {
   return {
     type: COUNTER_INCREMENT,
-    payload: value
+    payload: value,
   }
 }
 
@@ -21,34 +21,34 @@ export function increment (value = 1) {
     you'd probably want to dispatch an action of COUNTER_DOUBLE and let the
     reducer take care of this logic.  */
 
-export const doubleAsync = () => {
-  return (dispatch, getState) => {
-    return new Promise((resolve) => {
+export const doubleAsync = () => (
+  (dispatch, getState) => (
+    new Promise((resolve) => {
       setTimeout(() => {
         dispatch(increment(getState().counter))
         resolve()
       }, 200)
     })
-  }
-}
+  )
+)
 
 export const actions = {
   increment,
-  doubleAsync
+  doubleAsync,
 }
 
 // ------------------------------------
 // Action Handlers
 // ------------------------------------
 const ACTION_HANDLERS = {
-  [COUNTER_INCREMENT]: (state, action) => state + action.payload
+  [COUNTER_INCREMENT]: (state, action) => state + action.payload,
 }
 
 // ------------------------------------
 // Reducer
 // ------------------------------------
 const initialState = 0
-export default function counterReducer (state = initialState, action) {
+export default function counterReducer(state = initialState, action) {
   const handler = ACTION_HANDLERS[action.type]
 
   return handler ? handler(state, action) : state

+ 3 - 2
src/routes/Home/components/HomeView.js

@@ -6,9 +6,10 @@ export const HomeView = () => (
   <div>
     <h4>Welcome!</h4>
     <img
-      alt='This is a duck, because Redux!'
+      alt="This is a duck, because Redux!"
       className={classes.duck}
-      src={DuckImage} />
+      src={DuckImage}
+    />
   </div>
 )
 

+ 1 - 1
src/routes/Home/index.js

@@ -2,5 +2,5 @@ import HomeView from './components/HomeView'
 
 // Sync route definition
 export default {
-  component: HomeView
+  component: HomeView,
 }

+ 2 - 2
src/routes/NotFound/NotFound.css

@@ -1,3 +1,3 @@
 .notFound {
-
-}
+  padding: 20px;
+}

+ 2 - 2
src/routes/NotFound/NotFound.js

@@ -10,8 +10,8 @@ const goBack = (e) => {
 export const NotFound = () => (
   <div className={classes.notFound}>
     <h4>Page not found!</h4>
-    <p><a href='#' onClick={goBack}>&larr; Back</a></p>
+    <p><a href="#" onClick={goBack}>&larr; Back</a></p>
   </div>
 )
 
-export default NotFound
+export default NotFound

+ 2 - 2
src/routes/NotFound/index.js

@@ -2,5 +2,5 @@ import NotFound from './NotFound'
 
 export default {
   path: '*',
-  component: NotFound
-}
+  component: NotFound,
+}

+ 3 - 3
src/routes/index.js

@@ -2,7 +2,7 @@
 import Layout from '../components/Layout'
 import Home from './Home'
 import NotFound from './NotFound'
-import CounterRoute from './Counter'
+import counterRoute from './Counter'
 
 /*  Note: Instead of using JSX, we recommend using react-router
     PlainRoute objects to build route definitions.   */
@@ -12,9 +12,9 @@ export const createRoutes = (store) => ({
   component: Layout,
   indexRoute: Home,
   childRoutes: [
-    CounterRoute(store),
+    counterRoute(store),
     NotFound,
-  ]
+  ],
 })
 
 /*  Note: childRoutes can be chunked or otherwise loaded programmatically

+ 0 - 17
src/static/index.html

@@ -1,17 +0,0 @@
-<!doctype html>
-<html class="no-js">
-  <head>
-    <meta charset="utf-8">
-    <meta http-equiv="x-ua-compatible" content="ie=edge">
-    <title>React Redux Starter Kit</title>
-    <meta name="description" content="">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <link rel="apple-touch-icon" href="apple-touch-icon.png">
-    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
-  </head>
-  <body>
-    <div id="container"></div>
-    <script src="/assets/vendor.js"></script>
-    <script src="/assets/app.js"></script>
-  </body>
-</html>

+ 2 - 0
src/store/createStore.js

@@ -1,3 +1,5 @@
+/* eslint-disable no-undef */
+
 import { applyMiddleware, compose, createStore } from 'redux'
 import routerMiddleware from 'react-router-redux/lib/middleware'
 import thunk from 'redux-thunk'

+ 4 - 4
src/store/reducers.js

@@ -1,13 +1,13 @@
 import { combineReducers } from 'redux'
 import { routerReducer as router } from 'react-router-redux/lib/reducer'
 
-export const makeRootReducer = (asyncReducers) => {
-  return combineReducers({
+export const makeRootReducer = (asyncReducers) => (
+  combineReducers({
     // Add sync reducers here
     router,
-    ...asyncReducers
+    ...asyncReducers,
   })
-}
+)
 
 export const injectReducer = (store, { key, reducer }) => {
   store.asyncReducers[key] = reducer

+ 4 - 4
test/spec.js

@@ -8,12 +8,12 @@
  * LICENSE.txt file in the root directory of this source tree.
  */
 
-import { expect } from 'chai';
+import { expect } from 'chai'
 
 describe('test suite', () => {
 
   it('test', () => {
-    expect(true).to.be.equal.true;
-  });
+    expect(true).to.be.equal.true
+  })
 
-});
+})

+ 0 - 6
tools/.eslintrc

@@ -1,6 +0,0 @@
-{
-  "rules": {
-    "global-require": 0,
-    "no-console": 0
-  }
-}

+ 2 - 2
tools/build.js

@@ -8,10 +8,10 @@
  * LICENSE.txt file in the root directory of this source tree.
  */
 
-const task = require('./task');
+const task = require('./task')
 
 module.exports = task('build', () => Promise.resolve()
   .then(() => require('./clean'))
   .then(() => require('./copy'))
   .then(() => require('./bundle'))
-);
+)

+ 10 - 10
tools/bundle.js

@@ -8,19 +8,19 @@
  * LICENSE.txt file in the root directory of this source tree.
  */
 
-const webpack = require('webpack');
-const task = require('./task');
-const config = require('./webpack.config');
+const webpack = require('webpack')
+const task = require('./task')
+const webpackConfig = require('../webpack.config')
 
 module.exports = task('bundle', new Promise((resolve, reject) => {
-  const bundler = webpack(config);
+  const bundler = webpack(webpackConfig)
   const run = (err, stats) => {
     if (err) {
-      reject(err);
+      reject(err)
     } else {
-      console.log(stats.toString(config.stats));
-      resolve();
+      console.log(stats.toString(webpackConfig.stats))
+      resolve()
     }
-  };
-  bundler.run(run);
-}));
+  }
+  bundler.run(run)
+}))

+ 5 - 3
tools/clean.js

@@ -8,7 +8,9 @@
  * LICENSE.txt file in the root directory of this source tree.
  */
 
-const del = require('del');
-const task = require('./task');
+const del = require('del')
+const task = require('./task')
 
-module.exports = task('clean', () => del(['build/*', '!build/.git'], { dot: true }));
+module.exports = task('clean', () =>
+  del(['./build/*', '!./build/.git'], { dot: true })
+)

+ 1 - 1
tools/copy.js

@@ -15,4 +15,4 @@ const fs = require('fs-extra')
  * Copies static files such as robots.txt, favicon.ico to the
  * output (build) folder.
  */
-module.exports = task('copy', fs.copy('./src/static', './build/static'))
+module.exports = task('copy', fs.copy('./src/static', './build'))

+ 35 - 25
tools/start.js

@@ -8,39 +8,44 @@
  * LICENSE.txt file in the root directory of this source tree.
  */
 
-const browserSync = require('browser-sync');
-const webpack = require('webpack');
-const webpackDevMiddleware = require('webpack-dev-middleware');
-const webpackHotMiddleware = require('webpack-hot-middleware');
-const task = require('./task');
-const config = require('./webpack.config');
+const path = require('path')
+const browserSync = require('browser-sync')
+const webpack = require('webpack')
+const webpackDevMiddleware = require('webpack-dev-middleware')
+const webpackHotMiddleware = require('webpack-hot-middleware')
+const task = require('./task')
+const webpackConfig = require('../webpack.config')
 
 task('start', () => new Promise(resolve => {
   // Hot Module Replacement (HMR) + React Hot Reload
-  if (config.debug) {
-    config.entry.vendor.unshift('react-hot-loader/patch', 'webpack-hot-middleware/client');
-    config.module.loaders.find(x => x.loader === 'babel-loader')
-      .query.plugins.unshift('react-hot-loader/babel');
-    config.plugins.push(new webpack.HotModuleReplacementPlugin());
-    config.plugins.push(new webpack.NoErrorsPlugin());
+  if (webpackConfig.debug) {
+    webpackConfig.entry.vendor
+      .unshift('react-hot-loader/patch', 'webpack-hot-middleware/client')
+    webpackConfig.module.loaders
+      .find(x => x.loader === 'babel-loader').query.plugins
+      .unshift('react-hot-loader/babel')
+    webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin())
+    webpackConfig.plugins.push(new webpack.NoErrorsPlugin())
   }
 
-  const bundler = webpack(config);
+  const bundler = webpack(webpackConfig)
 
   browserSync({
     port: Number(process.env.PORT || 3000),
-    ui: { port: Number(process.env.PORT || 3000) + 1 },
+    ui: {
+      port: Number(process.env.PORT || 3000) + 1,
+    },
     server: {
-      baseDir: 'src/static',
+      baseDir: './src',
 
       middleware: [
         webpackDevMiddleware(bundler, {
-          // IMPORTANT: dev middleware can't access config, so we should
+          // IMPORTANT: dev middleware can't access webpackConfig, so we should
           // provide publicPath by ourselves
-          publicPath: config.output.publicPath,
+          publicPath: webpackConfig.output.publicPath,
 
           // pretty colored output
-          stats: config.stats,
+          stats: webpackConfig.stats,
 
           // for other settings see
           // http://webpack.github.io/docs/webpack-dev-middleware.html
@@ -51,10 +56,15 @@ task('start', () => new Promise(resolve => {
 
         // Serve index.html for all unknown requests
         (req, res, next) => {
-          if (req.headers.accept.startsWith('text/html')) {
-            req.url = '/index.html'; // eslint-disable-line no-param-reassign
-          }
-          next();
+          const filename = path.join(bundler.outputPath, 'index.html')
+          bundler.outputFileSystem.readFile(filename, (err, result) => {
+            if (err) {
+              next(err)
+              return
+            }
+            res.setHeader('content-type', 'text/html')
+            res.end(result)
+          })
         },
       ],
     },
@@ -65,7 +75,7 @@ task('start', () => new Promise(resolve => {
       'build/**/*.css',
       'build/**/*.html',
     ],
-  });
+  })
 
-  resolve();
-}));
+  resolve()
+}))

+ 8 - 8
tools/task.js

@@ -9,17 +9,17 @@
  */
 
 function format(time) {
-  return time.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
+  return time.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1')
 }
 
 function task(name, action) {
-  const start = new Date();
-  console.log(`[${format(start)}] Starting '${name}'...`);
+  const start = new Date()
+  console.log(`[${format(start)}] Starting '${name}'...`)
   return Promise.resolve(action instanceof Function ? action() : action).then(() => {
-    const end = new Date();
-    const time = end.getTime() - start.getTime();
-    console.log(`[${format(end)}] Finished '${name}' after ${time}ms`);
-  }, err => console.error(err.stack));
+    const end = new Date()
+    const time = end.getTime() - start.getTime()
+    console.log(`[${format(end)}] Finished '${name}' after ${time}ms`)
+  }, err => console.error(err.stack))
 }
 
-module.exports = task;
+module.exports = task

+ 0 - 238
tools/webpack.config.js

@@ -1,238 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- *
- * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE.txt file in the root directory of this source tree.
- */
-
-const path = require('path');
-const webpack = require('webpack');
-const extend = require('extend');
-
-const DEBUG = !(process.argv.slice(2) == '--release');
-const VERBOSE = process.argv.slice(2) == '--verbose';
-const AUTOPREFIXER_BROWSERS = [
-  'Android 2.3',
-  'Android >= 4',
-  'Chrome >= 35',
-  'Firefox >= 31',
-  'Explorer >= 9',
-  'iOS >= 7',
-  'Opera >= 12',
-  'Safari >= 7.1',
-];
-
-/**
- * Webpack configuration (core/main.js => build/bundle.js)
- * http://webpack.github.io/docs/configuration.html
- */
-const config = {
-
-  // The base directory
-  context: path.resolve(__dirname, '../src'),
-
-  // The entry point for the bundle
-  entry: {
-    app: [      
-      './main.js'
-    ],
-    vendor: [
-      'es5-shim',
-      'es5-shim/es5-sham',
-      'es6-promise',
-      'babel-polyfill',
-      'fetch-detector',
-      'fetch-ie8',
-    ]
-  },
-
-  // Options affecting the output of the compilation
-  output: {
-    path: path.resolve(__dirname, '../build'),
-    publicPath: '/',
-    filename: 'assets/[name].js',
-    chunkFilename: 'assets/[name].js',
-    sourcePrefix: '  ',
-  },
-
-  // Switch loaders to debug or release mode
-  debug: DEBUG,
-  cache: DEBUG,
-
-  // Developer tool to enhance debugging, source maps
-  // http://webpack.github.io/docs/configuration.html#devtool
-  devtool: DEBUG ? 'source-map' : false,
-
-  // What information should be printed to the console
-  stats: {
-    colors: true,
-    reasons: DEBUG,
-    hash: VERBOSE,
-    version: VERBOSE,
-    timings: true,
-    chunks: VERBOSE,
-    chunkModules: VERBOSE,
-    cached: VERBOSE,
-    cachedAssets: VERBOSE,
-  },
-
-  // The list of plugins for Webpack compiler
-  plugins: [
-    new webpack.optimize.OccurenceOrderPlugin(),
-    new webpack.optimize.CommonsChunkPlugin({
-      name: "vendor",
-      minChunks: Infinity,
-    }),
-    new webpack.DefinePlugin({
-      'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"',
-      __DEV__: DEBUG,
-      __BASENAME__: JSON.stringify(process.env.BASENAME || '')
-    }),
-  ],
-
-  // Options affecting the normal modules
-  module: {
-    loaders: [
-      {
-        test: /\.jsx?$/,
-        include: [
-          path.resolve(__dirname, '../src'),
-        ],
-        loader: 'babel-loader',
-        query: {
-          // https://github.com/babel/babel-loader#options
-          cacheDirectory: DEBUG,
-
-          // https://babeljs.io/docs/usage/options/
-          babelrc: false,
-          presets: [
-            'react',
-            'es2015-loose',
-            'stage-1',
-          ],
-          plugins: [
-            'transform-runtime',
-            ...DEBUG ? [] : [
-              'transform-react-remove-prop-types',
-              'transform-react-constant-elements',
-              'transform-react-inline-elements',
-              'transform-es3-modules-literals',
-              'transform-es3-member-expression-literals',
-              'transform-es3-property-literals'
-            ],
-          ],
-        },
-      },
-      {
-        test: /\.css/,
-        loaders: [
-          'style-loader',
-          `css-loader?${JSON.stringify({
-            sourceMap: DEBUG,
-            // CSS Modules https://github.com/css-modules/css-modules
-            modules: true,
-            localIdentName: DEBUG ? '[name]_[local]_[hash:base64:3]' : '[hash:base64:4]',
-            // CSS Nano http://cssnano.co/options/
-            minimize: !DEBUG,
-          })}`,
-          'postcss-loader?pack=default',
-        ],
-      },
-      {
-        test: /\.scss$/,
-        loaders: [
-          'style-loader',
-          `css-loader?${JSON.stringify({ sourceMap: DEBUG, minimize: !DEBUG })}`,
-          'postcss-loader?pack=sass',
-          'sass-loader',
-        ],
-      },
-      {
-        test: /\.json$/,
-        loader: 'json-loader',
-      },
-      {
-        test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/,
-        loader: 'url-loader',
-        query: {
-          name: DEBUG ? 'assets/[path][name].[ext]?[hash]' : '[hash].[ext]',
-          limit: 10000,
-        },
-      },
-      {
-        test: /\.(eot|ttf|wav|mp3)$/,
-        loader: 'file-loader',
-        query: {
-          name: DEBUG ? 'assets/[path][name].[ext]?[hash]' : '[hash].[ext]',
-        },
-      },
-    ]
-  },
-
-  // The list of plugins for PostCSS
-  // https://github.com/postcss/postcss
-  postcss(bundler) {
-    return {
-      default: [
-        // Transfer @import rule by inlining content, e.g. @import 'normalize.css'
-        // https://github.com/postcss/postcss-import
-        require('postcss-import')({ addDependencyTo: bundler }),
-        // W3C variables, e.g. :root { --color: red; } div { background: var(--color); }
-        // https://github.com/postcss/postcss-custom-properties
-        require('postcss-custom-properties')(),
-        // W3C CSS Custom Media Queries, e.g. @custom-media --small-viewport (max-width: 30em);
-        // https://github.com/postcss/postcss-custom-media
-        require('postcss-custom-media')(),
-        // CSS4 Media Queries, e.g. @media screen and (width >= 500px) and (width <= 1200px) { }
-        // https://github.com/postcss/postcss-media-minmax
-        require('postcss-media-minmax')(),
-        // W3C CSS Custom Selectors, e.g. @custom-selector :--heading h1, h2, h3, h4, h5, h6;
-        // https://github.com/postcss/postcss-custom-selectors
-        require('postcss-custom-selectors')(),
-        // W3C calc() function, e.g. div { height: calc(100px - 2em); }
-        // https://github.com/postcss/postcss-calc
-        require('postcss-calc')(),
-        // Allows you to nest one style rule inside another
-        // https://github.com/jonathantneal/postcss-nesting
-        require('postcss-nesting')(),
-        // W3C color() function, e.g. div { background: color(red alpha(90%)); }
-        // https://github.com/postcss/postcss-color-function
-        require('postcss-color-function')(),
-        // Convert CSS shorthand filters to SVG equivalent, e.g. .blur { filter: blur(4px); }
-        // https://github.com/iamvdo/pleeease-filters
-        require('pleeease-filters')(),
-        // Generate pixel fallback for "rem" units, e.g. div { margin: 2.5rem 2px 3em 100%; }
-        // https://github.com/robwierzbowski/node-pixrem
-        require('pixrem')(),
-        // W3C CSS Level4 :matches() pseudo class, e.g. p:matches(:first-child, .special) { }
-        // https://github.com/postcss/postcss-selector-matches
-        require('postcss-selector-matches')(),
-        // Transforms :not() W3C CSS Level 4 pseudo class to :not() CSS Level 3 selectors
-        // https://github.com/postcss/postcss-selector-not
-        require('postcss-selector-not')(),
-        // Add vendor prefixes to CSS rules using values from caniuse.com
-        // https://github.com/postcss/autoprefixer
-        require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }),
-      ],
-      sass: [
-        require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }),
-      ],
-    };
-  },
-};
-
-// Optimize the bundle in release (production) mode
-if (!DEBUG) {
-  config.plugins.push(new webpack.optimize.DedupePlugin());
-  config.plugins.push(new webpack.optimize.UglifyJsPlugin({
-    compress: { warnings: VERBOSE, screw_ie8: false },
-    mangle: { screw_ie8: false },
-    output: { screw_ie8: false }
-  }));
-  config.plugins.push(new webpack.optimize.AggressiveMergingPlugin());
-}
-
-module.exports = config;

+ 182 - 0
webpack.config.js

@@ -0,0 +1,182 @@
+const path = require('path');
+const webpack = require('webpack');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+const DEBUG = !(process.argv.slice(2) == '--release');
+const VERBOSE = process.argv.slice(2) == '--verbose';
+
+/**
+ * Webpack configuration (core/main.js => build/bundle.js)
+ * http://webpack.github.io/docs/configuration.html
+ */
+const config = {
+
+  // The base directory
+  context: path.resolve(__dirname, './src'),
+
+  // The entry point for the bundle
+  entry: {
+    app: [
+      './main.js',
+    ],
+    vendor: [
+      'es5-shim',
+      'es5-shim/es5-sham',
+      'es6-promise',
+      'babel-polyfill',
+      'fetch-detector',
+      'fetch-ie8',
+      'fetch-jsonp',
+      'react',
+      'react-dom',
+      'react-redux',
+      'react-router',
+      'react-router-redux',
+      'redux',
+      'redux-thunk',
+    ]
+  },
+
+  // Options affecting the output of the compilation
+  output: {
+    path: path.resolve(__dirname, 'build'),
+    publicPath: '/',
+    filename: 'assets/[name].js',
+    chunkFilename: 'assets/[name].js',
+    sourcePrefix: '  ',
+  },
+
+  // Switch loaders to debug or release mode
+  debug: DEBUG,
+  cache: DEBUG,
+
+  // Developer tool to enhance debugging, source maps
+  // http://webpack.github.io/docs/configuration.html#devtool
+  devtool: DEBUG ? 'source-map' : false,
+
+  // What information should be printed to the console
+  stats: {
+    colors: true,
+    reasons: DEBUG,
+    hash: VERBOSE,
+    version: VERBOSE,
+    timings: true,
+    chunks: VERBOSE,
+    chunkModules: VERBOSE,
+    cached: VERBOSE,
+    cachedAssets: VERBOSE,
+    children: false,
+  },
+
+  // The list of plugins for Webpack compiler
+  plugins: [
+    new webpack.optimize.OccurenceOrderPlugin(),
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'vendor',
+      minChunks: Infinity,
+    }),
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"',
+      __DEV__: DEBUG,
+      __BASENAME__: JSON.stringify(process.env.BASENAME || '')
+    }),
+    new ExtractTextPlugin(
+      'assets/styles.css',
+      {
+        minimize: !DEBUG,
+        allChunks: true,
+      }
+    ),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, './src/index.ejs'),
+      filename: 'index.html',
+      minify: !DEBUG ? {
+        collapseWhitespace: true,
+      } : null,
+      hash: true,
+    }),
+  ],
+
+  // Options affecting the normal modules
+  module: {
+    loaders: [
+      {
+        test: /\.jsx?$/,
+        include: [
+          path.resolve(__dirname, './src'),
+        ],
+        loader: 'babel-loader',
+        query: {
+          plugins: [],
+        },
+      },
+      {
+        test: /\.css/,
+        loader: ExtractTextPlugin.extract(
+          'style-loader',
+          'css-loader?-autoprefixer!postcss-loader'
+        ),
+      },
+      {
+        test: /\.scss$/,
+        loader: ExtractTextPlugin.extract(
+          'style-loader',
+          'css-loader?-autoprefixer!postcss-loader!sass-loader'
+        ),
+      },
+      {
+        test: /\.json$/,
+        loader: 'json-loader',
+      },
+      {
+        test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/,
+        loader: 'url-loader',
+        query: {
+          name: 'assets/[path][name].[ext]',
+          limit: 10000,
+        },
+      },
+      {
+        test: /\.(eot|ttf|wav|mp3|ogg)$/,
+        loader: 'file-loader',
+        query: {
+          name: 'assets/[path][name].[ext]',
+        },
+      },
+    ],
+  },
+
+  // Alias
+  resolve: {
+    alias: {
+      components: path.resolve(__dirname, './src/components/'),
+      routes: path.resolve(__dirname, './src/routes/'),
+      services: path.resolve(__dirname, './src/services/'),
+      store: path.resolve(__dirname, './src/store/'),
+    },
+  },
+};
+
+// Optimize the bundle in release (production) mode
+if (!DEBUG) {
+  config.plugins.push(new webpack.optimize.DedupePlugin());
+  config.plugins.push(new webpack.optimize.UglifyJsPlugin({
+    compress: { warnings: VERBOSE, screw_ie8: false },
+    mangle: { screw_ie8: false },
+    output: { screw_ie8: false },
+  }));
+  config.plugins.push(new webpack.optimize.AggressiveMergingPlugin());
+  config.module.loaders
+    .find(x => x.loader === 'babel-loader').query.plugins
+    .unshift(
+    'transform-react-remove-prop-types',
+    'transform-react-constant-elements',
+    'transform-react-inline-elements',
+    'transform-es3-modules-literals',
+    'transform-es3-member-expression-literals',
+    'transform-es3-property-literals'
+    );
+}
+
+module.exports = config;