Tree Shaking:从原理到Vue3实践

什么是Tree Shaking?

Tree Shaking 是一种优化技术,主要用于减少 JavaScript 或 TypeScript项目中未使用的代码。其原理是通过静态分析并标记未被引用的模块、函数、变量等,将其从最终构建结果中去除掉,进而达到减小文件大小和提升项目性能的目的。

静态分析能力的提供方 ESM(ECMAScript Modules)

ESM的特点

ESM 使用 import 导入模块,使用 export 导出模块。

采用静态导入

1. import 语句应该位于代码文件的最顶层或者其他声明之前。

这样可以提高可读性并且使得依赖关系更加清晰和可预测。因为在运行时解析依赖关系时,需要先执行所有的 import 声明以确定加载哪些模块。

2. import 语句用于导入其他模块,并且要求导入的模块名必须是一个字符串常量。

这意味着,在编译时期就需要确定所需导入的模块路径,而不能使用动态或变量来指定导入的路径。这样做有助于使依赖关系更加明确和静态化。

3. 在一个模块中无法对从其他模块导入进来的变量进行重新赋值操作。

这种设计有助于避免意外修改外部引用,并提高代码的可维护性和安全性。

CommonJS规范为什么不可以使用?

CommonJS 使用 require() 导入模块,使用 module.exportsexports 导出模块。

采用动态导入

CommonJS 定义的模块化规范开发的项目中,通常无法直接使用 Tree Shaking。

这是因为 CommonJS 模块系统采用了动态导入(dynamic import)和运行时加载机制,与静态分析和优化相关的 Tree Shaking 技术不兼容。

前端生态 + Tree Shaking

Babel + Tree Shaking

Babel 默认会将ESM规范编译为CommonJs规范,如需避免此默认转换,在 .babelrc 文件中,可以添加以下配置:

相关配置项

js 复制代码
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
        //"modules": 'commonjs'  
      }
    ]
  ]
}

详细了解关于babel请查看博客:Babel 前端语言的巴别塔

Webpack + Tree Shaking

Wepack4.0以上版本在mode为production时,会自动开启Tree shaking

相关配置项

js 复制代码
const config = {
 mode: 'production',
 optimization: {
  usedExports: true,
  minimizer: [
   new TerserPlugin({...}) // 支持删除死代码的压缩器
  ]
 }
}

CSS + Tree Shaking

PostCSS 提供了一个解析器,它能够将 CSS 解析成 AST 抽象语法树,我们可以通过 PostCSS 插件对 CSS 对应的 AST 进行操作,达到 Tree Shaking 的目的。

实现思路

CSS 的 Tree Shaking 要在样式表中,找出没有被应用到选择器样式,进行删除。

思路步骤

  • 遍历所有 CSS 文件的选择器
  • 根据所有 CSS 文件的选择器,在 JavaScript 代码中进行选择器匹配
  • 如果没有匹配到,则删除对应选择器的样式代码

代码解析

  • 监听 Webpack compilation 完成阶段,从 compilation 中找到所有的 CSS 文件(对应源码):
ts 复制代码
//导出一个名为PurgeCSSPlugin 的默认类
export default class PurgeCSSPlugin {
//声明变量
  options: UserDefinedOptions;
  purgedStats: PurgedStats = {};
//构造函数用于初始化实例时传入参数
  constructor(options: UserDefinedOptions) {
    this.options = options;
  }
//接受一个参数compiler 并在编译过程中用compilation钩子来初始化插件
  apply(compiler: Compiler): void {
    compiler.hooks.compilation.tap(
      pluginName,
      this.initializePlugin.bind(this)
    );
  }

  //...

}

将所有的 CSS 文件交给 PostCss 处理(源码关键部分,对 CSS AST 应用规则):

js 复制代码
public walkThroughCSS(
    root: postcss.Root, // 参数:根节点对象(postcss.Root类型)
    selectors: ExtractorResultSets   // 参数:选择器结果集(ExtractorResultSets类型)
  ): void {
    root.walk((node) => {  // 遍历根节点下的所有子节点
      if (node.type === "rule") {   // 如果当前子节点是一个规则(选择器)块
        return this.evaluateRule(node, selectors); // 调用 evaluateRule 方法来处理该规则块并传入选择器结果集
      }
      if (node.type === "atrule") { // 如果当前子节点是一个 at-rule 块(如 @media、@keyframes 等)
        return this.evaluateAtRule(node);  // 调用 evaluateAtRule 方法来处理该 at-rule 块
      }
      if (node.type === "comment") {  // 如果当前子节点是一个注释块
        if (isIgnoreAnnotation(node, "start")) {  // 判断是否为开始忽略注释
          this.ignore = true;  // 设置 ignore 属性为 true,表示需要忽略接下来的样式内容
          node.remove();  // 移除开始忽略注释,即删除此行注释内容
        } else if (isIgnoreAnnotation(node, "end")) {  // 判断是否为结束忽略注释
          this.ignore = false;
          node.remove();  // 移除结束忽略注释,即删除此行注释内容
        }
      }
    });
  }

vue3中的Tree Shaking实践

Vue3中的Treeshaking主要通过以下两点实现:

源码级的Tree-shaking

Vue3源码采用ES module编写。

这可以确保编译时只引入实际使用的代码,没有引入无用模块。

比如通过按需引入 Composition API,而不是全量引入等。

编译阶段的静态提升

Vue3的编译器可以检测在模板、JSX中没有被使用的模块,直接舍弃不打包这些模块。

同时还会执行常量提升、代码inline等优化。

大幅减少模块引用。

优势

相比Vue2有以下优势:

  • 提升了运行效率,减少内存消耗;

  • 减少打包后大小,加快应用加载;

  • 依赖更精确,编译效率更高。

编译比较

假设有一个组件库my-lib,包含按钮、弹窗、表单等20个组件。

vue2导入:

js 复制代码
// 导入整个my-lib
import * as myLib from 'my-lib' 

// 注册2个组件
components: {
  'my-button': myLib.Button,
  'my-dialog': myLib.Dialog
}

打包后my-lib整个大小200kb,实际只用了2个组件。

vue3导入:

js 复制代码
// 按需导入
import {Button, Dialog} from 'my-lib'

components: {
  'my-button': Button,
  'my-dialog': Dialog 
}

打包后只包含用到的Button和Dialog代码,总大小仅为30kb。

结论及总结

打包体积比较

Vue2: 200kb Vue3: 30kb

在这个例子中,Vue3的Tree-shaking使得打包体积减少了约85%,只包含实际用到的代码,而不是打包整个库。

这展示了在同样代码条件下,通过Tree-shaking,Vue3可以明显减小打包体积,起到很好的优化效果。

相关推荐
程序媛小果10 分钟前
基于java+SpringBoot+Vue的桂林旅游景点导游平台设计与实现
java·vue.js·spring boot
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
man20171 小时前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
吖秧吖2 小时前
three.js 杂记
开发语言·前端·javascript
前端小超超2 小时前
vue3 ts项目结合vant4 复选框+气泡弹框实现一个类似Select样式的下拉选择功能
前端·javascript·vue.js