阅读原文tree-sharking介绍tree-sharking是Webpack2后续版本的优化功能,顾名思义,就是把多余的代码“摇一摇”。在开发中,我们经常会用到一些第三方库,而这些第三方库只使用了这个库的部分功能或者代码,没有用到的代码也会被打包进来,所以导出的文件会很大,tree-sharking帮助我们解决了这个问题,它可以把每个模块中没有用到的方法过滤掉,只打包有效的代码。AST语法树分析假设我们现在正在使用ElementUI库的两个组件,这两个组件通常使用解构赋值引入。//优化前import{Button,Alert}from"element-ui";这样引用资源,webpack在打包的时候会找到element-ui,把里面的代码全部打包到导出文件中,我们只用了两个组件,所有的打包都不是我们想要的,tree-sharking是通过配置babel实现的-plugin-在Webpack中导入插件,可以将解构后的代码转换成如下形式。//优化后,从“element-ui/lib/button”导入Button;从“element-ui/lib/Alert”导入警报;转换后会去node_modules中的element-ui模块找到Button和Alert文件对应的两个组件,打包成一个导出文件。从上面的转换可以看出,tree-sharking的实现原理是通过改变AST语法树的结构来实现的。如果对抽象语法树不了解,可以参考AST抽象语法树。我们可以通过网站http://esprima.org/demo/parse在线转换...将JS代码解析成AST语法树。优化前的AST语法树:{"type":"Program","body":[{"type":"ImportDeclaration","specifiers":[{"type":"ImportSpecifier","local":{"type":"Identifier","name":"Button"},"imported":{"type":"Identifier","name":"Button"}},{"type":"ImportSpecifier","local"":{"type":"Identifier","name":"Alert"},"imported":{"type":"Identifier","name":"Alert"}}],"source":{"类型”:“文字”,“价值”:“元素用户界面”,"raw":"\"element-ui\""}}],"sourceType":"module"}优化后的AST语法树:{"type":"Program","body":[{"type":"ImportDeclaration","specifiers":[{"type":"ImportDefaultSpecifier","local":{"type":"Identifier","name":"Button"}}],"source":{"type":"Literal","value":"element-ui/lib/button","raw":"\"element-ui/lib/button\""}},{"type":"ImportDeclaration","说明符”:[{“类型”:“ImportDefaultSpecifier”,“本地”:{“类型”:“标识符”,“名称”:“警报”}}}],“来源”:{"type":"Literal","value":"element-ui/lib/Alert","raw":"\"element-ui/lib/Alert\""}}],"sourceType":"module"}从上面的语法树对比可以看出,优化前body中只有一个对象,使用的组件信息存储在specifiers中,source指向element-ui。优化后,两个组件被拆分为两个对象存在于body中,每个对象的说明符只存储一个组件,并指向源中当前组件对应的路径。模拟tree-starking既然我们已经明确了要在哪里修改语法树,那么我们就使用AST来模拟tree-starking的sharking功能,语法树的操作依赖于babel-core和babel这两个核心模块-types,并首先安装依赖项。npminstallbabel-corebabel-types//文件:babel-plugin-my-import.jsconstbabel=require("babel-core");consttypes=require("babel-types");letcode=`import{Button,Alert}from"element-ui"`;letimportPlugin={visitor:{ImportDeclaration(path){letnode=path.node;让source=node.source.value;让说明符=node.specifiers;//判断是否是默认导出,如果其中一个不是默认导出,则也不是默认导出if(!types.isImportDefaultSpecifier(specifiers[0])){//如果不是默认导出,你需要转换specifiers=specifiers.map(specifier=>{//数组内容:当前默认的导出标识,从哪里导入returntypes.importDeclaration([types.importDefaultSpecifier(specifier.local)],types.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`));});//替换树结构path.replaceWithMultiple(speci菲尔);}}}};letresult=babel.transform(code,{plugins:[importPlugin]});console.log(result.code);//从“element-ui/lib/button”导入按钮;//从“element-ui/lib/alert”导入警报;通过上面的代码,我们可以发现我们使用了babel-core和babel-types这两个模块的核心方法来遍历、修改和替换语法书。更详细的API可以查看https://github.com/babel/babe...使用插件配合Webpack只是在tree-sharking验证了JS语法的转换过程,然后将上面的代码转换成一个插件,配合Webpack使用,彻底感受tree-sharking的工作过程。//文件:~node_modules/babel-plugin-my-import.jsconstbabel=require("babel-core");consttypes=require("babel-types");letimportPlugin={visitor:{ImportDeclaration(path){让节点=路径.节点;让source=node.source.value;让说明符=node.specifiers;//判断是否为默认导出,如果其中一个不是默认导出,则两者都不是默认导出if(!types.isImportDefaultSpecifier(specifiers[0])){//如果不是默认导出,你需要转换specifiers=specifiers.map(specifier=>{//数组内容:当前默认导出标识,导入到哪里returntypes.importDeclaration([types.importDefaultSpecifier(specifier.local)],types.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`));});//替换树解构path.replaceWithMultiple(specifiers);}}}};module.exports=importPlugin;在删除多余的测试代码,导出模块中的importPlugin插件,将babel-plugin-my-import.js移动到node_modules中,接下来安装需要的依赖:npminstallwebpackwebpack-clibabel-loaderbabel-presets-envnpminstallvueelement-ui--save安装好依赖后,写一个需要编译的文件,使用webpack打包,查看使用插件前后导出文件的大小。//文件:import.jsimportVuefrom"vue";从“element-ui”导入{Button,Alert};让我们写一个简单的Webpack配置文件。//文件:webpcak.config.jsmodule.exports={mode:"development",entry:"import.js",output:{filename:"bundle.js",path:__dirname},module:{rules:[{test:/\.js$/,use:{loader:"babel-loader",options:{presets:["env",],plugins:[//plugins:不要使用plugins打包注释掉这一行["my-import",{libararyName:"element-ui"}]]}},exclude:/node_modules/}]}};为了防止babel相关依赖升级到7.0后,webpack因为某些问题无法启动,这里贴出package.json文件,根据对应版本下载依赖,保证上面的webpack配置生效。文件:package.json{"name":"ast-lesson","version":"1.0.0","description":"tree-starking","main":"index.js","scripts":{"test":"echo\"Error:notestspecified\"&&exit1"},"keywords":[],"author":"","license":"ISC","dependencies":{"babel-core":"^6.26.3","babel-loader":"^7.1.5","babel-preset-env":"^1.7.0","babel-types":"^6.26.0","escodegen":"^1.10.0","esprima":"^4.0.0","estraverse":"^4.2.0","webpack":"^4.16.0","webpack-cli":"^3.0.8"},"devDependencies":{"vue":"^2.5.17","element-ui":"^2.4.6"}}使用前后导出文件对比plugin接下来分别在使用插件和不使用插件时执行打包命令,查看导出文件bondle.js的大小。npxwebpack使用babel-plugin-my-import之前:使用babel-plugin-my-import之后:通过对比可以看到使用tree-sharking之后的打包导出,这是我们自己实现的babel-plugin-my-importplugin文件大大减少。原因是引入第三方库的所有不用的代码都被过滤掉了,只打包有效的代码。综上,我们上面分析了Webpack的tree-sharking,模拟babel-plugin-import简单实现了一个版本的tree-sharking优化插件。在这个过程中,相信大家已经了解了tree-sharking的原理和类似插件的实现。的想法,并且已经具备了开发类似插件的基础条件。最后,还要补充一点。tree-sharking优化方法是基于ES6语法import"static"引入的特性实现的。如果要说tree-sharking很强大,倒不如说ES6模块化规范的“静态”导入特性很强大。由于是基于“静态”导入,目前tree-sharking只支持遍历一层import关键字。
