Как сборщики взаимодействуют с модулями в Node.js
На примере WebPack’а. Интересно как webpack резолвит ESM модули
Бандл из входной точки cjs, которая импортирует esm модуль.
Сперва обратим своё внимание на IIFE(самовызывающаяся анонимная функция)
💡 Почему это было сделано? Так как это бандл, то означает, что вся кодовая база, в виде совокупности отдельных функциональных модулей, сосредоточенная в одном файле, то вспомогательные функции, переменные, такие как, например, webpack__require, должны быть изолированные от других файлов в общей среде под названием браузер. Коротко говоря, всё, что внутри функции, находиться в области видимости только этой функции, извне не получиться ни к чему получить доступ.
var __webpack_modules__ = {
123: (
__unused_webpack___webpack_module__,
__webpack_exports__,
__webpack_require__
) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
default: () => __WEBPACK_DEFAULT_EXPORT__,
substract: () => /* binding */ substract,
});
function substract(a, b) {
return a + b;
}
const __WEBPACK_DEFAULT_EXPORT__ = substract.bind(null, 2);
},
};
💡 Далее webpack генерирует объект, где ключи - id модуля, а значение - функция, аргументы которой следующие: __unused_webpack__webpack_module__ (это объект модуля), __webpack__exports (это объект exports), __webpack__require__ (вспомогательная функция для загрузки содержимого модуля)
❓ Но что же такое **webpack_require**.r, **webpack_require**.d, **__webpack_require__**.o ?
☝🏻 Сперва посмотрим на последнюю часть это большой функции IIFE. Тут мы можем заметить содержимое entry файла (index.cjs). Вместо require(’./modules.mjs’) используется __webpack__require(123) , где 123 - id модуля, который webpack сгенерировал. И тут мы обращаемся к следующему куску кода - не посредственно к самому function __webpack__require(moduleId)
// The require function
function __webpack_require__(moduleId) {
// Проверяет, есть ли модуль в кеше
var cachedModule = __webpack_module_cache__[moduleId];
// Если есть, возвращаем из кеша полученный объект module, а у него - объект exports
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Если нет в кеше, создаем объект module со свойством exports и записываем в кеш
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
// Выполняем функцию модуля из **__webpack_modules__**
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Возвращаем объект exports полученного модуля
return module.exports;
}
/* webpack/runtime/make namespace object */
(() => {
// Определяем признак через свойство __esModule для exports-объекта модуля
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
}
Object.defineProperty(exports, "__esModule", { value: true });
};
})();
💡 Symbol.toStringTag - это символ для геттера объекта, который возвращает всем известную нам строку, когда мы пытаемся привести объекта в строку посредством toString().
То есть вот это создаст для объекта exports свойство в виде символа Symbol.toStringTag и задаст значение value = “Module”. Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); Тогда получим такое поведение данного выражения: Object.prototype.toString.call(exports) // “[object Module]”
Если в среде нет поддержки этого символа, тогда для объекта назначается свойство __esModule: true , это нужно для определения того, что экспортируемый модуль является ESM.
/* webpack/runtime/hasOwnProperty сокращенная версия Object.prototype.hasOwnProperty(obj, prop) */
(() => {
__webpack_require__.o = (obj, prop) =>
// Проверяет, является ли prop собственным свойством объекта obj, вот и всё
Object.prototype.hasOwnProperty.call(obj, prop);
})();
/* webpack/runtime/define property getters */
(() => {
// define getter functions for harmony exports
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();
💡 Что бы хорошо понять, давайте подготовим контекст. Посмотри ещё раз как вызывается эта функция
__webpack_require__.d(__webpack_exports__, {
default: () => __WEBPACK_DEFAULT_EXPORT__,
substract: () => /* binding */ substract,
});
// вот это **__webpack__exports__** // \\/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
👉🏻 Получается что exports параметр - webpack__exports, который в свою очередь modules.exports из webpack__require функции:
__webpack_require__.d(__webpack_exports__, {
default: () => __WEBPACK_DEFAULT_EXPORT__,
substract: () => /* binding */ substract,
});
👉🏻 Вернемся обратно к функции d. Мы поняли, что такое webpack__exports, а вот definition - объект с содержимым модуля, который webpack распарсил при загрузке модуля module.mjs { default: () => __WEBPACK_DEFAULT_EXPORT__, substract: () => /* binding */ substract, }
/* webpack/runtime/define property getters */
(() => {
// Определяем геттеры для объекта exports
__webpack_require__.d = (exports, definition) => {
// Проходимся по ключам объекта definition
for (var key in definition) {
// если ключ есть в объекте definition и если его нет в exports, а его там нет
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
// то определяем для объекта exports геттер key(default | substract),
// как перечисляемое и возврающее значение по ключу key из definition
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();