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

Webpack 啓動後會從配置的 Entry 出發,解析出文件中的導入語句,再遞歸的解析。
在遇到導入語句時 Webpack 會作兩件事情:html

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

以上兩件事情雖然對於處理一個文件很是快,可是當項目大了之後文件量會變的很是多,這時候構建速度慢的問題就會暴露出來。
雖然以上兩件事情沒法避免,但須要儘可能減小以上兩件事情的發生,以提升速度。node

接下來一一介紹能夠優化它們的途徑。react

優化 loader 配置

因爲 Loader 對文件的轉換操做很耗時,須要讓儘量少的文件被 Loader 處理。webpack

2-3 Module 中介紹過在使用 Loader 時能夠經過 testincludeexclude 三個配置項來命中 Loader 要應用規則的文件。
爲了儘量少的讓文件被 Loader 處理,能夠經過 include 去命中只有哪些文件須要被處理。git

以採用 ES6 的項目爲例,在配置 babel-loader 時,能夠這樣:github

module.exports = {
  module: {
    rules: [
      {
        // 若是項目源碼中只有 js 文件就不要寫成 /\.jsx?$/,提高正則表達式性能
        test: /\.js$/,
        // babel-loader 支持緩存轉換出的結果,經過 cacheDirectory 選項開啓
        use: ['babel-loader?cacheDirectory'],
        // 只對項目根目錄下的 src 目錄中的文件採用 babel-loader
        include: path.resolve(__dirname, 'src'),
      },
    ]
  },
};
你能夠適當的調整項目的目錄結構,以方便在配置 Loader 時經過 include 去縮小命中範圍。

優化 resolve.modules 配置

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

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

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

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

優化 resolve.mainFields 配置

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

安裝的第三方模塊中都會有一個 package.json 文件用於描述這個模塊的屬性,其中有些字段用於描述入口文件在哪裏,resolve.mainFields 用於配置採用哪一個字段做爲入口文件的描述。

能夠存在多個字段描述入口文件的緣由是由於有些模塊能夠同時用在多個環境中,準對不一樣的運行環境須要使用不一樣的代碼。
isomorphic-fetch 爲例,它是 fetch API 的一個實現,但可同時用於瀏覽器和 Node.js 環境。
它的 package.json 中就有2個入口文件描述字段:

{
  "browser": "fetch-npm-browserify.js",
  "main": "fetch-npm-node.js"
}
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 = {
  resolve: {
    // 只採用 main 字段做爲入口文件描述字段,以減小搜索步驟
    mainFields: ['main'],
  },
};
使用本方法優化時,你須要考慮到全部運行時依賴的第三方模塊的入口文件描述字段,就算有一個模塊搞錯了均可能會形成構建出的代碼沒法正常運行。

優化 resolve.alias 配置

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

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

├── dist
│   ├── react.js
│   └── react.min.js
├── lib
│   ... 還有幾十個文件被忽略
│   ├── LinkedStateMixin.js
│   ├── createClass.js
│   └── React.js
├── package.json
└── react.js

能夠看到發佈出去的 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 = {
  resolve: {
    // 使用 alias 把導入 react 的語句換成直接使用單獨完整的 react.min.js 文件,
    // 減小耗時的遞歸解析操做
    alias: {
      'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
    }
  },
};
除了 React 庫外,大多數庫發佈到 Npm 倉庫中時都會包含打包好的完整文件,對於這些庫你也能夠對它們配置 alias。

可是對於有些庫使用本優化方法後會影響到後面要講的使用 Tree-Shaking 去除無效代碼的優化,由於打包好的完整文件中有部分代碼你的項目可能永遠用不上。
通常對總體性比較強的庫採用本方法優化,由於完整文件中的代碼是一個總體,每一行都是不可或缺的。
可是對於一些工具類的庫,例如 lodash,你的項目可能只用到了其中幾個工具函數,你就不能使用本方法去優化,由於這會致使你的輸出代碼中包含不少永遠不會執行的代碼。

優化 resolve.extensions 配置

在導入語句沒帶文件後綴時,Webpack 會自動帶上後綴後去嘗試詢問文件是否存在。
2-4 Resolve 中介紹過 resolve.extensions 用於配置在嘗試過程當中用到的後綴列表,默認是:

extensions: ['.js', '.json']

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

若是這個列表越長,或者正確的後綴在越後面,就會形成嘗試的次數越多,因此 resolve.extensions 的配置也會影響到構建的性能。
在配置 resolve.extensions 時你須要遵照如下幾點,以作到儘量的優化構建性能:

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

相關 Webpack 配置以下:

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

優化 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');

module.exports = {
  module: {
    // 獨完整的 `react.min.js` 文件就沒有采用模塊化,忽略對 `react.min.js` 文件的遞歸解析處理
    noParse: [/react\.min\.js$/],
  },
};
注意被忽略掉的文件裏不該該包含 importrequiredefine 等模塊化語句,否則會致使構建出的代碼中包含沒法在瀏覽器環境下執行的模塊化語句。

以上就是全部和縮小文件搜索範圍相關的構建性能優化了,在根據本身項目的須要去按照以上方法改造後,你的構建速度必定會有所提高。

本實例 提供項目完整代碼

《深刻淺出Webpack》全書在線閱讀連接

閱讀原文

相關文章
相關標籤/搜索