手撕webpack运行时项目中打包的产物那么多,你有没有看过打包出的runtime是怎么一回事? 看看webpack是怎
项目中打包的产物那么多,你有没有看过打包出的runtime是怎么一回事?
看看webpack是怎么用朴实无华的纯JS,实现浏览器端模块化的。
runtime的基础框架原理并不复杂,搞懂runtime运行时,你就知道为什么CommonJS是值的拷贝,ES Module是值的引用了。
一. 项目准备
实际上就是解释了CommonJS和ES Module的实现原理
index.js webpack入口文件
let my= require("./my")
/**
你可以打开下面ES Module的引用方式,并注释掉上面一行,看看webpack打包结果有什么不同。
*/
// import { name as my } from './jd'
console.log('my webpack runtime' + my)
my.js
let name = 'fangxiangming'
module.exports = name
/**
你可以打开下面ES Module的引用方式,并注释掉上面一行,看看webpack打包结果有什么不同。
*/
// export {
// name
// }
二. 打包产物分析
打开打包后的bundle,并梳理其中核心的代码结构。可以清晰的看到 webpackBootStrap 的实现结果。webpack之所以能实现模块化的秘密,都在这里。
下面代码中有我增加的注释,它们阐述了我的理解:
(function(modules) {
// 要在全局维护一个 模块地址-module.exports map
var installedModules = {};
// 他的职责就是 根据路径加载模块,并为模块传入它所对应的 module.exports 和 自身回调
function __webpack_require__(moduleId) {
// 如果模块已经执行过,就直接返回module.exports
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 注册当前模块的module
var module = installedModules[moduleId] = {
exports: {}
};
// 执行模块,this是当前模块
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 返回模块对应的 module.exports
return module.exports;
}
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
/************************************************************************/
({ // 这是一个 模块路径-模块包装一层function 的 map
"./src/index.js":
(function(module, exports, __webpack_require__) {
eval("let my= __webpack_require__(/*! ./my */ "./src/my.js")\n\nconsole.log('my webpack runtime' + my)\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/my.js":
(function(module, exports) {
eval("let name = 'fangxiangming'\n\nmodule.exports = name\n\n//# sourceURL=webpack:///./src/jd.js?");
})
});
上面的打包产物只包含使用了CommonJS规范。之所以CommonJS的模块特性是访问值的拷贝,是因为在读取模块值时,最终访问的是储存在installedModules中module对象的exports属性,所以如同解构对象,得到的是值的拷贝。
如果使用 ES Module 会有些不同,它会在 export 时通过 __webpack_require__.d
方法将模块的 module 设置 getter 特性(本质上利用了Object.defineProperty)。因为访问模块导出的变量时,永远会执行module属性的 getter 函数。getter 是一个函数,通过getter访问到的module的属性自然是实时最新值。这一点就如同使用 commonJS 时,为了访问实时最新值,所以会导出函数,而不是直接导出变量。
下面是只使用 ES Module 的打包产物,省略了和commonJS的相同点:
(function(modules) { // webpackBootstrap
var installedModules = {};
function __webpack_require__(moduleId) {
// ...
}
// 专门为 module.exports 暴露的接口设置 getter 的函数
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
"./src/index.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval(`
__webpack_require__.r(__webpack_exports__); // 可以忽略!这行实际上只是给模板加标志位
var _my__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/my.js");
console.log('my webpack runtime' + _my__WEBPACK_IMPORTED_MODULE_0__["name"])
`);
}),
"./src/my.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval(`
// 用 __webpack_require__.d 设置 module.exports 访问 name 属性时的 getter
__webpack_require__.d(__webpack_exports__, "name", function() { return name; });
let name = 'fangxiangming'
`);
})
});
三. 抽象webpack运行时流程图
转载自:https://juejin.cn/post/7369509651518111779