├── .babelrc ├── .eslintrc ├── .gitignore ├── CONTRIBUTORS.md ├── README.md ├── app ├── index.jsx └── main.css ├── karma.conf.js ├── package.json ├── tests └── demo_test.js ├── webpack.config.js └── webpack.parts.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "survivejs-kanban" 6 | ], 7 | "env": { 8 | "start": { 9 | "presets": [ 10 | "react-hmre" 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", "plugin:react/recommended" 4 | ], 5 | "parser": "babel-eslint", 6 | "env": { 7 | "browser": true, 8 | "node": true, 9 | "mocha": true 10 | }, 11 | "plugins": [ 12 | "react" 13 | ], 14 | "rules": { 15 | "quotes": [2, "single"], 16 | "no-debugger": 0, 17 | "no-console": 0, 18 | "no-unused-vars": 0, 19 | "react/display-name": 0, 20 | "react/prop-types": 0, 21 | "react/no-multi-comp": 0 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | coverage/ 4 | .eslintcache 5 | npm-debug.log 6 | 7 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | * [François Constant](https://github.com/FrancoisConstant) - Added `npm-debug.log` to `.gitignore`. 4 | * [iparips](https://github.com/iparips) - Fixed dependency issue with `html-webpack-template`. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-boilerplate - Boilerplate for "SurviveJS - React" 2 | 3 | See [SurviveJS - React](http://survivejs.com/react/introduction/) for the book. 4 | 5 | > If you use Vagrant or Cloud9, you'll need to tweak the development server port as instructed in **webpack.parts.js**. 6 | 7 | ## Getting Started 8 | 9 | 1. `npm i` - Install dependencies. This might take a while. 10 | 2. `npm start` - Run development build. If it doesn't start, make sure you aren't running anything else in the same port. In case you are on a Unix platform, you can try `PORT=3000 npm start`. It will pick up the port from the environment if it's set. 11 | 3. Surf to the port shown at terminal. 12 | 4. Start modifying the code. The browser should pick up the changes. 13 | 14 | ## Advanced Commands 15 | 16 | Beyond development, the boilerplate supports other tasks listed below: 17 | 18 | * `npm run build` - Generates a production build below `build/`. See the [Building with Webpack](http://survivejs.com/webpack/building-with-webpack/) part for more. 19 | * `npm run deploy` - Deploys the contents of the `build/` directory below the **gh-pages** branch. 20 | * `npm run test` - Runs `tests/` through Karma/Phantom/Mocha once. 21 | * `npm run test:tdd` - Runs `tests/` in a TDD mode (watches for changes and rebuilds). 22 | * `npm run test:lint` - Runs code through ESLint to spot code quality issues. 23 | * `npm run stats` - Generates Webpack build statistics. See the [Analyzing Build Statistics](http://survivejs.com/webpack/building-with-webpack/analyzing-build-statistics/) chapter. 24 | -------------------------------------------------------------------------------- /app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | if(process.env.NODE_ENV !== 'production') { 5 | React.Perf = require('react-addons-perf'); 6 | } 7 | 8 | ReactDOM.render( 9 |
Hello world
, 10 | document.getElementById('app') 11 | ); 12 | -------------------------------------------------------------------------------- /app/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: cornsilk; 3 | 4 | font-family: sans-serif; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Reference: http://karma-runner.github.io/0.13/config/configuration-file.html 2 | module.exports = function karmaConfig (config) { 3 | config.set({ 4 | frameworks: [ 5 | // Reference: https://github.com/karma-runner/karma-mocha 6 | // Set framework to mocha 7 | 'mocha' 8 | ], 9 | 10 | reporters: [ 11 | // Reference: https://github.com/mlex/karma-spec-reporter 12 | // Set reporter to print detailed results to console 13 | 'spec', 14 | 15 | // Reference: https://github.com/karma-runner/karma-coverage 16 | // Output code coverage files 17 | 'coverage' 18 | ], 19 | 20 | files: [ 21 | // Reference: https://www.npmjs.com/package/phantomjs-polyfill 22 | // Needed because React.js requires bind and phantomjs does not support it 23 | 'node_modules/phantomjs-polyfill/bind-polyfill.js', 24 | 25 | // Grab all files in the tests directory that contain _test. 26 | 'tests/**/*_test.*' 27 | ], 28 | 29 | preprocessors: { 30 | // Reference: http://webpack.github.io/docs/testing.html 31 | // Reference: https://github.com/webpack/karma-webpack 32 | // Convert files with webpack and load sourcemaps 33 | 'tests/**/*_test.*': ['webpack', 'sourcemap'], 34 | 'app/**/*.*': 'coverage' 35 | }, 36 | 37 | browsers: [ 38 | // Run tests using PhantomJS 39 | 'PhantomJS' 40 | ], 41 | 42 | singleRun: true, 43 | 44 | // Configure code coverage reporter 45 | coverageReporter: { 46 | reporters: [ 47 | // generates ./coverage/lcov.info 48 | { 49 | type: 'lcovonly', 50 | subdir: '.' 51 | }, 52 | // generates ./coverage/coverage-final.json 53 | { 54 | type: 'json', 55 | subdir: '.' 56 | }, 57 | // generates ./coverage/index.html 58 | { 59 | type: 'html', 60 | subdir: '.' 61 | } 62 | ] 63 | }, 64 | 65 | // Test webpack config 66 | webpack: require('./webpack.config'), 67 | 68 | // Hide webpack build information from output 69 | webpackMiddleware: { 70 | noInfo: true 71 | } 72 | }); 73 | }; 74 | 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-boilerplate", 3 | "private": true, 4 | "version": "2.5.6", 5 | "description": "Boilerplate for [SurviveJS - React](http://survivejs.com/react/introduction/)", 6 | "scripts": { 7 | "stats": "webpack --profile --json > stats.json", 8 | "start": "webpack-dev-server", 9 | "deploy": "gh-pages -d build", 10 | "build": "webpack", 11 | "test": "karma start", 12 | "test:tdd": "npm run test -- --auto-watch --no-single-run", 13 | "test:lint": "eslint ./app ./tests --ext .js --ext .jsx --ignore-path .gitignore --ignore-pattern dist --cache" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "babel-core": "^6.10.4", 20 | "babel-eslint": "^6.1.0", 21 | "babel-loader": "^6.2.4", 22 | "babel-preset-es2015": "^6.9.0", 23 | "babel-preset-react": "^6.5.0", 24 | "babel-preset-react-hmre": "^1.1.1", 25 | "babel-preset-survivejs-kanban": "^0.3.3", 26 | "clean-webpack-plugin": "^0.1.9", 27 | "css-loader": "^0.23.1", 28 | "eslint": "^3.13.1", 29 | "eslint-loader": "^1.3.0", 30 | "eslint-plugin-react": "^6.9.0", 31 | "expose-loader": "^0.7.1", 32 | "extract-text-webpack-plugin": "^1.0.1", 33 | "gh-pages": "^0.11.0", 34 | "html-webpack-plugin": "2.21.0", 35 | "html-webpack-template": "5.0.0", 36 | "isparta-loader": "^2.0.0", 37 | "karma": "^1.0.0", 38 | "karma-coverage": "^1.0.0", 39 | "karma-mocha": "^1.1.1", 40 | "karma-phantomjs-launcher": "^1.0.1", 41 | "karma-sourcemap-loader": "^0.3.7", 42 | "karma-spec-reporter": "0.0.26", 43 | "karma-webpack": "^1.7.0", 44 | "mocha": "^2.5.3", 45 | "npm-install-webpack-plugin": "4.0.4", 46 | "phantomjs-polyfill": "0.0.2", 47 | "phantomjs-prebuilt": "^2.1.7", 48 | "react-addons-perf": "^15.1.0", 49 | "react-addons-test-utils": "^15.1.0", 50 | "style-loader": "^0.13.1", 51 | "webpack": "^1.13.1", 52 | "webpack-dev-server": "^1.14.1", 53 | "webpack-merge": "^0.14.0", 54 | "webpack-validator": "^2.2.0" 55 | }, 56 | "dependencies": { 57 | "react": "^15.1.0", 58 | "react-dom": "^15.1.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/demo_test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | describe('add', function() { 4 | it('adds', function() { 5 | assert.equal(1 + 1, 2); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | const validate = require('webpack-validator'); 4 | 5 | const parts = require('./webpack.parts'); 6 | 7 | const TARGET = process.env.npm_lifecycle_event; 8 | const ENABLE_POLLING = process.env.ENABLE_POLLING; 9 | const PATHS = { 10 | app: path.join(__dirname, 'app'), 11 | style: [ 12 | path.join(__dirname, 'app', 'main.css') 13 | ], 14 | build: path.join(__dirname, 'build'), 15 | test: path.join(__dirname, 'tests') 16 | }; 17 | 18 | process.env.BABEL_ENV = TARGET; 19 | 20 | const common = merge( 21 | { 22 | // Entry accepts a path or an object of entries. 23 | // We'll be using the latter form given it's 24 | // convenient with more complex configurations. 25 | entry: { 26 | app: PATHS.app 27 | }, 28 | output: { 29 | path: PATHS.build, 30 | filename: '[name].js' 31 | }, 32 | resolve: { 33 | extensions: ['', '.js', '.jsx'] 34 | } 35 | }, 36 | parts.indexTemplate({ 37 | title: 'Kanban demo', 38 | appMountId: 'app' 39 | }), 40 | parts.loadJSX(PATHS.app), 41 | parts.lintJSX(PATHS.app) 42 | ); 43 | 44 | var config; 45 | 46 | // Detect how npm is run and branch based on that 47 | switch(TARGET) { 48 | case 'build': 49 | case 'stats': 50 | config = merge( 51 | common, 52 | { 53 | devtool: 'source-map', 54 | entry: { 55 | style: PATHS.style 56 | }, 57 | output: { 58 | // TODO: Set publicPath to match your GitHub project name 59 | // E.g., '/kanban-demo/'. Webpack will alter asset paths 60 | // based on this. You can even use an absolute path here 61 | // or even point to a CDN. 62 | //publicPath: '' 63 | path: PATHS.build, 64 | filename: '[name].[chunkhash].js', 65 | chunkFilename: '[chunkhash].js' 66 | } 67 | }, 68 | parts.clean(PATHS.build), 69 | parts.setFreeVariable( 70 | 'process.env.NODE_ENV', 71 | 'production' 72 | ), 73 | parts.extractBundle({ 74 | name: 'vendor', 75 | entries: ['react', 'react-dom'] 76 | }), 77 | parts.minify(), 78 | parts.extractCSS(PATHS.style) 79 | ); 80 | break; 81 | case 'test': 82 | case 'test:tdd': 83 | config = merge( 84 | common, 85 | { 86 | devtool: 'inline-source-map' 87 | }, 88 | parts.loadIsparta(PATHS.app), 89 | parts.loadJSX(PATHS.test) 90 | ); 91 | break; 92 | default: 93 | config = merge( 94 | common, 95 | { 96 | devtool: 'eval-source-map', 97 | entry: { 98 | style: PATHS.style 99 | } 100 | }, 101 | parts.setupCSS(PATHS.style), 102 | parts.devServer({ 103 | // Customize host/port here if needed 104 | host: process.env.HOST, 105 | port: process.env.PORT, 106 | poll: ENABLE_POLLING 107 | }), 108 | parts.enableReactPerformanceTools(), 109 | parts.npmInstall() 110 | ); 111 | } 112 | 113 | module.exports = validate(config, { 114 | quiet: true 115 | }); 116 | -------------------------------------------------------------------------------- /webpack.parts.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const NpmInstallPlugin = require('npm-install-webpack-plugin'); 6 | 7 | exports.indexTemplate = function(options) { 8 | return { 9 | plugins: [ 10 | new HtmlWebpackPlugin({ 11 | template: require('html-webpack-template'), 12 | title: options.title, 13 | appMountId: options.appMountId, 14 | inject: false 15 | }) 16 | ] 17 | }; 18 | } 19 | 20 | exports.loadJSX = function(include) { 21 | return { 22 | module: { 23 | loaders: [ 24 | { 25 | test: /\.(js|jsx)$/, 26 | // Enable caching for extra performance 27 | loaders: ['babel?cacheDirectory'], 28 | include: include 29 | } 30 | ] 31 | } 32 | }; 33 | } 34 | 35 | exports.loadIsparta = function(include) { 36 | return { 37 | module: { 38 | preLoaders: [ 39 | { 40 | test: /\.(js|jsx)$/, 41 | loaders: ['isparta'], 42 | include: include 43 | } 44 | ] 45 | } 46 | }; 47 | } 48 | 49 | exports.lintJSX = function(include) { 50 | return { 51 | module: { 52 | preLoaders: [ 53 | { 54 | test: /\.(js|jsx)$/, 55 | loaders: ['eslint'], 56 | include: include 57 | } 58 | ] 59 | } 60 | }; 61 | } 62 | 63 | exports.enableReactPerformanceTools = function() { 64 | return { 65 | module: { 66 | loaders: [ 67 | { 68 | test: require.resolve('react'), 69 | loader: 'expose?React' 70 | } 71 | ] 72 | } 73 | }; 74 | } 75 | 76 | exports.devServer = function(options) { 77 | const ret = { 78 | devServer: { 79 | // Enable history API fallback so HTML5 History API based 80 | // routing works. This is a good default that will come 81 | // in handy in more complicated setups. 82 | historyApiFallback: true, 83 | 84 | // Unlike the cli flag, this doesn't set 85 | // HotModuleReplacementPlugin! 86 | hot: true, 87 | inline: true, 88 | 89 | // Display only errors to reduce the amount of output. 90 | stats: 'errors-only', 91 | 92 | // Parse host and port from env to allow customization. 93 | // 94 | // If you use Vagrant or Cloud9, set 95 | // host: options.host || '0.0.0.0'; 96 | // 97 | // 0.0.0.0 is available to all network devices 98 | // unlike default `localhost`. 99 | host: options.host, // Defaults to `localhost` 100 | port: options.port // Defaults to 8080 101 | }, 102 | plugins: [ 103 | // Enable multi-pass compilation for enhanced performance 104 | // in larger projects. Good default. 105 | new webpack.HotModuleReplacementPlugin({ 106 | multiStep: true 107 | }) 108 | ] 109 | }; 110 | 111 | if(options.poll) { 112 | ret.watchOptions = { 113 | // Delay the rebuild after the first change 114 | aggregateTimeout: 300, 115 | // Poll using interval (in ms, accepts boolean too) 116 | poll: 1000 117 | }; 118 | } 119 | 120 | return ret; 121 | } 122 | 123 | exports.setupCSS = function(paths) { 124 | return { 125 | module: { 126 | loaders: [ 127 | { 128 | test: /\.css$/, 129 | loaders: ['style', 'css'], 130 | include: paths 131 | } 132 | ] 133 | } 134 | }; 135 | } 136 | 137 | exports.minify = function() { 138 | return { 139 | plugins: [ 140 | new webpack.optimize.UglifyJsPlugin({ 141 | compress: { 142 | warnings: false 143 | } 144 | }) 145 | ] 146 | }; 147 | } 148 | 149 | exports.setFreeVariable = function(key, value) { 150 | const env = {}; 151 | env[key] = JSON.stringify(value); 152 | 153 | return { 154 | plugins: [ 155 | new webpack.DefinePlugin(env) 156 | ] 157 | }; 158 | } 159 | 160 | exports.extractBundle = function(options) { 161 | const entry = {}; 162 | entry[options.name] = options.entries; 163 | 164 | return { 165 | // Define an entry point needed for splitting. 166 | entry: entry, 167 | plugins: [ 168 | // Extract bundle and manifest files. Manifest is 169 | // needed for reliable caching. 170 | new webpack.optimize.CommonsChunkPlugin({ 171 | names: [options.name, 'manifest'], 172 | 173 | // options.name modules only 174 | minChunks: Infinity 175 | }) 176 | ] 177 | }; 178 | } 179 | 180 | exports.clean = function(path) { 181 | return { 182 | plugins: [ 183 | new CleanWebpackPlugin([path], { 184 | root: process.cwd() 185 | }) 186 | ] 187 | }; 188 | } 189 | 190 | exports.extractCSS = function(paths) { 191 | return { 192 | module: { 193 | loaders: [ 194 | // Extract CSS during build 195 | { 196 | test: /\.css$/, 197 | loader: ExtractTextPlugin.extract('style', 'css'), 198 | include: paths 199 | } 200 | ] 201 | }, 202 | plugins: [ 203 | // Output extracted CSS to a file 204 | new ExtractTextPlugin('[name].[chunkhash].css') 205 | ] 206 | }; 207 | } 208 | 209 | exports.npmInstall = function(options) { 210 | options = options || {}; 211 | 212 | return { 213 | plugins: [ 214 | new NpmInstallPlugin(options) 215 | ] 216 | }; 217 | } 218 | --------------------------------------------------------------------------------