1. 程式人生 > >webpack 程式碼分離

webpack 程式碼分離

本指南擴充套件了起步管理輸出中提供的示例。請確保您至少已熟悉其中提供的示例。

程式碼分離是 webpack 中最引人注目的特性之一。此特效能夠把程式碼分離到不同的 bundle 中,然後可以按需載入或並行載入這些檔案。程式碼分離可以用於獲取更小的 bundle,以及控制資源載入優先順序,如果使用合理,會極大影響載入時間。

有三種常用的程式碼分離方法:

  • 入口起點:使用 entry 配置手動地分離程式碼。
  • 防止重複:使用 CommonsChunkPlugin 去重和分離 chunk。
  • 動態匯入:通過模組的行內函數呼叫來分離程式碼。

1. 入口起點(entry points)

這是迄今為止最簡單、最直觀的分離程式碼的方式。不過,這種方式手動配置較多,並有一些陷阱,我們將會解決這些問題。先來看看如何從 main bundle 中分離另一個模組:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- another-module.js
|- /node_modules

 

another-module.js

import _ from 'lodash';

console.log(
  _.join([
'Another', 'module', 'loaded!'], ' ') );

 

webpack.config.js

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: './src/index.js',
    another: './src/another-module.js'
  },
  plugins: [
    new HTMLWebpackPlugin({
      title: 
'Code Splitting' }) ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } };

 

這將生成如下構建結果:

Hash: 309402710a14167f42a8
Version: webpack 2.6.1
Time: 570ms
            Asset    Size  Chunks                    Chunk Names
  index.bundle.js  544 kB       0  [emitted]  [big]  index
another.bundle.js  544 kB       1  [emitted]  [big]  another
   [0] ./~/lodash/lodash.js 540 kB {0} {1} [built]
   [1] (webpack)/buildin/global.js 509 bytes {0} {1} [built]
   [2] (webpack)/buildin/module.js 517 bytes {0} {1} [built]
   [3] ./src/another-module.js 87 bytes {1} [built]
   [4] ./src/index.js 216 bytes {0} [built]

 

正如前面提到的,這種方法存在一些問題:

  • 如果入口 chunks 之間包含重複的模組,那些重複模組都會被引入到各個 bundle 中。
  • 這種方法不夠靈活,並且不能將核心應用程式邏輯進行動態拆分程式碼。

以上兩點中,第一點對我們的示例來說無疑是個問題,因為之前我們在 ./src/index.js 中也引入過 lodash,這樣就在兩個 bundle 中造成重複引用。接著,我們通過使用 CommonsChunkPlugin 來移除重複的模組。

2. 防止重複(prevent duplication)

CommonsChunkPlugin 外掛可以將公共的依賴模組提取到已有的入口 chunk 中,或者提取到一個新生成的 chunk。讓我們使用這個外掛,將之前的示例中重複的 lodash 模組去除:

webpack.config.js

 const path = require('path');
+ const webpack = require('webpack');
  const HTMLWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
      index: './src/index.js',
      another: './src/another-module.js'
    },
    plugins: [
      new HTMLWebpackPlugin({
        title: 'Code Splitting'
-     })
+     }),
+     new webpack.optimize.CommonsChunkPlugin({
+       name: 'common' // 指定公共 bundle 的名稱。
+     })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

 

這裡我們使用 CommonsChunkPlugin 之後,現在應該可以看出,index.bundle.js 中已經移除了重複的依賴模組。需要注意的是,CommonsChunkPlugin 外掛將 lodash 分離到單獨的 chunk,並且將其從 main bundle 中移除,減輕了大小。執行 npm run build 檢視效果:

Hash: 70a59f8d46ff12575481
Version: webpack 2.6.1
Time: 510ms
            Asset       Size  Chunks                    Chunk Names
  index.bundle.js  665 bytes       0  [emitted]         index
another.bundle.js  537 bytes       1  [emitted]         another
 common.bundle.js     547 kB       2  [emitted]  [big]  common
   [0] ./~/lodash/lodash.js 540 kB {2} [built]
   [1] (webpack)/buildin/global.js 509 bytes {2} [built]
   [2] (webpack)/buildin/module.js 517 bytes {2} [built]
   [3] ./src/another-module.js 87 bytes {1} [built]
   [4] ./src/index.js 216 bytes {0} [built]

 

以下是由社群提供的,一些對於程式碼分離很有幫助的外掛和 loaders:

CommonsChunkPlugin 外掛還可以通過使用顯式的 vendor chunks 功能,從應用程式程式碼中分離 vendor 模組。

3. 動態匯入(dynamic imports)

當涉及到動態程式碼拆分時,webpack 提供了兩個類似的技術。對於動態匯入,第一種,也是優先選擇的方式是,使用符合 ECMAScript 提案 的 import() 語法。第二種,則是使用 webpack 特定的 require.ensure。讓我們先嚐試使用第一種……

import() 呼叫使用會在內部用到 promises。如果在舊有版本瀏覽器中使用 import(),記得使用 一個 polyfill 庫(例如 es6-promise 或 promise-polyfill),來 shim Promise

在我們開始本節之前,先從配置中移除掉多餘的 entry 和 CommonsChunkPlugin,因為接下來的演示中並不需要它們:

webpack.config.js

  const path = require('path');
- const webpack = require('webpack');
  const HTMLWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
+     index: './src/index.js'
-     index: './src/index.js',
-     another: './src/another-module.js'
    },
    plugins: [
      new HTMLWebpackPlugin({
        title: 'Code Splitting'
-     }),
+     })
-     new webpack.optimize.CommonsChunkPlugin({
-       name: 'common' // 指定公共 bundle 的名稱。
-     })
    ],
    output: {
      filename: '[name].bundle.js',
+     chunkFilename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

 

注意,這裡使用了 chunkFilename,它決定非入口 chunk 的名稱。想了解 chunkFilename 更多資訊,請檢視 output 相關文件。接著,更新我們的專案,移除掉那些現在不會用到的檔案:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
- |- another-module.js
|- /node_modules

 

現在,我們不再使用靜態匯入 lodash,而是通過使用動態匯入來分離一個 chunk:

src/index.js

- import _ from 'lodash';
-
- function component() {
+ function getComponent() {
-   var element = document.createElement('div');
-
-   // Lodash, now imported by this script
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
+     var element = document.createElement('div');
+
+     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+     return element;
+
+   }).catch(error => 'An error occurred while loading the component');
  }

- document.body.appendChild(component());
+ getComponent().then(component => {
+   document.body.appendChild(component);
+ })

 

注意,在註釋中使用了 webpackChunkName。這樣做會導致我們的 bundle 被命名為 lodash.bundle.js,而不是 [id].bundle.js 。想了解更多關於 webpackChunkName 和其他可用選項,請檢視 import()相關文件。讓我們執行 webpack,檢視 lodash 是否會分離到一個單獨的 bundle:

Hash: a27e5bf1dd73c675d5c9
Version: webpack 2.6.1
Time: 544ms
           Asset     Size  Chunks                    Chunk Names
lodash.bundle.js   541 kB       0  [emitted]  [big]  lodash
 index.bundle.js  6.35 kB       1  [emitted]         index
   [0] ./~/lodash/lodash.js 540 kB {0} [built]
   [1] ./src/index.js 377 bytes {1} [built]
   [2] (webpack)/buildin/global.js 509 bytes {0} [built]
   [3] (webpack)/buildin/module.js 517 bytes {0} [built]

 

如果你已經通過類似 babel 的前處理器(pre-processor)啟用 async 函式,請注意,你可以像下面那樣簡化程式碼,因為 import() 語句恰好會返回 promises:

src/index.js

- function getComponent() {
+ async function getComponent() {
-   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
-     var element = document.createElement('div');
-
-     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
-
-     return element;
-
-   }).catch(error => 'An error occurred while loading the component');
+   var element = document.createElement('div');
+   const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
+
+   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+   return element;
  }

  getComponent().then(component => {
    document.body.appendChild(component);
  });

 

4. bundle 分析(bundle analysis)

如果我們以分離程式碼作為開始,那麼就以檢查模組作為結束,分析輸出結果是很有用處的。官方分析工具 是一個好的初始選擇。下面是一些社群支援(community-supported)的可選工具:

  • webpack-chart: webpack 資料互動餅圖。
  • webpack-visualizer: 視覺化並分析你的 bundle,檢查哪些模組佔用空間,哪些可能是重複使用的。
  • webpack-bundle-analyzer: 一款分析 bundle 內容的外掛及 CLI 工具,以便捷的、互動式、可縮放的樹狀圖形式展現給使用者。