├── .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 |
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 |
13 | );
14 | TestUtils.isCompositeComponent(button).should.be.ok;
15 | button.getDOMNode().textContent.should.be.eql('button');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/HelloWorld.react_test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react/addons';
2 | import should from 'should';
3 |
4 | import HelloWorld from '../app/jsx/components/HelloWorld.react';
5 |
6 | const TestUtils = React.addons.TestUtils;
7 |
8 |
9 | describe('HelloWorld', () => {
10 | it('renders h1 tag', () => {
11 | const helloWorld = TestUtils.renderIntoDocument(
12 |
13 | );
14 | TestUtils.isCompositeComponent(helloWorld).should.be.ok;
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/test/sanity_test.jsx:
--------------------------------------------------------------------------------
1 | import should from 'should';
2 |
3 | describe('sanity test', () => {
4 | it('true should be true', () => {
5 | true.should.be.ok;
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var fs = require('fs');
3 | var path = require('path');
4 |
5 | var webpack = require('webpack');
6 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
7 |
8 | var commonLoaders = [
9 | {test: /.*\.json$/, loader: 'json'},
10 | {test: /.*\.md$/, loader: 'file'},
11 | {test: /\.jsx?$/, loaders: ['react-hot', 'babel-loader'], exclude: /node_modules/},
12 | {test: /.*\.(gif|png|jpg)$/, loaders: ['file?hash=sha512&digest=hex&size=16&name=[hash].[ext]', 'image-webpack-loader?optimizationLevel=7&interlaced=false']},
13 | {test: /.*\.(eot|woff|ttf|svg)/, loader: 'file?hash=sha512&digest=hex&size=16&name=cd [hash].[ext]'}
14 | ];
15 | var assetsPath = path.join(__dirname, 'public/assets');
16 | var publicPath = 'assets/';
17 | var extensions = ['', '.js', '.jsx', '.styl'];
18 |
19 | module.exports = [
20 | {
21 | name: 'browser',
22 | entry: './app/jsx/app.jsx',
23 | output: {
24 | path: assetsPath,
25 | publicPath: publicPath,
26 | filename: 'app.[hash].js'
27 | },
28 | resolve: {
29 | extensions: extensions
30 | },
31 | module: {
32 | loaders: commonLoaders.concat([{test: /\.styl$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!stylus')}])
33 | },
34 | plugins: [
35 | function() {
36 | this.plugin('done', function(stats) {
37 | fs.writeFileSync(path.join(__dirname, 'server', 'stats.generated.json'), JSON.stringify(stats.toJson()));
38 | });
39 | },
40 | new ExtractTextPlugin('style.[contenthash].css', {
41 | allChunks: true
42 | })
43 | ]
44 | },
45 | {
46 | name: 'server',
47 | entry: './server/page.jsx',
48 | target: 'node',
49 | output: {
50 | path: assetsPath,
51 | filename: '../../server/page.generated.js',
52 | publicPath: publicPath,
53 | libraryTarget: 'commonjs2'
54 | },
55 | resolve: {
56 | extensions: extensions
57 | },
58 | externals: /^[a-z\-0-9]+$/, // Every non-relative module is external,
59 | module: {
60 | loaders: commonLoaders.concat([
61 | {test: /\.styl$/, loader: 'null'}
62 | ])
63 | },
64 | plugins: [
65 | new webpack.DefinePlugin({
66 | PUBLIC_PATH: JSON.stringify(publicPath)
67 | })
68 | ]
69 | }
70 | ];
71 |
--------------------------------------------------------------------------------