
Tree-Shaking性能优化实践 - 实践篇
上一篇文章 Tree-Shaking性能优化实践 - 原理篇 介绍了 tree-shaking 的原理,本文主要介绍 tree-shaking 的实践
linxi:Tree-Shaking性能优化实践 - 原理篇
三. tree-shaking实践
webpack2 发布,宣布支持tree-shaking,webpack 3发布,支持作用域提升,生成的bundle文件更小。 再没有升级webpack之前,增幻想我们的性能又要大幅提升了,对升级充满了期待。实际上事实是这样的
升级完之后,bundle文件大小并没有大幅减少,当时有较大的心理落差,然后去研究了为什么效果不理想,原因见 Tree-Shaking性能优化实践 - 原理篇 。
优化还是要继续的,虽然工具自带的tree-shaking不能去除太多无用代码,在去除无用代码这一方面也还是有可以做的事情。我们从三个方面做里一些优化。
(1)对组件库引用的优化
先来看一个问题
当我们使用组件库的时候,import {Button} from 'element-ui',相对于Vue.use(elementUI),已经是具有性能意识,是比较推荐的做法,但如果我们写成右边的形式,具体到文件的引用,打包之后的区别是非常大的,以antd为例,右边形式bundle体积减少约80%。
这个引用也属于有副作用,webpack不能把其他组件进行tree-shaking。既然工具本身是做不了,那我们可以做工具把左边代码自动改成右边代码这种形式。这个工具antd库本身也是提供的。我在antd的工具基础上做了少量的修改,不用任何配置,原生支持我们自己的组件库, wui 和 xcui 以及一些其他常用的库
babel-plugin-import-fix ,缩小引用范围

下面介绍一下原理
这是一个babel的插件,babel通过核心babylon将ES6代码转换成AST抽象语法树,然后插件遍历语法树找出类似import {Button} from 'element-ui'这样的语句,进行转换,最后重新生成代码。
babel-plugin-import-fix默认支持antd,element,meterial-UI,wui,xcui和d3,只需要再.babelrc中配置插件本身就可以。
.babelrc
{ "presets": [ ["es2015", { "modules": false }], "react" ], "plugins": ["import-fix"]}
其实是想把所有常用的库都默认支持,但很多常用的库却不支持缩小引用范围。因为没有独立输出各个子模块,不能把引用修改为对单个子模块的引用。
(2)CSS Tree-shaking
我们前面所说的tree-shaking都是针对js文件,通过静态分析,尽可能消除无用的代码,那对于css我们能做tree-shaking吗?
随着CSS3,LESS,SASS等各种css预处理语言的普及,css文件在整个工程中占比是不可忽视的。随着大项目功能的不停迭代,导致css中可能就存在着无用的代码。我实现了一个webpack插件来解决这个问题,找出css代码无用的代码。
webpack-css-treeshaking-plugin,对css进行tree-shaking
webpack-css-treeshaking-plugin下面介绍一下原理
整体思路是这样的,遍历所有的css文件中的selector选择器,然后去所有js代码中匹配,如果选择器没有在代码出现过,则认为该选择器是无用代码。
首先面临的问题是,如何优雅的遍历所有的选择器呢?难道要用正则表达式很苦逼的去匹配分割吗?
babel是js世界的福星,其实css世界也有利器,那就是postCss。
PostCSS 提供了一个解析器,它能够将 CSS 解析成AST抽象语法树。然后我们能写各种插件,对抽象语法树做处理,最终生成新的css文件,以达到对css进行精确修改的目的。
整体又是一个webpack的插件,架构图如下:
主要流程:
- 插件监听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 })) })
- postCss 遍历,匹配,删除过程
module.exports = postcss.plugin('list-selectors', function (options) { // 从根节点开始遍历 cssRoot.walkRules(function (rule) { // Ignore keyframes, which can log e.g. 10%, 20% as selectors if (rule.parent.type === 'atrule' && /keyframes/.test(rule.parent.name)) return // 对每一个规则进行处理 checkRule(rule).then(result => { if (result.selectors.length === 0) { // 选择器全部被删除 let log = ' ✂️ [' + rule.selector + '] shaked, [1]' console.log(log) if (config.remove) { rule.remove() } } else { // 选择器被部分删除 let shaked = rule.selectors.filter(item => { return result.selectors.indexOf(item) === -1 }) if (shaked && shaked.length > 0) { let log = ' ✂️ [' + shaked.join(' ') + '] shaked, [2]' console.log(log) } if (config.remove) { // 修改AST抽象语法树 rule.selectors = result.selectors } } }) })
checkRule 处理每一个规则核心代码
let checkRule = (rule) => { return new Promise(resolve => { ... let secs = rule.selectors.filter(function (selector) { let result = true let processor = parser(function (selectors) { for (let i = 0, len = selectors.nodes.length; i < len; i++) { let node = selectors.nodes[i] if (_.includes(['comment', 'combinator', 'pseudo'], node.type)) continue for (let j = 0, len2 = node.nodes.length; j < len2; j++) { let n = node.nodes[j] if (!notCache[n.value]) { switch (n.type) { case 'tag': // nothing break case 'id': case 'class': if (!classInJs(n.value)) { // 调用classInJs判断是否在JS中出现过 notCache[n.value] = true result = false break } break default: // nothing break } } else { result = false break
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。