Skip to content
This repository has been archived by the owner on Aug 10, 2022. It is now read-only.

Commit

Permalink
Webpack guide updates for webpack 4 (#5798)
Browse files Browse the repository at this point in the history
* Update the “Decrease front-end size” section for webpack 4

* Fix the paragraph incorrectly merged into note + slightly improve wording

* Update the caching section for webpack 4

+ Update wording and improve formatting

* Rewrite the section on module concatenation + format “in webpack 4” phrases like in the caching section

* Update the monitoring section for webpack 4

* Improve formatting

* Fix the line length warnings where possible

* Minificator → minifier

* Replace InlineChunksPlugin with InlineSourcePlugin

* Proof-read the article

* Fix the plugin link

(Use an original repo instead of a fork)

* Fix the regular expression

`^` and `$` stopped working for some reason (tested on the training project)
  • Loading branch information
iamakulov authored and addyosmani committed Mar 23, 2018
1 parent acea6d9 commit acbbe31
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ project_path: /web/fundamentals/_project.yaml
book_path: /web/fundamentals/_book.yaml
description: How to use webpack to make your app as small as possible

{# wf_updated_on: 2018-02-24 #}
{# wf_updated_on: 2018-03-01 #}
{# wf_published_on: 2017-12-18 #}
{# wf_blink_components: N/A #}

Expand All @@ -13,11 +13,28 @@ description: How to use webpack to make your app as small as possible
One of the first things to do when you’re optimizing an application is to make it as small as
possible. Here’s how to do this with webpack.

Note: This page covers optimization strategies for webpack 3. A few parts
of it aren’t necessary or work slightly differently with webpack 4. We’re waiting
for the webpack ecosystem to stabilize, and then we’ll update this guide.
## Use the production mode (webpack 4 only) {: #use-the-production-mode }

## Enable minification
Webpack 4 introduced the new `mode` flag. You could set this flag to `'development'` or
`'production'` to hint webpack that you’re building the application for a specific environment:

// webpack.config.js
module.exports = {
mode: 'production',
};

Make sure to enable the `production` mode when you’re building your app for production.
This will make webpack apply optimizations like minification, removal of development-only code
in libraries, [and more](https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a).

### Further reading {: .hide-from-toc }

* [What specific things the `mode` flag configures](https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a)

## Enable minification {: #enable-minification }

Note: if you’re using [webpack 4 with the production mode](#enable-the-production-mode), the
bundle-level minification is already enabled. You’ll only need to enable loader-specific options.

Minification is when you compress the code by removing extra spaces, shortening variable names and
so on. Like this:
Expand All @@ -39,11 +56,10 @@ so on. Like this:
// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}

Webpack supports two ways to minify the code: _the UglifyJS plugin_ and _loader-specific options_.
They should be used simultaneously.
Webpack supports two ways to minify the code: _the bundle-level minification_ and
_loader-specific options_. They should be used simultaneously.

[The UglifyJS plugin](https://github.com/webpack-contrib/uglifyjs-webpack-plugin) works on the level
of the bundle – it compresses the bundle after compilation. Here’s how it works:
The bundle-level minification compresses the whole bundle after compilation. Here’s how it works:

<ol>
<li>
Expand Down Expand Up @@ -82,7 +98,7 @@ function render(data, target) {

<li>

The UglifyJS plugin minifies it into approximately the following:
A minifier compresses it into approximately the following:

<pre class="prettyprint">
// minified bundle.js (part of)
Expand All @@ -93,7 +109,14 @@ Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
</li>
</ol>

The plugin comes bundled with webpack. To enable it, add it to the `plugins` section of the config:
**In webpack 4,** the bundle-level minification is enabled automatically – both in the production
mode and without one. It uses [the UglifyJS minifier](https://github.com/mishoo/UglifyJS2)
under the hood. (If you ever need to disable minification, just use the development mode
or pass `false` to the `optimization.minimize` option.)

**In webpack 3,** you need to use [the UglifyJS plugin](https://github.com/webpack-contrib/uglifyjs-webpack-plugin)
directly. The plugin comes bundled with webpack; to enable it, add it to the `plugins`
section of the config:

// webpack.config.js
const webpack = require('webpack');
Expand All @@ -104,9 +127,17 @@ The plugin comes bundled with webpack. To enable it, add it to the `plugins` sec
],
};

Note: In webpack 3, the UglifyJS plugin can’t compile the ES2015+ (ES6+) code. This means
that if your code uses classes, arrow functions or other new language features,
and you don’t compile them into ES5, the plugin will throw an error. <br><br>
If you need to compile the new syntax, use the
[uglifyjs-webpack-plugin](https://github.com/webpack-contrib/uglifyjs-webpack-plugin) package. This
is the same plugin that’s bundled with webpack, but newer, and it’s able to compile the ES2015+
code.

The second way is loader-specific options ([what a loader
is](https://webpack.js.org/concepts/loaders/)). With loader options, you can compress things that
the UglifyJS plugin can’t minify. For example, when you import a CSS file with
the minifier can’t minify. For example, when you import a CSS file with
[`css-loader`](https://github.com/webpack-contrib/css-loader), the file is compiled into a string:

/* comments.css */
Expand All @@ -122,7 +153,7 @@ exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,<strong>".comment {\r\n color: black;\r\n}"</strong>,""]);
</pre>

UglifyJS can’t compress this code because it’s a string. To minify the file content, we need to
The minifier can’t compress this code because it’s a string. To minify the file content, we need to
configure the loader to do this:

<pre class="prettyprint">
Expand All @@ -142,13 +173,6 @@ module.exports = {
};
</pre>

Note: The UglifyJS plugin can’t compile the ES2015+ (ES6+) code. This means that if your code uses
classes, arrow functions or other new language features, and you don’t compile them into ES5, the
plugin will throw an error. <br><br> If you need to compile the new syntax, use the
[uglifyjs-webpack-plugin](https://github.com/webpack-contrib/uglifyjs-webpack-plugin) package. This
is the same plugin that’s bundled with webpack, but newer, and it’s able to compile the ES2015+
code.

### Further reading {: .hide-from-toc }

* [The UglifyJsPlugin docs](https://github.com/webpack-contrib/uglifyjs-webpack-plugin)
Expand All @@ -159,6 +183,9 @@ code.

## Specify `NODE_ENV=production`

Note: if you’re using [webpack 4 with the production mode](#enable-the-production-mode), the
`NODE_ENV=production` optimization is already enabled. Feel free to skip this section.

Another way to decrease the front-end size is to set the `NODE_ENV`
[environmental variable](https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them)
in your code to the value `production`.
Expand Down Expand Up @@ -194,10 +221,20 @@ React works similarly – it loads a development build that includes the warning
// …

Such checks and warnings are usually unnecessary in production, but they remain in the code and
increase the library size. Configure webpack to remove them with the
[`DefinePlugin`](https://webpack.js.org/plugins/define-plugin/):
increase the library size. **In webpack 4,** remove them by adding
the `optimization.nodeEnv: 'production'` option:

// webpack.config.js
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
nodeEnv: 'production',
minimize: true,
},
};

**In webpack 3,** use the [`DefinePlugin`](https://webpack.js.org/plugins/define-plugin/) instead:

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
Expand All @@ -209,13 +246,14 @@ increase the library size. Configure webpack to remove them with the
],
};

The `DefinePlugin` replaces all occurrences of a specified variable with a specific value. With the
Both the `optimization.nodeEnv` option and the `DefinePlugin` work the same way –
they replace all occurrences of `process.env.NODE_ENV` with the specified value. With the
config from above:

<ol>
<li>

The <code>DefinePlugin</code> will replace all occurrences of <code>process.env.NODE_ENV</code> with
Webpack will replace all occurrences of <code>process.env.NODE_ENV</code> with
<code>"production"</code>:

<pre class="prettyprint">
Expand All @@ -241,18 +279,11 @@ if (typeof val === 'string') {
</pre>

</li>
</ol>

Note: If you prefer to configure environment variables via CLI, take a look at the
[EnvironmentPlugin](https://webpack.js.org/plugins/environment-plugin/). It works like the
`DefinePlugin`, but reads the environment and replaces `process.env.` expressions automatically.

<ol start="2">
<li>

And then the <code>UglifyJsPlugin</code> will remove all such <code>if</code> branches – because
<code>"production" !== 'production'</code> is always false, and the plugin understands that the code
inside these branches will never execute:
And then <a href="#enable-minification">the minifier</a> will remove all such
<code>if</code> branches – because <code>"production" !== 'production'</code> is always false,
and the plugin understands that the code inside these branches will never execute:

<pre class="prettyprint">
// vue/dist/vue.runtime.esm.js
Expand All @@ -277,11 +308,6 @@ if (typeof val === 'string') {
</li>
</ol>

Note: You’re not required to use the `UglifyJsPlugin`. You can use any different minifier as soon as
it supports dead code removal (e.g., the [Babel Minify
plugin](https://github.com/webpack-contrib/babel-minify-webpack-plugin) or the [Google Closure
Compiler plugin](https://github.com/roman01la/webpack-closure-compiler)).

### Further reading {: .hide-from-toc }

* [What “environment variables” are](https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them)
Expand Down Expand Up @@ -336,7 +362,7 @@ separate export point in the bundle:

<li>

The <code>UglifyJsPlugin</code> removes the unused variable:
[The minifier](#enable-minification) removes the unused variable:

<pre class="prettyprint">
// bundle.js (part that corresponds to comments.js)
Expand All @@ -349,10 +375,10 @@ The <code>UglifyJsPlugin</code> removes the unused variable:
This works even with libraries if they are written with ES modules.

Note: In webpack, tree-shaking doesn’t work without a minifier. Webpack just removes export
statements for exports that aren’t used; it’s the `UglifyJsPlugin` that removes unused code.
statements for exports that aren’t used; it’s the minifier that removes unused code.
Therefore, if you compile the bundle without the minifier, it won’t get smaller. <br><br>
You aren’t required to use precisely this plugin though. Any minifier that supports
dead code removal
You aren’t required to use precisely webpack’s built-in minifier (`UglifyJsPlugin`) though.
Any minifier that supports dead code removal
(e.g. [Babel Minify plugin](https://github.com/webpack-contrib/babel-minify-webpack-plugin)
or [Google Closure Compiler plugin](https://github.com/roman01la/webpack-closure-compiler))
will do the trick.
Expand All @@ -363,7 +389,8 @@ If you use Babel with `babel-preset-env` or `babel-preset-es2015`, check the set
presets. By default, they transpile ES’ `import` and `export` to CommonJS’ `require` and
`module.exports`. [Pass the `{ modules: false }`
option](https://github.com/babel/babel/tree/master/experimental/babel-preset-env) to disable this.
<br><br>The same with TypeScript: remember to set `{ "compilerOptions": { "module": "es2015" } }` in your `tsconfig.json`.
<br><br>The same with TypeScript: remember to set `{ "compilerOptions": { "module": "es2015" } }`
in your `tsconfig.json`.

### Further reading {: .hide-from-toc }

Expand Down Expand Up @@ -495,11 +522,14 @@ files](https://github.com/moment/moment/tree/4caa268356434f3ae9b5041985d62a0e8c2
you don’t use Moment.js with multiple languages, these files will bloat the bundle without a
purpose.

All these dependencies can be easily optimized. We’ve collected optimization approaches in a GitHub
repo – [check it out](https://github.com/GoogleChromeLabs/webpack-libs-optimizations)!
All these dependencies can be easily optimized. We’ve collected optimization approaches in
a GitHub repo – [check it out](https://github.com/GoogleChromeLabs/webpack-libs-optimizations)!

## Enable module concatenation for ES modules (aka scope hoisting)

Note: if you’re using [webpack 4 with the production mode](#enable-the-production-mode),
module concatenation is already enabled. Feel free to skip this section.

When you are building a bundle, webpack is wrapping each module into a function:

// index.js
Expand Down Expand Up @@ -539,8 +569,8 @@ a size and performance overhead for each module.

Webpack 2 introduced support for ES modules which, unlike CommonJS and AMD modules, can be bundled
without wrapping each with a function. And webpack 3 made such bundling possible – with
[`ModuleConcatenationPlugin`](https://webpack.js.org/plugins/module-concatenation-plugin/). Here’s
what this plugin does:
[module concatenation](https://webpack.js.org/plugins/module-concatenation-plugin/). Here’s
what module concatenation does:

// index.js
import {render} from './comments.js';
Expand Down Expand Up @@ -574,12 +604,21 @@ what this plugin does:
})

See the difference? In the plain bundle, module 0 was requiring `render` from module 1. With
`ModuleConcatenationPlugin`, `require` is simply replaced with required function, and module 1 is
module concatenation, `require` is simply replaced with required function, and module 1 is
removed. The bundle has fewer modules – and less module overhead!

To enable this behavior, add `ModuleConcatenationPlugin` into the list of plugins:
To turn on this behavior, **in webpack 4**, enable the `optimization.concatenateModules` option:

// webpack.config.js
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true,
},
};

**In webpack 3,** use the `ModuleConcatenationPlugin`:

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
Expand Down Expand Up @@ -685,8 +724,9 @@ be excluded from the bundle. This is reasonable – webpack doesn’t know if `i

## Summing up

* Minimize your code with the `UglifyJsPlugin` and loader options
* Remove the development-only code with the `DefinePlugin`
* Enable the production mode if you use webpack 4
* Minimize your code with the bundle-level minifier and loader options
* Remove the development-only code by replacing `NODE_ENV` with `production`
* Use ES modules to enable tree shaking
* Compress images
* Apply dependency-specific optimizations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ project_path: /web/fundamentals/_project.yaml
book_path: /web/fundamentals/_book.yaml
description: What tools to use to keep track of and analyze the webpack bundle

{# wf_updated_on: 2018-02-15 #}
{# wf_updated_on: 2018-02-16 #}
{# wf_published_on: 2017-12-18 #}
{# wf_blink_components: N/A #}

Expand Down Expand Up @@ -195,8 +195,8 @@ You might want to dig deeper into the bundle to see what modules take space in i
<video src="./webpack-bundle-analyzer.mp4" alt="A screen recording of the webpack bundle analyzer
page" autoplay controls loop></video>
<figcaption>(Screen recording from <a
href="https://github.com/webpack-contrib/webpack-bundle-analyzer">github.com/webpack-contrib/webpack
-bundle-analyzer</a>)</figcaption>
href="https://github.com/webpack-contrib/webpack-bundle-analyzer">github.com/webpack-contrib/webpack-bundle-analyzer</a>)
</figcaption>
</figure>

webpack-bundle-analyzer scans the bundle and builds a visualization of what’s inside it. Use this
Expand Down Expand Up @@ -236,7 +236,8 @@ instead of React)? Do you use all the code it includes (e.g., Moment.js includes
[that are often not used and could be dropped](https://github.com/GoogleChromeLabs/webpack-libs-optimizations#moment))?

- **Duplicated dependencies.** Do you see the same library repeating in multiple files? (Use, e.g.,
the `CommonsChunkPlugin` to move it into a common file.) Or does the bundle have multiple versions
the `optimization.splitChunks.chunks` option – in webpack 4 – or the `CommonsChunkPlugin`
in webpack 3 – to move it into a common file.) Or does the bundle have multiple versions
of the same library?

- **Similar dependencies.** Are there similar libraries that do approximately the same job? (E.g.
Expand Down
Loading

0 comments on commit acbbe31

Please sign in to comment.