Skip to content

Commit 7a5668b

Browse files
authored
Production bundle optimizations (codesandbox#273)
* Enable Module Concatenation * Use new Uglify plugin * Optimize webpack config, name all imports * Fix common chunks * Fix chunks * Add descriptions
1 parent c8cd6af commit 7a5668b

File tree

23 files changed

+509
-106
lines changed

23 files changed

+509
-106
lines changed

config/webpack.common.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ module.exports = {
3838
require.resolve('./polyfills'),
3939
path.join(paths.embedSrc, 'index.js'),
4040
],
41-
vendor: ['react', 'react-dom', 'styled-components'],
4241
},
4342
target: 'web',
4443
node: {
@@ -158,7 +157,7 @@ module.exports = {
158157
// Generates an `index.html` file with the <script> injected.
159158
new HtmlWebpackPlugin({
160159
inject: true,
161-
chunks: ['vendor', 'common', 'app'],
160+
chunks: ['common-sandbox', 'common', 'app'],
162161
filename: 'app.html',
163162
template: paths.appHtml,
164163
minify: __PROD__ && {
@@ -176,7 +175,7 @@ module.exports = {
176175
}),
177176
new HtmlWebpackPlugin({
178177
inject: true,
179-
chunks: ['vendor', 'common', 'sandbox'],
178+
chunks: ['common-sandbox', 'sandbox'],
180179
filename: 'frame.html',
181180
template: paths.sandboxHtml,
182181
minify: __PROD__ && {
@@ -194,7 +193,7 @@ module.exports = {
194193
}),
195194
new HtmlWebpackPlugin({
196195
inject: true,
197-
chunks: ['vendor', 'embed'],
196+
chunks: ['common-sandbox', 'common', 'embed'],
198197
filename: 'embed.html',
199198
template: path.join(paths.embedSrc, 'index.html'),
200199
minify: __PROD__ && {
@@ -257,14 +256,17 @@ module.exports = {
257256
},
258257
].filter(x => x)
259258
),
260-
// Try to dedupe duplicated modules, if any:
259+
// We first create a common chunk between embed and app, to share components
260+
// and dependencies.
261261
new webpack.optimize.CommonsChunkPlugin({
262262
name: 'common',
263-
chunks: ['app', 'sandbox'],
263+
chunks: ['app', 'embed'],
264264
}),
265+
// Then we find all commonalities between sandbox and common, because sandbox
266+
// is always loaded by embed and app.
265267
new webpack.optimize.CommonsChunkPlugin({
266-
name: 'vendor',
267-
minChunks: Infinity,
268+
name: 'common-sandbox',
269+
chunks: ['common', 'sandbox'],
268270
}),
269271
new webpack.optimize.CommonsChunkPlugin({
270272
async: true,

config/webpack.prod.js

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const merge = require('webpack-merge');
22
const webpack = require('webpack');
33
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
4+
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
45
const childProcess = require('child_process');
56
const commonConfig = require('./webpack.common');
67

@@ -23,26 +24,15 @@ module.exports = merge(commonConfig, {
2324
sourceMapFilename: '[file].map', // Default
2425
},
2526
plugins: [
27+
new webpack.optimize.ModuleConcatenationPlugin(),
2628
new webpack.DefinePlugin({ VERSION: JSON.stringify(VERSION) }),
2729
new webpack.LoaderOptionsPlugin({
2830
minimize: true,
2931
debug: false,
3032
}),
31-
new webpack.optimize.UglifyJsPlugin({
32-
compress: {
33-
warnings: false,
34-
// Disabled because of an issue with Uglify breaking seemingly valid code:
35-
// https://github.com/facebookincubator/create-react-app/issues/2376
36-
// Pending further investigation:
37-
// https://github.com/mishoo/UglifyJS2/issues/2011
38-
comparisons: false,
39-
},
40-
output: {
41-
comments: false,
42-
// Turned on because emoji and regex is not minified properly using default
43-
// https://github.com/facebookincubator/create-react-app/issues/2488
44-
ascii_only: true,
45-
},
33+
new UglifyJSPlugin({
34+
cache: true,
35+
parallel: true,
4636
sourceMap: true,
4737
}),
4838
// Generate a service worker script that will precache, and keep up to date,

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@
7878
"style-loader": "^0.18.2",
7979
"sw-precache-webpack-plugin": "^0.11.4",
8080
"typescript": "^2.4.2",
81+
"uglifyjs-webpack-plugin": "^1.0.1",
8182
"url-loader": "^0.5.9",
8283
"webpack": "^3.5.4",
84+
"webpack-bundle-analyzer": "^2.9.0",
8385
"webpack-dev-middleware": "^1.11.0",
8486
"webpack-dev-server": "^2.5.1",
8587
"webpack-merge": "^4.1.0",

src/app/components/sandbox/CodeEditor/CodeMirror.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -301,13 +301,19 @@ export default class CodeEditor extends React.Component<Props, State> {
301301

302302
if (kind) {
303303
if (kind[1] === 'css') {
304-
await System.import('codemirror/mode/css/css');
304+
await System.import(
305+
/* webpackChunkName: 'codemirror-css' */ 'codemirror/mode/css/css'
306+
);
305307
return 'css';
306308
} else if (kind[1] === 'html' || kind[1] === 'vue') {
307-
await System.import('codemirror/mode/htmlmixed/htmlmixed');
309+
await System.import(
310+
/* webpackChunkName: 'codemirror-html' */ 'codemirror/mode/htmlmixed/htmlmixed'
311+
);
308312
return 'htmlmixed';
309313
} else if (kind[1] === 'md') {
310-
await System.import('codemirror/mode/markdown/markdown');
314+
await System.import(
315+
/* webpackChunkName: 'codemirror-markdown' */ 'codemirror/mode/markdown/markdown'
316+
);
311317
return 'markdown';
312318
}
313319
}
@@ -370,8 +376,12 @@ export default class CodeEditor extends React.Component<Props, State> {
370376
};
371377

372378
if (preferences.autoCompleteEnabled) {
373-
const tern = await System.import('tern');
374-
const defs = await System.import('tern/defs/ecmascript.json');
379+
const tern = await System.import(
380+
/* webpackChunkName: 'codemirror-tern' */ 'tern'
381+
);
382+
const defs = await System.import(
383+
/* webpackChunkName: 'codemirror-tern-definitions' */ 'tern/defs/ecmascript.json'
384+
);
375385
window.tern = tern;
376386
this.server =
377387
this.server ||
@@ -427,15 +437,19 @@ export default class CodeEditor extends React.Component<Props, State> {
427437
}
428438

429439
if (preferences.vimMode) {
430-
await System.import('codemirror/keymap/vim');
440+
await System.import(
441+
/* webpackChunkName: 'codemirror-vim' */ 'codemirror/keymap/vim'
442+
);
431443
this.codemirror.setOption('keyMap', 'vim');
432444
} else {
433445
this.codemirror.setOption('keyMap', 'sublime');
434446
}
435447

436448
if (preferences.lintEnabled) {
437449
const initialized = 'eslint' in window;
438-
System.import('app/utils/codemirror/eslint-lint')
450+
System.import(
451+
/* webpackChunkName: 'codemirror-eslint' */ 'app/utils/codemirror/eslint-lint'
452+
)
439453
.then(initializer => !initialized && initializer.default())
440454
.then(() => {
441455
this.codemirror.setOption('lint', true);
@@ -459,7 +473,7 @@ export default class CodeEditor extends React.Component<Props, State> {
459473
const mode = await this.getMode(title);
460474
if (mode === 'jsx' || mode === 'typescript' || mode === 'css') {
461475
try {
462-
const prettify = await import('app/utils/codemirror/prettify');
476+
const prettify = await import(/* webpackChunkName: 'prettier' */ 'app/utils/codemirror/prettify');
463477
const newCode = await prettify.default(
464478
code,
465479
mode,

src/app/components/sandbox/CodeEditor/Monaco.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import theme from 'common/theme';
1515
import getTemplate from 'common/templates';
1616

1717
/* eslint-disable import/no-webpack-loader-syntax */
18-
import SyntaxHighlightWorker from 'worker-loader!./monaco/workers/syntax-highlighter';
19-
import LinterWorker from 'worker-loader!./monaco/workers/linter';
20-
import TypingsFetcherWorker from 'worker-loader!./monaco/workers/fetch-dependency-typings';
18+
import SyntaxHighlightWorker from 'worker-loader?name=monaco-syntax-highlighter.[hash].worker.js!./monaco/workers/syntax-highlighter';
19+
import LinterWorker from 'worker-loader?name=monaco-linter.[hash].worker.js!./monaco/workers/linter';
20+
import TypingsFetcherWorker from 'worker-loader?name=monaco-typings-ata.[hash].worker.js!./monaco/workers/fetch-dependency-typings';
2121
/* eslint-enable import/no-webpack-loader-syntax */
2222

2323
import Header from './Header';
@@ -661,9 +661,11 @@ export default class CodeEditor extends React.Component<Props, State> {
661661
await this.openNewModel(this.props.id, this.props.title);
662662

663663
this.addKeyCommands();
664-
import('./monaco/enable-emmet').then(enableEmmet => {
665-
enableEmmet.default(editor, monaco, {});
666-
});
664+
import(/* webpackChunkName: 'monaco-emmet' */ './monaco/enable-emmet').then(
665+
enableEmmet => {
666+
enableEmmet.default(editor, monaco, {});
667+
}
668+
);
667669

668670
window.addEventListener('resize', this.resizeEditor);
669671
this.sizeProbeInterval = setInterval(this.resizeEditor.bind(this), 3000);
@@ -882,7 +884,7 @@ export default class CodeEditor extends React.Component<Props, State> {
882884

883885
if (mode === 'javascript' || mode === 'typescript' || mode === 'css') {
884886
try {
885-
const prettify = await import('app/utils/codemirror/prettify');
887+
const prettify = await import(/* webpackChunkName: 'prettier' */ 'app/utils/codemirror/prettify');
886888
const newCode = await prettify.default(
887889
code,
888890
mode === 'javascript' ? 'jsx' : mode,

src/app/components/sandbox/CodeEditor/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type Props = {
1010
};
1111

1212
const CodeMirror = Loadable({
13-
loader: () => import('./CodeMirror'),
13+
loader: () => import(/* webpackChunkName: 'codemirror' */ './CodeMirror'),
1414
LoadingComponent: Loading,
1515
});
1616

src/app/pages/index.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,43 +34,44 @@ const Content = styled.div`
3434
`;
3535

3636
const SignIn = Loadable({
37-
loader: () => import('./SignIn'),
37+
loader: () => import(/* webpackChunkName: 'page-sign-in' */ './SignIn'),
3838
LoadingComponent: Loading,
3939
});
4040
const ZeitSignIn = Loadable({
41-
loader: () => import('./auth/Zeit'),
41+
loader: () => import(/* webpackChunkName: 'page-zeit' */ './auth/Zeit'),
4242
LoadingComponent: Loading,
4343
});
4444
const NotFound = Loadable({
45-
loader: () => import('./NotFound'),
45+
loader: () => import(/* webpackChunkName: 'page-not-found' */ './NotFound'),
4646
LoadingComponent: Loading,
4747
});
4848
const Profile = Loadable({
49-
loader: () => import('./Profile'),
49+
loader: () => import(/* webpackChunkName: 'page-profile' */ './Profile'),
5050
LoadingComponent: Loading,
5151
});
5252
const Search = Loadable({
53-
loader: () => import('./Search'),
53+
loader: () => import(/* webpackChunkName: 'page-search' */ './Search'),
5454
LoadingComponent: Loading,
5555
});
5656
const CLI = Loadable({
57-
loader: () => import('./CLI'),
57+
loader: () => import(/* webpackChunkName: 'page-cli' */ './CLI'),
5858
LoadingComponent: Loading,
5959
});
6060
const GitHub = Loadable({
61-
loader: () => import('./GitHub'),
61+
loader: () => import(/* webpackChunkName: 'page-github' */ './GitHub'),
6262
LoadingComponent: Loading,
6363
});
6464
const CliInstructions = Loadable({
65-
loader: () => import('./CliInstructions'),
65+
loader: () =>
66+
import(/* webpackChunkName: 'page-cli-instructions' */ './CliInstructions'),
6667
LoadingComponent: Loading,
6768
});
6869
const Patron = Loadable({
69-
loader: () => import('./Patron'),
70+
loader: () => import(/* webpackChunkName: 'page-patron' */ './Patron'),
7071
LoadingComponent: Loading,
7172
});
7273
const Terms = Loadable({
73-
loader: () => import('./Terms'),
74+
loader: () => import(/* webpackChunkName: 'page-terms' */ './Terms'),
7475
LoadingComponent: Loading,
7576
});
7677

src/app/store/entities/sandboxes/actions/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ export default {
318318
const directories = directoriesSelector(getState());
319319
const sandbox = singleSandboxSelector(getState(), { id });
320320

321-
const createZip = await import('../utils/create-zip');
321+
const createZip = await import(/* webpackChunkName: 'create-zip' */ '../utils/create-zip');
322322

323323
createZip.default(
324324
sandbox,
@@ -336,7 +336,7 @@ export default {
336336
const directories = directoriesSelector(getState());
337337
const sandbox = singleSandboxSelector(getState(), { id });
338338

339-
const deploy = await import('../utils/deploy');
339+
const deploy = await import(/* webpackChunkName: 'deploy' */ '../utils/deploy');
340340

341341
const apiData = await deploy.default(
342342
sandbox,

src/app/store/entities/sandboxes/utils/create-zip/index.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,24 +102,24 @@ export async function createZip(
102102

103103
let promise = null;
104104
if (sandbox.template === react.name) {
105-
promise = import('./create-react-app').then(generator =>
106-
generator.default(zip, sandbox, modules, directories)
105+
promise = import(/* webpackChunkName: 'create-react-app-zip' */ './create-react-app').then(
106+
generator => generator.default(zip, sandbox, modules, directories)
107107
);
108108
} else if (sandbox.template === reactTs.name) {
109-
promise = import('./create-react-app-typescript').then(generator =>
110-
generator.default(zip, sandbox, modules, directories)
109+
promise = import(/* webpackChunkName: 'create-react-app-typescript-zip' */ './create-react-app-typescript').then(
110+
generator => generator.default(zip, sandbox, modules, directories)
111111
);
112112
} else if (sandbox.template === vue.name) {
113-
promise = import('./vue-cli').then(generator =>
114-
generator.default(zip, sandbox, modules, directories)
113+
promise = import(/* webpackChunkName: 'vue-zip' */ './vue-cli').then(
114+
generator => generator.default(zip, sandbox, modules, directories)
115115
);
116116
} else if (sandbox.template === preact.name) {
117-
promise = import('./preact-cli').then(generator =>
118-
generator.default(zip, sandbox, modules, directories)
117+
promise = import(/* webpackChunkName: 'preact-zip' */ './preact-cli').then(
118+
generator => generator.default(zip, sandbox, modules, directories)
119119
);
120120
} else if (sandbox.template === svelte.name) {
121-
promise = import('./svelte').then(generator =>
122-
generator.default(zip, sandbox, modules, directories)
121+
promise = import(/* webpackChunkName: 'svelte-zip' */ './svelte').then(
122+
generator => generator.default(zip, sandbox, modules, directories)
123123
);
124124
}
125125

src/common/load-dynamic-polyfills.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function detectOpera() {
2424

2525
export default function requirePolyfills() {
2626
if (detectIE() || detectOpera()) {
27-
return import('babel-polyfill');
27+
return import(/* webpackChunkName: 'polyfills' */ 'babel-polyfill');
2828
}
2929

3030
return Promise.resolve();

0 commit comments

Comments
 (0)