webpack 4 源码主流程分析(十四):webpack 优化

原文首发于 blog.flqin.com。若有错误,请联系笔者。分析码字不易,转载请代表出处,谢谢!css

前面一至十一章,介绍了在 development 的模式下,整个完整了构建主流程。在了解构建流程的基础上,本章整理一些与 webpack 优化相关的知识点。html

production 模式

咱们参考 production 模式里,里面已经作了大部分的优化,如压缩,Scope Hoistingtree-shaking 等给予咱们启发,接下来具体分析各个点。webpack

production 模式启用的插件

  • FlagDependencyUsagePlugin
    • 触发时机:compilation.hooks.optimizeDependencies
    • 功能:标记模块导出中被使用的导出,存在 module.usedExports 里。用于 Tree shaking
    • 对应配置项:optimization.usedExports:true
  • FlagIncludedChunksPlugin
    • 触发时机:compilation.hooks.optimizeChunkId
    • 功能:给每一个 chunk 添加了 ids,用于判断避免加载没必要要的 chunk
  • ModuleConcatenationPlugin
    • 触发时机:compilation.hooks.optimizeChunkModules
    • 功能:使用 esm 语法能够做用域提高(Scope Hoisting)或预编译全部模块到一个闭包中,提高代码在浏览器中的执行速度
    • 对应配置项:optimization.concatenateModules:true
  • NoEmitOnErrorsPlugin
    • 触发时机:compiler.hooks.shouldEmitcompilation.hooks.shouldRecord
    • 功能:若是在 compilation 编译时有 error,则不执行 Record 相关的钩子,而且抛错和不编译资源
  • OccurrenceOrderModuleIdsPluginOccurrenceOrderChunkIdsPlugin
    • 注意不是文档写的 OccurrenceOrderPlugin,这个没用
    • 触发时机:compilation.hooks.optimizeModuleOrdercompilation.hooks.optimizeChunkOrder
    • 功能:根据模块初始调用次数或者总调用次数排序(配置),这样在后面分配 ID 的时候常被调用 ID 就靠前,除此以外,还可让 id 为路径,hash 等。
    • 对应配置项:optimization.occurrenceOrderoptimization.chunkIdsoptimization.moduleIds
  • SideEffectsFlagPlugin
    • 触发时机:normalModuleFactory.hooks.modulecompilation.hooks.optimizeDependencies
    • 功能:
      • normalModuleFactory.hooks.module 钩子里读取 package.json 里的 sideEffects 字段和读取 module.rule 里的 sideEffects 赋给 module.factoryMeta(纯的 ES2015 模块);
      • compilation.hooks.optimizeDependencies 钩子里根据 sideEffects 配置,删除未用到的 export 导出
    • 对应配置项:optimization.sideEffects:true(默认)
  • TerserPlugin
    • 触发时机:template.hooks.hashForChunkcompilation.hooks.optimizeChunkAssets
    • 功能:
      • template.hooks.hashForChunk 钩子即在 chunks 生成 hash 阶段会把压缩相关的信息也打入到里面
      • compilation.hooks.optimizeChunkAssets 钩子触发资源压缩事件
    • 对应配置项:
      • optimization.minimize 是否开启压缩
      • optimization.minimizer 定制 Terser

另:development 模式单独启用的插件:git

  • NamedChunksPlugin
    • 触发时机:compilation.hooks.beforeChunkIds
    • 功能:以名称固化 chunk id
    • 对应配置项:optimization.chunkIds
  • NamedModulesPlugin
    • 触发时机:compilation.hooks.beforeModuleIds
    • 功能:以名称固化 module id
    • 对应配置项:optimization.moduleIds

持久化缓存

在更新部署页面资源时,不管是先部署页面,仍是先部署其余静态资源,都会由于新老资源替换后的缓存缘由,或者部署间隔缘由,都会致使资源不对应而引发页面错误。github

持久化缓存方案就是在各静态资源的名字后面加惟一的 hash 值,这样在每次修改文件后生成的不一样的 hash 值,而后在增量式发布文件时,就能够避免覆盖掉以前旧的文件。获取到新文件的用户就能够访问新的资源,而浏览器有缓存等状况的用户则继续访问老资源,保证新老资源同时存在且互不影响不出错。web

  • 对于 html:不开启缓存,把 html 放到单独的服务器上并关闭服务器的缓存,须要保证每次的 html 都为最新
  • 对于 jscssimg 等其余静态资源:开启缓存,将静态资源上传到 cdn,对资源开启长期缓存,由于有惟一 hash 的缘故因此不会致使资源被覆盖,用户在初次访问能够将这些长效缓存下载到本地,而后在后续的访问能够直接从缓存里读,节约网络资源。

webpack 中的持久化缓存

  • js 使用 chunkhash ,对 css 应用 mini-css-extract-plugin 插件并使用 contenthash
  • 经过 optimization.moduleIds 属性设置 module id
    • 开发环境 moduleIds 设为 named 即便用 NamedModulesPlugin (相对路径为 key)来固化 module id
    • 生产环境 moduleIds 设为 hashed 即便用 HashedModuleIdsPlugin (将路径转换为 hashkey)来固化 module id,保证在某一模块增删后,不会影响其余模块的 module id
  • 经过 optimization.chunkIds 属性设置为 namedoptimization.namedChunks 属性设置为 true (经过将 chunk name 复制到 chunk id)固化 chunk id,该属性会启用 NamedChunksPlugin
    • NamedChunksPlugin 插件里能够自定义 nameResolver 设置 name
    • splitChunks.cacheGroups[].name 也能够设置 chunk name
    • 魔法注释也能够设置:import(/* webpackChunkName: "my-chunk-name" */ 'module')
  • 经过 optimization.splitChunks 属性抽离库 vendor,业务公共代码 common
  • 经过 optimization.runtimeChunk 属性抽离运行时 runtime,其中 runtime 也能够经过 script-ext-html-webpack-plugin 插件嵌入到 html

Tree Sharing

Tree Sharing 是一个术语,一般用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。由 rollup 普及,在 webpack 里由 TerserPlugin 实现。json

tree-sharing 原理

  • ES6 的模块引入是静态分析的,故而能够在编译时正确判断到底加载了什么代码
  • 分析程序流,判断哪些变量未被使用、引用,进而删除此代码

若是咱们引入的模块被标记为 sideEffects: false,只要它任意一个导出都没有被其余模块引用到,那么无论它是否真的有反作用,整个模块都会被完整的移除。api

"side effect(反作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局做用域,而且一般不提供 export浏览器

启用 tree shaking 须要知足

  • 使用 ES2015 模块语法(即 importexport),目的是为了供程序静态分析
  • 确保没有 compilerES2015 模块语法转换为 CommonJS 模块(设置 babel.config.js presets: [['@babel/env', { modules: false }]]
  • package.json 或者 module.rule 设置 sideEffects : false,告诉 webpack 该项目或者该文件没有反作用
  • mode 选项设置为 production,其中会启用 FlagDependencyUsagePluginTerserPlugin 完成 tree shaking

Scope Hoisting

Scope Hoisting 即 做用域提高,可让 webpack 打包出来的代码文件更小,运行更快。缓存

Scope Hoisting 优势

  • 代码体积会变小,由于函数声明语句会产生大量代码
  • 代码在运行时由于建立的函数做用域减小了,内存开销也随之变小

Scope Hoisting 原理

ES6 的静态模块分析,分析出模块之间的依赖关系,按照引用顺序尽量地把模块放到同一个函数做用域中,而后适当的重命名一些变量以防止变量名冲突。

异步 import() 不会启用 Scope Hoisting

启用 Scope Hoisting 须要知足

  • 使用 ES2015 模块语法(即 importexport
  • mode 选项设置为 production,其中会启用 ModuleConcatenationPlugin 插件完成 Scope Hoisting

一些插件

如下列举部分我用过优化相关的插件及 loader

  1. happypack 多线程编译,加快编译速度
  2. webpackbar 编译进度条
  3. mini-css-extract-plugin 提取 css 样式到单独文件
  4. style-ext-html-webpack-plugin 加强 HtmlWebpackPlugin,将 css 内联到 html
  5. script-ext-html-webpack-plugin 加强 HtmlWebpackPlugin,将 js 内联到 html
  6. optimize-css-assets-webpack-plugin 使用cssnano压缩优化 css
  7. webpack-bundle-analyzer 模块分析
  8. url-loader 将文件转换为 DataURL,减小请求数

各插件随着时间推移,有的可能废弃,有的可能被更好的所替代,已社区流行为准。

后记

webpack 源码开始,到后面打包结果分析,watchwebpack 优化总结等,前先后后花了一个月的时间,但收获也颇多。因为对 webpack 底层有了认知,海量配置也清楚具体实现,不用死记硬背了;因为对 webpack 底层有了认知,因此遇到的各类构建问题都能定位到是构建的哪一步;因为对 webpack 底层有了认知,也可以很快速的根据业务写一些针对性的 loaderplugin;最重要的是经过对源码分析,大型工程的组织架构,扩展性,健壮性等给人带来一些新的思路和启发。

本系列到此结束,后续会不断的更新优化。对 webpack 源码的分析解除了我心中不少的构建相关的疑惑,整我的都升华了。因此每一个人精力、时间都有限,选择作本身喜欢的事情,方为上策。

若有错误,请联系笔者。分析码字不易,转载请代表出处,谢谢!

相关文章
相关标签/搜索