Webpack版本
分析版本為3.6.0
4.0為最近升級的版本,與之前版本變化較大,編譯輸出的文件與3.0版本會不一致,目前項目中使用的版本3.0版本,所以基于3.0版本進行分析學習。
Webpack構建流程
- 初始化:啟動構建,讀取與合并配置參數,加載Plugin,實例化Complier
- 編譯:從Entry發出,針對每個Module串行調用對應的Loader去轉換文件內容,再找到該Module依賴的Module,遞歸進行編譯處理。
- 輸出:對編譯后對Module組合成Chunk,把Chunk轉換成文件,輸出到文件系統。
輸出文件分析
原輸出文件結構:
簡化后的文件結構:
(function(modules) {// 模擬 require 語句function __webpack_require__() {}// 執行存放所有模塊數組中的第0個模塊__webpack_require__(0);
})([/*存放所有模塊的數組*/])
分割代碼時輸出
當使用按需加載加載文件時,Webpack的輸出文件會發生變化。
// 異步加載 show.js
import('./show').then((show) => {// 執行 show 函數show('Webpack');
});
重新構建后會輸出兩個文件,分別是執行入口文件bundle.js和異步加載文件0.bundle.js
異步加載文件默認輸入的文件名為 [id].js,可以在Webpack配置文件的output項中配置輸出文件名
其中0.bundle.js內容如下:
// 加載在本文件(0.bundle.js)中包含的模塊
webpackJsonp(// 在其它文件中存放著的模塊的 ID[0],// 本文件所包含的模塊[// show.js 所對應的模塊(function (module, exports) {function show(content) {window.document.getElementById('app').innerText = 'Hello,' + content;}module.exports = show;})]
);
bundle.js內容如下:
(function (modules) {/**** webpackJsonp 用于從異步加載的文件中安裝模塊。* 把 webpackJsonp 掛載到全局是為了方便在其它文件中調用。** @param chunkIds 異步加載的文件中存放的需要安裝的模塊對應的 Chunk ID* @param moreModules 異步加載的文件中存放的需要安裝的模塊列表* @param executeModules 在異步加載的文件中存放的需要安裝的模塊都安裝成功后,需要執行的模塊對應的 index*/window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {// 把 moreModules 添加到 modules 對象中// 把所有 chunkIds 對應的模塊都標記成已經加載成功 var moduleId, chunkId, i = 0, resolves = [], result;for (; i < chunkIds.length; i++) {chunkId = chunkIds[i];if (installedChunks[chunkId]) {resolves.push(installedChunks[chunkId][0]);}installedChunks[chunkId] = 0;}for (moduleId in moreModules) {if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {modules[moduleId] = moreModules[moduleId];}}while (resolves.length) {resolves.shift()();}};// 緩存已經安裝的模塊var installedModules = {};// 存儲每個 Chunk 的加載狀態;// 鍵為 Chunk 的 ID,值為0代表已經加載成功var installedChunks = {1: 0};// 模擬 require 語句,和上面介紹的一致function __webpack_require__(moduleId) {// ... 省略和上面一樣的內容}/*** 用于加載被分割出去的,需要異步加載的 Chunk 對應的文件* @param chunkId 需要異步加載的 Chunk 對應的 ID* @returns {Promise}*/__webpack_require__.e = function requireEnsure(chunkId) {// 從上面定義的 installedChunks 中獲取 chunkId 對應的 Chunk 的加載狀態var installedChunkData = installedChunks[chunkId];// 如果加載狀態為0表示該 Chunk 已經加載成功了,直接返回 resolve Promiseif (installedChunkData === 0) {return new Promise(function (resolve) {resolve();});}// installedChunkData 不為空且不為0表示該 Chunk 正在網絡加載中if (installedChunkData) {// 返回存放在 installedChunkData 數組中的 Promise 對象return installedChunkData[2];}// installedChunkData 為空,表示該 Chunk 還沒有加載過,去加載該 Chunk 對應的文件var promise = new Promise(function (resolve, reject) {installedChunkData = installedChunks[chunkId] = [resolve, reject];});installedChunkData[2] = promise;// 通過 DOM 操作,往 HTML head 中插入一個 script 標簽去異步加載 Chunk 對應的 JavaScript 文件var head = document.getElementsByTagName('head')[0];var script = document.createElement('script');script.type = 'text/javascript';script.charset = 'utf-8';script.async = true;script.timeout = 120000;// 文件的路徑為配置的 publicPath、chunkId 拼接而成script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";// 設置異步加載的最長超時時間var timeout = setTimeout(onScriptComplete, 120000);script.onerror = script.onload = onScriptComplete;// 在 script 加載和執行完成時回調function onScriptComplete() {// 防止內存泄露script.onerror = script.onload = null;clearTimeout(timeout);// 去檢查 chunkId 對應的 Chunk 是否安裝成功,安裝成功時才會存在于 installedChunks 中var chunk = installedChunks[chunkId];if (chunk !== 0) {if (chunk) {chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));}installedChunks[chunkId] = undefined;}};head.appendChild(script);return promise;};// 加載并執行入口模塊,和上面介紹的一致return __webpack_require__(__webpack_require__.s = 0);
})
(// 存放所有沒有經過異步加載的,隨著執行入口文件加載的模塊[// main.js 對應的模塊(function (module, exports, __webpack_require__) {// 通過 __webpack_require__.e 去異步加載 show.js 對應的 Chunk__webpack_require__.e(0).then(__webpack_require__.bind(null, 1)).then((show) => {// 執行 show 函數show('Webpack');});})]
);
這里的 bundle.js 和上面所講的 bundle.js 非常相似,區別在于:
- 多了一個 webpack_require.e 用于加載被分割出去的,需要異步加載的 Chunk 對應的文件;
- 多了一個 webpackJsonp 函數用于從異步加載的文件中安裝模塊。
參考
深入淺出WebPack