What is tree shaking

Tree shaking is something that is not always shaking.

In computing, tree shaking is a dead code elimination technique that is applied when optimizing code written in ECMAScript dialects like Dart, JavaScript, or TypeScript into a single bundle that is loaded by a web browser

In computers, tree shaking is a dead-code elimination technique used to optimize applications written in languages like Dart, JavaScript, or TypeScript that are loaded by web browsers as individual packages.

What is “dead code”? This is code that can’t be executed or used when the program is running. The classic example is if we use a library like LoDash, but we use less utils in the project, but the build tool usually packages the entire package into the resulting JS bundle. This is where tree shaking comes in handy.

Treeshaker originated in LISP in the 1990s, expressing the idea that all possible execution flows of a program can be represented as a function call tree, so that functions that have never been called can be eliminated by some technical means. So why is “tree shaking” only emerging in JavaScript in recent years?

The growth of Tree Shaking and the opportunities brought by the ES6 Module

We know that JavaScript is a dynamic language, and dead code elimination in dynamic languages is a harder problem than in static languages. But there were some good early attempts at this, as the algorithm was applied to JavaScript in Google Closure Tools in 2012, and then to the Dart language in the Dart 2JS compiler, also written by Google. In 2013, author Chris Buckett wrote in Dart in Action:

When the code is converted from Dart to JavaScript, the compiler performs a tree shake. In JavaScript, the entire library must be added even if only one function is needed, but due to tree jitter, the Dart derived JavaScript contains only the individual functions needed in the library.

Tree Shaking the next wave of popularity is thanks to the Rollup project developed in 2015 by Rich Harris, author of Rollup and Svelte.

With the emergence of ES6, the modular scheme in ES6 has become the standard of JS in the future, which also marks that JS has officially entered the era of modular programming. The ES6 Module is a Module mechanism for doing static analysis, which makes Tree Shaking’s technology indispensable for packaging tools. In fact, the current mainstream Tree Shaking technology relies on the import and export module mechanism in ES6, where a wrapper detects whether modules in your code are exported, imported, and used by JavaScript files.

Tree shaking in Webpack

The official release of Weback2 has started supporting ES6 module syntax (also known as harmony modules), which also includes dead code detection capabilities. The official release of webpack4 extends and enhances tree shaking technology.

sideEffects

How do I let Webpack know that the modules in your project or specified modules are ES6 modules, so that Webpack can safely eliminate Dead Code when building? One way to do this is to add the sideEffects property to package.json and set it to false to tell WebPack that the project is “pure” (pure ES6 module) and that it is safe to remove the unused export.

If we want to tell WebPack that some files have side effects and cannot be shaken away, we can specify an array, for example:

{
  "name": "your-project"."sideEffects": [
    "./src/some-side-effectful-file.js"]}Copy the code

To tell Webpack that these files cannot be optimized. Array mode supports relative path, absolute path and Glob pattern matching related files. B Files contained in arrays will not be affected by Tree shaking, as all imported files are affected by Tree shaking by default. This means that if you use something like CSS-Loader in your project and import a CSS file, you need to add it to the Side Effect list to avoid accidentally removing it in production mode:

{
  "name": "your-project"."sideEffects": [
    "./src/some-side-effectful-file.js"."*.css"]}Copy the code

You can also set sideEffects using the Module. rules configuration option, see the documentation module.rules.

usedExports

In addition to sideEffects, we can also prompt Webpack to do tree shaking optimization by configuring the usedExports attribute. Such as:

const path = require('path');

module.exports = {
  entry: './src/index.js'.output: {
    filename: 'bundle.js'.path: path.resolve(__dirname, 'dist'),},mode: 'development'.optimization: {
    usedExports: true,}};Copy the code

SideEffects and usedExports are two different optimizations, but sideEffects is more efficient because it allows skipping entire modules/files and entire file subtrees, making optimizations more efficient. UsedExports relies on Terser, a JavaScript parser, compression, and optimization toolkit for ES6+, to detect side effects in statements. It’s a JavaScript task and it’s not as straightforward as sideEffects.

Look at an example from the official website

While usedExports generally have no problem analyzing export functions, the React framework’s higher-order functions (HOC) can be problematic in this case.

Let’s look at an example:

import { Button } from '@shopify/polaris';
Copy the code

The pre-packaged version of the file looks like this:

import hoistStatics from 'hoist-non-react-statics';

function Button(_ref) {
  // ...
}

function merge() {
  var _final = {};

  for (var _len = arguments.length, objs = new Array(_len), _key = 0; _key < _len; _key++) {
    objs[_key] = arguments[_key];
  }

  for (var _i = 0, _objs = objs; _i < _objs.length; _i++) {
    var obj = _objs[_i];
    mergeRecursively(_final, obj);
  }

  return _final;
}

function withAppProvider() {
  return function addProvider(WrappedComponent) {
    var WithProvider =
    /*#__PURE__*/
    function (_React$Component{/ /...return WithProvider;
    }(Component);

    WithProvider.contextTypes = WrappedComponent.contextTypes ? merge(WrappedComponent.contextTypes, polarisAppProviderContextTypes) : polarisAppProviderContextTypes;
    var FinalComponent = hoistStatics(WithProvider, WrappedComponent);
    return FinalComponent;
  };
}

var ButtonThe $1 = withAppProvider()(Button);

export {
  // ...,
  ButtonThe $1
};
Copy the code

If Button is not being used, the tool can effectively remove export {Button$1} and leave all remaining code. But the question is, can the rest of the code be cleaned up or does it have side effects? It’s hard to say. In particular, withAppProvider()(Button). The withAppProvider is called, and the returned value is also called. Are there any side effects when you call merge or hoistStatics? When to WithProvider. ContextTypes (Setter? Assignment or when read WrappedComponent. ContextTypes (Getter), will there be any side effects?

Although Terser tries to solve the above problem, for the most part, it is uncertain. It’s not that Terser doesn’t work well because it can’t solve these problems, but because it’s hard to know for sure in a dynamic language like JavaScript.

We can help Terser by adding the /*#__PURE__*/ comment, which tells Terser that this call has no side effects and can be optimized using Tree shaking.

var Button$1 = /*#__PURE__*/ withAppProvider()(Button);
Copy the code

Such a tag would allow Terser to remove the code, but there might be some import issues that need to be evaluated because of the side effects they contain.

To better solve these problems, you can use the sideEffects property directly. Although its functionality is similar to /*#__PURE__*/, it works at the module level, not the code statement level. This property tells WebPack to skip the side effects analysis of a module marked as side-effect-free if it is not directly exported for use.

In an example of Shopify Polaris, the original modules are as follows:

index.js

import './configure';
export * from './types';
export * from './components';
Copy the code

components/index.js

export { default as Breadcrumbs } from './Breadcrumbs';
export { default as Button, buttonFrom, buttonsFrom, } from './Button';
export { default as ButtonGroup } from './ButtonGroup';
Copy the code

package.json

// ...
"sideEffects": [
  "**/*.css"."**/*.scss"."./esnext/index.js"."./esnext/configure.js"].// ...
Copy the code

The above optimization can be applied to other projects. For example, buttonFrom and buttonsFrom exported from button.js are also not used. The usedExports optimization preserves this code and Terser can pick out these statements from the bundle. Module merging will also be applied, so the four modules, plus the entry module (which may also have more dependencies) will be merged.

Mark the function call as side-effect-free

We can also tell Webpack that a function call has no side effects by using /*#__PURE__*/, and that the comment is usually placed before the function call. Such as:

/ *#__PURE__*/ add(55, 45);
Copy the code

Of course, the arguments passed into the function cannot be marked by the comments above, so each tag must be marked separately. If you want to clean up unused variables, which is considered dead code, WebPack has other configurations to do this optimization. For details, see Optimization.innergraph.

conclusion

From the development history of Tree Shaking to the specific use of Tree shaking in Webpack and some need to pay attention to the hole in a comprehensive explanation of the powerful Webpack Tree shaking technology. We concluded that if you want your project to take advantage of this technology, you need to pay attention to:

  • Use ES2015 module syntax (that is, import and export).
  • Make sure no compiler converts ES2015 module syntax in your project to CommonJS (this is the default behavior of the now common @babel/preset-env, see the documentation for details).
  • In your project’s package.json file, add the “sideEffects” property.
  • Use the mode configuration item for “Production” to enable more optimizations, including compressed code and tree shaking.

If you think of the application’s source code as a tree, the green leaves represent the actual source code used, that is, the leaves on the tree that are still alive. And the brown leaves represent dead code, the withered leaves on trees in autumn. To remove withered leaves from a tree, you need to shake the tree.

Reference

  • Webpack Tree Shaking
  • Wiki Tree Shaking
  • Tree-shaking versus dead code elimination