webpack 打包後的文件以下,省略中間部分邏輯代碼,能夠看到是一個自執行函數,傳入了一個對象,該對象的鍵 ./src/index.js
和 ./src/title.js
都是文件路徑,值爲導出的模塊。javascript
// webpackBootstrap (function (modules) { // ... // ... // ... // Load entry module and return exports return __webpack_require__((__webpack_require__.s = "./src/index.js")); })({ "./src/index.js": function (module, exports, __webpack_require__) { const info = __webpack_require__(/*! ./title */ "./src/title.js"); console.log(info); }, "./src/title.js": function (module, exports) { module.exports = { name: "Nicholas C.Zakas", books: ["JavaScript高級程序設計"], }; }, });
接下來,逐個分析 bundle.js
中各個函數的功能。java
內部實現的一個自定義模塊引入函數webpack
installedModules
用於緩存已經加載過的模塊,根據 moduleId
存儲模塊導出內容web
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
json
執行模塊函數,好比這個:promise
function (module, exports, __webpack_require__) { const title = __webpack_require__(/*! ./title */ "./src/title.js"); console.log(title); },
call
函數傳入 module.exports
用於綁定執行函數內部 this
指針,後續再傳入三個函數 module
, module.exports
, __webpack_require__
瀏覽器
上述執行模塊函數執行後會給傳入的 module
參數的module.exports
進行賦值,改變 __webpack_require__
中 module
對象,最後進行導出;緩存
// The module cache var installedModules = {}; // The require function function __webpack_require__(moduleId) { // Check if module is in cache if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag the module as loaded module.l = true; // Return the exports of the module return module.exports; }
export
對象定義新屬性,屬性值是一個 getter
函數// define getter function for harmony exports __webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { enumerable: true, get: getter }); } };
定義 ES6 模塊,在 exports
對象上定義 __esModule
屬性;app
給傳入的對象定義 __esModule
屬性,而且改變對象的 toStringTag
屬性值爲 Module
,使得 Object.prototype.toString.call(exports)
值爲 [object Module]
異步
// define __esModule on exports __webpack_require__.r = function(exports) { if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); } Object.defineProperty(exports, '__esModule', { value: true }); };
// create a fake namespace object // mode & 1: value is a module id, require it // mode & 2: merge all properties of value into the ns // mode & 4: return value when already ns object // mode & 8|1: behave like require __webpack_require__.t = function(value, mode) { if(mode & 1) value = __webpack_require__(value); if(mode & 8) return value; if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; var ns = Object.create(null); __webpack_require__.r(ns); Object.defineProperty(ns, 'default', { enumerable: true, value: value }); if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); return ns; };
__webpack_require__.n
返回的對象的 a
屬性能夠找到默認導出。// getDefaultExport function for compatibility with non-harmony modules __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
函數的包裝// Object.prototype.hasOwnProperty.call __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
chunkId
,返回 Promise對象installedChunkData
保存了[resolve, reject, promise]// This file contains only the entry chunk. // The chunk loading function for additional chunks __webpack_require__.e = function requireEnsure(chunkId) { var promises = []; // JSONP chunk loading for javascript var installedChunkData = installedChunks[chunkId]; if (installedChunkData !== 0) { // 0 means "already installed". // a Promise means "currently loading". if (installedChunkData) { promises.push(installedChunkData[2]); } else { // setup Promise in chunk cache var promise = new Promise(function (resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); promises.push((installedChunkData[2] = promise)); // start chunk loading var script = document.createElement("script"); var onScriptComplete; script.charset = "utf-8"; script.timeout = 120; if (__webpack_require__.nc) { script.setAttribute("nonce", __webpack_require__.nc); } script.src = jsonpScriptSrc(chunkId); // create error before stack unwound to get useful stacktrace later var error = new Error(); onScriptComplete = function (event) { // avoid mem leaks in IE. script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if (chunk !== 0) { if (chunk) { var errorType = event && (event.type === "load" ? "missing" : event.type); var realSrc = event && event.target && event.target.src; error.message = "Loading chunk " + chunkId + " failed.\n(" + errorType + ": " + realSrc + ")"; error.name = "ChunkLoadError"; error.type = errorType; error.request = realSrc; chunk[1](error); } installedChunks[chunkId] = undefined; } }; var timeout = setTimeout(function () { onScriptComplete({ type: "timeout", target: script }); }, 120000); script.onerror = script.onload = onScriptComplete; document.head.appendChild(script); } } return Promise.all(promises); };
// index.js const info = require("./title"); console.log(info);
// title.js module.exports = { name: "Nicholas C.Zakas", books: ['JavaScript高級程序設計'] }
// bundle.js (function (modules) { // ... // ... // ... // Load entry module and return exports return __webpack_require__((__webpack_require__.s = "./src/index.js")); })({ "./src/index.js": function (module, exports, __webpack_require__) { const info = __webpack_require__(/*! ./title */ "./src/title.js"); console.log(info); }, "./src/title.js": function (module, exports) { module.exports = { name: "Nicholas C.Zakas", books: ["JavaScript高級程序設計"], }; }, });
僅僅使用 webpack 內部定義的 __webpack_require__
函數實現了模塊的導入。
// index.js const info = require("./title"); console.log(info);
// title.js export const name = "Nicholas C.Zakas"; const books = ["JavaScript高級程序設計"]; export default books;
// bundle.js (function (modules) { // ... // ... // ... // Load entry module and return exports return __webpack_require__((__webpack_require__.s = "./src/index.js")); })({ "./src/index.js": function (module, exports, __webpack_require__) { const info = __webpack_require__(/*! ./title */ "./src/title.js"); console.log(info); }, "./src/title.js": function ( module, __webpack_exports__, __webpack_require__ ) { "use strict"; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "name", function () { return name; }); const name = "Nicholas C.Zakas"; const books = ["JavaScript高級程序設計"]; /* harmony default export */ __webpack_exports__["default"] = books; }, });
**注意:**這裏在 return name
後 const name
並無錯,使用了 Object.defineProperty
給對象定義 get 屬性時,在讀取該對象的 name
屬性時纔會訪問棧內存中的 name
,因此說這裏並無錯。
以下代碼就是示例:
let obj = {}; Object.defineProperty(obj, "name", { enumerable: true, get: function () { return name; }, }); const name = "webpack"; console.log(obj.name); // webpack
執行函數分析
__webpack_require__.r
給 __webpack_exports__
對象上定義 __esModule
屬性並改變它的 toStringTags
值爲 Module
;__webpack_require__.d
經過 Object.defineProperty
定義導出變量 name
的 getter
;__webpack_exports__
對象的 default
上定義 ES6 默認導出的值// index.js import books, { name } from "./title"; console.log(books); console.log(name);
// title.js export const name = "Nicholas C.Zakas"; const books = ["JavaScript高級程序設計"]; export default books;
// bundle.js (function (modules) { // ... // ... // ... // Load entry module and return exports return __webpack_require__((__webpack_require__.s = "./src/index.js")); })({ "./src/index.js": function ( module, __webpack_exports__, __webpack_require__ ) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./title */ "./src/title.js" ); console.log(_title__WEBPACK_IMPORTED_MODULE_0__["default"]); console.log(_title__WEBPACK_IMPORTED_MODULE_0__["name"]); }, "./src/title.js": function ( module, __webpack_exports__, __webpack_require__ ) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d( __webpack_exports__, "name", function () { return name; } ); const name = "Nicholas C.Zakas"; const books = ["JavaScript高級程序設計"]; /* harmony default export */ __webpack_exports__["default"] = books; }, });
./src/index.js
函數裏面也添加了 ES6 模塊規則,使用了 __webpack_require__.r
函數給 __webpack_exports__
對象添加 __esModule
_title__WEBPACK_IMPORTED_MODULE_0__
能夠拿到導出對象的 default
和 name
值// index.js import info, { name } from "./title"; console.log(info); console.log(name);
// title.js module.exports = { name: "Nicholas C.Zakas", books: ['JavaScript高級程序設計'] }
// bundle.js (function (modules) { // ... // ... // ... // Load entry module and return exports return __webpack_require__((__webpack_require__.s = "./src/index.js")); })({ "./src/index.js": function ( module, __webpack_exports__, __webpack_require__ ) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./title */ "./src/title.js" ); /* harmony import */ var _title__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/ __webpack_require__.n( _title__WEBPACK_IMPORTED_MODULE_0__ ); console.log(_title__WEBPACK_IMPORTED_MODULE_0___default.a); console.log(_title__WEBPACK_IMPORTED_MODULE_0__["name"]); }, "./src/title.js": function (module, exports) { module.exports = { name: "Nicholas C.Zakas", books: ["JavaScript高級程序設計"], }; }, });
__webpack_require__.n
返回了一個 getter
,並在 getter 上定義了一個 a
屬性做爲默認導出,不管是 commonjs 的默認導出仍是 ES6 的默認導出。採用監聽按鈕點擊事件動態引入模塊的demo。
使用了 __webpack_require__.e
作模塊動態引入
__webpack_require__.e
函數建立 script 標籤並插入 body 使得瀏覽器訪問 0.bundle.js
0.bundle.js 中調用了 push
方法,然而 push 方法已經在 bundle.js 中被重寫爲 webpackJsonpCallback
,調用 push
等於調用 webpackJsonpCallback
,調用 webpackJsonpCallback
就會調用以下邏輯,使得 promise
被 resolve
resolve
了 Promise ,就能夠調用 then
函數的方法
while(resolves.length) { resolves.shift()(); }
// title.js module.exports = { name: "Nicholas C.Zakas", books: ['JavaScript高級程序設計'] }
// index.js document.getElementById("btn").addEventListener("click", () => { import("./title").then((res) => { console.log(res); }); });
打包後的文件
// 0.bundle.jsjs (window["webpackJsonp"] = window["webpackJsonp"] || []).push([ [0], { "./src/title.js": function (module, exports) { module.exports = { name: "Nicholas C.Zakas", books: ["JavaScript高級程序設計"], }; }, }, ]);
// script path function function jsonpScriptSrc(chunkId) { return __webpack_require__.p + "" + chunkId + ".bundle.js"; } // This file contains only the entry chunk. // The chunk loading function for additional chunks __webpack_require__.e = function requireEnsure(chunkId) {}