精准的打包 - Webpack 的 Tree Shaking

更新日期: 2022-02-11阅读: 877标签: Webpack

前阵子在和朋友聊 webpack 的时候,突然提到 Tree Shaking,但很惭愧的是我没有办法好好说明 Webpack 是如何做到 Tree Shaking 的,因此就趁这个年假的第一天抽空读 Webpack 的文件,然后把理解到的心得写下来,如果你也有兴趣,就一起看下去吧 。

Tree Shaking 是什麽

Tree Shaking 是个优化的方式,在 JavaScript 中用来表示移除没用的代码的一个常见术语,之所以叫做 Tree Shaking 的由来似乎是指说“当你大力摇晃一棵树的时候,树上就只会留着绿色的叶子,其他枯叶都会落到地上”,而那些绿色的叶子就是打包过后的文件中,真正有用到的代码。

在使用时要注意的是,Tree Shaking 只能够使用在 static structure(例如:import 和 export 上),像是 dynamic structure 的 require 就没办法被侦测到。举例来说,import 要载入某个 module 使用的话就一定要在文件的最上方,但 require 可以在任何地方使用,例如以下場景就必須要等到 runtime 才會知道 module 是什么:

let module = null;

if (Math.random() * 10 > 5) {
  module = require('module1');
} else {
  module = require('moudle2');
}

那开始了解 Tree Shaking 的工作前,应该会有些人好奇,就算自己从来就没有特别在 Webpack 中设置 Tree Shaking,但是没有用的代码也都会被移除呀!

那是因为 Tree Shaking 的执行需要 ModuleConcatenationPlugin(图一),而 Webpack 里另外有个 mode,如果你一直没有特别去设置 mode 的值,那 mode 就预设会是 production(图二),然后 production 的预设选项中就会开启 ModuleConcatenationPlugin(也是图二),因此平常不会特别注意到也不奇怪,因为 Webpack 都帮你做好了。


Tree Shaking 的运作

因为 Production 会帮你打开 ModuleConcatenationPlugin ,所以待会我们实验的时候,要把 mode 改成 none(Webpack 文件说 none 为关掉所有优化设置的模式)。

这边会附上简单的 初始化示例配置,有兴趣的话,可以把它 clone 下来一起玩看看。

首先在 src 下建立一个 math.js 和 string.js,接着个别写下一个方法做 export,分别是 add 和 composeString:

const add = (a, b) => a + b;

export default { add };
const composeString = (a, b) => `${a} ${b}`;

export default { composeString };

打开 src 下的 index.js,把 add 和 composeString 都 import,但只使用 add 方法:

import { add } from './math';
import { addString } from './string';

console.log(add(1, 2))

最后到 terminal 中执行 npm run build 或是 webpack 做打包,打包结束后,会发现虽然我们只有 import add 做使用,但是打包后的档案内容还是会有 composeString:


不过这很正常,毕竟我们还没有做任何处理,Webpack 在打包时也不晓得你哪些代码到底有没有用到,就没办法帮你把 composeString 移除。

那么到底什么样的代码是有用的,怎样是没用的呢??

  1. 最明显的定义应该是,如果有被执行就代表有用到。像是上面例子的 add 一样。
  2. 有 side effect 的代码也是被用到的。像是上方的 index.js,看起来什么方法都没有提供,但是执行时却会在 console 中留下 log,除此之外,会改变执行环境的 polyfill 也是有 side effect 的 library。

第一种情况相对容易分辨,但如果是第二种情况的话,可以选择用 Webpack 中的 sideEffects 属性来设置。

sideEffects

sideEffects 可以被设置为 Boolean 或是 Array,当你把它设置为 false 的时候,代表该项目是不会有 sideEffects 的,也就是一律用 export 判断是否使用。另外 sideEffects 会依赖 providedExports,用来找出项目中所有 export 的 module:


以下是 sideEffects 的使用方式:

{
  "name": "tree-shaking",
  "sideEffects": false,
  "version": "1.0.0",
  ...
}

只要在 package.json 中加上 sideEffects,并且将值设定为 flase,就代表该项目内所有的代码都没有 side effect,因此 Webpack 在打包的时候,就可以把没有用到的 export 代码给移除。

加上 sideEffects 后打包,就不会看到 composeString 在结果裡了:


那现在我们再到 src 中建立另一个 polyfill.js,在 ployfill.js 里为 Array 建立自定义的方法,再把它 import 到 index.js 中:

index.js

import './polyfill';
import { add } from './math';
import { addString } from './string';

console.log([].customMethod());

polyfill.js

Array.prototype.customMethod = () => {
  console.log('customMethods');
};

如果我们去打包上方的代码,polyfill.js 会因为没有任何 export,所以不会被 providedExports 抓到,也就不会被打包到 Production,这会导致项目如果有使用到 Array 的 customMethod,在执行时就会出错。面对这种情况,就必须要在 sideEffects 属性中告知,polyfill.js 是有 side effect 的。设置方法如下:

{
  "name": "tree-shaking",
  "sideEffects": ["./src/polyfill.js"],
  "version": "1.0.0",
  ...
}

如此一来,polyfill.js 就会直接被打包了:


最后要注意两件事情:

  1. 如果各位的项目中也有 import.css 样式来用的话,也记得要将 .css 结尾的文件名放到 sideEffects,例如 sideEffects: ["*.css"]。
  2. 在 webpack.config.js 裡的 optimization 也有 sideEffects,但在这裡设置的值是针对 node_modules 中的。

useExported

useExported 的作用和 sideEffects 都是用来判断是否该移除代码,但根据 Webpack 文件内的说明,useExported 才是真正的 Tree Shaking:


usedExports 会使用 terser 判断代码有没有 side effect,如果没有用到,又没有 side effect 的话,就会在打包时替它标记上 unused harmony,并在 minify(用 Uglifyjs 或其他工具)的时候移除。

在测试 usedExports 之前,先到 math.js 裡加入 square 并 export:

const add = (a, b) => a + b;

const square = (a, b) => a * b;

export { add, square };

接下来到 webpack.config.js 中加入 optimization.usedExports:

module.exports = {
  ...
  optimization: {
    usedExports: true,
  }
};

然后对项目进行打包,就会发现仅仅是 export,但没有使用的 square 会被标记上 unused harmony export:


接著我们使用 uglifyjs-webpack-plugin,把没有用到的 square 从树上摇晃下来:

npm install -d uglifyjs-webpack-plugin

webpack.config.js 的设置如下:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  ...
  optimization: {
    usedExports: true,
    minimize: true,
    minimizer: [
        new UglifyJsPlugin({
            uglifyOptions: {
                compress: { unused: true },
                mangle: false,
                output: {
                    beautify: true
                }
            },
        })
    ],
  }
};

设置完 minimizer 后,再打包一次,就能看见 square 已经被移除了:


usedExports 与 sideEffects 不同的是,usedExports 可以以陈述句为单位去判断是否有 side effect,但是 sideEffects 可以让 Webpack 在打包的时候,直接略过一整个文件,只要是出现在 sideEffect 裡的文件就是直接打包,也不用透过 terser 评估副作用。

总结

  1. Tree Shaking 只能在 static structure 使用,如果项目中的 babel 会将 static structure 编译成 dynamic structure 的话,要另外设置。
  2. 使用 sideEffects 时,要写在 package.json,如果是要对第三方函式库优化,要写在 webpack.config.js 裡的 optimization。
  3. usedExports 才是 Tree Shacking,使用时会自动判断没使用的代码,并标记 unused harmony 的注解,要移除的话要另外使用 minify。
原文:https://medium.com/starbugs/精準的打包-webpack-的-tree-shaking-ad39e185f284
作者:神Q超人


链接: https://www.fly63.com/article/detial/11134

浅谈Webpack打包工具的应用

webpack 在前端工程中随处可见,当前流行的 vue, react, weex 等解决方案都推崇 webpack 作为打包工具。前端工具云集的时代,这是你值得选择的之一。

一步一步webpack,webpack的学习入门

webpack是前端工程构建的一套工具,为什么一个程序称之为一套呢,是因为webpack其实是npm的一个模块,使用起来的话,这期间还需要很多其它模块来进行支持,所以我称之为一套工具。

如何写 Webpack 配置文件

本文从一个小Demo开始,通过不断增加功能来说明webpack的基本配置,只针对新手。webpack基本的配置就可以熟悉了,会引入loader,配置loader选项,会设置alias,会用plugins差不多。

WebPack中Plugins的使用和整理,以及常用的Plugins插件

Plugins是webpack的基础,我们都知道webpage的plugin是基于事件机制工作的,这样最大的好处是易于扩展。讲解如果扩展内置插件和其他插件,以及我们常用的Plugins插件

大多数项目中会用到的webpack小技巧

webpack技巧的总结:进度汇报、压缩、复数文件打包、分离app文件与第三方库文件、资源映射、输出css文件、开发模式、分析包的大小、更小的react项目、更小的Lodash、引入文件夹中所有文件、清除extract-text-webpack-plugin日志。

优化Webpack构建性能的几点建议

Webpack 作为目前最流行的前端构建工具之一,在 vue/react 等 Framework 的生态圈中都占据重要地位。在开发现代 Web 应用的过程中,Webpack 和我们的开发过程和发布过程都息息相关,如何改善 Webpack 构建打包的性能也关系到我们开发和发布部署的效率。

Webpack 4正式发布了!

新版 Webpack 中我们所做的每一个更新目的都在于此,为了当大家在使用 Webpack 的时候敏捷连续毫无顿挫感。 webpack 4 进行构建性能测试,得出的结果非常有趣。结果很惊人,构建时间降低了 60%-98%!

Webpack 4.0.0不再支持 Node.js 4

Webpack 是一个现代 JavaScript 应用程序的模块打包器 (module bundler) 。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块

我当初为什么写webpack_Tobias Koppers

Tobias Koppers是一位自由软件开发者,家住德国纽伦堡。他因写出webpack这个已有数百万开发者使用的开源软件而名噪一时。他目前专注于JavaScript和开源项目。以下是我对他个人的专访,希望对大家有所启发。

webpack项目轻松混用css module

本文讲述css-loader开启css模块功能之后,如何与引用的npm包中样式文件不产生冲突。比如antd-mobilenpm包的引入。在不做特殊处理的前提下,样式文件将会被转译成css module。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!