Как сборщики взаимодействуют с модулями в 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], }); } } }; })();