tree shaking功能及使用原理详细解析

目录
  • 前言
  • 准备工作
  • 生产环境配置tree-shaking
  • 开发环境观察tree-shaking
  • tree-shaking的步骤
  • sideEffects副作用
  • commonjs能不能tree-shaking
  • 写在最后

前言

前端在做性能优化的时候,其中一种有效的方式就是减少包体积的大小。而减少包体积大小的其中一种有效的方式就是 tree-shaking,翻译成中文就是摇树。这个词非常形象,当果树结果后,如果用力摇树,那些成熟了但是还挂在树上的果子就会掉下来,减轻树的负担,因为果子已经成熟了,没有必要在呆在树上了。类比到程序里面,就是在打包的时候把“死代码”删除掉,而“死代码”就是定义了,但是没有使用的代码。本文会对 tree-shaking 这个功能做一个详细的解析。

准备工作

为了更好的观察效果,需要初始化一个demo项目,该项目会使用最新的 webpack5。

创建 package.json

npm init -y

安装 webpack 和 webpack-cli

npm install -D webpack webpack-cli

在项目根目录下创建一个 webpack.config.js,导出一个空对象就可以,一会会在里面添加配置

module.exports = {
}

创建 src 文件夹,里面创建 helper.js 文件,添加两个函数

export function a () {
  console.log('我是a')
}
export function b () {
  console.log('我是b')
}

再在 src 文件夹下创建 index.js,引入刚才创建的函数 a,并且写几段“死代码”

import { a } from './helper.js'
a()
if (false) {
  console.log('永远不会执行')
}
const unused = '定义了没有用到'

这里只引入了 a 函数并且执行,方法 b 并没有引入。if 中是 false,所以 console.log 不会执行,而 unused 变量定义了但是并没有使用,它们都是所谓的“死代码”。

生产环境配置tree-shaking

准备工作做完之后,我们就要开始 tree-shaking 了,由于使用的是 webpack5,所以配置非常简单,在 webpack.config.js 配置一下环境就可以了

module.exports = {
  mode: 'production'
}

此时运行

npx webpack

打开 dist 文件夹中的 main.js 观察结果

可以看到 webpack 已经帮你做了极致的优化,只留下了有用的代码,并且通过静态分析,把引入模块的步骤都做了。也就是说在生产环境,可能你自己都不知道 webpack 已经帮你 tree-shaking 了。

开发环境观察tree-shaking

刚才的过程有点像猪八戒吃人参果,还没尝到味道就结束了。根本看不出是什么删除的,所以我们要用开发环境一步一步的观察。

在 webpack.config.js 中把环境修改为开发环境。devtool 修改为 source-map,这和 tree-shaking 配置无关,主要是为了便于观察,因为默认的 eval 模式会让代码在一行。最后就是最重要的配置了:usedExports,这个属性这个会告诉 webpack 去收集 exportimport 的变量在程序中是否用到,并以注释的形式标记出来。

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  optimization: {
    usedExports: true
  }
}

运行 npx webpack 打包,然后在 dist 文件夹中的 main.js 从第11行开始查看

虽然声明了 ab ,但是通过静态分析后,只导出了 a ,并且添加了注释 /* unused harmony export b */,意思是 b 并没有用到

再从79行开始看

先运行了函数 a,然后 if 中所有的逻辑都被删除掉了,但是定义的 unused 还在

此时已经完成了一半工作,标记没有用到的代码。而另一半工作就是删除带有标记的代码,此时就要使用压缩工具了,继续修改 webpack.config.js 的配置

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  optimization: {
    minimize: true, // 增加了这一行
    usedExports: true
  }
}

minimize: true,表示使用压缩,webpack5 默认的压缩工具是 taser,它会在压缩的同时检测到 /* unused harmony export xxx */ 这种注释就会将对应的代码删除掉

运行打包命令后,生成的文件因为压缩的缘故变成了一行,格式化后查看 b 函数真的没有了,也就是说我们 tree-shaking 成功了

tree-shaking的步骤

  • 依赖收集:webpack在打包时,会将每个模块中的导出语句格式化后保存在一个数组中,并最终存储在ModuleGraph,它记录了模块间的依赖关系
  • 标记:遍历所有模块,如果用到了收集来的导出的变量,将会打上已使用的标记
  • 删除:通过默认的压缩工具 taser 的 taser-webpack-plugin,对于没有打标记的导出,就会被删除掉

sideEffects副作用

webpack 怎么会知道哪些模块没有用到,哪些模块用到了呢?

  • 场景1:引入并且使用
import { a } from './helper.js'
a()

这种情况刚才已经验证过了 a 依然存在,因为它被使用过了

  • 场景2:引入不使用
import { a } from './helper.js'

这种情况 a 虽然被引入,但是没有使用,所以和 b 函数一样,也被删除掉了,打包结果变成了一个空文件

  • 场景3:引入js文件

这种情况通常用于在全局加 polyfill,为了方便观察,在 src 目录下新建 polyfill.js

window.a = '全局a'

在 index.js 中引入

import './polyfill'

运行打包 npx webpack,打开 dist/main.js 观察结果

虽然只是引入,没有运行,但是也被保留下来了

其实场景1和场景2的结果都没有争议,但是场景3就不同了,它引入一个有副作用的模块,而这个模块到底应不应该保留呢?如果我想把它 tree-shaking 掉应该怎么配置呢?这时候就需要 sideEffects 配置了,我们先配置再讲怎么用,在 package.json (对,就是这个文件)中增加一行

"sideEffects": false,

运行打包 npx webpack,打开 dist 文件夹 cat main.js 观察结果结果,又变成了一个空文件,说明被 tree-shaking 掉了

这个配置是什么意思呢?它有三种属性可选

  • true:所有文件都有副作用,不可以 tree-shaking
  • false:所有文件没有副作用,可以 tree-shaking
  • 数组:只有数组中的文件有副作用,其它的都可以 tree-shaking ,例如你可以这么写
"sideEffects": [
    "./src/a.js",
    "./src/*.global.js
]

commonjs能不能tree-shaking

答案是 webpack4 不能,webpack5 添加了部分支持。因为 tree-shaking 依赖于 ES2015 模块系统中的静态结构特性,也就是在编译时而非运行时就已经知道哪些模块被用到了而哪些没有用到,但是 commonjs 却不一定可以,例如:

let myModlue = null
if (Math.random() > 0.5) {
    myModlue = require('a')
} else {
    myModlue = require('b')
}

请问此时哪个模块用到了,哪个模块没用到?连人都不能判断,电脑就更不能判断了,但是如果是确定的引用关系呢?

在 src 下增加 helper-cjs.js 文件

exports.a = () => console.log('我是a')
exports.b = () => console.log('我是b')

在 index.js 中引入

import { a } from './helper-cjs.js'
a()

运行打包并观察结果

和 esmodule 一样,无关的代码也被 tree-shaking 掉了

写在最后

本文带你详细了解了 webpack5 的 tree-shaking 使用以及原理,有问题的话欢迎在评论区进行讨论。不出意外的话这是我今天发的最后一篇文章了。各大互联网公司应该也已经封版不再上线新功能,归家有期,团圆有盼。马上就是春节了,愿故乡的暖意驱散过去一年的事与愿违。愿来年日子常新与胜意,又是一年好四季~

我们其它相关文章!

(0)

相关推荐

  • 浅谈Webpack4 Tree Shaking 终极优化指南

    几个月前,我的任务是将我们组的 Vue.js 项目构建配置升级到 Webpack 4.我们的主要目标之一是利用 tree-shaking 的优势,即 Webpack 去掉了实际上并没有使用的代码来减少包的大小.现在,tree-shaking 的好处将根据你的代码库而有所不同.由于我们的几个架构决策,我们从公司内部的其他库中提取了大量代码,而我们只使用了其中的一小部分. 我写这篇文章是因为恰当地优化 Webpack 并不简单.一开始我以为这是一种简单的魔法,但后来我花了一个月的时间在网上搜索我遇到

  • JS 加载性能Tree Shaking优化详解

    目录 正文 什么是 Tree Shaking 寻找 Tree Shaking 的机会 防止 Babel 将 ES6 模块转换为 CommonJS 模块 留意 side effects 只导入你需要的 更复杂的情况 总结 正文 随着 web 应用复杂性增加,JS 代码文件的大小也在不断的攀升,截住 2021年9月,在 httparchive 上有统计显示——在移动设备上 JS 传输大小大约为 447 KB,桌面端 JS 传输大小大约为 495 KB,注意这仅仅是在网络中传输的 JS 文件大小,JS

  • tree shaking对打包体积优化及作用

    目录 背景 有啥用? 实践 前置准备 打包 sideEffects 副作用 sideEffects的使用 优化体积 背景 大家平时在查 webpack构建体积优化 ,可能都会查到 tree-shaking 这个东西,很多人看到这个东西,就会把它背下来,用来应付以后面试官可能会问到的情况. 但是,又有多少人去真的了解一下 tree-shaking 呢?自己去实践一下看 tree-shaking 到底起了哪些作用?对于我们的打包体积的优化又有多少呢? 有啥用? Tree Shaking中文含义是摇树

  • Tree Shaking实现方法指南

    目录 正文 方式一:JavaScript模拟 方式二:利用AST实现 正文 当使用JavaScript框架或库时,代码中可能会存在许多未使用的函数和变量,这些未使用的代码会使应用程序的文件大小变大,从而影响应用程序的性能.Tree shaking可以解决这个问题,它可以通过检测和删除未使用的代码来减小文件大小并提高应用程序性能. 接下来我们将通过两种方式实现Tree shaking 方式一:JavaScript模拟 1.首先,你需要使用ES6模块来导出和导入代码.ES6模块可以静态分析,从而使T

  • Tree-Shaking 机制快速掌握

    目录 写在前面 直奔主题 最后 写在前面 最近在读霍老师的<Vue.js设计与实现>,感觉收获很多,由于霍老师是官方Vue维护成员,会从很通俗易懂的角度去讲Vue的实现细节.而不是按照源码死讲解,很不错,推荐给大伙! 直奔主题 Tree-Shaking 的本质其实就是消除无用代码也就是dead code,减小打包后文件,不太清楚dead code概念的不用担心,下面会讲到.Tree-Shaking是打包构建工具常用的优化手段.在我们日常的开发最常使用的,可能就是ESM的使用,会触发默认的Tre

  • Flutter 分页功能表格控件详细解析

    前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析. PaginatedDataTable PaginatedDataTable是一个带分页功能的DataTable,生成一批数据,项目中此一般通过服务器获取,定义model类: class User { User(this.name, this.age, this.sex); final String name; final int age; final String sex; } 生成数据: List<User> _d

  • vue.js diff算法原理详细解析

    目录 diff算法的概念 虚拟Dom h函数 diff对比规则 patch patchVnode updateChildren 总结 diff算法的概念 diff算法可以看作是一种对比算法,对比的对象是新旧虚拟Dom.顾名思义,diff算法可以找到新旧虚拟Dom之间的差异,但diff算法中其实并不是只有对比虚拟Dom,还有根据对比后的结果更新真实Dom. 虚拟Dom 上面的概念我们提到了虚拟Dom,相信大家对这个名词并不陌生,下面为大家解释一下虚拟Dom的概念,以及diff算法中为什么要对比虚拟

  • golang切片原理详细解析

    目录 切片的解析 切片的初始化 字面量初始化 make初始化 切片的截取 切片的复制 切片的扩容 总结 切片的解析 当我们的代码敲下[]时,便会被go编译器解析为抽象语法树上的切片节点, 被初始化为切片表达式SliceType: // go/src/cmd/compile/internal/syntax/parser.go // TypeSpec = identifier [ TypeParams ] [ "=" ] Type . func (p *parser) typeDecl(g

  • Golang Mutex 原理详细解析

    目录 前言 Lock 单协程加锁 加锁被阻塞 Unlock 无协程阻塞下的解锁 解锁并唤醒协程 自旋 什么是自旋 自旋条件 自旋的优势 自旋的问题 Mutex 的模式 Normal 模式 Starving 模式 Woken 状态 前言 互斥锁是在并发程序中对共享资源进行访问控制的主要手段.对此 Go 语言提供了简单易用的 Mutex.Mutex 和 Goroutine 合作紧密,概念容易混淆,一定注意要区分各自的概念. Mutex 是一个结构体,对外提供 Lock()和Unlock()两个方法,

  • Tomcat中的catalina.bat原理详细解析

    前言 本文主要给大家详细解析了关于Tomcat中catalina.bat原理的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. tomcat 的真正启动是在 catalina.bat 设置并启动的.startup.bat 只是找到catalina.bat 然后执行catalina.bat 来启动tomat的.下面我们来分析下catalina.bat 验证CATALINA_HOME 环境变量 验证CATALINA_HOME 设置是否正确,如果不正确,重新设置CATALIN

  • Tomcat中的startup.bat原理详细解析

    前言 在刚开始接触计算机,一开始就是win2000,所以对批处理脚本命令都不会.平时启TOMCAT都是鼠标双击startup.bat了,很少看过里面写的是什么,也借学习TOMCAT的机会学习一下批处理的常用命令,不求都记住,但求以后再见到批处理命令能看的懂,说的出是干什么的.本文主要给大家介绍了关于Tomcat中startup.bat原理的相关内容,下面话不多说了,来一起看看详细的介绍吧. startup.bat 解析 验证CATALINA_HOME 环境变量是否设置,如果没有设置则通过CATA

  • Android 操作系统获取Root权限 原理详细解析

    android root权限破解分析 许多机友新购来的Android机器没有破解过Root权限,无法使用一些需要高权限的软件,以及进行一些高权限的操作,其实破解手机Root权限是比较简单及安全的,破解Root权限的原理就是在手机的/system/bin/或/system/xbin/目录下放置一个可执行文件"su",这是一个二进制文件,相当于电脑上的exe文件,仅仅在系统中置入这个"su"文件是不会给手机的软件或硬件造成任何故障. 下面的代码是android系统原版的

  • C++多态的实现及原理详细解析

    1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数.2. 存在虚函数的类都有一个一维的虚函数表叫做虚表.类的对象有一个指向虚表开始的虚指针.虚表是和类对应的,虚表指针是和对象对应的.3. 多态性是一个接口多种实现,是面向对象的核心.分为类的多态性和函数的多态性.4. 多态用虚函数来实现,结合动态绑定.5. 纯虚函数是虚函数再加上= 0.6. 抽象类是指包括至少一个纯虚函数的类. 纯虚函数:virtual void breathe()=0:即抽象类!必须在子类实现这个函数!

随机推荐