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

Webpack性能系列五:使用ScopeHoisting

时间:2023-03-13 20:01:43 科技观察

1.什么是ScopeHoisting?默认情况下,Webpack打包的模块资源会被组织成函数式的形式。比如更多产品打包形式的知识可以参考上一篇《Webpack 原理系列八:产物转译打包逻辑》//common.jsexportdefault"common";//index.jsimportcommonfrom'./common';控制台日志(常见);上面的例子最终会被打包成一个产品,结构如下:"./src/common.js":((__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{const__WEBPACK_DEFAULT_EXPORT__=("common");__webpack_require__.d(__webpack_exports__,{/*harmonyexport*/"default":()=>(__WEBPACK_DEFAULT_EXPORT__)/*harmonyexport*/});}),"./src/index.js":((__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{var_common__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(/*!./common*/"./src/common.js");console.log(_common__WEBPACK_IMPORTED_MODULE_0__)})这个结构有两个影响运行性能的问题:重复的函数模板代码会增加产品体积,并消耗更多的网络流量。需要创建和销毁函数。域空间影响运行性能针对这些问题,Webpack3引入了ScopeHoisting功能,本质上是将多个符合条件的模块合并到同一个函数空间,减少运行时函数声明的模板代码和频繁的栈操作,从而封装“体积更小”、“运行性能”更好的封装比如上面的例子经过ScopeHoisting优化后,生成的代码:((__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{;//CONCATEDMODULE:./src/common.js/*harmonydefaultexport*/constcommon=("common");;//CONCATENATEDMODULE:./src/index.jsconsole.log(common);})二、使用ScopeHoisting2.1开启ScopeHoisting功能Webpack提供了三种开启ScopeHoisting功能的方式:EnableProductionmode而使用optimization.concatenateModules配置项直接使用ModuleConcatenationPlugin插件对应的代码如下:constModuleConcatenationPlugin=require('webpack/lib/optimize/ModuleConcatenationPlugin');module.exports={//方法一:设置`mode`toproduction启用模式:"production",//方法二:设置`optimization.concatenateModules`为trueoptimization:{concatenateModules:true,usedExports:true,providedExports:true,},//方法三:直接使用`ModuleConcatenationPlugin`插件插件:[newModuleConcatenationPlugin()]};三种方法的工作原理是相似的。最后会使用ModuleConcatenationPlugin来完成模块的解析合并操作。唯一需要注意的是,在使用optimization.concatenateModules时,需要设置usedExports和providedExports为true,标记模块的导入导出变量,完成合并操作2.2模块合并规则启用ScopeHoisting后,Webpack会尽可能多地将模块合并到同一个函数作用域中,但是合并函数依赖于ESM静态分析capability一方面;一方面,我们需要保证merge操作不会造成代码冗余。因此,开发者需要注意,ScopeHoisting在以下场景会失效:2.2.1非ESM模块对于AMD、CMD等模块,由于模块导入导出内容的动态性,Webpack无法保证是原始的模块合并后不会被修改。代码语义有导致ScopeHoisting失败的副作用,例如://common.jsmodule.exports='common';//index.jsimportcommonfrom'./common';在上面的例子中,由于common.js使用CommonJS导入模块内容,ScopeHoisting失败,两个模块无法合并。这个问题在导入NPM包时尤为常见。由于大部分框架都会自己打包然后上传到npm,而默认导出的是比较兼容CommonJS的模块方案,所以不能使用ScopeHoisting功能。这时候可以使用mainFileds属性尝试导入ESM版本的框架:module.exports={resolve:{//最好使用jsnext:main中指向的ES6模块化语法文件mainFields:['jsnext:main','browser','main']},};2.2.2模块被多个Chunk引用如果一个模块同时被多个Chunk引用,为了避免重复打包,ScopeHoisting也会失败,例如://common.jsexportdefault"common"//async.jsimportcommonfrom'./common';//index.jsimportcommonfrom'./common';import("./async");在上面的例子中,入口index.js通过异步引用导入了async.js模块,而async.js和index.js都依赖于common.js模块,async.js会根据Chunk的运行规则,也就是说common.js模块会同时被index.js对应的InitialChunk和async.js对应的AsyncChunk引用,此时ScopeHoisting失败,common.js不能合并到任何Chunk中,而是作为一个单独的scope生成,最终打包结果:"./src/common.js":(()=>{var__WEBPACK_DEFAULT_EXPORT__=("common");}),"./src/index.js":(()=>{var_common__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(/*!./common*/"./src/common.js");__webpack_require__.e(/*!import()*/"src_async_js").then(__webpack_require__.bind(__webpack_require__,/*!./async*/"./src/async.js"));}),关于Chunk更多信息请参考:《Webpack 性能系列四:分包优化》《有点难的知识点:Webpack Chunk 分包规则详解》3.总结默认情况下,Webpack会将模块一个一个的打包成单独的函数,这会在一定程度上造成代码冗余和性能问题。现状自Webpack3.0引入ModuleConcatenationPlugin后,开发者可以使用ScopeHoisting技术将多个模块合并为一个函数,减少性能问题