Skip to content

Commit 4fd4bdc

Browse files
committed
feat: allow dynamic imports to be enabled/disabled
1 parent 67399dc commit 4fd4bdc

File tree

9 files changed

+123
-77
lines changed

9 files changed

+123
-77
lines changed

config/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const { mapValues, keyBy } = require('lodash');
22

33
module.exports = {
4+
// Enable or disable dynamic imports (code splitting)
5+
enableDynamicImports: false,
6+
47
// The env vars to expose on the client side. If you add them here, they will
58
// be available on the client as process.env[VAR_NAME], same as they would be
69
// in node.js.

server/index.js

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,38 +75,42 @@ const startServer = () => {
7575
if (!module.parent) {
7676
let server;
7777

78-
// Ensure react-loadable stats file exists before starting the server
79-
const statsPath = path.join(__dirname, '..', 'react-loadable.json');
80-
const watcher = chokidar.watch(statsPath, { persistent: true });
78+
if (!config.enableDynamicImports) {
79+
startServer();
80+
} else {
81+
// Ensure react-loadable stats file exists before starting the server
82+
const statsPath = path.join(__dirname, '..', 'react-loadable.json');
83+
const watcher = chokidar.watch(statsPath, { persistent: true });
8184

82-
console.info(`Checking/waiting for ${statsPath}...`);
85+
console.info(`Checking/waiting for ${statsPath}...`);
8386

84-
watcher.on('all', (event, path) => {
85-
if (event === 'add') {
86-
if (env === 'production') {
87-
watcher.close();
88-
}
87+
watcher.on('all', (event, path) => {
88+
if (event === 'add') {
89+
if (env === 'production') {
90+
watcher.close();
91+
}
8992

90-
console.info(`Stats file found at ${path}, starting server...`);
93+
console.info(`Stats file found at ${path}, starting server...`);
9194

92-
startServer().then(s => server = s);
95+
startServer().then(s => server = s);
9396

94-
} else if (event === 'change') {
95-
if (env === 'production') {
96-
watcher.close();
97-
}
97+
} else if (event === 'change') {
98+
if (env === 'production') {
99+
watcher.close();
100+
}
98101

99-
console.info('Stats file changed, restarting server...');
102+
console.info('Stats file changed, restarting server...');
100103

101-
if (server) {
102-
// if the server is already started, restart it.
103-
server.close(() => {
104+
if (server) {
105+
// if the server is already started, restart it.
106+
server.close(() => {
107+
startServer().then(s => server = s);
108+
});
109+
} else {
110+
// otherwise, just start the server.
104111
startServer().then(s => server = s);
105-
});
106-
} else {
107-
// otherwise, just start the server.
108-
startServer().then(s => server = s);
112+
}
109113
}
110-
}
111-
});
114+
});
115+
}
112116
}

server/renderer/render.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,22 @@ export default function render(html, initialState = {}, bundles = []) {
1313

1414
const assets = global.ISOTools.assets();
1515
const appJs = assets.javascript.app;
16+
const vendorJs = assets.javascript.vendor;
1617
const helmet = Helmet.renderStatic();
1718
const appCss = assets.styles.app;
19+
const vendorCss = assets.styles.vendor;
1820
const chunkCss = bundles.filter(bundle => bundle.file.match(/.css/));
1921
const chunkJs = bundles.filter(bundle => bundle.file.match(/.js/));
2022

21-
return compile(
22-
{ html, helmet, appCss, appJs, chunkCss, chunkJs, initialState }
23-
);
23+
return compile({
24+
html,
25+
helmet,
26+
appCss,
27+
appJs,
28+
vendorJs,
29+
vendorCss,
30+
chunkCss,
31+
chunkJs,
32+
initialState
33+
});
2434
}

server/templates/layouts/application.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55
<meta charset="utf-8" />
66
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width height=device-height" />
77
<link rel="shortcut icon" type="image/x-icon" href="/assets/favicon.png">
8+
<%= vendorCss ? `<link type="text/css" rel="stylesheet" href="${vendorCss}" />` : '' %>
89
<link type="text/css" rel="stylesheet" href="<%= appCss %>" />
9-
<%= chunkCss.map(bundle => {
10-
return ` <link type="text/css" rel="stylesheet" href="${process.env.PUBLIC_ASSET_PATH}${bundle.file}" />`
11-
}).join('\n') %>
10+
<%= chunkCss.map(bundle => {
11+
return `<link type="text/css" rel="stylesheet" href="${process.env.PUBLIC_ASSET_PATH}${bundle.file}" />`
12+
}).join('\n') %>
1213
</head>
1314
<body>
1415
<div id="app"><%= html %></div>
1516
<script>window.__INITIAL_STATE__ = <%= JSON.stringify(initialState) %>;</script>
17+
<%= vendorJs ? `<script src="${vendorJs}"></script>` : '' %>
1618
<script src="<%= appJs %>"></script>
17-
<%= chunkJs.map(bundle => {
18-
return ` <script src="${process.env.PUBLIC_ASSET_PATH}${bundle.file}"></script>`
19-
}).join('\n') %>
19+
<%= chunkJs.map(bundle => {
20+
return `<script src="${process.env.PUBLIC_ASSET_PATH}${bundle.file}"></script>`
21+
}).join('\n') %>
2022
<script>window.main();</script>
2123
</body>
2224
</html>

webpack/base.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,38 @@ import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
1111
import { ReactLoadablePlugin } from 'react-loadable/webpack';
1212
import config from '../config';
1313

14-
const ssr = yn(process.env.SSR) || false;
15-
const isoPlugin = new IsoPlugin(config.isomorphicConfig).development(isDev);
16-
const extractTextPlugin = new ExtractTextPlugin({
14+
let ssr = yn(process.env.SSR) || false;
15+
let isoPlugin = new IsoPlugin(config.isomorphicConfig).development(isDev);
16+
let extractTextPlugin = new ExtractTextPlugin({
1717
filename: isDev ? '[name].css' : '[name].[contenthash].css',
1818
allChunks: true,
1919
disable: ssr
2020
});
21-
const plugins = [
21+
22+
let plugins = [
2223
isoPlugin,
23-
new ReactLoadablePlugin({
24-
filename: path.join(__dirname, '..', 'react-loadable.json')
25-
}),
2624
extractTextPlugin,
2725
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en|es/),
2826
new webpack.DefinePlugin({
2927
'process.env': config.clientEnv
3028
})
3129
];
3230

31+
let output = {
32+
path: path.join(__dirname, '..', process.env.PUBLIC_OUTPUT_PATH),
33+
filename: '[name].bundle.js',
34+
publicPath: process.env.PUBLIC_ASSET_PATH || '/'
35+
};
36+
37+
// Enable dynamically imported code-splitting
38+
if (config.enableDynamicImports) {
39+
plugins.unshift(new ReactLoadablePlugin({
40+
filename: path.join(__dirname, '..', 'react-loadable.json')
41+
}));
42+
43+
output.chunkFilename = '[name].bundle.js';
44+
}
45+
3346
if (process.env.ANALYZE) {
3447
plugins.push(new BundleAnalyzerPlugin());
3548
}
@@ -39,12 +52,7 @@ export default {
3952
entry: {
4053
app: ['./client/index']
4154
},
42-
output: {
43-
path: path.join(__dirname, '..', process.env.PUBLIC_OUTPUT_PATH),
44-
filename: '[name].bundle.js',
45-
chunkFilename: '[name].bundle.js',
46-
publicPath: process.env.PUBLIC_ASSET_PATH || '/'
47-
},
55+
output,
4856
resolve: {
4957
extensions: ['.js', '.jsx', '.scss'],
5058
alias: mapValues(config.clientResolvePaths, str =>

webpack/development.hot.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import WebpackDevServer from 'webpack-dev-server';
33
import webpack from 'webpack';
44
import baseConfig from './base';
5+
import config from '../config';
56

67
const {
78
DEV_SERVER_PORT,
@@ -16,41 +17,52 @@ const entry = [
1617
];
1718

1819
// Additional plugins
19-
const plugins = [
20+
let plugins = [
2021
new webpack.HotModuleReplacementPlugin(),
2122
new webpack.NoEmitOnErrorsPlugin(),
2223
new webpack.NamedModulesPlugin()
2324
];
2425

26+
if (!config.enableDynamicImports) {
27+
plugins.push(new webpack.optimize.CommonsChunkPlugin({
28+
name: 'vendor',
29+
filename: '[name].js',
30+
minChunks: module => /node_modules/.test(module.resource)
31+
}));
32+
}
33+
2534
// Additional loaders
2635
const loaders = [];
2736

28-
const config = Object.assign({}, baseConfig, {
37+
const webpackConfig = {
38+
...baseConfig,
2939
devtool: 'eval',
30-
entry: Object.assign({}, baseConfig.entry, {
40+
entry: {
41+
...baseConfig.entry,
3142
app: [
3243
...entry,
3344
...baseConfig.entry.app
3445
]
35-
}),
46+
},
3647
plugins: [
3748
// don't use the first plugin (isomorphic plugin)
3849
...baseConfig.plugins,
3950
...plugins
4051
],
41-
module: Object.assign({}, baseConfig.module, {
52+
module: {
53+
...baseConfig.module,
4254
rules: [
4355
...baseConfig.module.rules,
4456
...loaders
4557
]
46-
})
47-
});
58+
}
59+
};
4860

4961
console.info('Firing up Webpack dev server...\n');
5062

51-
new WebpackDevServer(webpack(config), {
63+
new WebpackDevServer(webpack(webpackConfig), {
5264
port: DEV_SERVER_PORT,
53-
publicPath: config.output.publicPath,
65+
publicPath: webpackConfig.output.publicPath,
5466
hot: true,
5567
historyApiFallback: true,
5668
noInfo: false,

webpack/production.babel.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,11 @@ export default {
1313
...baseConfig,
1414
output: {
1515
...baseConfig.output,
16-
...{
17-
filename: '[name].[hash].js'
18-
}
16+
filename: '[name].[hash].js'
1917
},
2018
plugins,
2119
module: {
2220
...baseConfig.module,
23-
...{
24-
rules: [...baseConfig.module.rules, ...loaders]
25-
}
21+
rules: [...baseConfig.module.rules, ...loaders]
2622
}
2723
};

webpack/production.client.babel.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import webpack from 'webpack';
22
import baseConfig from './production.babel';
33
import CompressionPlugin from 'compression-webpack-plugin';
44
import UglifyJSPlugin from 'uglifyjs-webpack-plugin';
5+
import config from '../config';
56

6-
const plugins = [
7+
let plugins = [
8+
...baseConfig.plugins,
79
new webpack.BannerPlugin({
810
banner:
911
'hash:[hash], chunkhash:[chunkhash], name:[name], ' +
@@ -26,25 +28,34 @@ const plugins = [
2628
test: /\.css$|\.js$|\.html$/,
2729
threshold: 10240,
2830
minRatio: 0.8
31+
}),
32+
new webpack.optimize.CommonsChunkPlugin({
33+
name: 'vendor',
34+
filename: '[name].[hash].js',
35+
minChunks: module => /node_modules/.test(module.resource)
2936
})
3037
];
3138

32-
const loaders = [];
39+
let output = {
40+
...baseConfig.output,
41+
filename: '[name].[hash].js'
42+
};
43+
44+
let loaders = [];
45+
46+
if (config.enableDynamicImports) {
47+
// remove commons chunk plugin
48+
plugins = plugins.slice(0, plugins.length - 1);
49+
50+
output.chunkFilename = '[name].[hash].js';
51+
}
3352

3453
export default {
3554
...baseConfig,
36-
output: {
37-
...baseConfig.output,
38-
...{
39-
filename: '[name].[hash].js',
40-
chunkFilename: '[name].[hash].js',
41-
}
42-
},
43-
plugins: [...baseConfig.plugins, ...plugins],
55+
output,
56+
plugins,
4457
module: {
4558
...baseConfig.module,
46-
...{
47-
rules: [...baseConfig.module.rules, ...loaders]
48-
}
59+
rules: [...baseConfig.module.rules, ...loaders]
4960
}
5061
};

webpack/production.server.babel.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { babel } from '../package.json';
1111
const babelOpts = set(babel, 'presets[0][1].targets.uglify', true);
1212

1313
const plugins = [
14-
// we don't need the isomorphic or react-loadable plugins here
15-
...baseConfig.plugins.slice(2),
14+
// we don't need the isomorphic and react-loadable plugins here
15+
...baseConfig.plugins.slice(config.enableDynamicImports ? 2 : 1),
1616
new UglifyJSPlugin({
1717
sourceMap: true
1818
}),

0 commit comments

Comments
 (0)