標籤:

加速Webpack-縮小文件搜索範圍

Webpack 啟動後會從配置的 Entry 出發,解析出文件中的導入語句,再遞歸的解析。

在遇到導入語句時 Webpack 會做兩件事情:

  1. 根據導入語句去尋找對應的要導入的文件。例如 require(react) 導入語句對應的文件是 ./node_modules/react/react.jsrequire(./util) 對應的文件是 ./util.js
  2. 根據找到的要導入文件的後綴,使用配置中的 Loader 去處理文件。例如使用 ES6 開發的 JavaScript 文件需要使用 babel-loader 去處理。

以上兩件事情雖然對於處理一個文件非常快,但是當項目大了以後文件量會變的非常多,這時候構建速度慢的問題就會暴露出來。

雖然以上兩件事情無法避免,但需要盡量減少以上兩件事情的發生,以提高速度。

接下來一一介紹可以優化它們的途徑。

優化 loader 配置

由於 Loader 對文件的轉換操作很耗時,需要讓儘可能少的文件被 Loader 處理。

在2-3 Module 中介紹過在使用 Loader 時可以通過 testincludeexclude 三個配置項來命中 Loader 要應用規則的文件。

為了儘可能少的讓文件被 Loader 處理,可以通過 include 去命中只有哪些文件需要被處理。

以採用 ES6 的項目為例,在配置 babel-loader 時,可以這樣:

module.exports = {n module: {n rules: [n {n // 如果項目源碼中只有 js 文件就不要寫成 /.jsx?$/,提升正則表達式性能n test: /.js$/,n // babel-loader 支持緩存轉換出的結果,通過 cacheDirectory 選項開啟n use: [babel-loader?cacheDirectory],n // 只對項目根目錄下的 src 目錄中的文件採用 babel-loadern include: path.resolve(__dirname, src),n },n ]n },n};n

你可以適當的調整項目的目錄結構,以方便在配置 Loader 時通過 include 去縮小命中範圍。

優化 resolve.modules 配置

在2-4 Resolve 中介紹過 resolve.modules 用於配置 Webpack 去哪些目錄下尋找第三方模塊。

resolve.modules 的默認值是 [node_modules],含義是先去當前目錄下的 ./node_modules 目錄下去找想找的模塊,如果沒找到就去上一級目錄 ../node_modules 中找,再沒有就去 ../../node_modules 中找,以此類推,這和 Node.js 的模塊尋找機制很相似。

當安裝的第三方模塊都放在項目根目錄下的 ./node_modules 目錄下時,沒有必要按照默認的方式去一層層的尋找,可以指明存放第三方模塊的絕對路徑,以減少尋找,配置如下:

module.exports = {n resolve: {n // 使用絕對路徑指明第三方模塊存放的位置,以減少搜索步驟n // 其中 __dirname 表示當前工作目錄,也就是項目根目錄n modules: [path.resolve(__dirname, node_modules)]n },n};n

優化 resolve.mainFields 配置

在2-4 Resolve 中介紹過 resolve.mainFields 用於配置第三方模塊使用哪個入口文件。

安裝的第三方模塊中都會有一個 package.json 文件用於描述這個模塊的屬性,其中有些欄位用於描述入口文件在哪裡,resolve.mainFields 用於配置採用哪個欄位作為入口文件的描述。

可以存在多個欄位描述入口文件的原因是因為有些模塊可以同時用在多個環境中,准對不同的運行環境需要使用不同的代碼。

以 isomorphic-fetch 為例,它是 fetch API 的一個實現,但可同時用於瀏覽器和 Node.js 環境。

它的 package.json 中就有2個入口文件描述欄位:

{n "browser": "fetch-npm-browserify.js",n "main": "fetch-npm-node.js"n}n

isomorphic-fetch 在不同的運行環境下使用不同的代碼是因為 fetch API 的實現機制不一樣,在瀏覽器中通過原生的 fetch 或者 XMLHttpRequest 實現,在 Node.js 中通過 http 模塊實現。

resolve.mainFields 的默認值和當前的 target 配置有關係,對應關係如下:

  • targetweb 或者 webworker 時,值是 ["browser", "module", "main"]
  • target 為其它情況時,值是 ["module", "main"]

target 等於 web 為例,Webpack 會先採用第三方模塊中的 browser 欄位去尋找模塊的入口文件,如果不存在就採用 module 欄位,以此類推。

為了減少搜索步驟,在你明確第三方模塊的入口文件描述欄位時,你可以把它設置的盡量少。

由於大多數第三方模塊都採用 main 欄位去描述入口文件的位置,可以這樣配置 Webpack:

module.exports = {n resolve: {n // 只採用 main 欄位作為入口文件描述欄位,以減少搜索步驟n mainFields: [main],n },n};n

使用本方法優化時,你需要考慮到所有運行時依賴的第三方模塊的入口文件描述欄位,就算有一個模塊搞錯了都可能會造成構建出的代碼無法正常運行。

優化 resolve.alias 配置

在2-4 Resolve 中介紹過 resolve.alias 配置項通過別名來把原導入路徑映射成一個新的導入路徑。

在實戰項目中經常會依賴一些龐大的第三方模塊,以 React 庫為例,安裝到 node_modules 目錄下的 React 庫的目錄結構如下:

├── distn│ ├── react.jsn│ └── react.min.jsn├── libn│ ... 還有幾十個文件被忽略n│ ├── LinkedStateMixin.jsn│ ├── createClass.jsn│ └── React.jsn├── package.jsonn└── react.jsn

可以看到發布出去的 React 庫中包含兩套代碼:

  • 一套是採用 CommonJS 規範的模塊化代碼,這些文件都放在 lib 目錄下,以 package.json 中指定的入口文件 react.js為模塊的入口。
  • 一套是把 React 所有相關的代碼打包好的完整代碼放到一個單獨的文件中,這些代碼沒有採用模塊化可以直接執行。其中 dist/react.js 是用於開發環境,裡面包含檢查和警告的代碼。dist/react.min.js 是用於線上環境,被最小化了。

默認情況下 Webpack 會從入口文件 ./node_modules/react/react.js 開始遞歸的解析和處理依賴的幾十個文件,這會時一個耗時的操作。

通過配置 resolve.alias 可以讓 Webpack 在處理 React 庫時,直接使用單獨完整的 react.min.js 文件,從而跳過耗時的遞歸解析操作。

相關 Webpack 配置如下:

module.exports = {n resolve: {n // 使用 alias 把導入 react 的語句換成直接使用單獨完整的 react.min.js 文件,n // 減少耗時的遞歸解析操作n alias: {n react: path.resolve(__dirname, ./node_modules/react/dist/react.min.js),n }n },n};n

除了 React 庫外,大多數庫發布到 Npm 倉庫中時都會包含打包好的完整文件,對於這些庫你也可以對它們配置 alias。但是對於有些庫使用本優化方法後會影響到後面要講的使用 Tree-Shaking 去除無效代碼的優化,因為打包好的完整文件中有部分代碼你的項目可能永遠用不上。

一般對整體性比較強的庫採用本方法優化,因為完整文件中的代碼是一個整體,每一行都是不可或缺的。

但是對於一些工具類的庫,例如 lodash,你的項目可能只用到了其中幾個工具函數,你就不能使用本方法去優化,因為這會導致你的輸出代碼中包含很多永遠不會執行的代碼。

優化 resolve.extensions 配置

在導入語句沒帶文件後綴時,Webpack 會自動帶上後綴後去嘗試詢問文件是否存在。

在2-4 Resolve 中介紹過 resolve.extensions 用於配置在嘗試過程中用到的後綴列表,默認是:

extensions: [.js, .json]n

也就是說當遇到 require(./data) 這樣的導入語句時,Webpack 會先去尋找 ./data.js 文件,如果該文件不存在就去尋找 ./data.json 文件,如果還是找不到就報錯。

如果這個列表越長,或者正確的後綴在越後面,就會造成嘗試的次數越多,所以 resolve.extensions 的配置也會影響到構建的性能。

在配置 resolve.extensions 時你需要遵守以下幾點,以做到儘可能的優化構建性能:

  • 後綴嘗試列表要儘可能的小,不要把項目中不可能存在的情況寫到後綴嘗試列表中。
  • 頻率出現最高的文件後綴要優先放在最前面,以做到儘快的退出尋找過程。
  • 在源碼中寫導入語句時,要儘可能的帶上後綴,從而可以避免尋找過程。例如在你確定的情況下把 require(./data) 寫成 require(./data.json)

相關 Webpack 配置如下:

module.exports = {n resolve: {n // 儘可能的減少後綴嘗試的可能性n extensions: [js],n },n};n

優化 module.noParse 配置

在2-3 Module 中介紹過 module.noParse 配置項可以讓 Webpack 忽略對部分沒採用模塊化的文件的遞歸解析處理,這樣做的好處是能提高構建性能。

原因是一些庫,例如 jQuery 、ChartJS, 它們龐大又沒有採用模塊化標準,讓 Webpack 去解析這些文件耗時又沒有意義。

在上面的 優化 resolve.alias 配置 中講到單獨完整的 react.min.js 文件就沒有採用模塊化,讓我們來通過配置 module.noParse忽略對 react.min.js 文件的遞歸解析處理,

相關 Webpack 配置如下:

const path = require(path);nnmodule.exports = {n module: {n // 獨完整的 `react.min.js` 文件就沒有採用模塊化,忽略對 `react.min.js` 文件的遞歸解析處理n noParse: [/react.min.js$/],n },n};n

注意被忽略掉的文件里不應該包含 importrequiredefine 等模塊化語句,不然會導致構建出的代碼中包含無法在瀏覽器環境下執行的模塊化語句。


以上就是所有和縮小文件搜索範圍相關的構建性能優化了,在根據自己項目的需要去按照以上方法改造後,你的構建速度一定會有所提升。

本實例提供項目完整代碼

《深入淺出Webpack》全書在線閱讀鏈接

閱讀原文

推薦閱讀:

翻譯 | 上手 Webpack ? 這篇就夠了!
webpack 之 代碼拆分
webpack:從入門到真實項目配置
重溫 Webpack, Babel 和 React
用webpack,輸出多個出口文件,每個出口文件對應一個頁面,重複引用jquery?

TAG:webpack |