The current hot vue.js 3.0 framework from the source code, performance and syntax API three major aspects of optimization. In terms of performance optimization, source volume optimization is mainly reflected in removing some unpopular features (such as filter and inline-template) and introducing tree-shaking to reduce packaging volume. Since Rollup coined the term, there has been a lot of tree-shaking when it comes to packaging performance optimization, so it’s important to understand tree-shaking.
After reading this article, you can Get answers to the following questions,
- What is tree-shaking? The history of tree-shaking?
- What does tree-shaking work for?
- What is Dead Code?
- Module mechanics for ECMAScript 6?
- Tree-shaking in Webpack?
The story begins: Rich Harris and his Rollup
Rich Harris, author of the well-known module packer Rollup.js, first mentioned tree-shaking in his blog tree-shaking Versus Dead Code Elimination in December 2015 Since then, the official version of Webpack 2 has built in support for the ECMAScript 2015 module, adding tree-shaking support, and before that, Google’s developer tool, Closure Compiler, has been doing something similar.
I have worked (albeit sporadically of late, admittedly) on a tool called Rollup, which bundles together JavaScript modules. One of its features is tree-shaking, by which I mean that it only includes the bits of code your bundle actually needs to run.
Rich Harris talked about tree-shaking for Dead Code elimination. This is a common compiler optimization technique, which simply means eliminating Dead code. So what is Dead Code?
Dead code
Dead code, also known as Dead code, useless code, its category mainly includes the following two points:
- Unreachable Code that will not be run
- Dead Variables that only affect the results of irrelevant programs
Let’s try to understand it with some snippets of JavaScript code.
First, the code that won’t be run is well understood, such as the code after a function return statement,
function foo() {
return 'foo';
var bar = 'bar'; // The function has already returned, and the assignment statement is never executed
}
Copy the code
Or blocks of false value conditions that won’t execute,
if(0) {
// The code inside the conditional block is never executed
}
Copy the code
Dead Variables are common as unused Variables,
function add(a, b) {
let c = 1; // unused variable can be used as dead code
return a + b;
}
Copy the code
Note that modules can also be Dead code if they are not used, such as the bar module below,
// foo.js
function foo() {
console.log('foo');
}
export default foo;
// bar.js
function bar() {
console.log('bar');
}
export default bar;
// index.js
import foo from './foo.js';
import bar from './bar.js';
foo();
// The entry file references the module bar, but does not use it. The module bar can also be regarded as dead code
Copy the code
Dead Code we know, what is tree-shaking?
In traditional static programming language compilers, the compiler can determine that some Code does not affect the output at all. We can use the compiler to remove Dead Code from the AST (abstract syntax tree), but JavaScript is a dynamic language, and the compiler cannot help us with Dead Code elimination. We need to implement Dead Code Elimination ourselves.
Tree-shaking is a kind of Dead Code Elimination. It takes advantage of the module mechanism of ECMAScript 6 and focuses on the elimination of useless modules. It eliminates modules that are referenced but not used.
To better understand tree-shaking, we need to understand ES6’s modular mechanics.
ECMAScript 6 module
The modularization of JavaScript has gone through a long development process. We know that JavaScript did not have the concept of modules at the beginning. At first, we could only use IIFE to minimize the pollution to the global environment. Later, AMD specification represented by RequireJS and CMD specification represented by sea-.js appeared in the community for the browser side, CommonJS specification also appeared on the server side, and then JavaScript native introduced ES Module. Replace community solutions with browser-side consistent modular solutions.
ES Module can now also be used on servers, and Node.js implements ES Module support in v12.0.0.
For those who do not know the basic syntax of ES Module, please refer to the following article. Next, we will mainly understand its mechanism.
- Introduction to ES Modules
- ECMAScript 6 Getting started, Module syntax
- JavaScript modules | MDN
Contrast is a very effective means of understanding knowledge. We understand the Module mechanism of ES Module by comparing the difference between ES Module and CommonJS. The difference is mainly reflected in the output and execution of the Module.
- ES Module prints references to values, while CommonJS prints copies of values
- ES Modules are executed at compile time, whereas CommonJS modules are loaded at run time
So the biggest feature of ES Module is that it is static. At compile time, you can determine Module dependencies, as well as input and output values. What does this mean? It means that module dependencies are deterministic, independent of runtime state, and can be reliably analyzed statically. It is on this basis that tree-shaking is possible. This is why both Rollup and Webpack 2 use the ES6 Module syntax to support tree-shaking.
Now that you know how it works, let’s take a look at how to implement tree-shaking.
Tree Shaking
With static module analysis, the general idea for tree-shaking implementation is to use the static structure of ES6 module syntax to find and tag modules that are not introduced during compilation, and then use compression tools like Uglip-js to remove the unused code during compression.
Is that so? Let’s take Webpack as an example to verify the above ideas.
Initialize the project to install the latest WebPack and webpack-CLI, which as of this writing is v5.35.1,
$ mkdir tree-shaking && cd tree-shaking
$ npm init -y
$ npm i webpack webpack-cli -D
Copy the code
Add a simple configuration file and a Math module, where we reference only the Cube function of the Math module,
// webpack.config.js
const path = require("path");
module.exports = {
mode: "development".entry: "./src/index.js".output: {
filename: "bundle.js".path: path.resolve(__dirname, "dist"),},optimization: {
// Enable usedExports to collect Dead Code information
usedExports: true,}};// src/math.js
export function square(x) {
return x * x;
}
export function cube(x) {
var a, b, c; // Three unused variables are introduced as Dead code
return x * x * x;
}
// src/index.js
import { cube } from "./math.js";
function component() {
var element = document.createElement("pre");
element.innerHTML = "5 cubed is equal to " + cube(5);
return element;
}
document.body.appendChild(component());
Copy the code
Run the package command to locate the bundle.js packaged math module code,
/ * * * / "./src/math.js":
/ *! * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/math.js ***! \ * * * * * * * * * * * * * * * * * * * * * /
/ * * * / ((__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {
eval("/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"cube\": () => (/* binding */ cube)\n/* harmony export */ }); \n/* unused harmony export square */\nfunction square(x) {\r\n return x * x; \r\n}\r\n\r\nfunction cube(x) {\r\n var a, b, c; \r\n return x * x * x; \r\n}\r\n\n\n//# sourceURL=webpack://tree-shaking/./src/math.js?");
/ * * * / })
Copy the code
To make it easier to read, let’s remove the line break from eval and format it a little bit.
/* harmony export */
__webpack_require__.d(__webpack_exports__, {
/* harmony export */
cube: () = > /* binding */ cube /* harmony export */});/* unused harmony export square */
function square(x) {
return x * x;
}
function cube(x) {
var a, b, c;
return x * x * x;
}
Copy the code
The __webpack_exports__ function is exported as cube, and the unused square function is marked with /* unused harmony export square */. However, the square function declaration and the unused variable declarations a, B, and c in the Cube function are still packaged. This confirms our previous assumption that WebPack can find modules that are not imported through tree-shaking and does not remove Dead code.
Then we switch mode to Production to enable uglip-js compression, and run the package command again,
(() = > {
"use strict";
var e, t;
document.body.appendChild(
(((t = document.createElement("pre")).innerHTML =
"5 cubed is equal to " + (e = 5) * e * e), t) ); }) ();Copy the code
As expected, uglip-JS compresses and removes Dead Code, including,
- unused
square
function - No variables used
a, b, c
We can also test this by introducing Uglip-JS alone,
// math.js
function cube(x) {
var a, b, c;
return x * x * x;
}
// minify.js
const fs = require("fs");
const UglifyJS = require("uglify-js");
const code = fs.readFileSync("math.js"."utf-8");
const result = UglifyJS.minify(code, {
compress: {
dead_code: true.// dead_code defaults to true}});console.log(result.code); // function cube(n){return n*n*n}
Copy the code
summary
We start with tree-shaking as an implementation of Dead Code Elimination, and then we take a look at the evolution of JavaScript modularization. It is the static structure of ES Modules that makes a module-level tree-shaking implementation possible. Finally, taking webpack as an example, combining ugliffe – JS compression tool, the realization principle of tree-shaking is explained.
Refer to the link
- What is tree shaking? 🌲
- Tree-shaking versus dead code elimination
- Tree-Shaking Performance Optimization Practices – Principles
- UglifyJS | JavaScript parser, minifier, compressor and beautifier toolkit
- Webpack Documentation | Tree Shaking
Write in the last
This article first in my blog, uneducated, unavoidably have mistakes, the article is wrong also hope not hesitate to correct!
If you have any questions or find errors, you can ask questions or correct errors in the corresponding issues
If you like or are inspired by it, welcome star and encourage the author
(after)