Webpack學習-工作原理(下)
繼上篇文章介紹了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陣列中載入對應moduleId(index)的模組,與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陣列中載入對應moduleId(index)的模組,與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函式,用於從非同步載入的檔案中去安裝模組。