当前位置: 首页 > 科技观察

精准打包——Webpack的TreeShaking

时间:2023-03-22 15:25:48 科技观察

前段时间和朋友聊Webpack的时候突然提到了TreeShaking,但是很惭愧没有办法解释Webpack是如何实现TreeShaking的,就借用一下本次年假的第一天,抽空看了下Webpack文件,然后把自己理解的写下来。如果你也感兴趣,我们一起来读一读吧。什么是TreeShakingTreeShaking是一种优化方法,JavaScript中常用的一个术语,表示去除无用的代码,之所以叫TreeShaking似乎是说“当你使劲摇动一棵树时,只会有绿叶在树上,其他枯叶会掉到地上”,那些绿叶才是打包文件中真正有用的代码。使用的时候要注意TreeShaking只能用在静态结构上(比如:import和export),动态结构的require是检测不到的。例如,如果要加载到模块中,import必须位于文件的顶部,但require可以在任何地方使用。例如,下面的场景必须等到运行时才能知道模块是什么:letmodule=null;if(Math.random()*10>5){module=require('module1');}else{module=require('moudle2');}在开始了解TreeShaking的工作之前,有些人应该很好奇,即使你从未在Webpack中专门设置TreeShaking,无用的代码也会被删除!那是因为TreeShaking的执行需要ModuleConcatenationPlugin(图1),而Webpack中还有另外一种模式,如果你一直没有特别设置mode的值,那么默认的模式就是production(图2)),然后ModuleConcatenationPlugin将在生产的默认选项中启用(也是图2),因此您通常不会注意到它也就不足为奇了,因为Webpack为您做了这一切。TreeShaking的操作是因为Production会帮你开启ModuleConcatenationPlugin,所以我们后面实验的时候需要把mode改成none(webpack文件说none是关闭所有优化设置的mode)。这里将附上一个简单的初始化示例配置。如果您有兴趣,可以克隆它并使用它。先在src下创建一个math.js和string.js,然后记下一个export的方法,分别是add和composeString:constadd=(a,b)=>a+b;导出默认{添加};constcomposeString=(a,b)=>`${a}${b}`;导出默认值{composeString};打开src下的index.js,同时导入add和composeString,但只使用add方法:import{add}from'./math';import{addString}from'./string';console.log(add(1,2))最后在终端执行npmrunbuild或webpack进行打包。打包完成后,你会发现虽然我们只使用了importadd,但是打包后的文件内容还是会有composeString:的,不过这是正常的,毕竟我们没有做任何处理,Webpack不知道你的代码是否打包时是否使用,所以不是帮你去掉composeString的方法。那么什么样的代码有用,什么没用呢?最明显的定义应该是,如果被执行了,就意味着它是有用的。就像在上面的例子中添加。也使用有副作用的代码。像上面的index.js,貌似没有提供方法,但是执行的时候会在控制台留下日志。另外,会改变执行环境的polyfill也是一个有副作用的库。第一种情况比较容易区分,但是如果是第二种情况,可以选择使用Webpack中的sideEffects属性来设置。sideEffectssideEffects可以设置为布尔值或数组。当你设置为false时,表示该项目不会有sideEffects,即一直使用export来判断是否使用。另外sideEffects会依赖providedExports找出项目中所有导出的模块:下面是sideEffects的使用方法:{"name":"tree-shaking","sideEffects":false,"version":"1.0.0",...}只要在package.json中加入sideEffects并将值设置为flase,就意味着项目中的所有代码都没有副作用,这样Webpack就可以将不用的导出代码打包去掉。添加sideEffects并打包后,结果中不会看到composeString:现在我们去src再创建一个polyfill.js,在polyfill.js中为Array创建一个自定义方法,然后导入到index.js中:index。jsimport'./polyfill';import{add}from'./math';import{addString}from'./string';console.log([].customMethod());**polyfill.js**Array.prototype.customMethod=()=>{console.log('customMethods');};如果我们去打包上面的代码,polyfill.js不会因为没有export而被providedExports抓到,不会被打包到Production中,如果项目中有customMethod使用了Array,执行时会报错。面对这种情况,必须在sideEffects属性中告知polyfill.js有副作用。设置方法如下:{"name":"tree-shaking","sideEffects":["./src/polyfill.js"],"version":"1.0.0",...}在这个way,polyfill.js会直接打包:最后,需要注意两点:如果你的项目中也有import.css样式,记得把.css结尾的文件名放到sideEffects里面,比如sideEffects:["*.css”]。webpack.config.js中的优化也有sideEffects,不过这里设置的值是针对node_modules的。useExporteduseExported和sideEffects的功能都是用来判断代码是否应该被移除,但是根据Webpack文件中的描述,useExported才是真正的TreeShaking:usedExports会使用terser来判断代码是否有副作用。如果不使用,则不是。sideeffect,打包时会标记为unusedharmony,minify时会移除(使用Uglifyjs或其他工具)。在测试usedExports之前,将square添加到math.js并导出:constadd=(a,b)=>a+b;constsquare=(a,b)=>a*b;export{add,square};接下来,将optimization.usedExports添加到webpack.config.js中:module.exports={...optimization:{usedExports:true,}};然后打包项目,你会发现它只导出了,没有使用方块会被标记为未使用和谐导出:然后我们使用uglifyjs-webpack-plugin将未使用的方块从树上摇下来:npminstall-duglifyjs-webpack-pluginwebpack.config.js设置如下:constUglifyJsPlugin=require('uglifyjs-webpack-plugin');module.exports={...optimization:{usedExports:true,minimize:true,minimizer:[newUglifyJsPlugin({uglifyOptions:{compress:{unused:true},mangle:false,output:{beautify:true}},})],}};设置好minimizer后,再次打包,可以看到去掉了方块:usedExports和sideEffects的区别在于,usedExports可以以statements为单位来判断是否有副作用,而sideEffects可以让Webpack在打包的时候直接跳过整个文件。只要文件出现在sideEffect中,就会直接打包,不需要通过terser评估副作用。总结TreeShaking只能用在静态结构中。如果项目中的babel将静态结构编译成动态结构的话,还需要另外设置。使用sideEffects时,应该写在package.json中。如果是优化第三方库,应该写在webpack.config.js中的optimization中。usedExports是TreeShacking。使用时会自动判断未使用的代码,并标注未使用和声的注解。如果你想删除它,你需要使用minify。作者:神Q超人译者:前端小智来源:medium原文:https://medium.com/starbugs/%E7%B2%BE%E6%B96%E7%9A%84%E6%89%93%E5%8C%85-webpack-%E7%9A%84-摇树-ad39e185f284