Node.js理論實踐之《Webpack原理及優化》

工做原理

基本概念

  • Entry:入口,Webpack 執行構建的第一步將從 Entry 開始。
  • Module:模塊,在 Webpack 裏一切皆模塊,一個模塊對應着一個文件。Webpack 會從配置的 Entry 開始遞歸找出全部依賴的模塊。
  • Chunk:代碼塊,一個chunk由多個模塊組合而成,用於代碼合併與分割。
  • Loader:模塊轉換器,用於將模塊的原內容按照需求轉換成新內容。
  • Plugin:插件,在 Webpack 構建流程中的特定時機會廣播出對應的事件,插件能夠監聽這些事件的發生,在特定時機作對應的事情。

流程歸納

初始化參數 ——> 開始編譯 ——> 肯定入口 ——> 編譯模塊 ——> 完成模塊編譯 ——> 輸出資源 ——> 輸出完成javascript

  1. 初始化參數:從配置文件(默認webpack.config.js)和shell語句中讀取與合併參數,得出最終的參數
  2. 開始編譯(compile):用上一步獲得的參數初始化Compiler對象,加載全部配置的插件Plugin,經過執行對象的run方法開始執行編譯
  3. 肯定入口:根據配置中的entry找出全部的入口文件
  4. 編譯模塊:從入口文件出發,調用全部配置的Loader對模塊進行編譯,再找出該模塊依賴的模塊,再遞歸本步驟直到全部入口依賴的文件都通過處理
  5. 完成編譯模塊:通過第四步以後,獲得了每一個模塊被翻譯以後的最終內容以及他們之間的依賴關係
  6. 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的chunk,再將每一個chunk轉換成一個單獨的文件加入輸出列表中,這是能夠修改輸出內容的最後機會
  7. 輸出完成:在肯定好輸出內容後,根據配置(webpack.config.js && shell)肯定輸出的路徑和文件名,將文件的內容寫入文件系統中(fs)

總結一下,Webpack的構建流程能夠分爲如下三大階段java

  1. 初始化:啓動構建,讀取與合併配置參數,加載 Plugin,實例化 Compiler。
  2. 編譯:從 Entry 發出,針對每一個 Module 串行調用對應的 Loader 去翻譯文件內容,再找到該 Module 依賴的 Module,遞歸地進行編譯處理。
  3. 輸出:對編譯後的 Module 組合成 Chunk,把 Chunk 轉換成文件,輸出到文件系統。

bundle.js

bundle.js實際上是一個當即執行函數,bundle.js能直接運行在瀏覽器中的緣由在於輸出的文件中經過__webpack_require__函數定義了一個能夠在瀏覽器中執行的加載函數來模擬Node.js中的require語句。 並且Webpack 作了緩存優化,執行加載過的模塊不會再執行第二次,執行結果會緩存在內存中,當某個模塊第二次被訪問時會直接去內存中讀取被緩存的返回值。node

(function(modules){
    //模擬require語句
    function __webpack_require__(){}
    //執行存放全部模塊數組中的第0個模塊(main.js)
    __webpack_require_[0]
})([/*存放全部模塊的數組*/])
複製代碼

性能優化

減小Webpack打包時間

  1. 優化Loader:優化 Loader 的文件搜索範圍(exclude掉node_modules)、將Babel編譯過的文件緩存起來(loader: 'babel-loader?cacheDirectory=true')。 對於 Loader 來講,影響打包效率首當其衝必屬 Babel 了。由於 Babel 會將代碼轉爲字符串生成 AST,而後對 AST 繼續進行轉變最後再生成新的代碼項目越大,轉換代碼越多,效率就越低
  2. HappyPack: 能夠將Loader的同步執行轉換爲並行的。
module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 後面的內容對應下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 開啓 4 個線程
    threads: 4
  })
]
複製代碼
  1. DllPlugin: 能夠將特定的類庫提早打包而後引入。極大的減小打包類庫的次數,只有當類庫更新版本纔有須要從新打包。
// 單獨配置在一個文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想統一打包的類庫
    vendor: ['react']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必須和 output.library 一致
      name: '[name]-[hash]',
      // 該屬性須要與 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

// 使用 DllReferencePlugin 將依賴文件引入項目中
// webpack.conf.js
module.exports = {
  // ...省略其餘配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是以前打包出來的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}
複製代碼
  1. 代碼壓縮:webpack3中使用 webpack-parallel-uglify-plugin 來並行運行 UglifyJS(單線程),webpack4中將mode設置爲production則默認開啓壓縮。

減小Webpack打包後的文件體積

  1. 按需加載:每一個路由頁面單獨打包爲一個文件、loadash 這種大型類庫一樣能夠使用這個功能。
  2. Scope Hoisting: 會分析出模塊之間的依賴關係,儘量的把打包出來的模塊合併到一個函數中去。Webpack4 中開啓這個功能,只須要啓用 optimization.concatenateModules
// test.js
export const a = 1
// index.js
import { a } from './test.js'
複製代碼

打包上面兩個文件後,生成代碼相似這樣:react

[
  /* 0 */
  function (module, exports, require) {
    //...
  },
  /* 1 */
  function (module, exports, require) {
    //...
  }
]
複製代碼

若是使用Scope Hositing,會生成這樣的相似代碼:webpack

[
  /* 0 */
  function (module, exports, require) {
    //...
  }
]
複製代碼
  1. Tree Shaking:能夠實現刪除項目中未被引用的代碼。Webpack4的生產環境默認開啓這個功能。
// test.js
export const a = 1
export const b = 2
// index.js
import { a } from './test.js'
複製代碼

test 文件中的變量 b 若是沒有在項目中使用到的話,就不會被打包到文件中。web

相關文章
相關標籤/搜索