项目中遇到了循环依赖的问题,便浅浅分析一下。。。
基于打包后的代码逆向分析 webpack是如何实现es与cjs这两种模块化的
测试环境搭建:
依赖
{
"name": "test",
"version": "0.0.1",
"description": "",
"author":{},
"main": "index.js",
"license": "MIT",
"engines": {
"node": ">=8.11.1"
},
"scripts": {
"build": "webpack --config webpack.config.js"
},
"dependencies": {
},
"devDependencies": {
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}
webpackConfig
const path = require('path');
module.exports = {
mode: "development",
devtool: false,
entry: {
main: "./src/test.js",
},
output: {
filename: "main.js",
path: path.resolve(__dirname, "./dist"),
},
};
测试文件:
test.js
const com = require('./com')
console.log('%c +>>>>>>>>>>','color:red')
console.log('引入的模块')
console.dir(com)
com.js
console.log('导出模块测试')
module.exports = {
name: 'lz'
}
打包后的源码
/******/ (() => {
// webpackBootstrap
/******/ var __webpack_modules__ = {
/***/ "./src/com.js":
/*!********************!*\
!*** ./src/com.js ***!
\********************/
/***/ (module) => {
console.log("导出模块测试");
module.exports = {
name: "lz",
};
/***/
},
/******/
};
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/
}
/******/ // Create a new module (and put it into the cache)
/******/ var module = (__webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {},
/******/
});
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](
module,
module.exports,
__webpack_require__
);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/
}
/******/
/************************************************************************/
/*!*********************!*\
!*** ./src/test.js ***!
\*********************/
const com = __webpack_require__(/*! ./com */ "./src/com.js");
console.log("%c +>>>>>>>>>>", "color:red");
console.log("引入的模块");
console.dir(com);
/******/
})();
其实主要是关注require方法的实现,
首先就是定义modules,key为路径,value是模块的源代码
定义cacheModules, 用于缓存模块的内容
moduleId为路径
实现require方法:先是在cacheModules里面找有没有对应的缓存,有的话之间return cachedModule.exports; 没有则给cachedModule[moduleId]赋值 {exports:{ }}
然后执行modules[modukeId],将cachedModule[moduleId]作为参数参数传递,因为modules[modukeId]会更改传入的module的exports属性的值为exports的值,最后return cachedModule[moduleId].exports
en,代码有出入,不过思想是一致的。
那么搞懂原理后,可以想一下如何处理循环依赖的

引入com时,因为modules里面没有,所以会先给他赋值 exports = {},然后之前其源码,但是在com中有引入comTest,在require('./coomTest')会执行comTest的源码,但是comTets中也有引入com,但是,这时在cacheModules中已经定义了com的exports,所以会直接return,然后继续执行下一步即可,简单来说,循环依赖是通过modulesCache去解决的,
if (cachedModule !== undefined) {
return cachedModule.exports;
}
在看一下es规范实现的模块化的处理
其实是差不多的,模块加载的实现仍然是require方法, 区别的点在与es是直接通过export 和 export default进行暴露的,这时就没有module进行绑定了
看一下打包之后的代码
/******/ (() => {
// webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = {
/***/ "./src/com.js":
/*!********************!*\
!*** ./src/com.js ***!
\********************/
/***/ (
__unused_webpack_module,
__webpack_exports__rwfXZjj,
__webpack_require__
) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
/* harmony export */ obj: () => /* binding */ obj,
/* harmony export */
});
console.log("导出模块测试");
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = {
name: "lz",
};
const obj = {
name: "zp",
};
/***/
},
/******/
};
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/
}
/******/ // Create a new module (and put it into the cache)
/******/ var module = (__webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {},
/******/
});
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](
module,
module.exports,
__webpack_require__
);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/
}
/******/
/************************************************************************/
/******/ /* 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/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
/******/
})();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
/******/
}
/******/ Object.defineProperty(exports, "__esModule", { value: true });
/******/
};
/******/
})();
/******/
/************************************************************************/
var __webpack_exports__ = {};
/*!*********************!*\
!*** ./src/test.js ***!
\*********************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _com__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__(/*! ./com */ "./src/com.js");
console.log("%c +>>>>>>>>>>", "color:red");
console.log("引入的模块");
console.dir(_com__WEBPACK_IMPORTED_MODULE_0__.obj);
console.dir(_com__WEBPACK_IMPORTED_MODULE_0__["default"]);
/******/
})();
区别最大的点在于 打包之后模块的源码,多了两个步骤的处理
第一步是给module打上es的tag,区分这是es模块,是通过Object.defineProperty 去exports加的一层处理,修改了元数据
第二步是给exports赋值,default exports其实也就是绑定default属性,
__webpack_require__.d(__webpack_exports__, {
default: () => __WEBPACK_DEFAULT_EXPORT__,
obj: () => obj,
});
那么余下的步骤就与cjs是一致的。
那么还有个问题,通过es的规范家在cjs的模块,是如何处理的
因为require方法 return 的就是 { exports: {} } 的形式,而es的本质也是通过require进行导入的,那么他们拿到的数据的结构就是一致的,只需要在做一层转化即可,那么区分es与cjs自然是通过之前打的tag区分的
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* 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] });
/******/ }
/******/ }
/******/ };
/******/ })();
TODO 后续更新require.js与sea.js
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:http://www.cx330.cloud/index.php/2024/08/04/%e6%a8%a1%e5%9d%97%e5%8c%96%e5%8e%9f%e7%90%86/
共有 0 条评论