webpack 進階

內容目錄:

  • 配置分離

  • code splitting 非同步載入

  • 理解 webpack chunk

  • webpack 調試

2.5.1 配置分離

在大型項目中,可能 webpack.config.js 會變得越來越臃腫,這個時候可以利用做 webpack-merge 插件。將配置定義在一個目錄下面的不同文件中,然後通過 webpack-merge 來合併成最終的配置。

webpack-merge 詳見 npmjs.com/package/webpa

2.5.2 code splitting 非同步載入

代碼分割不僅僅是提取業務的公共代碼,更應該關注的是實現代碼的按需載入。 通過在模塊中定義 split point ,webpack 會在打包的時候自動的分割代碼塊。定義 split point 的方式有兩種

CommonJs 方式

require.ensure 方法:

/**n * @param dependencies [Array] 模塊依賴的數組n * @param callback [Function]n */nrequire.ensure(dependencies, callback)n

eg:

// 模塊 index.js

/**n * [description]n * @param {[type]} ) { const async [description]n * @return {[type]} [description]n */nrequire.ensure([./async], function() {n const async = require(./async);n console.log(async.default)n});n

// 模塊 async.js

export default {n a: 1n}n

webpack 打包過後會生成三個文件

index.js 1.1.js vendor.common.jsn

index.js 的內容為

webpackJsonp([0],[n/* 0 */n/***/ function(module, exports, __webpack_require__) {nn use strict;n __webpack_require__.e/* nsure */(1, function () {n var async = __webpack_require__(1);n console.log(async.default);n });nn/***/ }n]);n//# sourceMappingURL=home.js.mapn

1.1.js 的內容為

webpackJsonp([1],[n/* 0 */,n/* 1 */n/***/ function(module, exports) {nn "use strict";n n Object.defineProperty(exports, "__esModule", {n value: truen });n /**n * async modulen */n n exports.default = {n a: 1n };nn/***/ }n]);n//# sourceMappingURL=1.1.js.mapn

webpackJsonp 方法定義在 vendor.bundle.js 中:

window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {n/******/ // add "moreModules" to the modules object,n/******/ // then flag all "chunkIds" as loaded and fire callbackn/******/ var moduleId, chunkId, i = 0, callbacks = [];n/******/ for(;i < chunkIds.length; i++) {n/******/ chunkId = chunkIds[i];n/******/ if(installedChunks[chunkId])n/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);n/******/ installedChunks[chunkId] = 0;n/******/ }n/******/ for(moduleId in moreModules) {n/******/ modules[moduleId] = moreModules[moduleId];n/******/ }n/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);n/******/ while(callbacks.length)n/******/ callbacks.shift().call(null, __webpack_require__);n/******/ if(moreModules[0]) {n/******/ installedModules[0] = 0;n/******/ return __webpack_require__(0);n/******/ }n/******/ };n

所以在 html 中應該先載入 vendor.bundle.js

<script src="js/vendor.bundle.js"></script>n<script src="js/index.js"></script>n

在使用非同步載入的時候需要注意一下兩點:

  1. 配置 output.publicPath: publicPath 為腳本在伺服器中的文字,只有定義了 publicPath 才能通過 webpackJsonp 獲取;

  2. 是對於非同步的 CommonJs 模塊引入只能通過 require 的方式引用,不能通過 Es6 import 的方式引入;

AMD 方式

AMD 的方式也可以實現非同步載入,這和使用 require.js 的使用方式基本相同,在定義模塊的時候需要按照 AMD 的規範來定義

/**n * @param dependencies [Array] 模塊依賴n * @param callback [Function]n */nrequire(dependencies, callback)n

eg:

// 定義模塊ndefine(modula-a, [module-c], function(c) {n //n return ...n})nn// 依賴模塊nrequire(["module-a", "module-b"], function(a, b) {n console.log(a, b);n});n

這時候的 require 實現是非同步的方式,只有依賴的模塊載入完成並執行回調,才會執行模塊的 callback,依賴模塊的回調結果會作為參數傳入 a, b 中。

2.5.3 代碼塊 Chunk

Chunk 是什麼?

webpack 中 Chunk 實際上就是輸出的 .js 文件,可能包含多個模塊,主要的作用是為了優化非同步載入。

Chunk 包含了哪些內容?

對於同步的情況,一個 Chunk 會遞歸的把模塊中所有的依賴都加入到 Chunk 中。

對於非同步的情況,在每一個 split point 上所有依賴的模塊會打包進一個新的 chunk,和同步一樣,依賴也是遞歸的,如果子模塊依賴其他模塊也會加入到 chunk 中,依賴的回調函數中 require 的其他模塊也會打包進 chunk 中,以下公式表示 chunk 內容:

chunk content = recursive(ensure 依賴) + recursive(callback 依賴)

Chunk 分類

Entry Chunk

入口代碼塊包含了 webpack 運行時需要的一些函數,如 webpackJsonp, __webpack_require__ 等以及依賴的一系列模塊。

Normal Chunk

普通代碼塊沒有包含運行時需要的代碼,只包含模塊代碼,其結構有載入方式決定,如基於 CommonJs 非同步的方式可能會包含 webpackJsonp 的調用。 The chunk also contains a list of chunk id that it fulfills.

Initial chunk

與入口代碼塊對應的一個概念是入口模塊(module 0),如果入口代碼塊中包含了入口模塊 webpack 會立即執行這個模塊,否則會等待包含入口模塊的代碼塊,包含入口模塊的代碼塊其實就是 initial chunk。 以上面的 CommonJs 非同步載入為例:

<!-- 入口 Chunk, 未包含入口模塊 -->n<script src="js/vendor.bundle.js"></script>n<!-- 包含入口模塊的 Initial Chunk,執行入口模塊 -->n<script src="js/index.js"></script>n

Commons chunk 與 CommonsChunkPlugin

之前我們已經利用 CommonsChunkPlugin 來分割公共代碼如 react, react-dom 到 vendor.bundle.js 中,這裡介紹相關的原理。

以下面的配置為例

var webpack = require("webpack");nmodule.exports = {n entry: { a: "./a", b: "./b" },n output: { filename: "[name].js" },n plugins: [ new webpack.optimize.CommonsChunkPlugin("init.js") ]n}n

當有多個入口的時候,CommonsChunkPlugin 會把 a,b 模塊公共依賴的模塊抽離出來,並加上 webpack 運行時代碼,形成一個新的代碼塊,這個代碼塊類型為 entry chunk。a,b 兩個入口會形成兩個單獨的代碼塊,這兩個代碼塊為 initial chunk。

在 html 中,可以如下載入:

<!-- entry chunk -->n<script src="init.js"></script>n<!-- inital chunk a -->n<script src="a.js"></script>n<!-- initial chunk b -->n<script src="b.js"></script>n

更多 CommonChunkPlugin 的參數參見 webpack.github.io/docs/

require.include

可以使用 require.include 方法,直接引入模塊,如下例子:

require.ensure(["./file"], function(require) {n require("./file2");n});nn// is equal tonnrequire.ensure([], function(require) {n require.include("./file");n require("./file2");n});n

這個方法可以實現一些分塊的優化,當一個 chunk-parent 可能會非同步引用多個 chunk-child 而這些 chunk-child 可能都包含了 moduleA, 那麼可以在 chunk-parent 中 require.include(moduleA) 就可以避免重複載入 moduleA。

給非同步 Chunk 命名

根據 split point 生成出來的 chunk 名稱都是數字,可以在 split point 上定義 chunk 名稱:

/**n * @param chunkName [String] chunk 名稱n */nrequire.ensure(dependencies, callBack, chunkName)n

也可以在 webpack.config.js 中配置修改 output.chunkName 來修改 chunk 名稱

Chunk 優化

在一些特殊的場景可以利用如下這些插件來完成 Chunk 的優化,

  • LimitChunkCountPlugin

  • MinChunkSizePlugin

  • AggressiveMergingPlugin

2.5.4 調試 webpack

在配置 webpack 的過程中,可以利用 webpack 提供的一些工具和參數來調試。

命令行參數

通過調用

$ webpack [--params,...]n

  1. --progress: 能夠看到打包進度

  2. --json: 可以將打包結果輸出為 json

  3. --display-chunks: 可以看到打包出來的 chunk 信息

更多的參數見 webpack.github.io/docs/

webpack analyse

analyse 地址:webpack.github.io/analy

可以通過 analyse 網站分析 webpack 的編譯結果,如下圖,可以分析 chunks, modules, Assets 等。

推薦閱讀:

前端構建系統 Gulp 的使用與常用插件推薦 - 上篇
2016前端探索總結——前端工程與未來
用 husky 和 lint-staged 構建超溜的代碼檢查工作流
2.1 前端工程化概述
如何將一個已經上線的項目前端部分平滑過渡至組件化和工程化?

TAG:React | 前端工程化 | 前端开发 |