Tree Shaking in webpack

Daybrush (Younkue Choi)
NAVER FE Platform
Published in
6 min readOct 1, 2018

I organized the experience gained by applying Tree Shaking to egjs InfiniteGrid components.

The environment is webpack 3.x, 4.x | babel-loader 8.x | babel 7.x.

npm install babel-loader @babel/core @babel/preset-env webpack

Tree Shaking?

Tree Shaking is a way to reduce size by removing unused code.

Referencing a particular library will increase the size of the final bundle by the size of the referenced library. However, not all code in a particular library is used.

  • As shown above, APP uses 2 of C. If so, do you need to include C’s 1 and 3 in your code?
  • In addition, 2 of C refers to 3 of B, 3 of B refers to 3 of A. If so, do we need to include B’s 1, 2 A’s 1, 2 in the code?

Tree Shaking is handled by Bundler(webpack, rollup) and UglifyJS.

  • The role of the Bundler is to remove the export with the comment that it is not used for unused modules.
    (Rollup and webpack4(sideEffects enabled) remove unused modules directly.)
  • UglifyJS will be removed as a dead code if the export is removed via the Bundler.

This is the screenshot I built by referring to InfiniteGrid in React components.

  • Before Tree Shaking
  • After Tree Shaking

What would be a good idea to apply Tree Shaking?

A case in which the module is detached and the user uses only specific modules

  • InfiniteGrid offers 5 layouts, and you do not need to use the other four because you only use one of them.
  • react-infinitegrid does not use 2/5 of the InfiniteGrid.
  • view360 is also available for only one of the two viewers, and Axes likewise provides four inputs and does not use three if only one is used.

How to apply Tree Shaking?

You have to set it up on the side that provides the library. (However, the results may vary depending on your bundle.)

It should consist of export default and export, not require, not module.export (s).

the static structure of ES2015 module syntax, i.e. import and export

Use the module field in package.json.

"main": "dist/infinitegrid.js",
"module": "dist/esm/",

In the .babelrc preset, specify “modules”: false.

If modules is set to false, import and export do not change to require and module.exports. (Tree Shaking Condition)

  • Before .babelrc
{
“presets”: [
[
@babel/preset-env”,
{
“loose”: true
}
]
]
}
  • After .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"loose": true
}
]
]
}

Cases where Tree Shaking does not apply

It may be included in the code even if it is not used. I do not use it but the bundler thinks it can affect other code. It is said that Side Effect occurred in this case.

1.If you use global functions (Object, Math, String, RegExp)

export const isMobile = /mobi|ios|android/.test(agent);

2. If you changes and returns a member variable in the function execution code

3. If you use static class properties (babel)

https://babeljs.io/docs/en/next/babel-plugin-proposal-class-properties.html
The static method does not have side effects.

So I created my own babel plugin and solved it. (babel-plugin-no-side-effect-class-properties)

4. If you are using a class (babel <7)

Beginning with babel 7, the class will be labeled as /*#__PURE__*/.

Side effects do not occur even if you use a prototype because the /*#__PURE__*/ label is considered an area where side effects do not occur.
https://babeljs.io/blog/2017/09/12/planning-for-7.0#pure-annotation-in-specific-transforms-for-minifiers

In versions prior to babel 7, it is available as a plugin.
https://www.npmjs.com/package/babel-plugin-annotate-pure-calls

5. If you do not use functions that use imported libraries (webpack 3)

If “B” is not used, “B” is removed, but “A” is not removed when side effect is displayed because “A” is in “B”.

6. If the imported library was exported to “export default …” again (webpack 3)

  • If you use “export default …”, referenced code will be added even if you do not use the module.
  • You can do this by changing “export default …” to “export {… as default}”.

How to prevent code that causes side effects

This can be a side effect, but it does not actually affect the code.

  • If your code is confusing that it does not affect other code or the outside world, specify in package.json that there is no side effect. (webpack4)
{
// no side effects
"sideEffects": false,
// partial side effects
"sideEffects": ["./dist/....", "./dist/...."]
}

There is a way to attach the /*#__PURE__*/ label directly.

  • When you attach the /*#__PURE__*/ label, it is judged that the side effect does not occur.

How to Force Side Effects

Some elements, such as VERSION, must be included unconditionally.

Create a file and use module.exports instead of export default to avoid Tree Shaking.

module.exports = "#__VERSION__#";

How to apply in the typescript environment

If you transpile with tsc, Class is labeled as /**@class */.

UglifyJS does not support /**@class*/ label so you need to manually attach the /*#__PURE__*/ label.

"scripts": {
"replace": "replace-in-file '/** @class */' '/*#__PURE__*/' dist/*/*.js"
}
$ npm run replace

Result

I built the project using only one of the 13 modules.

import {GridLayout} from “@egjs/infinitegrid”;GridLayout();
  • When referring to an es3-style bundle, Tree Shaking is not applied, so it is increased by the size of the bundle.
  • If you use webpack3 to refer to the es module, only those modules that are not referenced at all are removed.
  • If the imported library was exported to “export default …” again in webpack3, change it to “export {default as …}” and apply Tree Shaking.
  • In webpack4, all references are removed unless they are referenced. However, side effects can not be eliminated.
  • If you use webpack4 and set “sideEffects”: false in package.json, the side effect will be removed if you do not use the module.

As shown in the screenshot above, Tree Shaking removes unused code from 51kb to 4kb. We hope that our experience will be a bit of a reference for those who are struggling to reduce size in WebPack environments.

--

--