Webpack Tree-Shaking 原理与 TypeScript 配置实战

作者:函数茶馆

时间:2026-10-22

浏览:285

本文深入解析 Webpack 中 Tree-Shaking 的工作原理,指出其依赖于 ES6 模块的静态解析特性。通过对比 CommonJS 与 ES6 模块的差异,演示如何在 JavaScript 项目中启用并验证摇树优化。重点解决 TypeScript 编译后未能触发 Tree-Shaking 的问题,通过调整 tsconfig.json 目标为 ES6 实现完整的代码剔除。

文章目录

Tree-Shaking 原理与 ES6 模块基础

1.什么是tree-shaking

Webpack 2 引入的最重要特性之一是 Tree-Shaking。该技术最初源自 Rollup.js,如今已被 Webpack 2 采纳并实现类似功能。

Webpack 中 Tree-Shaking 的实现得益于 ES6 规范模块。前端模块化规范众多,包括 CommonJS、AMD 和 ES6 等。这里简要分析 CommonJS 与 ES6 模块的核心区别。

CommonJS 模块

CommonJS 模块规范在 Node.js 环境中广泛应用,其主要特性包括以下几点:

1.动态加载模块

CommonJS 与 ES6 的最大区别在于此。CommonJS 模块支持动态加载,从而轻松实现懒加载,优化用户体验。

2.加载整个模块

在 CommonJS 模块中,导出的是整个模块对象。

3.每个模块皆为对象

CommonJS 将每个模块视为一个对象。

4.值拷贝

CommonJS 模块的输出类似于函数的值传递,即进行值的拷贝。

ES6 模块

1.静态解析

即在解析阶段就确定输出的模块,因此 ES6 模块的 import 语句通常位于被引入文件的开头。

2.模块不是对象

在 ES6 中,每个模块不被视为一个对象。

3.加载的不是整个模块

在 ES6 模块中,经常可以看到一个模块包含多个 export 导出。

4.模块的引用

ES6 模块导出的并非模块的值拷贝,而是对模块的引用。

结合 ES6 与 CommonJS 模块的区别可知,ES6 的特点是静态解析,而 CommonJS 模块的特点是动态解析。因此,借助 ES6 模块的静态解析能力,Tree-Shaking 的实现才成为可能。 在 Webpack 中,Tree-Shaking 指的是按需加载,即未被引用的模块不会被打包进来,从而减少包体积,缩短应用加载时间,为用户提供更佳的体验。

JavaScript 项目中的 Tree-Shaking 配置

2.怎么使用tree-shaking

那么,具体如何启用 Tree-Shaking 呢? Webpack 默认对使用 ES6 规范编写的模块启用 Tree-Shaking。具体含义如下例所示。

首先展示我的 demo 目录结构如下:

├─dist └─index.html ├─node_modules └─... ├─src ├─scripts ├─assets ├─webpack.config.js └─package.json

dist 目录用于存放打包后的代码。

src 目录相反,用于存放源文件。

src 中的 scripts 目录用于存放 JS 脚本文件,assets 用于存放静态资源文件。

执行以下几条命令后,开始我们的 Tree-Shaking 之旅。


npm install --save-dev webpack webpack-dev-server
webpack.config.js
const webpack = require('webpack')
const path = require('path')
module.exports = {
  entry:'./src/scripts/main.js',
  output:{
    path:path.resolve(__dirname,'dist/'),
    filename:'main.bundle.js'
  },
  plugins:[
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer:{
    port:4200,
    contentBase:path.resolve(__dirname,'dist/'),
    historyApiFallback:true,
    hot:true
  }
}

接下来是 main.js,直接引入了 sayHello。

import { sayHello } from './greeter.ts';

sayHello();

相应的 main.js 依赖 greeter.js。


export function sayHello(){
  alert('hello')
}

export function sayWorld(){
  alert('world')
}

在 dist 目录下有一个 index.html,用于引入打包后的 bundle。


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <script type="text/javascript" src="./main.bundle.js"></script>
</body>
</html>

以上为整个 demo 的代码,接下来直接执行 Webpack 打包试试看。

去掉打包后冗长的代码,只看 chunk 传参的部分:

[ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__person__ = __webpack_require__(1); Object(__WEBPACK_IMPORTED_MODULE_0__person__["a /* sayHello */])(); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return sayHello; }); /* unused harmony export sayWorld */ function sayHello(){ alert('hello'); } function sayWorld(){ alert('world'); } /***/ }) /******/ ]

我们关注这一行。


/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return sayHello; });

实际上只 return 了一个 sayHello。

因此,我们现在只需要压缩整个 JS 代码,就能把未引用的 sayWorld 剔除。

键入以下命令进行压缩。

webpack --optimize-minimize

由于压缩后的代码只有一行了,我们移步尾部:


function(e,n,r){"use strict";function t(){alert("hello")}r.d(n,"a",function(){return t})}]);

可以看到 sayWorld 函数已经被成功剔除。

我们启动 webpack-dev-server。

webpack-dev-server

在浏览器中输入。

http://localhost:4200

Tree-Shaking 原理与 ES6 模块基础

每次都需要在命令行里输入参数,岂不是很麻烦,还有没有其他更好的办法呢?

(1)我们可以把这串命令放入 package.json 的 scripts 字段,然后通过 npm start 来自动执行。

(2)其实 --optimize-minimize 的底层实现是一个插件 UglifyJsPlugin,因此,我们可以直接在 webpack.config.js 里配置它。

在 webpack.config.js 里配置插件。


const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry:'./src/scripts/main.js',
  output:{
    filename:'main.bundle.js',
    path:path.join(__dirname,'dist')
  },
  plugins:[
    new webpack.optimize.UglifyJsPlugin(), // <----------- 压缩js
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer:{
    port:4200,
    historyApiFallback:true,
    hot:true,
    contentBase:path.join(__dirname,"dist/")
  }
}

然后我们 webpack 打包。

即看到同样的效果。


function(e,n,r){"use strict";function t(){alert("hello")}r.d(n,"a",function(){return t})}]);

在 Tree-Shaking 触发打包后,仅仅是撇开了模块的引用,但还是要结合压缩工具来进行,这才是完整的一次 Tree-Shaking。

那如果是 TypeScript 该怎么使用 Tree-Shaking 呢?

TypeScript 环境下的 Tree-Shaking 解决方案

3.如何在typescript里使用tree-shaking

要在 Webpack 里使用 TS,首先我们必须安装 tsc。

npm install --save-dev typescript

之后我们需要解析 TS 文件的 loader。

npm install --save-dev ts-loader

然后在 webpack.config.js 进行配置。


const webpack = require('webpack')
const path = require('path')

module.exports = {
  entry:'./src/scripts/main.ts',
  output:{
    path:path.resolve(__dirname,'dist/'),
    filename:'main.bundle.js'
  },
  module:{
    rules:[
      {
        test:/\.ts$/,
        use:['ts-loader']
      }
    ]
  },
  plugins:[
    new webpack.optimize.UglifyJsPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer:{
    port:4200,
    contentBase:path.resolve(__dirname,'dist/'),
    historyApiFallback:true,
    hot:true
  }
}

献上我的两份文件 main.ts , greeter.ts (这两份文件除了后缀名基本没有改动)。

main.ts


import { sayHello } from './greeter.ts';

sayHello();

greeter.ts


export var sayHello = function(){
  alert('hello')
}

export var sayWorld = function(){
  alert('world')
}

之后我们需要做的是,创建一个 tsconfig.json 的配置文件供 tsc 解析,这时,坑来了。

下面是我的 tsconfig.json 文件。


{
  "compilerOptions":{
    "target":"es5",
    "sourceMap":true
  },
  "exclude":[
    "./node_modules"
  ]
}

好像没有什么不对。

接着我们 webpack。

看下打包压缩后的代码的最后一部分:


"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sayHello=function(){alert("hello")},n.sayWorld=function(){alert("world")}}]);

sayWorld 居然还是存在!!!怎么回事,为什么没有被触发 Tree-Shaking 优化?

这是因为 tsc 编译后的代码为 ES5 ,而正因如此,tsc 默认使用了 CommonJS 的规范来加载模块,因此并没有触发 Tree-Shaking,那我们要怎么做?

修改一下 tsconfig.json,把 target 改为 ES6 即可!


{
  "compilerOptions":{
    "target":"es6",
    "sourceMap":true
  },
  "exclude":[
    "./node_modules"
  ]
}

再次打包。

看一下打包后的 bundle。


function(e,n,r){"use strict";r.d(n,"a",function(){return t});var t=function({alert("hello")}}]);

果然是触发了 Tree-Shaking。

开启 webpack-dev-server。

webpack-dev-server

可以看到成功打印 hello。

Tree-Shaking 原理与 ES6 模块基础

以上就是我对 Webpack Tree-Shaking 的总结,希望对大家的学习有所帮助

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章