跟著webpack產物來學習webpack是如何做模組處理

語言: CN / TW / HK
ead>

theme: channing-cyan

一、思考

webpack是何如實現 ESM(Es Module) 與 CMS(Commonjs) 相互匯出引用呢?帶著這些問題,咱們通過簡單檔案的打包來分析不同型別的模組是如何處理的

二、極簡單的程式碼

webpack.config.js

```js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = { devtool: "none", mode: "development", entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist"), }, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", }), ], }; ```

index.js

js const home = require("./home"); console.log("Hello World"); console.log(home);

home.js

js module.exports = "我是主頁面";

index.html

```html

跟著webpack產物來學習webpack是如何做模組處理

跟著webpack產物來學習webpack是如何做模組處理

```

三、分析產物的公共方法和屬性

1. 完整的產物

js (function (modules) { // webpackBootstrap // 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; } // expose the modules object (__webpack_modules__) __webpack_require__.m = modules; // expose the module cache __webpack_require__.c = installedModules; // 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 }); } }; // 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; }; // 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; }; // Object.prototype.hasOwnProperty.call __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; // __webpack_public_path__ __webpack_require__.p = ""; // Load entry module and return exports return __webpack_require__((__webpack_require__.s = "./src/index.js")); })({ "./src/home.js": /*! no static exports found */ function (module, exports) { module.exports = "我是主頁面"; }, "./src/index.js": /*! no static exports found */ function (module, exports, __webpack_require__) { const home = __webpack_require__(/*! ./home */ "./src/home.js"); console.log("Hello World"); console.log(home); }, }); 是一個IIFE模組,實參是moduleId 和 對應的模組(函式)

2. 公共方法分析

a. 模組匯入方法:__webpack_require__

js // 模組匯入方法:快取模組資訊,執行模組函式,將模組的匯出return出去 function __webpack_require__(moduleId) { // 快取已經載入過的模組,比如:key是 ./src/index.js,value 是模組資訊 if (installedModules[moduleId]) { // 將模組匯出的內容 return 出去 return installedModules[moduleId].exports; } // 建立一個新的模組,並將其放入快取中 var module = (installedModules[moduleId] = { i: moduleId, // 模組的id 比如:./src/index.js l: false, // 標記當前模組是否被載入 當時是未被載入 exports: {}, // 儲存模組匯出的內容 }); // Execute the module function // 執行模組函式 modules[moduleId].call( module.exports, // 模組內的this執行的是模組匯出的內容 module, // 當前的模組 module.exports, // 儲存模組匯出的內容 __webpack_require__ // // 匯入方法 ); // 標記當前模組已經被載入 module.l = true; // 返回模組匯出的內容 return module.exports; }

b. 判斷物件上是否有 xxx 屬性

js // 判斷物件上是否有 xxx 屬性 __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

c. 向exports物件上自定義屬性和屬性的getter方法

劇透:向ESM的exports新增匯出內容,後面有具體的分析 js // 向exports物件上自定義屬性和屬性的getter方法 __webpack_require__.d = function (exports, name, getter) { // 當前 匯出物件中沒有有 xxx 屬性時候 if (!__webpack_require__.o(exports, name)) { // 向 exports 物件新增 xxx 屬性,並設定值為 getter 方法的返回值,可列舉的 Object.defineProperty(exports, name, { enumerable: true, get: getter }); } };

d.標記 exports 是 esModule 模組方法

js // 標記 exports 是 esModule 模組 __webpack_require__.r = function (exports) { // 自定義 exports.toString 為 [object Module],標記當前 exports 是 esModule 模組 if (typeof Symbol !== "undefined" && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); } // 在exports新增屬性 __esModule 值為 true Object.defineProperty(exports, "__esModule", { value: true }); };

e.相容 ESM 和 CMS 預設匯出

js __webpack_require__.n = function (module) { // 如果是 esModule 模組,建立 getter 獲取 模組 default 值 // 非 esModule 模組,建立 getter 獲取 模組 exports 值 var getter = module && module.__esModule ? function getDefault() { return module["default"]; } : function getModuleExports() { return module; }; // 在 getter 函式新增 a 為當前模組 預設 匯出的值 __webpack_require__.d(getter, "a", getter); // 獲取 當前模組 預設 匯出的值 的 getter return getter; };

f.當所有的modules 掛在到 __webpack_require__ 方法上

js __webpack_require__.m = modules;

g. 模組的快取,掛在到 __webpack_require__ 方法上

js __webpack_require__.c = installedModules;

h. 配置的public path 掛在到 __webpack_require__ 方法上

js __webpack_require__.p = "";

以上屬於掛在到 __webpack_require__ 方法上 主要是為了後面方便獲取對應的值

四、分析ESM(Es Module) 與 CMS(Commonjs)的相互匯出引用

1. 匯出:CMS, 匯入:CMS

a.原始碼

js // index.js -- 匯入 const home = require("./home"); console.log("Hello World"); console.log(home); js // home.js -- 匯出 module.exports = "我是主頁面";

b. 產物分析

實參 js { "./src/home.js": function (module, exports) { module.exports = "我是主頁面"; }, "./src/index.js": function (module, exports, __webpack_require__) { const home = __webpack_require__(/*! ./home */ "./src/home.js"); console.log("Hello World"); console.log(home); }, } image.png

由上圖可看出將 ./src/index.js 作為moduleId傳給 __webpack_require__
這個函式作用:模組匯入方法:快取模組資訊,執行模組函式,將模組的匯出return出去 具體這個函式坐上,到【公共方法分析】檢視,這裡注重分析模組執行 image.png 上面執行載入 home 模組,並將home匯出的內容答應出來,下面看看載入home的時候,怎樣將內容匯出去 image.png 由上圖可以值,“index.js” 中home 值是 “我是主頁面”

2. 匯出:CMS, 匯入:ESM

a.原始碼

js // index.js -- 匯入 import home from "./home"; console.log(home); js // home.js -- 匯出 module.exports = "我是主頁面";

b. 產物分析

實參 ```js { "./src/home.js": function (module, exports) { module.exports = "我是主頁面"; }, "./src/index.js": function ( module, webpack_exports, webpack_require ) { "use strict"; webpack_require.r(webpack_exports); var _home__WEBPACK_IMPORTED_MODULE_0__ = webpack_require( /! ./home / "./src/home.js" ); var _home__WEBPACK_IMPORTED_MODULE_0default = _webpack_require.n( _home__WEBPACK_IMPORTED_MODULE_0__ );

console.log(_home__WEBPACK_IMPORTED_MODULE_0___default.a);

}, } `` 執行順序和上面的一樣,咱們直接分析./src/index.js` 模組的執行

image.png 首先執行 __webpack_require__.r(__webpack_exports__);,這個函式就是標記 當前模組exports 物件 為 esModules image.png 接著載入home模組 js var _home__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./home */ "./src/home.js" ); 此時 _home__WEBPACK_IMPORTED_MODULE_0__ 值 為 “我是主頁面” image.png 接著 做一波 相容 ESM 和 CMS 預設匯出 __webpack_require__.n 的操作 js var _home__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n( _home__WEBPACK_IMPORTED_MODULE_0__ ); image.png image.png 可以看出,_home__WEBPACK_IMPORTED_MODULE_0___default就是一個模組匯出的的getter,獲取home的值。
最後 console.log(_home__WEBPACK_IMPORTED_MODULE_0___default.a); 列印的就是 ‘我是主頁面’

3. 匯出:ESM, 匯入:CMS

a.原始碼

js // index.js -- 匯入 const home = require("./home"); console.log("default", home.default); console.log("name", home.name); js // home.js -- 匯出 export const name = "Hello Home"; export default "我是主頁面";

b. 產物分析

實參 js { "./src/home.js": /*! exports provided: name, default */ 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 = "Hello Home"; /* harmony default export */ __webpack_exports__["default"] = "我是主頁面"; }, "./src/index.js": /*! no static exports found */ function (module, exports, __webpack_require__) { const home = __webpack_require__(/*! ./home */ "./src/home.js"); console.log("default", home.default); console.log("name", home.name); }, } 執行順序和上面的一樣,咱們直接分析 ./src/index.js 模組的執行 image.png 這塊,上面其實已經分析過了,載入 home模組,並將home匯出的內容打印出來,核心關注 home是做模組匯出的 image.png 從這裡就能看到 ./src/index.js 列印的default 和 name 就是 home模組 的 "Hello World" 和 "我是主頁面"

4. 匯出:ESM, 匯入:ESM

a.原始碼

js // index.js -- 匯入 const home = require("./home"); console.log("default", home.default); console.log("name", home.name); js // home.js -- 匯出 export const name = "Hello Home"; export default "我是主頁面";

b. 產物分析

實參 js { "./src/home.js": /*! exports provided: name, default */ 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 = "Hello Home"; /* harmony default export */ __webpack_exports__["default"] = "我是主頁面"; }, "./src/index.js": /*! no exports provided */ function (module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _home__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./home */ "./src/home.js"); console.log("home", _home__WEBPACK_IMPORTED_MODULE_0__["default"]); console.log("name", _home__WEBPACK_IMPORTED_MODULE_0__["name"]); }, }); 執行順序和上面的一樣,咱們直接分析 ./src/index.js 模組的執行 image.png 那麼 我們去看看 home 模組做了哪些 image.png 從這裡就能看到 ./src/index.js 列印的default 和 name 就是 home模組 的 "Hello World" 和 "我是主頁面"

五、總結

  1. ESM匯出做了三步:
    a. 標記當前匯出物件為 ES Module
    b. 如果匯出 有default,直接將值掛在到 匯出物件的 default屬性上
    c. 如果匯出具名的內容,首先將當前屬性通過 defineProperty 定義在 匯出物件上,getter就是獲取當前作用於下的該屬性,並在當前模組定宣告該屬性並賦值
  2. CMS匯出:直接將內容掛在到 匯出的物件上
  3. ESM匯入做了四步:
    a. 標記當前匯出物件為 ES Module
    b. 匯入被匯入的模組
    c. 對匯入的內容 default 做 ESM 和 CMS的 getter 相容
    d. 在getter的a屬性獲取被匯入的值
  4. CMS匯入:直接將匯入的內容打印出來即可