如何理解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相关经验文章,请点击: