├── .babelrc ├── .flowconfig ├── .gitignore ├── README.md ├── app ├── images │ └── example.jpg ├── index.html └── jsx │ ├── app.jsx │ └── components │ ├── Button.react.jsx │ └── HelloWorld.react.jsx ├── gulpfile.js ├── karma.conf.js ├── package.json ├── server ├── index.js └── page.jsx ├── test ├── Button.react_test.jsx ├── HelloWorld.react_test.jsx └── sanity_test.jsx └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 1 3 | } 4 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /home/thomas/Workspace/boilerplate-webpack-react/node_modules/flow-bin/vendor 3 | /home/thomas/Workspace/boilerplate-webpack-react/app/images 4 | 5 | [include] 6 | /home/thomas/Workspace/boilerplate-webpack-react/app/images/jsx 7 | /home/thomas/Workspace/boilerplate-webpack-react/server 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | dist 4 | public 5 | *.generated.* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boilerplate-webpack-react 2 | 3 | # Deprectated 4 | 5 | This repo is deprecated, I haven't updated in a while. Only use for inspiration. 6 | 7 | ## Introduction 8 | 9 | This is a simple boilerplate project to start fast with Reactjs, webpack and 10 | gulp, including offline rendering with hapi. 11 | 12 | This includes some webpack loaders like [image-webpack-loader](https://www.npmjs.org/package/image-webpack-loader) 13 | for optimizing images and other cool things like [Hot Module Replacement](http://gaearon.github.io/react-hot-loader/) 14 | for React. 15 | 16 | ### New 17 | 18 | Support for offline rendering. This uses [hapi](http://hapijs.com/) for the server 19 | but you can easily change that in `server\index.js` 20 | 21 | ## Usage 22 | 23 | Copy this repo. 24 | 25 | ### Webpack-dev-server: 26 | 27 | * Run `gulp` 28 | * Run `node server` 29 | * Open http://localhost:3000 30 | 31 | When running, you can edit HelloWorld.react.jsx and save it, your edits will 32 | be visible without reloading. 33 | 34 | ### Build for server: 35 | 36 | * Run `gulp build` 37 | 38 | This will create the minified build files 39 | 40 | To test it, run `node server`. 41 | 42 | ## TODO 43 | 44 | * expand the example code with fonts and json 45 | 46 | ## License 47 | 48 | Copyright (c) 2014 Thomas Coopoman 49 | 50 | MIT (http://opensource.org/licenses/MIT) 51 | -------------------------------------------------------------------------------- /app/images/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcoopman/boilerplate-webpack-react/e206e6c5d34b52c84b696cf91c965f5bdbf4c7e0/app/images/example.jpg -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Boilerplate Webpack Reactjs 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/jsx/app.jsx: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react'; 3 | import HelloWorld from './components/HelloWorld.react'; 4 | 5 | console.log(document.getElementById('react-content')); 6 | const render = () => React.render( 7 | , 8 | document.getElementById('react-content') 9 | ); 10 | render(); 11 | -------------------------------------------------------------------------------- /app/jsx/components/Button.react.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | export default React.createClass({ 5 | displayName: 'Button', 6 | 7 | 8 | render() { 9 | return
button
; 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /app/jsx/components/HelloWorld.react.jsx: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react'; 3 | 4 | var exampleImage = require('../../images/example.jpg'); 5 | 6 | 7 | export default class HelloWorld extends React.Component { 8 | render() { 9 | return ( 10 |
11 |

Hello World!!

12 | 13 |

Picture from https://unsplash.com/somewhere

14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var gulp = require('gulp'); 3 | var gutil = require('gulp-util'); 4 | var runSequence = require('run-sequence'); 5 | var del = require('del'); 6 | var webpack = require('webpack'); 7 | var WebpackDevServer = require('webpack-dev-server'); 8 | var webpackConfig = require('./webpack.config.js'); 9 | var path = require('path'); 10 | 11 | var dist = 'public'; 12 | var distJoin = path.join.bind(null, dist); 13 | 14 | // The development server (the recommended option for development) 15 | gulp.task('default', function(callback) { 16 | runSequence('clean', 'webpack-dev-server', callback); 17 | }); 18 | 19 | 20 | // Production build 21 | gulp.task('build', function(callback) { 22 | runSequence('clean', 'webpack:build', callback); 23 | }); 24 | 25 | 26 | gulp.task('clean', function(cb) { 27 | del(['./public/**/*'], cb); 28 | }); 29 | 30 | 31 | gulp.task('webpack:build', function(callback) { 32 | // modify some webpack config options 33 | var prodConfig = webpackConfig.map(function(config) { 34 | var myConfig = Object.create(config); 35 | myConfig.plugins = myConfig.plugins || []; 36 | myConfig.plugins = myConfig.plugins.concat( 37 | new webpack.DefinePlugin({ 38 | 'process.env': { 39 | // This has effect on the react lib size 40 | NODE_ENV: JSON.stringify('production') 41 | } 42 | }), 43 | new webpack.optimize.DedupePlugin(), 44 | new webpack.optimize.UglifyJsPlugin() 45 | ); 46 | 47 | return myConfig; 48 | }); 49 | 50 | // run webpack 51 | webpack(prodConfig, function(err, stats) { 52 | if (err) { 53 | throw new gutil.PluginError('webpack:build', err); 54 | } 55 | gutil.log('[webpack:build]', stats.toString({ 56 | colors: true 57 | })); 58 | callback(); 59 | }); 60 | }); 61 | 62 | 63 | gulp.task('webpack-dev-server', function(callback) { 64 | var publicPath = 'http://localhost:8080/assets/'; 65 | 66 | // modify some webpack config options 67 | var browserConfig = webpackConfig.filter(function(config) { 68 | return config.name === 'browser'; 69 | })[0]; 70 | 71 | browserConfig = Object.create(browserConfig); 72 | 73 | browserConfig.devtool = 'eval'; 74 | browserConfig.debug = true; 75 | browserConfig.plugins = browserConfig.plugins || []; 76 | browserConfig.plugins = browserConfig.plugins.concat([new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin()]); 77 | browserConfig.entry = [ 78 | 'webpack-dev-server/client?http://localhost:8080', 'webpack/hot/only-dev-server', './app/jsx/app.jsx' 79 | ]; 80 | 81 | // don't extract the css to a file for the dev server. 82 | browserConfig.module.loaders = browserConfig.module.loaders.map(function(loader) { 83 | if (loader.test.toString() === /\.styl$/.toString()) { 84 | return {test: /\.styl$/, loader: 'style!css!stylus'}; 85 | } else { 86 | return loader; 87 | } 88 | }); 89 | 90 | browserConfig.output.publicPath = publicPath; 91 | 92 | // modify some webpack config options of the server 93 | var serverConfig = Object.create(webpackConfig.filter(function(config) { 94 | return config.name === 'server'; 95 | })[0]); 96 | 97 | // FIXME we musn't overwrite the plugins, only change the PUBLIC_PATH 98 | serverConfig.plugins = [ 99 | new webpack.DefinePlugin({ 100 | PUBLIC_PATH: JSON.stringify(publicPath) 101 | }) 102 | ]; 103 | 104 | // run webpack 105 | webpack(serverConfig, function(err, stats) { 106 | if (err) { 107 | throw new gutil.PluginError('webpack:build', err); 108 | } 109 | gutil.log('[webpack:build]', stats.toString({ 110 | colors: true 111 | })); 112 | // Start a webpack-dev-server 113 | new WebpackDevServer(webpack(browserConfig), { 114 | contentBase: './public', 115 | hot: true, 116 | publicPath: '/assets/', 117 | stats: { 118 | colors: true 119 | } 120 | }).listen(8080, '0.0.0.0', function(err) { 121 | if(err) throw new gutil.PluginError('webpack-dev-server', err); 122 | gutil.log('[webpack-dev-server]', 'http://localhost:8080/webpack-dev-server/index.html'); 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | var path = require('path'); 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // ... normal karma configuration 7 | 8 | files: [ 9 | // all files ending in "_test" 10 | 'test/*_test.jsx', 11 | 'test/**/*_test.jsx' 12 | // each file acts as entry point for the webpack configuration 13 | ], 14 | 15 | 16 | frameworks: ['mocha'], 17 | 18 | 19 | browsers: ['Chrome'], 20 | 21 | 22 | preprocessors: { 23 | // add webpack as preprocessor 24 | 'test/data/*.jsx': ['webpack'], 25 | 'test/*_test.jsx': ['webpack'], 26 | 'test/**/*_test.jsx': ['webpack'] 27 | }, 28 | 29 | webpack: { 30 | // karma watches the test entry points 31 | // (you don't need to specify the entry option) 32 | // webpack watches dependencies 33 | 34 | // webpack configuration 35 | output: { 36 | path: path.join(__dirname, 'dist'), 37 | publicPath: '/', 38 | filename: 'app.js', 39 | chunkFilename: '[chunkhash].js' 40 | }, 41 | 42 | 43 | resolve: { 44 | extensions: ['', '.js', '.jsx', '.styl'], 45 | packageMains: ["webpack", "browser", "web", "browserify", "main"] 46 | }, 47 | 48 | 49 | module: { 50 | loaders: [ 51 | {test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/}, 52 | {test: /.*\.(gif|png|jpg)$/, loaders: ['file?hash=sha512&digest=hex&size=16&name=[hash].[ext]', 'image-webpack-loader?optimizationLevel=7&interlaced=false']} 53 | ] 54 | } 55 | }, 56 | 57 | webpackMiddleware: { 58 | // webpack-dev-middleware configuration 59 | // i. e. 60 | noInfo: true 61 | }, 62 | 63 | plugins: [ 64 | require('karma-chrome-launcher'), 65 | require('karma-mocha'), 66 | require('karma-webpack') 67 | ] 68 | 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boilerplate-webpack-react", 3 | "version": "0.2.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node node_modules/karma/bin/karma start karma.conf.js" 8 | }, 9 | "dependencies": { 10 | "babel": "^5.1.11", 11 | "babel-core": "^5.1.11", 12 | "babel-loader": "^5.0.0", 13 | "css-loader": "^0.9.1", 14 | "del": "^1.1.1", 15 | "extract-text-webpack-plugin": "^0.3.8", 16 | "file-loader": "^0.8.1", 17 | "flow-bin": "^0.6.0", 18 | "good": "^5.1.0", 19 | "good-console": "^4.1.0", 20 | "gulp": "^3.8.11", 21 | "gulp-replace": "^0.5.0", 22 | "gulp-util": "^3.0.2", 23 | "hapi": "^8.4.0", 24 | "image-webpack-loader": "^1.3.1", 25 | "jsx-loader": "^0.12.2", 26 | "react": "^0.13.1", 27 | "react-hot-loader": "^1.2.5", 28 | "run-sequence": "^1.0.2", 29 | "style-loader": "^0.8.2", 30 | "stylus": "^0.50.0", 31 | "stylus-loader": "^1.0.0", 32 | "webpack": "^1.8.8", 33 | "webpack-dev-server": "^1.8.2" 34 | }, 35 | "devDependencies": { 36 | "karma": "^0.12.32", 37 | "karma-chrome-launcher": "^0.1.7", 38 | "karma-mocha": "^0.1.10", 39 | "karma-webpack": "^1.5.0", 40 | "mocha": "^2.1.0", 41 | "should": "^5.2.0" 42 | }, 43 | "author": "Thomas Coopman @tcoopman", 44 | "repository": { 45 | "type": "git", 46 | "url": "git@github.com:tcoopman/boilerplate-webpack-react.git" 47 | }, 48 | "licenses": [ 49 | { 50 | "type": "MIT", 51 | "url": "http://www.opensource.org/licenses/mit-license.php" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 'use strict'; 3 | var Hapi = require('hapi'); 4 | 5 | var page = require('./page.generated.js'); 6 | 7 | var stats = require('./stats.generated.json'); 8 | 9 | var server = new Hapi.Server(); 10 | server.connection({port: 3000}); 11 | 12 | 13 | server.route({ 14 | method: 'GET', 15 | path: '/{params*}', 16 | handler: function (request, reply) { 17 | var html = page(request, stats.assetsByChunkName.main); 18 | reply(html); 19 | } 20 | }); 21 | 22 | 23 | server.route({ 24 | method: 'GET', 25 | path: '/assets/{param*}', 26 | handler: { 27 | directory: { 28 | path: 'public/assets' 29 | } 30 | } 31 | }); 32 | 33 | 34 | var options = { 35 | opsInterval: 1000, 36 | reporters: [{ 37 | reporter: require('good-console'), 38 | args:[{ log: '*', request: '*' }] 39 | }] 40 | }; 41 | 42 | 43 | server.register({ 44 | register: require('good'), 45 | options: options 46 | }, function (err) { 47 | if (err) { 48 | console.log(err); 49 | return; 50 | } else { 51 | server.start(function () { 52 | server.log('info', 'Server running at: ' + server.info.uri); 53 | }); 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /server/page.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react'); 3 | 4 | 5 | module.exports = function(request, fileNames, cb) { 6 | var script, style; 7 | if (Array.isArray(fileNames)) { 8 | script = PUBLIC_PATH + fileNames[0]; 9 | style = PUBLIC_PATH + fileNames[1] 10 | } else { 11 | script = PUBLIC_PATH + fileNames; 12 | } 13 | 14 | var StyleElem; 15 | if (style) { 16 | StyleElem = 17 | } else { 18 | StyleElem = 19 | } 20 | 21 | // Change this to your main module 22 | var HelloWorld = require('../app/jsx/components/HelloWorld.react'); 23 | var html = React.renderToString(); 24 | 25 | console.log(html); 26 | 27 | return React.renderToString( 28 | 29 | 30 | {StyleElem} 31 | 32 | 33 |
34 | 35 | 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /test/Button.react_test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react/addons'; 2 | import should from 'should'; 3 | 4 | import Button from '../app/jsx/components/Button.react'; 5 | 6 | const TestUtils = React.addons.TestUtils; 7 | 8 | 9 | describe('Button', () => { 10 | it('renders button div', () => { 11 | const button = TestUtils.renderIntoDocument( 12 |