Skip to content

Commit b7b37c7

Browse files
committed
Validates production code splitting
1 parent 5ab2f56 commit b7b37c7

File tree

9 files changed

+250
-15
lines changed

9 files changed

+250
-15
lines changed

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"main": "index.js",
66
"scripts": {
77
"development": "NODE_PATH=./src NODE_ENV=development node ./src/server/server.babel.js",
8-
"production": "NODE_PATH=./src NODE_ENV=production node ./src/server/server.babel.js"
8+
"production": "NODE_PATH=./src NODE_ENV=production node ./src/server/server.babel.js",
9+
"build": "npm run build:client && npm run build:server",
10+
"build:server": "NODE_ENV=production webpack --config ./webpack/server.babel.js",
11+
"build:client": "NODE_ENV=production webpack --config ./webpack/production.babel.js"
912
},
1013
"keywords": [
1114
"react",
@@ -20,6 +23,7 @@
2023
"author": "Alexander J Ray <[email protected]>",
2124
"license": "MIT",
2225
"devDependencies": {
26+
"assets-webpack-plugin": "^3.5.1",
2327
"autoprefixer": "^6.7.7",
2428
"babel-loader": "^6.4.1",
2529
"babel-plugin-add-module-exports": "^0.2.1",
@@ -30,6 +34,7 @@
3034
"babel-preset-stage-0": "^6.22.0",
3135
"babel-register": "^6.24.0",
3236
"css-loader": "^0.28.0",
37+
"extract-text-webpack-plugin": "^2.1.0",
3338
"less-loader": "^4.0.3",
3439
"postcss-loader": "^1.3.3",
3540
"react-hot-loader": "^3.0.0-beta.6",

src/server/server.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ const PROD = process.env.NODE_ENV === 'production';
1313

1414
const app = express();
1515

16-
1716
if (PROD) {
18-
17+
app.use('/static', express.static('build'));
18+
app.get('*', renderPage);
1919
} else {
2020
const HMR = require('./hmr.js');
2121
// Hot Module Reloading

src/universal/routes/Routes.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ import React, {Component, PropTypes} from 'react';
33
import {Route, Redirect} from 'react-router';
44

55
// Routes
6-
import * as RouteMap from 'universal/routes/index.js';
6+
// For Development only
7+
// import * as RouteMap from '../routes/index.js';
8+
9+
// This is used in production for code splitting via `wepback.config.server.js`
10+
import * as RouteMap from 'universal/routes/async.js';
711

812
// Containers
913
import AppContainer from 'universal/containers/App/AppContainer.js';
1014
// import PrivateRouteContainer from 'universal/containers/PrivateRoute/PrivateRouteContainer.js';
1115

12-
// Styles
13-
import {
14-
background
15-
} from 'universal/styles/global.less';
16-
1716
class Routes extends Component {
1817
render () {
1918
const {

src/universal/routes/async.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
3+
function asyncRoute(getComponent) {
4+
return class AsyncComponent extends React.Component {
5+
state = {
6+
Component: null
7+
};
8+
9+
componentDidMount() {
10+
if ( this.state.Component === null ) {
11+
getComponent().then((Component) => {
12+
this.setState({Component: Component});
13+
})
14+
}
15+
}
16+
17+
render() {
18+
const {
19+
Component
20+
} = this.state;
21+
22+
if ( Component ) {
23+
return (<Component {...this.props} />);
24+
}
25+
return (<div>loading...</div>); // or <div /> with a loading spinner, etc..
26+
}
27+
}
28+
}
29+
30+
export const Home = asyncRoute(() => {
31+
return System.import('../components/Home/Home.js');
32+
});
33+
34+
export const Counter = asyncRoute(() => {
35+
return System.import('../modules/counter/containers/Counter/CounterContainer.js');
36+
});

webpack/production.babel.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require('babel-register');
2+
module.exports = require('./webpack.config.production.js');

webpack/server.babel.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require('babel-register');
2+
module.exports = require('./webpack.config.server.js');

webpack/webpack.config.development.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,14 @@ export default {
5555
},
5656
module: {
5757
loaders: [
58-
{test: /\.(png|j|jpeg|gif|svg|woff|woff2)$/, loader: 'url-loader?limit=10000'},
58+
{test: /\.(png|j|jpeg|gif|svg|woff|woff2)$/,
59+
use: {
60+
loader: 'url-loader',
61+
options: {
62+
limit: 10000
63+
}
64+
}
65+
},
5966

6067
// Javascript
6168
{test: /\.js$/,
@@ -65,7 +72,7 @@ export default {
6572
},
6673

6774
// CSS
68-
{test: /\.css|less$/,
75+
{test: /\.css$/,
6976
include: clientInclude,
7077
use: [
7178
{loader: 'style-loader'},
@@ -74,10 +81,8 @@ export default {
7481
root: src,
7582
modules: true,
7683
importLoaders: 1,
77-
localIdentName: '[path][name]-[local]'
78-
}},
79-
{loader: 'less-loader'},
80-
{loader: 'postcss-loader'}
84+
localIdentName: '[name]_[local]_[hash:base64:5]'
85+
}}
8186
]
8287
}
8388
]

webpack/webpack.config.production.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import path from 'path';
2+
import webpack from 'webpack';
3+
import qs from 'querystring';
4+
5+
import autoprefixer from 'autoprefixer';
6+
import AssetsPlugin from 'assets-webpack-plugin';
7+
import ExtractTextPlugin from 'extract-text-webpack-plugin';
8+
9+
const root = process.cwd();
10+
const src = path.join(root, 'src');
11+
const build = path.join(root, 'build');
12+
13+
const clientSrc = path.join(src, 'client');
14+
const universalSrc = path.join(src, 'universal');
15+
16+
const clientInclude = [clientSrc, universalSrc];
17+
18+
// Cache vendor && client javascript on CDN...
19+
const vendor = [
20+
'react',
21+
'react-dom',
22+
'react-router',
23+
'react-redux',
24+
'redux'
25+
];
26+
27+
export default {
28+
context: src,
29+
entry: {
30+
app: [
31+
'babel-polyfill/dist/polyfill.js',
32+
'./client/client.js'
33+
],
34+
vendor
35+
},
36+
output: {
37+
filename: '[name]_[chunkhash].js',
38+
chunkFilename: '[name]_[chunkhash].js',
39+
path: build,
40+
publicPath: '/static/'
41+
},
42+
resolve: {
43+
extensions: ['.js'],
44+
modules: [src, 'node_modules'],
45+
unsafeCache: true
46+
},
47+
node: {
48+
dns: 'mock',
49+
net: 'mock'
50+
},
51+
plugins: [
52+
new webpack.NamedModulesPlugin(),
53+
new ExtractTextPlugin('[name].css'),
54+
new webpack.NormalModuleReplacementPlugin(/\.\.\/routes\/index/, '../routes/async'),
55+
new webpack.optimize.CommonsChunkPlugin({
56+
names: ['vendor', 'manifest'],
57+
minChunks: Infinity
58+
}),
59+
new webpack.optimize.AggressiveMergingPlugin(),
60+
/* minChunkSize should be 50000 for production apps
61+
* 10 is for this example */
62+
new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10}),
63+
new webpack.optimize.UglifyJsPlugin({compressor: {warnings: false}, comments: /(?:)/}),
64+
new AssetsPlugin({path: build, filename: 'assets.json'}),
65+
new webpack.NoEmitOnErrorsPlugin(),
66+
new webpack.DefinePlugin({
67+
'__CLIENT__': true,
68+
'__PRODUCTION__': true,
69+
'process.env.NODE_ENV': JSON.stringify('production')
70+
})
71+
],
72+
module: {
73+
loaders: [
74+
{test: /\.(png|j|jpeg|gif|svg|woff|woff2)$/,
75+
use: {
76+
loader: 'url-loader',
77+
options: {
78+
limit: 10000
79+
}
80+
}
81+
},
82+
83+
// JavaScript
84+
{test: /\.js$/,
85+
loader: 'babel-loader',
86+
include: clientInclude
87+
},
88+
89+
// CSS
90+
{test: /\.css|less$/,
91+
include: clientInclude,
92+
loaders: ExtractTextPlugin.extract({
93+
fallback: 'style-loader',
94+
use: [
95+
{loader: 'css-loader',
96+
options: {
97+
root: src,
98+
modules: true,
99+
importLoaders: 1,
100+
localIdentName: '[name]_[local]_[hash:base64:5]'
101+
}}
102+
]})
103+
}
104+
105+
]
106+
}
107+
};

webpack/webpack.config.server.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import path from 'path';
2+
import webpack from 'webpack';
3+
import qs from 'querystring';
4+
import ExtractTextPlugin from 'extract-text-webpack-plugin';
5+
6+
// Paths
7+
const root = process.cwd();
8+
const src = path.join(root, 'src');
9+
const build = path.join(root, 'build');
10+
const universal = path.join(src, 'universal');
11+
const server = path.join(src, 'server');
12+
13+
const serverInclude = [server, universal];
14+
15+
export default {
16+
context: src,
17+
entry: {
18+
prerender: './universal/routes/Routes.js'
19+
},
20+
target: 'node',
21+
output: {
22+
path: build,
23+
chunkFilename: '[name]_[chunkhash].js',
24+
filename: '[name].js',
25+
libraryTarget: 'commonjs2',
26+
publicPath: '/static/'
27+
},
28+
resolve: {
29+
extensions: ['.js'],
30+
modules: [src, 'node_modules']
31+
},
32+
plugins: [
33+
new webpack.NoEmitOnErrorsPlugin(),
34+
new ExtractTextPlugin('[name].css'),
35+
new webpack.NormalModuleReplacementPlugin(/\.\.\/routes\/index/, '../routes/async'),
36+
new webpack.optimize.UglifyJsPlugin({compressor: {warnings: false}}),
37+
new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}),
38+
new webpack.DefinePlugin({
39+
'__CLIENT__': false,
40+
'__PRODUCTION__': true,
41+
'process.env.NODE_ENV': JSON.stringify('production')
42+
})
43+
],
44+
module: {
45+
loaders: [
46+
{test: /\.(png|j|jpeg|gif|svg|woff|woff2)$/,
47+
use: {
48+
loader: 'url-loader',
49+
options: {
50+
limit: 10000
51+
}
52+
}
53+
},
54+
55+
{
56+
test: /\.css$/,
57+
include: serverInclude,
58+
loader: ExtractTextPlugin.extract({
59+
fallback: 'style-loader',
60+
use: [
61+
{loader: 'css-loader',
62+
options: {
63+
root: src,
64+
modules: true,
65+
importLoaders: 1,
66+
localIdentName: '[name]_[local]_[hash:base64:5]'
67+
}}
68+
]})
69+
},
70+
71+
{
72+
test: /\.js$/,
73+
loader: 'babel-loader',
74+
include: serverInclude
75+
}
76+
77+
]
78+
}
79+
};

0 commit comments

Comments
 (0)