treeShaking(树摇Tree Shaking)

原理

本质上是 babel 通过核心 babylon 将 ES6 代码转换成AST抽象语法树,然后插件遍历语法树找出类似 import {Button} from 'element-ui'这样的语句,进行转换,最后重新生成代码。

tree-shaking 的消除原理是依赖于ES6的模块特性。

  • 只顶层语句:import 只能出现在模块顶层,无法嵌套在条件语句中。

  • 静态字符串 模块名必须是字符串常量(如 import x from './x'),不能是动态表达式。

  • 不可变绑定: import 的绑定是只读的(immutable),不会被运行时修改。

这些特性使得依赖关系在编译时就能被确定,与运行时状态无关,因此可以可靠地进行静态分析并消除未使用的代码。相比之下,CommonJS 的 require 支持动态加载(例如 require(变量)),需要在运行时才能确定依赖,无法通过静态分析优化。这也是 ES6 模块设计时的重要考量。

注意点

Tree Shaking 的局限性

Tree Shaking 并非万能,它有一些限制:

  1. 仅对函数有效

Tree Shaking 通常只能消除未使用的函数,而对类(class)的优化效果有限。原因在于类的副作用(如原型方法)较难静态分析。

function Menu() {
}

Menu.prototype.show = function() {
}

var a = 'Arr' + 'ay'
var b
if(a == 'Array') {
    b = Array
} else {
    b = Menu
}

b.prototype.unique = function() {
    // 将 array 中的重复元素去除
}

export default Menu;
  1. 副作用问题:

如果模块存在副作用(side effects),如修改全局变量或动态操作,Tree Shaking 可能无法安全移除代码。

css tree-shaking

下面介绍一下原理

整体思路是这样的,遍历所有的 css 文件中的 selector 选择器,然后去所有 js 代码中匹配,如果选择器没有在代码出现过,则认为该选择器是无用代码。

首先面临的问题是,如何优雅的遍历所有的选择器呢?难道要用正则表达式很苦逼的去匹配分割吗?

babel 是 js 世界的福星,其实 css 世界也有利器,那就是postCss。

PostCss 本质上是一个平台.

PostCSS 提供了一个解析器,它能够将 CSS 解析成AST抽象语法树。然后我们能写各种插件,对抽象语法树做处理,最终生成新的css文件,以达到对css进行精确修改的目的。

主要流程:

插件监听webapck编译完成事件,webpack编译完成之后,从 compilation 中找出所有的css文件和js文件

apply (compiler) {
    compiler.plugin('after-emit', (compilation, callback) => {

      let styleFiles = Object.keys(compilation.assets).filter(asset => {
        return /\.css$/.test(asset)
      })

      let jsFiles = Object.keys(compilation.assets).filter(asset => {
        return /\.(js|jsx)$/.test(asset)
      })
    })
     ....
}

将所有的 css 文件送至 postCSS处理, 找出无用代码

   let tasks = []
    styleFiles.forEach((filename) => {
        const source = compilation.assets[filename].source()
        let listOpts = {
          include: '',
          source: jsContents,  //传入全部js文件
          opts: this.options   //插件配置选项
        }
        tasks.push(postcss(treeShakingPlugin(listOpts)).process(source).then(result => {       
          let css = result.toString()  // postCss处理后的css AST  
          //替换webpack的编译产物compilation
          compilation.assets[filename] = {
            source: () => css,
            size: () => css.length
          }
          return result
        }))
    })

可以看到其实我只处理里 id选择器和class选择器,id和class相对来说副作用小,引起样式异常的可能性相对较小。判断css是否再js中出现过,是使用正则匹配。其实,后续还可以继续优化,比如对tag类的选择器,可以配置是否再html,jsx,template中出现过,如果出现过,没有出现过也可以认为是无用代码。

遇到的问题及解决

  1. 副作用导致无法消除:

一些模块可能包含副作用(如立即执行函数 IIFE),被标记为不可 Tree Shaking。 解决方法:

  • 在 package.json 中配置 "sideEffects": false,告诉构建工具该模块无副作用。

  • Webpack 中设置 { module: false },禁用某些模块的副作用推断。

  1. 动态逻辑干扰:

如上文示例中的条件语句,动态引用会导致 Tree Shaking 失效。

  • 解决方法:尽量将逻辑静态化,或通过工具(如 babel-plugin-transform-imports)优化导入。

最后更新于

这有帮助吗?