如何理解webpack打包中有关js的TreeShaking技术?
发布于 作者:苏南大叔 来源:程序如此灵动~

Tree Shaking
是一种通过消除未使用代码来优化JavaScript
包大小的技术。它依赖于ESM
模块的静态结构特性,可以在编译时确定哪些模块和函数是未使用的,并将其从最终的打包文件中移除。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:win10
,node@20.18.0
,webpack@5.98.0
。TreeShaking
可以想象成用力摇树,把树叶摇下来。就是说尽量减少没有用到的代码。很多打包工具都有TreeShaking
功能,不局限于webpack
。
仅 ESM 模块支持
两大模块加载方式:
commonjs
的require
,不支持TreeShaking
的,它只支持整体加载,不支持剪枝。ESM
(ES6
)的import
,支持TreeShaking
,所以目前来看,应用更广泛。
参考文章:
基础代码
首先,苏南大叔给出一个commonjs
的文件js-commonjs.js
:
module.exports.usedFunction = function () {
console.log("苏南大叔说:这是个被使用的函数");
}
module.exports.unusedFunction = function () {
console.log("苏南大叔说:这是一个不被使用的函数");
}
然后,作为对照。创建了一个包含两个导出函数的ESM
文件 js-esm.mjs
:
export function usedFunction2() {
console.log("苏南大叔说:这是个被使用的函数");
}
export function unusedFunction2() {
console.log("苏南大叔说:这是一个不被使用的函数");
}
入口文件 main.mjs
,只导入并使用 usedFunction()
和usedFunction2()
。
var js1 = require('./js-commonjs.js');
console.log(js1.usedFunction());
import {usedFunction2} from './js-esm.mjs';
console.log(usedFunction2());
创建一个 webpack.config.js
文件,并启用生产模式:
const path = require('path');
module.exports = {
mode: 'production',
entry: './main.mjs',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
webpack 打包结果
执行命令:
npm install --save-dev webpack webpack-cli
npx webpack
执行webpack
命令,在生产模式下,Webpack
会自动启用Tree Shaking
,并移除未使用的代码。得到的dist/bundle.js
代码如下:
(() => {
"use strict";
var o = require("./js-commonjs.js");
console.log(o.usedFunction());
console.log(void console.log("苏南大叔说:这是个被使用的函数"));
})();
结果非常明显。
commonjs
模块文件中的代码,依然独立存在。没有被打包进来,也没有被TreeShaking
。ESM
模块文件中的代码,被打包进来了。没有被使用到的函数,被TreeShaking
掉了。
browserify打包结果对比
根据这两篇文章:
执行命令:
browserify main.mjs -o dist/bundle2.js -t [ babelify --presets [ @babel/preset-env ] ]
得到的dist/bundle2.js
,代码如下:
(function () {
function r(e, n, t) {
function o(i, f) {
if (!n[i]) {
if (!e[i]) {
var c = "function" == typeof require && require;
if (!f && c) return c(i, !0);
if (u) return u(i, !0);
var a = new Error("Cannot find module '" + i + "'");
throw a.code = "MODULE_NOT_FOUND", a
}
var p = n[i] = { exports: {} };
e[i][0].call(p.exports, function (r) {
var n = e[i][4][r];
return o(n || r)
}, p, p.exports, r, e, n, t)
}
return n[i].exports
}
for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]);
return o
}
return r
})()({
1: [function (require, module, exports) {
"use strict";
module.exports.usedFunction = function () {
console.log("苏南大叔说:这是个被使用的函数");
};
module.exports.unusedFunction = function () {
console.log("苏南大叔说:这是一个不被使用的函数");
};
}, {}], 2: [function (require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.unusedFunction2 = unusedFunction2;
exports.usedFunction2 = usedFunction2;
function usedFunction2() {
console.log("苏南大叔说:这是个被使用的函数");
}
function unusedFunction2() {
console.log("苏南大叔说:这是一个不被使用的函数");
}
}, {}], 3: [function (require, module, exports) {
"use strict";
var _jsEsm = require("./js-esm.mjs");
var js1 = require('./js-commonjs.js');
console.log(js1.usedFunction());
console.log((0, _jsEsm.usedFunction2)());
}, { "./js-commonjs.js": 1, "./js-esm.mjs": 2 }]
}, {}, [3]);
所以,上述默认情况下。browserify
的结论是:
browerify
不但不支持treeshaking
。- 而且会无视
commonjs
还是esm
,统统打包到一起,所以代码量确实会增大很多。
结论
Tree Shaking
就是个概念,其实它早就在代码编写打包中使用了。它可以显著减少JavaScript
包的大小。通过使用支持Tree Shaking
的打包工具,就可以确保最终的打包文件只包含实际使用的代码,从而提高应用的性能。
更多苏南大叔的webpack
相关经验文章,请点击:


