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 源碼的分析解除了我心中不少的構建相關的疑惑,整我的都昇華了。因此每一個人精力、時間都有限,選擇作本身喜歡的事情,方爲上策。

若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!

相關文章
相關標籤/搜索