Webpack學習-工作原理(下)

25minutes發表於2021-09-09

繼上篇介紹了Webpack的基本概念完整流程以及打包過程中廣播的一些事件的作用這篇文章主要講生成的chunk檔案如何輸出成具體的檔案。分同步和非同步兩種情況來分析輸出的檔案使用的webpack版本3.8.0

模組檔案show.js

    function show(content) {
        window.document.getElementById('app').innerText = 'Hello,' + content;
    }

    // 透過 CommonJS 規範匯出 show 函式
    module.exports = show;

同步載入

// main.js

import show from './show';

show('TWENTY-FOUR K');

生成的bundle檔案

// webpackBootstrap啟動函式
// modules存放的是所有模組的陣列每個模組都是一個函式
(function(modules) {
	var installedModules = {}; // 快取安裝過的模組提升效能
	//去傳入的modules陣列中載入對應moduleIdindex的模組與node的require語句相似
	function __webpack_require__(moduleId) {
		// 檢查是否已經載入過如果有的話直接從快取installedModules中取出
		if(installedModules[moduleId]) {
			return installedModules[moduleId].exports;
		}
        // 如果沒有的話就直接新建一個模組並且存進快取installedModules
		var module = installedModules[moduleId] = {
			i: moduleId, // 對應modules的索引index也就是moduleId
			l: false, // 標誌模組是否已經載入
			exports: {}
		};
        // 執行對應模組函式並且傳入需要的引數
		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
		// 將標誌設定為true
		module.l = true;
		// 返回這個模組的匯出值
		return module.exports;
	}
	// 儲存modules陣列
	__webpack_require__.m = modules;
    // 儲存快取installedModules
	__webpack_require__.c = installedModules;
	// 為Harmony匯出定義getter函式
	__webpack_require__.d = function(exports, name, getter) {
		if(!__webpack_require__.o(exports, name)) {
			Object.defineProperty(exports, name, {
				configurable: false,
				enumerable: true,
				get: getter
			});
		}
	};
	// 用於與非協調模組相容的getdefaultexport函式將module.default或非module宣告成getter函式的a屬性上
	__webpack_require__.n = function(module) {
		var getter = module && module.__esModule ?
			function getDefault() { return module['default']; } :
			function getModuleExports() { return module; };
		__webpack_require__.d(getter, 'a', getter);
		return getter;
	};
	// 工具函式hasOwnProperty
	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
	// webpack配置中的publicPath用於載入被分割出去的非同步程式碼
	__webpack_require__.p = "";
	// 使用__webpack_require__函式去載入index為0的模組並且返回index為0的模組也就是主入口檔案的main.js的對應檔案__webpack_require__.s的含義是啟動模組對應的index
	return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
// 設定__esModule為true影響__webpack_require__.n函式的返回值
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// 同步載入index為1的依賴模組
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__show__ = __webpack_require__(1);
// 獲取index為1的依賴模組的export
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__show___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__show__);
// 同步載入


__WEBPACK_IMPORTED_MODULE_0__show___default()('wushaobin');

/***/ }),
/* 1 */
/***/ (function(module, exports) {

function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 透過 CommonJS 規範匯出 show 函式
module.exports = show;


/***/ })
]);

非同步載入

// main.js

// 非同步載入 show.js
import('./show').then((show) => {
  // 執行 show 函式
  show('TWENTY-FOUR K');
});


經webpack打包會生成兩個檔案0.bundle.js和bundle.js怎麼做的原因是可以吧show.js以非同步載入形式引入這也是分離程式碼達到減少檔案體積的最佳化方法兩個檔案分析如下。

0.bundle.js

// 載入本檔案0.bundle.js包含的模組 webpackJsonp用於從非同步載入的檔案中安裝模組掛載至全域性bundle.js供其他檔案使用
webpackJsonp(
// 在其他檔案中存放的模組id
[0],[
// 本檔案所包含的模組

/***/ (function(module, exports) {

function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 透過 CommonJS 規範匯出 show 函式
module.exports = show;


/***/ })
]);

bundle.js

 (function(modules) { // webpackBootstrap啟動函式
 	// 安裝用於塊載入的JSONP回撥
 	var parentJsonpFunction = window["webpackJsonp"];
	// chunkIds 非同步載入檔案0.bundle.js中存放的需要安裝的模組對應的chunkId
	// moreModules 非同步載入檔案0.bundle.js中存放需要安裝的模組列表
	// executeModules 非同步載入檔案0.bundle.js中存放需要安裝的模組安裝後需要執行的模組對應的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];
 			}
 		}
 		if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
 		// 執行
		while(resolves.length) {
 			resolves.shift()();
 		}

 	};

 	// 快取已經安裝的模組
 	var installedModules = {};

 	// 儲存每個chunk的載入狀態
	// 鍵為chunk的id值為0代表載入成功
 	var installedChunks = {
 		1: 0
 	};

 	// 去傳入的modules陣列中載入對應moduleIdindex的模組與node的require語句相似同上此處省略
 	function __webpack_require__(moduleId) {
		...
 	}

 	// 用於載入被分割出去的需要非同步載入的chunk對應的檔案
	// chunkId需要非同步載入的chunk對應的id返回的是一個promise
 	__webpack_require__.e = function requireEnsure(chunkId) {
		// 從installedChunks中獲取chunkId對應的chunk檔案的載入狀態
 		var installedChunkData = installedChunks[chunkId];
		// 如果載入狀態為0則表示該chunk已經載入成功直接返回promise resolve
 		if(installedChunkData === 0) {
 			return new Promise(function(resolve) { resolve(); });
 		}

		// installedChunkData不為空且不為0時表示chunk正在網路載入中
 		if(installedChunkData) {
 			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;
		// HTMLElement 介面的 nonce 屬性返回只使用一次的加密數字被內容安全政策用來決定這次請求是否被允許處理。
 		if (__webpack_require__.nc) {
 			script.setAttribute("nonce", __webpack_require__.nc);
 		}
		// 檔案的路徑由配置的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);
			
 			var chunk = installedChunks[chunkId];
			// 判斷chunkid對應chunk是否安裝成功
 			if(chunk !== 0) {
 				if(chunk) {
 					chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
 				}
 				installedChunks[chunkId] = undefined;
 			}
 		};
 		head.appendChild(script);

 		return promise;
 	};

 	// 這裡會給__webpack_require__設定多個屬性和方法同上此處省略
 	

 	// 非同步載入的出錯函式
 	__webpack_require__.oe = function(err) { console.error(err); throw err; };

 	// Load entry module and return exports
 	return __webpack_require__(__webpack_require__.s = 0);
 })
/************************************************************************/
 ([
// 存放沒有經過非同步載入的隨著執行入口檔案載入的模組也就是同步的模組
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
// 透過__webpack_require__.e非同步載入show.js對應的chunk
// 非同步載入 show.js
__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then((show) => {
  // 執行 show 函式
  show('Webpack');
});


/***/ })
 ]);

總結

這裡我們透過對比同步載入和非同步載入的簡單應用去分析兩種情況webpack打包出來檔案的差異性我們發現對於非同步載入的bundle.js與同步的bundle.js基本相似都是模擬node.js的require語句去匯入模組有所不同的是多了一個__webpack_require__.e函式用來載入分割出的需要非同步載入的chunk對應的檔案以及一個wepackJsonp函式用於從非同步載入的檔案中去安裝模組。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2822294/,如需轉載,請註明出處,否則將追究法律責任。

相關文章