前面两篇文章《Webpack 性能系列二:多进程打包》和《Webpack 性能系列一: 使用 Cache 提升构建性能》已经详细讨论了利用缓存和多进程能力提升Webpack编译性能的基本方法和实现原理。这两种方式都可以通过简单的配置,大大提高大型项目的编译效率。此外,一些通用的最佳实践可用于减少编译范围和编译步骤以提高Webpack性能,包括:使用最新版本的Webpack和Node配置resolve来控制资源搜索范围为npm包设置module.noParse以跳过编译步骤配置module.rules.exclude或module.rules.include减少Loader工作量配置watchOption.ignored减少监听文件数优化ts类型检查逻辑谨慎选择source-map值下面将一一展开讲解每个最佳实践逻辑背后的原因。1、使用最新版本从WebpackV3,到V4,再到最新的V5版本,虽然构建功能不断叠加增强,性能也在不断优化提升。这是由于Webpack开发团队一直非常重视构建性能。孜孜不倦地重构核心实现,例如:V3到V4重写Chunk依赖逻辑,将原来的父子树关系调整为ChunkGroup表达的有序图关系,提高代码分包效率。V4到V5引入缓存功能,支持将模块、模块关系图、产品等核心元素持久化缓存到硬盘,减少重复工作。因此,开发者应尽可能保持Webpack和Node、NPM或Yarn等基础环境的更新,并使用最新的稳定版本完成构建工作。2、缩小资源搜索范围Webpack默认提供了一套兼容CMD、AMD、ESM等模块化解决方案的资源搜索规则——enhanced-resolve,可以准确定位各种模块导入语句到对应的物理资源路径的模块。参考:https://github.com/webpack/enhanced-resolve例如:import'lodash'这样引入npm包的语句,会被enhanced-resolve定位到对应的包体文件路径node_modules/lodash/index.js;没有文件扩展名的import语句,例如'./a'可能位于./a.js文件中;import'``@/a'这样对别名路径的引用可能位于$PROJECT_ROOT/src/a.js文件中。需要注意的是,这个增强资源搜索体验的特性背后涉及很多IO操作,本身可能会造成较大的性能消耗。开发者可以根据实际情况调整resolve配置,缩小资源搜索范围。2.1resolve.extensions配置当模块import语句不带文件后缀,如import'./a',Webpack会遍历resolve.extensions项定义的后缀名列表,尝试将后缀名追加到'./a'路径,查找对应的物理文件。在Webpack5中,resolve.extensions的默认值为['.js','.json','.wasm'],这意味着Webpack可能需要对没有后缀名的import语句执行三种判断逻辑来完成对于文件搜索,针对这种情况可行的优化措施包括:修改resolve.extensions配置项,减少匹配数。设置resolve.enforceExtension=true以强制开发人员提供明确的模块扩展名。这种方法侵入性太强,不推荐。2.2resolve.modules配置类似于Node模块搜索逻辑。当Webpack遇到import'lodash'这样的npm包导入语句时,它会先尝试在当前项目的node_modules中搜索资源。如果找不到的话,尝试按照目录层级逐步搜索node_modules目录。如果还是找不到,最后会尝试在全局node_modules中查找。在一个比较好的依赖管理执行的业务系统中,我们通常会尽量让node_modules资源保持高内聚,将其控制在有限的一两层,所以Webpack的逐层查找的逻辑在大多数情况下是实用的不是high,开发者可以通过修改resolve.modules配置项主动关闭逐层搜索功能,例如://webpack.config.jsconstpath=require('path');module.exports={//...resolve:{modules:[path.resolve(__dirname,'node_modules')],},};2.3resolve.mainFiles配置与resolve.extensions类似,resolve.mainFiles配置项用于定义默认文件名例如,对于import'./dir'请求,假设resolve.mainFiles=['index','home'],Webpack将测试./dir/index和./dir/home文件是否存在于命令。所以在实际项目中,应该控制resolve.mainFiles数组的个数,减少匹配的个数。3.跳过文件编译很多npm包提供的资源版本是预先打包好的,默认不需要二次编译,例如:Vue包的node_modules/vue/dist/vue.runtime.esm.js文件React包为node_modules/react/umd/react.production.min.js文件,对于用户来说,这些资源版本是高度独立和内聚的代码片段。无需重复依赖解析和代码翻译操作。这时可以使用module.noParse配置项跳过这些npm包,例如://webpack.config.jsmodule.exports={//...module:{noParse:/vue|lodash|react/,},};配置该属性后,任何匹配该选项的包都会跳过耗时的解析过程,直接打包成chunk,提高编译速度。4.尽量缩小Loader的作用范围Loader组件用于将各种文件资源转换成JavaScript可以理解和运行的代码片段。正是这个特性支撑了Webpack强大的资源处理能力。但是Loader在进行内容转换的过程中可能需要做大量的CPU操作,比如babel-loader、eslint-loader、vue-loader等,所以开发者需要通过module.rules.include、module.rules.exclude等配置项限制了Loader的执行范围,例如://webpack.config.jsmodule.exports={//...module:{rules:[{test:/\.js$/,exclude:/node_modules/,//include:path.join(__dirname,'./src'),use:['babel-loader','eslint-loader']}]}};示例配置exclude:/node_modules/属性后,Webpack在处理node_modules中的js文件时,会直接跳过该规则项,后续的Loader不会对这些文件执行。5.最小化watch监控范围在watch模式下(通过npxwebpack--watch命令启动),Webpack会持续监控项目的所有代码文件,并在发生变化时重新构建最新产品。但是一般情况下,前端项目中有些资源是不会经常更新的,比如node_modules。这时候可以设置watchOptions.ignored属性来忽略这些文件,例如://webpack.config.jsmodule.exports={//...watchOptions:{ignored:/node_modules/},};6、跳过TS类型检查JavaScript本身是一种弱类型语言,在多人协作项目中经常会造成不必要的类型错误,影响开发效率。随着前端能力和功能范围的不断扩展,前端项目协作的复杂度和难度也越来越大,TypeScript提供的静态类型检查能力正在被越来越多的人采用。但是,类型检查涉及到AST解析、遍历等非常耗CPU的操作,会给工程过程带来性能负担。如果需要,开发者可以选择在主编译过程中关闭类型检查功能,并使用fork-ts-checker-webpack-plugin插件将其剥离到一个单独的进程执行,例如对于ts-loader:constForkTsCheckerWebpackPlugin=require('fork-ts-checker-webpack-plugin');module.exports={//...module:{rules:[{test:/\.ts$/,use:[{loader:'ts-loader',options:{transpileOnly:true}}],},],},plugins:[newForkTsCheckerWebpackPlugin()]};参考:-https://github.com/TypeStrong/ts-loader#transpileonly-https://github.com/TypeStrong/fork-ts-checker-webpack-plugin这样不仅可以获取Typescript静态类型检查功能,还可以提高整体编译速度。7、慎用source-mapsource-map是一种将编译、压缩、混淆后的代码映射回源代码的技术。可以帮助开发者快速定位到更有意义、结构化的源代码,方便调试。但是,同样的source-map操作本身就有很大的性能开销。建议读者根据实际场景谨慎选择最合适的source-map方案。对于source-map功能,Webpack提供了devtool选项,可以配置eval、source-map、cheap-source-map等值。在不考虑其他因素的情况下,最佳实践:开发环境使用eval保证最佳编译速度生产环境使用source-map获取最高质量参考:https://webpack.js.org/configuration/devtool/
