跟著webpack產物來學習webpack是如何做模組處理
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是如何做模組處理
```
三、分析產物的公共方法和屬性
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);
},
}
由上圖可看出將 ./src/index.js
作為moduleId傳給 __webpack_require__
這個函式作用:模組匯入方法:快取模組資訊,執行模組函式,將模組的匯出return出去
具體這個函式坐上,到【公共方法分析】檢視,這裡注重分析模組執行
上面執行載入 home 模組,並將home匯出的內容答應出來,下面看看載入home的時候,怎樣將內容匯出去
由上圖可以值,“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` 模組的執行
首先執行 __webpack_require__.r(__webpack_exports__);
,這個函式就是標記 當前模組exports 物件 為 esModules
接著載入home模組
js
var _home__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
/*! ./home */ "./src/home.js"
);
此時 _home__WEBPACK_IMPORTED_MODULE_0__ 值 為 “我是主頁面”
接著 做一波 相容 ESM 和 CMS 預設匯出 __webpack_require__.n
的操作
js
var _home__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(
_home__WEBPACK_IMPORTED_MODULE_0__
);
可以看出,_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
模組的執行
這塊,上面其實已經分析過了,載入 home模組,並將home匯出的內容打印出來,核心關注 home是做模組匯出的
從這裡就能看到 ./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
模組的執行
那麼 我們去看看 home 模組做了哪些
從這裡就能看到 ./src/index.js
列印的default 和 name 就是 home模組 的 "Hello World" 和 "我是主頁面"
五、總結
- ESM匯出做了三步:
a. 標記當前匯出物件為 ES Module
b. 如果匯出 有default,直接將值掛在到 匯出物件的 default屬性上
c. 如果匯出具名的內容,首先將當前屬性通過defineProperty
定義在 匯出物件上,getter就是獲取當前作用於下的該屬性,並在當前模組定宣告該屬性並賦值 - CMS匯出:直接將內容掛在到 匯出的物件上
- ESM匯入做了四步:
a. 標記當前匯出物件為 ES Module
b. 匯入被匯入的模組
c. 對匯入的內容 default 做 ESM 和 CMS的 getter 相容
d. 在getter的a屬性獲取被匯入的值 - CMS匯入:直接將匯入的內容打印出來即可