Tree-shaking 最早由打包工具 Rollup 提出

DCE 作用于模块内(webpack 的 DCE 通过 UglifyJS 完成),而 Tree-shaking 则是在打包的时候通过模块之间的信息打包必须的代码。

Webpack 从 2 开始也支持 Tree-shaking,对于一个模块,没有被使用过的引入代码并不会被打包

DCE

  • AST 对 JS 代码进行语法分析后得出的语法树 (Abstract Syntax Tree)。AST语法树可以把一段 JS 代码的每一个语句都转化为树中的一个节点。

  • DCE Dead Code Elimination [ɪˌlɪmɪˈneɪʃn],在保持代码运行结果不变的前提下,去除无用的代码。这样的好处是:

    • 减少程序体积

    • 减少程序执行时间

    • 便于将来对程序架构进行优化

  • 而所谓 Dead Code 主要包括:

    • 程序中没有执行的代码 (如不可能进入的分支,return 之后的语句等)

    • 导致 dead variable 的代码(写入变量之后不再读取的代码)

tree shaking 是 rollup 作者首先提出的。相比于排除不使用的代码,tree shaking 其实是找出使用的代码。

tree shaking

可以先回顾下《再唠叨JS模块化加载之CommonJS、AMD、CMD、ES6 》

  • CommonJS 的设计过于灵活,对静态分析不友好。

  • ES6 module 则有诸多限制:比如说只能在文件的顶部 import(CommonJS 的 require 语法允许在文件的任意位置调用),export { ... } 语法保证了导出的变量不会是 getter/setter 之类奇怪的东西(这个 block 不是一个 Object),变量也不能被重新绑定。以上种种设计可以让分析器一定程度上判断出导入和导出变量的关系,让这个插件的实现成为了可能。

基于ES6的静态引用,tree shaking 通过扫描所有 ES6 的export,找出被import 的内容并添加到最终代码中。 webpack 的实现是把所有import 标记为有使用/无使用两种,在后续压缩时进行区别处理。

根本原理则是作用域分析。在编译器领域,还有超级多各种高大上的静态分析方法(比如说数据流分析),但是对于 ES 来说,他们实现的难度太大。据我所知,现在还没有针对 JS 的,能在生产环境能用的,基于数据流分析的优化器。这也是为啥现在这些打包器还不能去除没有用到的类成员方法(class method)webpack tree shaking 只处理顶层内容,例如类和对象内部都不会再被分别处理

所谓作用域分析,就是可以分析出代码里面变量所属的作用域以及他们之间的引用关系。有了这些信息,就可以推导出导出变量和导入变量之间的引用关系

而对于 webpack 来说,webpack 可以通过 entry 和 module 之间的调用得知对于一个 module 来说,哪个变量是会被使用到的。就如同上文的例子 :我的插件可以从 webpack 得知 file1.js 的导出变量 one 被使用了。我的插件通过分析出模块中的作用域,遍历引用到的作用域,找到真正需要 import 的变量,比如说 isNumber,然后再把结果返回 webpack。

  • 使用 ES6 Module:不仅是项目本身,引入的库最好也是 es 版本,比如用 lodash-es 代替 lodash。另外注意 TypeScript 和 Babel 的配置是否会把代码编译成非 es module 版本。

  • 最纯函数调用使用 PURE 注释:由于无法判断副作用,所以对于导出的函数调用最好使用 PURE 注释,不过一般来说有相关的 babel 插件自动添加。

合理模块设计才是减少代码体积的关键

启用tree shaking

首先源码必须遵循 ES6 的模块规范 (import&export),如果是 CommonJS 规范 (require) 则无法使用。

在编写支持 tree-shaking 的代码时,导入方式非常重要。你应该避免将整个库导入到单个 JavaScript 对象中。当你这样做时,你是在告诉 Webpack 你需要整个库, Webpack 就不会摇它

以流行的库 Lodash 为例。一次导入整个库是一个很大的错误,但是导入单个的模块要好得多。当然,Lodash 还需要其他的步骤来做 tree-shaking,但这是个很好的起点。

// 全部导入 (不支持 tree-shaking)
import _ from 'lodash';
// 具名导入(支持 tree-shaking)
import { debounce } from 'lodash';
// 直接导入具体的模块 (支持 tree-shaking)
import debounce from 'lodash/lib/debounce';

webpack 3 和 4 默认支持,webpack2需要特别配置

webpack2

根据 Webpack 官网的提示,webpack2 支持 tree-shaking,需要修改配置文件,指定 babel 处理 js 文件时不要将 ES6 模块转成 CommonJS 模块,具体做法就是:

在 .babelrc 设置 babel-preset-es2015 的 modules 为 fasle,表示不对 ES6 模块进行处理。

// .babelrc
{
    "presets": [
        ["es2015", {"modules": false}]
    ]
}

webpack 负责对代码进行标记,把import&export标记为 3 类:

  1. 所有import标记为/* harmony import */

  2. 被使用过的export标记为/* harmony export ([type]) */,其中[type]和 webpack 内部有关,可能是binding, immutable等等。

  3. 没被使用过的export标记为/* unused harmony export [FuncName] */,其中 [FuncName]为export的方法名称

之后在 Uglifyjs (或者其他类似的工具) 步骤进行代码精简,把没用的都删除。

webpack tree shaking副作用

pure_funcs

webpack.config.js 增加参数pure_funcs,告诉webpack 那些函数是没有副作用的,你可以放心删除:

plugins: [
  new UglifyJSPlugin({
    uglifyOptions: {
      compress: {
          pure_funcs: ['Math.floor']
      }
    }
  })
],

Math.floor这类全局方法不会重命名,才会生效。因此适用性不算太强。

package.json 的 sideEffects

webpack 4 在 package.json 新增了一个配置项叫做sideEffects, 值为false表示整个包都没有副作用;或者是一个数组列出有副作用的模块。详细的例子可以查看 webpack 官方提供的例子

{
  "name": "your-project",
  "sideEffects": false}

这种方式是通过 package.json 的 "sideEffects" 属性来实现的。

concatenateModule 压缩输出

webpack 4  `mode = 'production'

使用 -p(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。

把本来“每个模块包裹在一个闭包里”的情况,优化成“所有模块都包裹在同一个闭包里”的情况。本身对于代码缩小体积有很大的提升,这里也能侧面解决副作用的问题。

参考文章:

Webpack 4 Tree Shaking 终极优化指南 https://juejin.im/post/6844903998634328072

Tree Shaking in Webpack https://juejin.im/post/5c58df43e51d457ffc1bd065

浅谈 ES 模块和 Webpack Tree-shaking https://zhuanlan.zhihu.com/p/43844419

转载本站文章《webpack原理(2):ES6 module在Webpack中如何Tree-shaking构建》,
请注明出处:https://www.zhoulujun.cn/html/tools/Bundler/webpackTheory/8504.html

webpack原理(2):ES6 module在Webpack中如何Tree-shaking构建的更多相关文章

  1. Webpack 中的 Tree Shaking

    Tree Shaking Tree shaking 用于描述移除JavaScript上下文中的未引用代码(dead-code). 为了更方便地理解tree shaking,我们可以将应用程序想象成一棵 ...

  2. 深入 CommonJs 与 ES6 Module

    目前主流的模块规范 UMD CommonJs es6 module umd 模块(通用模块) (function (global, factory) { typeof exports === 'obj ...

  3. es6 module + webpack

    其实在之前本人就看了 es6 里面的一部分内容,当然是阮一峰大神的 ECMAScript 6 入门. 最近闲来无事又来看下,其中 Module 的语法 这章时候,用里面代码跑的时候,理所当然的报错 S ...

  4. webpack原理与实战

    webpack是一个js打包工具,不一个完整的前端构建工具.它的流行得益于模块化和单页应用的流行.webpack提供扩展机制,在庞大的社区支持下各种场景基本它都可找到解决方案.本文的目的是教会你用we ...

  5. webpack原理探究 && 打包优化

    在做vue项目和react项目时,都用到了webpack.webpack帮助我们很好地提高了工作效率,但是一直以来没有对其原理进行探究,略有遗憾. 因为使用一个工具,能够深入了解其原理才能更好地使用. ...

  6. webpack原理类型问题

    1.webpack底层原理 (实现一个webpack) 步骤:1.拿到入口文件的代码并读出来转化为js对象(抽象语法术parser)2.拿到所有模块的依赖 ‘./message.js’,放进数组中 引 ...

  7. Webpack 原理浅析

    作者: 凹凸曼 - 风魔小次郎 背景 Webpack 迭代到4.x版本后,其源码已经十分庞大,对各种开发场景进行了高度抽象,阅读成本也愈发昂贵.但是为了了解其内部的工作原理,让我们尝试从一个最简单的 ...

  8. webpack+react+redux+es6开发模式

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  9. [webpack] 配置react+es6开发环境

    写在前面 每次开新项目都要重新安装需要的包,简单记录一下. 以下仅包含最简单的功能: 编译react 编译es6 打包src中入口文件index.js至dist webpack配置react+es6开 ...

  10. webpack使用tree shaking的问题。及关于UglifyJs不支持ES6的解决方案。

    webpack: plugins:[ new webpack.optimize.UglifyJsPlugin({ compress:{warning:true} }) ] 是的,一些dead code ...

随机推荐

  1. RLChina2022公开课-博弈论

    纯博弈: 单纯的动机组合,离散的集合 混合博弈: 加入了概率论,以百分比的概率执行不同的的动机.,概率分布 零和博弈.合作博弈.协同博弈 扩展博弈和非完美信息 扩展博弈.贝叶斯博弈 纳什均衡 任何一位 ...

  2. EhCache使用详细介绍

    http://hi.baidu.com/yjl_zzh/item/18e6518397cdd1d9d1f8cdfb 2.EhCache的使用注意点    当用Hibernate的方式修改表数据(sav ...

  3. 3.1 IDA Pro编写IDC脚本入门

    IDA Pro内置的IDC脚本语言是一种灵活的.C语言风格的脚本语言,旨在帮助逆向工程师更轻松地进行反汇编和静态分析.IDC脚本语言支持变量.表达式.循环.分支.函数等C语言中的常见语法结构,并且还提 ...

  4. 一款.NET开源的小巧、智能、免费的Windows内存清理工具 - WinMemoryCleaner

    前言 我们在使用Windows系统的时候经常会遇到一些程序不会释放已分配的内存,从而导致电脑变得缓慢.今天给大家推荐一款.NET开源的小巧.智能.免费的Windows内存清理工具:WinMemoryC ...

  5. Cadence SPB 22.1 -- 原理图的电器元件放置03Day

    1.新增原理图:"SCHEMATIC"-->"New Page" 2.元器件放置 ①.添加原理图库 ②.放置元器件 选择对应元件库,再选择需要放置的元件, ...

  6. 【ASP.NET Core】MVC过滤器:常见用法

    前面老周给大伙伴们演示了过滤器的运行流程,大伙只需要知道下面知识点即可: 1.过滤器分为授权过滤.资源访问过滤.操作方法(Action)过滤.结果过滤.异常过滤.终结点过滤.上一次咱们没有说异常过滤和 ...

  7. MySQL中IN()按照指定列指定规则排序

    现在我有这么一个需求,我需要通过IN(id1,id2,......)查询id字段,并且id字段按照IN()中的顺序排序 例如:IN(5,1,2,4) ===> 查询出来的结果也应该为 5,1,2 ...

  8. crictl命令

    containerd提供了ctr命令行用于镜像管理容器,但功能比较简单 所以一般会用k8s提供的crictl命令. 该命令的特点是:只要符合K8S的CRI接口的,都可以使用. 另外一点就是,cricr ...

  9. selenium滚动加载数据解决方案

    有些网站时一直滚动就会加载新数据的,在selenium中解决方法: def loaddata_by_scroll(self, driver): js = 'return document.body.s ...

  10. 华企盾DSC编辑文件不加密常见问题

    1.先查看客户端日志主进程是否是加密进程.日志中是不是勾选智能半透明.加密类型是否有添加 2.用procmon监控保存的文件找出writefile的进程是否有添加,进程树是否有父进程,加密类型是否正确 ...