webpack 打包後 bundle.js 文件分析

Bundle Analysis

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_require_ 函數

內部實現的一個自定義模塊引入函數webpack

  • installedModules 用於緩存已經加載過的模塊,根據 moduleId 存儲模塊導出內容web

    • i: 模塊id
    • l: 是否 loaded
    • exports: 模塊導出的對象
  • 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;
 	}

_webpack_require_.d 函數

  • 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 });
 		}
 	};

_webpack_require_.r 函數

  • 定義 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 });
};

_webpack_require_.t

  • 對這個邏輯很無語,不想解釋,使用 & 運算符作邏輯判斷真的過份!
// 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

  • 定義默認導出,在 調用 __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;
};

_webpack_require_.o 函數

  • hasOwnProperty 函數的包裝
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

_webpack_require_.e 函數

  • 接收 chunkId ,返回 Promise對象
  • installedChunkData 保存了[resolve, reject, promise]
  • body 中插入 script 標籤
// 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);
};

同步加載模塊

CommonJS 加載 CommonJS

// 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__ 函數實現了模塊的導入。

CommonJS 加載 ES6

// 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 nameconst 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 定義導出變量 namegetter;
  • __webpack_exports__ 對象的 default 上定義 ES6 默認導出的值

在這裏插入圖片描述

ES6加載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__ 能夠拿到導出對象的 defaultname

ES6加載CommonJS

// 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 就會調用以下邏輯,使得 promiseresolve

  • 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) {}
相關文章
相關標籤/搜索