天天看點

Webpack的DllPlugin和DLLReferencePlugin應用

一:作用

Webpack官網中對這兩個插件的介紹:DLLPlugin 和 DLLReferencePlugin 能實作了拆分 bundles,同時還大大提升了建構的速度。實際上就是可以事先将常用的基礎子產品(如React、react-dom)等抽離出來,打包到一個個單獨的動态連結庫(dll)中去,後面再打包業務代碼時若遇到需要導入的子產品存在于前面打包好的某個動态連結庫中時,就跳過相關的依賴的打包,直接使用動态連結庫中的代碼,以此來縮短webpack建構時間,提升建構速度。

二:項目背景

目前所在項目是一個內建了很多事項服務的一戶通App,其中包含門戶主系統以及根據不同的事項拆分出的多個系統,一個事項對應一個前端工程。為提高加載速度,增強使用者體驗,考慮将H5建構産物打進離線包,但因事項數量會不斷增加,将所有事項打入離線包會導緻離線包大小不斷膨脹。雖然将事項全部代碼打入離線包不可行,但各個事項工程有公共的依賴,如React、antd-mobile、項目組内封裝的公共的元件庫、jsApi、以及ahooks、query-string等,可以考慮将公共依賴從bundles中拆分出來,隻将公共依賴加入離線包,能最高成本效益地控制離線包大小,并能減少加載事項服務時的網絡延遲,提高加載速度。

三:使用介紹

Webpack 内置了對動态連結庫的支援,需要通過 2 個内置插件通路。DllPlugin:在一個額外的webpack配置中設定,用于将指定子產品抽離,打包出動态連結庫檔案;DLLReferencePlugin:用于在webpack主配置檔案中配置如何引入已經打包好的動态連結庫檔案。

下面以基本的 Demo 為例,介紹 DllPlugin和DLLReferencePlugin的使用。

第一步:建構出動态連結庫檔案

假設項目中需要将react、react-dom、antd、ahooks拆分出來,打包成動态連結庫檔案。我們首先需要建立一個webpack配置檔案,因為動态連結庫相關産物需要由一份獨立的建構輸出。

1.1 在根目錄下建立Webpack配置檔案vendors.webpack.config.js,配置主要内容如下:

const webpack = require('webpack');
const path = require('path');

module.exports = {
mode: 'production',
entry: {
  // 把 React 相關子產品的放到一個單獨的動态連結庫
  react: ['react', 'react-dom'],
  antd: ['antd'],
  vendors: ['ahooks'],
},
output: {
  // 輸出的動态連結庫的檔案名稱,[name] 代表目前動态連結庫的名稱,
  // 也就是 entry 中配置的 react 、 antd 和 vendors
  path: path.join(__dirname, 'dist'),
  filename: `[name].dll.js`,
  // 存放動态連結庫的全局變量名稱
  library: '[name]_dll',
},
plugins: [
  new webpack.DllPlugin({
    path: path.join(__dirname, `dist/[name].manifest.json`),
    name: '[name]_dll',
  }),
],
};           

1.2 執行建構指令

npx webpack --config vendors.webpack.config.js           

1.3 建構産物

├── dist
  ├── antd.dll.js
  ├── antd.manifest.json
  ├── react.dll.js
  ├── react.manifest.json
  ├── vendors.dll.js
  └── vendors.manifest.json           

其中包含三個動态連結庫檔案,分别是antd.dll.js、react.dll.js、vendors.dll.js,以 react.dll.js 檔案為例,其檔案内容大緻如下:

var react_dll = (function(e) {
// ... 
}({
0: function(e, t, n) {
  // 子產品 ID 為 0 的子產品對應的代碼
},
141: function(e, t, n) {
  // 子產品 ID 為 141 的子產品對應的代碼
},
// ... 此處省略剩下的子產品對應的代碼 
}));           

産物中react_dll.js中包含了子產品的代碼,子產品存放在一個對象中,用對象的key作為 ID。 并且還通過 

react_dll 變量把自己暴露在了全局中,也就是可以通過 window.react_dll(ID) 可以通路到它裡面包含的子產品。

再看另一種産物 *.manifest.json, 以react.manifest.json為例子:

{
"name": "react_dll",
"content": {
  "./node_modules/react/index.js": { "id": 0, "buildMeta": { "providedExports": true } },
  "./node_modules/react-dom/index.js": { "id": 43, "buildMeta": { "providedExports": true } },
  "./node_modules/object-assign/index.js": {
    "id": 141,
    "buildMeta": { "providedExports": true }
  },
  "./node_modules/react/cjs/react.production.min.js": {
    "id": 201,
    "buildMeta": { "providedExports": true }
  },
  "./node_modules/react-dom/cjs/react-dom.production.min.js": {
    "id": 369,
    "buildMeta": { "providedExports": true }
  },
  "./node_modules/scheduler/index.js": { "id": 370, "buildMeta": { "providedExports": true } },
  "./node_modules/scheduler/cjs/scheduler.production.min.js": {
    "id": 371,
    "buildMeta": { "providedExports": true }
  }
}
}           

可見 manifest.json 檔案清楚地描述了與其對應的 dll.js 檔案中包含了哪些子產品,以及每個子產品的路徑和 ID。

第二步:在主webpack檔案中使用動态連結庫檔案

2.1 在根目錄下Webpack主配置檔案webpack.config.js,配置主要内容如下:

const path = require('path');
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

module.exports = {
entry: {
  main: './main.js'
},
output: {
  filename: '[name].js',
  path: path.resolve(__dirname, 'dist'),
},
plugins: [
  // 告訴 Webpack 使用了哪些動态連結庫
  new DllReferencePlugin({
    manifest: require('./dist/antd.manifest.json'),
  }),
  new DllReferencePlugin({
    manifest: require('./dist/react.manifest.json'),
  }),
  new DllReferencePlugin({
    manifest: require('./dist/vendors.manifest.json'),
  }),
],
};           

2.2 結合umi使用的場景及建構産物(即在umi中實作2.1的webpack配置)

import { defineConfig } from 'umi';

export default defineConfig({
// ... umi其他配置
chainWebpack(config, { webpack }) {
  config.plugin('DllReferencePlugin-0').use(
    new webpack.DllReferencePlugin({
      manifest: require(`./dist/antd.manifest.json`),
    }),
  );
  config.plugin('DllReferencePlugin-1').use(
    new webpack.DllReferencePlugin({
      manifest: require(`./dist/react.manifest.json`),
    }),
  );
  config.plugin('DllReferencePlugin-2').use(
    new webpack.DllReferencePlugin({
      manifest: require(`./dist/vendors.manifest.json`),
    }),
  );
},
});           

産物:

DONE  Compiled successfully in 34147ms 
 File                         Size                   Gzipped
 dist/umi.d7332489.js         2.0 MB                 691.9 KB
 dist/umi.26e98a26.css        185.3 KB               33.3 KB           

效果對比:

若是去掉DllPlugin和DLLReferencePlugin相關的配置,建構出的産物:

DONE  Compiled successfully in 58566ms 
 File                         Size                   Gzipped
 dist/umi.e3d6af5a.js         3.2 MB                 1.0 MB
 dist/umi.26e98a26.css        185.3 KB               33.3 KB           

可見使用DllPlugin和DLLReferencePlugin後線上包建構産物的體積已經減少50%,時間上也加快了40%,上面的例子隻是提取了react、antd-mobile等4個公共依賴,若是抽取全部的公共依賴,這個效果會更好。

第三步:在index.html中引入*.dll.js

最後我們需要在index.html中引入*.dll.js以及umi.js才能使項目正常運轉。可以使用umi中的addHTMLHeadScripts配置,其他方法在此就不再詳細介紹。

最終生成的index.html:

Webpack的DllPlugin和DLLReferencePlugin應用