├── LICENSE ├── README.md ├── meta.js └── template ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── build ├── config.js ├── index.html ├── server.js ├── webpack.base.js ├── webpack.dev.js └── webpack.prod.js ├── package.json ├── src ├── components │ ├── Home │ │ ├── Home.css │ │ ├── Home.js │ │ └── index.js │ └── Test │ │ ├── Test.css │ │ ├── Test.js │ │ └── index.js ├── containers │ ├── App.js │ ├── HomePage.js │ └── TestPage.js ├── index.js ├── modules │ ├── home.js │ ├── index.js │ └── test.js ├── routes.js └── store │ ├── index.js │ ├── store.development.js │ └── store.production.js └── test ├── .eslintrc ├── index.js ├── karma.conf.js └── specs └── Hello.spec.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 xfcf1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-webpack-starter 2 | Quick start with React and antd UI 3 | 4 | # How to use 5 | 6 | 1.global install magic-cli 7 | 8 | ``` 9 | $ yarn global add magic-cli 10 | ``` 11 | or 12 | ``` 13 | $ npm install -g magic-cli 14 | ``` 15 | 16 | 2.init project in directory 17 | 18 | ``` 19 | $ magic new xfcf1/react-webpack-starter my_react_project 20 | ``` 21 | 22 | or you can use alias 23 | 24 | ``` 25 | $ magic alias aliasName xfcf1/react-webpack-starter 26 | 27 | $ magic new aliasName my_react_project 28 | ``` 29 | -------------------------------------------------------------------------------- /meta.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prompts: { 3 | name: { 4 | type: 'string', 5 | required: true, 6 | message: 'Project name' 7 | }, 8 | description: { 9 | type: 'string', 10 | required: false, 11 | message: 'Project description', 12 | default: 'My React Project' 13 | }, 14 | author: { 15 | type: 'string', 16 | message: 'Author' 17 | }, 18 | port: { 19 | type: 'string', 20 | required: false, 21 | message: 'Listening port', 22 | default: '4000' 23 | }, 24 | theme: { 25 | type: 'list', 26 | required: true, 27 | message: 'Select which UI framework to install', 28 | choices: ['antd', 'antd-mobile'] 29 | }, 30 | eslint: { 31 | type: 'confirm', 32 | require: true, 33 | message: 'Use linting with ESLint?', 34 | default: true 35 | }, 36 | eslintConfig: { 37 | when: 'eslint', 38 | type: 'list', 39 | message: 'Which eslint config would you like to use?', 40 | choices: [{ 41 | name: 'Standard (https://github.com/feross/standard)', 42 | value: 'standard', 43 | short: 'Standard' 44 | }, { 45 | name: 'none (configure it yourself)', 46 | value: 'none', 47 | short: 'none' 48 | }] 49 | }, 50 | unit: { 51 | type: 'confirm', 52 | require: true, 53 | message: 'Setup unit tests with Karma + Mocha?', 54 | default: true 55 | } 56 | }, 57 | filters: { 58 | '.eslintignore': 'eslint', 59 | '.eslintrc.js': 'eslint', 60 | 'test/**/*': 'unit' 61 | }, 62 | completeMessage: 'To get started:\n\n cd {{destDirName}}\n npm install\n npm run dev' 63 | } 64 | -------------------------------------------------------------------------------- /template/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [["import", [ 3 | { "libraryName": "{{theme}}", "style": true } 4 | ]]], 5 | "presets": ["es2015", "stage-0", "react"], 6 | "env": { 7 | "production": { 8 | "plugins": [ 9 | "transform-react-constant-elements", 10 | "transform-react-remove-prop-types", 11 | "transform-react-pure-class-to-function" 12 | ] 13 | }, 14 | "development": { 15 | "presets": [ 16 | "react-hmre" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /template/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "standard", 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "experimentalObjectRestSpread": true, 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "plugins": [ 15 | "react" 16 | ], 17 | "rules": { 18 | "quotes": [ 19 | "error", 20 | "single" 21 | ], 22 | "semi": [ 23 | "error", 24 | "never" 25 | ], 26 | "react/jsx-uses-react": "error", 27 | "react/jsx-uses-vars": "error", 28 | 'no-debugger': 0 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | .DS_Store 4 | dist 5 | test/coverage 6 | .idea -------------------------------------------------------------------------------- /template/build/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | port: '{{port}}', 3 | title: '{{name}}', 4 | vendor: [ 5 | 'react', 6 | 'react-dom', 7 | 'redux', 8 | 'react-redux', 9 | 'react-router', 10 | 'redux-thunk' 11 | ], 12 | postcss: [ 13 | require('postcss-cssnext')({ 14 | browsers: ['last 2 versions', 'ie > 8'] 15 | }) 16 | ] 17 | } 18 | 19 | module.exports = config 20 | -------------------------------------------------------------------------------- /template/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= htmlWebpackPlugin.options.title %> 11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /template/build/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const express = require('express') 3 | const webpack = require('webpack') 4 | // const DashboardPlugin = require('webpack-dashboard/plugin') 5 | 6 | const webpackConfig = require('./webpack.dev') 7 | const config = require('./config') 8 | 9 | const app = express() 10 | 11 | webpackConfig.entry.client = [ 12 | 'webpack-hot-middleware/client', 13 | webpackConfig.entry.client 14 | ] 15 | 16 | const compiler = webpack(webpackConfig) 17 | 18 | // compiler.apply(new DashboardPlugin()) 19 | 20 | const devMiddleWare = require('webpack-dev-middleware')(compiler, { 21 | publicPath: webpackConfig.output.publicPath, 22 | stats: { 23 | colors: true, 24 | modules: false, 25 | children: false, 26 | chunks: false, 27 | chunkModules: false 28 | } 29 | }) 30 | app.use(devMiddleWare) 31 | app.use(require('webpack-hot-middleware')(compiler)) 32 | 33 | app.get('*', (req, res) => { 34 | const fs = devMiddleWare.fileSystem 35 | devMiddleWare.waitUntilValid(() => { 36 | const html = fs.readFileSync(path.join(webpackConfig.output.path, './index.html')) 37 | res.end(html) 38 | }) 39 | }) 40 | 41 | app.listen(config.port, () => { 42 | console.log(`Listening at http://localhost:${config.port}`) 43 | }) 44 | -------------------------------------------------------------------------------- /template/build/webpack.base.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | const config = require('./config') 5 | 6 | module.exports = { 7 | entry: { 8 | client: './src/index.js' 9 | }, 10 | output: { 11 | path: path.join(__dirname, '../dist'), 12 | filename: '[name].js', 13 | publicPath: './' 14 | }, 15 | resolve: { 16 | {{#if_eq theme 'antd'}} 17 | extensions: ['.js', '.jsx', '.json'] 18 | {{else}} 19 | extensions: ['.web.js', '.js', '.jsx', '.json'] 20 | {{/if_eq}} 21 | }, 22 | performance: {}, 23 | module: { 24 | rules: [ 25 | {{#if eslint}} 26 | { 27 | enforce: 'pre', 28 | test: /.js[x]?$/, 29 | loader: 'eslint-loader', 30 | exclude: /node_modules/ 31 | }, 32 | {{/if}} 33 | { 34 | test: /\.js[x]?$/, 35 | loader: 'babel-loader', 36 | exclude: [/node_modules/] 37 | }, 38 | { 39 | test: /\.(ico|jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/, 40 | loader: 'file-loader?limit=8192' 41 | } 42 | ] 43 | }, 44 | plugins: [ 45 | new HtmlWebpackPlugin({ 46 | title: config.title, 47 | template: path.join(__dirname, '/index.html'), 48 | filename: './index.html' 49 | }) 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /template/build/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const ProgressBarPlugin = require('progress-bar-webpack-plugin') 3 | 4 | const base = require('./webpack.base') 5 | const config = require('./config') 6 | 7 | base.devtool = 'eval-source-map' 8 | base.output.publicPath = '/assets/' 9 | base.performance.hints = false 10 | 11 | // Plugins Configuration 12 | base.plugins.push( 13 | new ProgressBarPlugin(), 14 | new webpack.DefinePlugin({ 15 | 'process.env.NODE_ENV': JSON.stringify('development') 16 | }), 17 | new webpack.HotModuleReplacementPlugin(), 18 | new webpack.NoErrorsPlugin(), 19 | new webpack.LoaderOptionsPlugin({ 20 | options: { 21 | postcss: config.postcss 22 | } 23 | }) 24 | ) 25 | 26 | base.module.rules.push( 27 | { 28 | test: /\.css$/, 29 | loaders: [ 30 | 'style-loader', 31 | 'css-loader?modules&localIdentName=[name]__[local]___[hash:base64:5]', 32 | 'postcss-loader' 33 | ], 34 | exclude: /node_modules/ 35 | }, 36 | { 37 | test: /\.css$/, 38 | loaders: [ 39 | 'style-loader', 40 | 'css-loader', 41 | 'postcss-loader' 42 | ], 43 | include: /node_modules/ 44 | }, 45 | { 46 | test: /\.less$/, 47 | loader: 'style-loader!css-loader!less-loader' 48 | } 49 | ) 50 | 51 | module.exports = base 52 | -------------------------------------------------------------------------------- /template/build/webpack.prod.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const exec = require('child_process').execSync 3 | const webpack = require('webpack') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const ProgressBarPlugin = require('progress-bar-webpack-plugin') 6 | 7 | const base = require('./webpack.base') 8 | const config = require('./config') 9 | 10 | exec('rm -rf dist/') 11 | 12 | const extractCSS = new ExtractTextPlugin('[name].css') 13 | 14 | base.entry.vendor = config.vendor 15 | base.output.filename = '[name].[chunkhash:8].js' 16 | 17 | // Plugins Configuration 18 | base.plugins.push( 19 | new ProgressBarPlugin(), 20 | new ExtractTextPlugin('styles.[contenthash:8].css'), 21 | new webpack.DefinePlugin({ 22 | 'process.env.NODE_ENV': JSON.stringify('production') 23 | }), 24 | new webpack.optimize.UglifyJsPlugin({ 25 | compress: { 26 | warnings: false 27 | }, 28 | output: { 29 | comments: false 30 | } 31 | }), 32 | new webpack.optimize.CommonsChunkPlugin({ 33 | name: 'vendor', 34 | filename: 'vendor.[chunkhash:8].js' 35 | }), 36 | new webpack.LoaderOptionsPlugin({ 37 | minimize: true, 38 | options: { 39 | postcss: config.postcss 40 | } 41 | }) 42 | ) 43 | 44 | base.module.rules.push( 45 | { 46 | test: /\.css$/, 47 | loader: extractCSS.extract(['css-loader?modules&localIdentName=[name]__[local]___[hash:base64:5]', 'postcss-loader']), 48 | exclude: /node_modules/ 49 | }, 50 | { 51 | test: /\.css$/, 52 | loader: extractCSS.extract(['css-loader', 'postcss-loader']), 53 | include: /node_modules/ 54 | }, 55 | { 56 | test: /\.less$/, 57 | loader: extractCSS.extract(['css-loader', 'less-loader']) 58 | } 59 | ) 60 | base.plugins.push( 61 | extractCSS 62 | ) 63 | 64 | module.exports = base 65 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "{{version}}", 4 | "description": "{{description}}", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "NODE_ENV='production' webpack --config build/webpack.prod.js", 8 | "dev": "node build/server.js"{{#unit}}, 9 | "unit": "karma start test/karma.conf.js --single-run"{{/unit}} 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/xfcf1/react-webpack-starter.git" 14 | }, 15 | "keywords": [ 16 | "smart", 17 | "react", 18 | "webpack" 19 | ], 20 | "author": "{{author}}", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/xfcf1/react-webpack-starter/issues" 24 | }, 25 | "homepage": "https://github.com/xfcf1/react-webpack-starter#readme", 26 | "devDependencies": { 27 | "babel-core": "^6.21.0", 28 | "babel-loader": "^6.2.10", 29 | "babel-plugin-import": "^1.1.0", 30 | "babel-preset-es2015": "^6.18.0", 31 | "babel-preset-react": "^6.16.0", 32 | "babel-preset-react-hmre": "^1.1.1", 33 | "babel-plugin-transform-react-constant-elements": "^6.9.1", 34 | "babel-plugin-transform-react-pure-class-to-function": "^1.0.1", 35 | "babel-plugin-transform-react-remove-prop-types": "^0.2.11", 36 | "babel-preset-stage-0": "^6.16.0", 37 | "css-loader": "^0.26.0", 38 | {{#eslint}} 39 | "eslint": "^3.12.2", 40 | "eslint-loader": "^1.6.1", 41 | "eslint-plugin-react": "^6.8.0", 42 | {{#if_eq eslintConfig 'standard'}} 43 | "eslint-config-standard": "^6.2.1", 44 | "eslint-plugin-promise": "^3.4.0", 45 | "eslint-plugin-standard": "^2.0.1", 46 | {{/if_eq}} 47 | {{/eslint}} 48 | "express": "^4.14.0", 49 | "extract-text-webpack-plugin": "^2.0.0-beta.4", 50 | "html-webpack-plugin": "^2.24.1", 51 | {{#unit}} 52 | "karma": "^1.3.0", 53 | "karma-coverage": "^1.1.1", 54 | "karma-mocha": "^1.2.0", 55 | "karma-phantomjs-launcher": "^1.0.0", 56 | "karma-sinon-chai": "^1.2.0", 57 | "karma-sourcemap-loader": "^0.3.7", 58 | "karma-spec-reporter": "0.0.26", 59 | "karma-webpack": "^1.7.0", 60 | "lolex": "^1.4.0", 61 | "mocha": "^3.1.0", 62 | "chai": "^3.5.0", 63 | "sinon": "^1.17.3", 64 | "sinon-chai": "^2.8.0", 65 | "inject-loader": "^3.0.0-beta2", 66 | "isparta-loader": "^2.0.0", 67 | "phantomjs-prebuilt": "^2.1.3", 68 | {{/unit}} 69 | "less": "^2.7.1", 70 | "less-loader": "^2.2.3", 71 | "postcss-cssnext": "^2.9.0", 72 | "postcss-loader": "^1.2.1", 73 | "progress-bar-webpack-plugin": "^1.9.1", 74 | "redux-logger": "^2.7.4", 75 | "standard": "^8.6.0", 76 | "style-loader": "^0.13.1", 77 | "url-loader": "^0.5.7", 78 | "webpack": "^2.2.0-rc.2", 79 | "webpack-dashboard": "^0.2.0", 80 | "webpack-dev-middleware": "^1.9.0", 81 | "webpack-hot-middleware": "^2.13.2" 82 | }, 83 | "dependencies": { 84 | "{{theme}}": "latest", 85 | "react": "15.4.0", 86 | "react-dom": "15.4.0", 87 | "react-redux": "^5.0.1", 88 | "react-router": "^3.0.0", 89 | "react-router-redux": "^4.0.0", 90 | "redux": "^3.6.0", 91 | "redux-act": "latest", 92 | "redux-thunk": "latest" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /template/src/components/Home/Home.css: -------------------------------------------------------------------------------- 1 | .blue { 2 | color: #0f87dd; 3 | } -------------------------------------------------------------------------------- /template/src/components/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from 'react-router' 3 | import { Button } from '{{theme}}' 4 | import styles from './Home.css' 5 | 6 | class Home extends Component { 7 | clickHandle () { 8 | const { router } = this.props 9 | router.push('test') 10 | } 11 | render () { 12 | const { homeReducer, toggleClassName } = this.props 13 | const { className } = homeReducer 14 | return ( 15 |
16 |

Hello

17 | 22 |
23 |
24 | 29 |
30 | ) 31 | } 32 | } 33 | 34 | export default withRouter(Home) 35 | -------------------------------------------------------------------------------- /template/src/components/Home/index.js: -------------------------------------------------------------------------------- 1 | import Home from './Home' 2 | 3 | export default Home 4 | -------------------------------------------------------------------------------- /template/src/components/Test/Test.css: -------------------------------------------------------------------------------- 1 | .red { 2 | color: red; 3 | } 4 | h2 span { 5 | color: blue; 6 | } 7 | span { 8 | font-size: 18pt; 9 | } 10 | -------------------------------------------------------------------------------- /template/src/components/Test/Test.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | import styles from './Test.css' 4 | 5 | class Test extends Component { 6 | render () { 7 | const { homeReducer, testReducer } = this.props 8 | const { className } = homeReducer 9 | const { text } = testReducer 10 | return ( 11 |
12 |

this is router test page

13 |

{text}{`${className}`}

14 | link back to home page 15 |
16 | ) 17 | } 18 | } 19 | 20 | export default Test 21 | -------------------------------------------------------------------------------- /template/src/components/Test/index.js: -------------------------------------------------------------------------------- 1 | import Test from './Test' 2 | 3 | export default Test 4 | -------------------------------------------------------------------------------- /template/src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class App extends Component { 4 | render () { 5 | return ( 6 |
7 | { this.props.children } 8 |
9 | ) 10 | } 11 | } 12 | 13 | export default App 14 | -------------------------------------------------------------------------------- /template/src/containers/HomePage.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { bindActionCreators } from 'redux' 3 | import Home from '../components/Home' 4 | import * as HomeActions from '../modules/home' 5 | 6 | function mapStateToProps (state) { 7 | return { 8 | homeReducer: state.homeReducer 9 | } 10 | } 11 | 12 | function mapDispatchToProps (dispatch) { 13 | return bindActionCreators(HomeActions, dispatch) 14 | } 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(Home) 17 | -------------------------------------------------------------------------------- /template/src/containers/TestPage.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { bindActionCreators } from 'redux' 3 | import Test from '../components/Test' 4 | import * as TestActions from '../modules/test' 5 | 6 | function mapStateToProps (state) { 7 | return { 8 | homeReducer: state.homeReducer, 9 | testReducer: state.testReducer 10 | } 11 | } 12 | 13 | function mapDispatchToProps (dispatch) { 14 | return bindActionCreators(TestActions, dispatch) 15 | } 16 | 17 | export default connect(mapStateToProps, mapDispatchToProps)(Test) 18 | -------------------------------------------------------------------------------- /template/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import { Router, hashHistory } from 'react-router' 5 | import { syncHistoryWithStore } from 'react-router-redux' 6 | 7 | import configureStore from './store' 8 | 9 | import routes from './routes' 10 | 11 | const store = configureStore() 12 | const history = syncHistoryWithStore(hashHistory, store) 13 | 14 | render( 15 | 16 | 17 | , 18 | document.getElementById('app') 19 | ) 20 | -------------------------------------------------------------------------------- /template/src/modules/home.js: -------------------------------------------------------------------------------- 1 | import { createAction, createReducer } from 'redux-act' 2 | 3 | export const toggleClassName = createAction('toggle class name') 4 | 5 | const initialState = { 6 | className: true 7 | } 8 | 9 | export default createReducer({ 10 | [toggleClassName]: (state, data) => ({ ...state, className: data }) 11 | }, initialState) 12 | -------------------------------------------------------------------------------- /template/src/modules/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { routerReducer as routing } from 'react-router-redux' 3 | 4 | import homeReducer from './home' 5 | import testReducer from './test' 6 | 7 | const rootReducer = combineReducers({ 8 | routing, 9 | homeReducer, 10 | testReducer 11 | }) 12 | 13 | export default rootReducer 14 | -------------------------------------------------------------------------------- /template/src/modules/test.js: -------------------------------------------------------------------------------- 1 | import { createReducer } from 'redux-act' 2 | 3 | const initialState = { 4 | text: 'home page className value: ' 5 | } 6 | 7 | export default createReducer({}, initialState) 8 | -------------------------------------------------------------------------------- /template/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, IndexRoute } from 'react-router' 3 | 4 | import App from './containers/App' 5 | import HomePage from './containers/HomePage' 6 | import TestPage from './containers/TestPage' 7 | 8 | export default ( 9 | 10 | 11 | 12 | 13 | ) 14 | -------------------------------------------------------------------------------- /template/src/store/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./store.production') 3 | } else { 4 | module.exports = require('./store.development') 5 | } 6 | -------------------------------------------------------------------------------- /template/src/store/store.development.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import createLogger from 'redux-logger' 4 | import { hashHistory } from 'react-router' 5 | import { routerMiddleware } from 'react-router-redux' 6 | import rootReducer from '../modules' 7 | 8 | const logger = () => { 9 | return createLogger({ 10 | level: 'info', 11 | collapsed: true 12 | }) 13 | } 14 | 15 | const router = routerMiddleware(hashHistory) 16 | 17 | const enhancer = applyMiddleware(thunk, router, logger()) 18 | 19 | export default function configureStore (initialState) { 20 | const store = createStore(rootReducer, initialState, enhancer) 21 | 22 | if (module.hot) { 23 | module.hot.accept('../modules', () => 24 | store.replaceReducer(require('../modules')) 25 | ) 26 | } 27 | 28 | return store 29 | } 30 | -------------------------------------------------------------------------------- /template/src/store/store.production.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import { hashHistory } from 'react-router' 4 | import { routerMiddleware } from 'react-router-redux' 5 | import rootReducer from '../modules' 6 | 7 | const router = routerMiddleware(hashHistory) 8 | 9 | const enhancer = applyMiddleware(thunk, router) 10 | 11 | export default function configureStore (initialState) { 12 | return createStore(rootReducer, initialState, enhancer) 13 | } 14 | -------------------------------------------------------------------------------- /template/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /template/test/index.js: -------------------------------------------------------------------------------- 1 | // Polyfill fn.bind() for PhantomJS 2 | /* eslint-disable no-extend-native */ 3 | Function.prototype.bind = require('function-bind') 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../src', true, /^\.\/(?!index(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /template/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | const path = require('path') 7 | const webpack = require('webpack') 8 | const base = require('../build/webpack.base') 9 | 10 | const config = require('../build/config') 11 | 12 | base.plugins.push( 13 | new webpack.DefinePlugin({ 14 | 'process.env.NODE_ENV': JSON.stringify('test') 15 | }), 16 | new webpack.LoaderOptionsPlugin({ 17 | minimize: true, 18 | options: { 19 | babel: config.babel, 20 | postcss: config.postcss, 21 | vue: { 22 | loaders: { 23 | css: 'style-loader!css-loader!postcss-loader' 24 | }, 25 | postcss: config.postcss 26 | } 27 | } 28 | }) 29 | ) 30 | 31 | base.module.rules.push({ 32 | test: /\.css$/, 33 | loader: 'style-loader!css-loader!postcss-loader' 34 | }) 35 | 36 | // no need for app entry during tests 37 | delete base.entry 38 | 39 | // make sure isparta loader is applied before eslint 40 | base.module.rules.unshift({ 41 | enforce: 'pre', 42 | test: /\.js$/, 43 | loader: 'isparta-loader', 44 | include: path.resolve('../src') 45 | }) 46 | 47 | // only apply babel for test files when using isparta 48 | base.module.rules.some((loader, i) => { 49 | if (loader.loader === 'babel') { 50 | loader.include = path.resolve('../test') 51 | return true 52 | } 53 | }) 54 | 55 | module.exports = (config) => { 56 | config.set({ 57 | // to run in additional browsers: 58 | // 1. install corresponding karma launcher 59 | // http://karma-runner.github.io/0.13/config/browsers.html 60 | // 2. add it to the `browsers` array below. 61 | browsers: ['PhantomJS'], 62 | frameworks: ['mocha', 'sinon-chai'], 63 | reporters: ['spec', 'coverage'], 64 | files: ['./index.js'], 65 | preprocessors: { 66 | './index.js': ['webpack', 'sourcemap'] 67 | }, 68 | webpack: base, 69 | webpackMiddleware: { 70 | noInfo: true 71 | }, 72 | coverageReporter: { 73 | dir: './coverage', 74 | reporters: [ 75 | { type: 'lcov', subdir: '.' }, 76 | { type: 'text-summary' } 77 | ] 78 | } 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /template/test/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | // import Home from 'components/Home' 4 | 5 | describe('Hello', () => { 6 | it('should render correct contents', () => { 7 | render(

Hello

, document.querySelector('#app')) 8 | 9 | expect(document.querySelector('#hello').textContent) 10 | .to.equal('Hello') 11 | }) 12 | }) 13 | --------------------------------------------------------------------------------