一、导语
在commonJS中,导出模块的方式是改变module.exports,但是对于es6来讲并不存在module这个变量。
对于ES6说,并不存在module这个变量,他的导出方式是通过关键则export实现
在我们书写脚本js文件时,你会发现无论是commonJS写法还是es6写法,都能实现,这是因为webpack进行兼容处理
根据野鸡博客,它会做以下事情
- import a from 'a.js'
- 先给当前模块打上ES6模块标识符
- 加载模块
- webpack根据关键词对导出内容进行兼容性解析
- 根据模块的导出形式(export default /exprots )给缓存模块的exports属性赋予值
二、webpack工程配置
在index.js 中
import a from './a.js'
console.log(a, '草泥马')
在a.js中
export default {
name: '看到了点心做的屋子'
}
设定webpack配置
module exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
}
三、bundle.js可读化
(function (modules) {
var installedModules = {};
function __webpack_require__ (moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
__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;
};
__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;
};
__webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
__webpack_require__.p = "";
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
"./src/a.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\r\n name: '看到了点心做的屋子'\r\n});\n\n//# sourceURL=webpack:///./src/a.js?");
}),
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ \"./src/a.js\");\n\r\n\r\nconsole.log(_a_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"], '草泥马')\n\n//# sourceURL=webpack:///./src/index.js?");
})
});
一共60行代码,也不多,由于IIFE可读性比较差,我将其拆分为
function init (modules) {
var installedModules = {} // set
// __webpack__require构造函数
function __webpack__require (moduId) {
// 找缓存id
if (installedModules[moduleId]) {
return installedModules[moduleId].exports // 返回它的exports对象
}
// 如果缓存没有 注册模块对象
var module = installedModules[moduleId] = {
i: moduleId,
l: false, // 是否已经加载
exprots: {}
}
modules[moduleId].call(module.exprots, module, module.exprots, __webpack_require__)
module.l = true
return module.exprots
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
// define方法 将模块暴露内容、属性名,定义getter与可遍历属性
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// require方法 给模块打上标签
__webpack_require__.r = function (exports) {
// 每个部署了ES6的类,都有Symbol.toStringTag属性,返回[[class]]除了object后面那一段
// Symbol.toStringTag = Symbol
// 这段代码检测是否支持Symbol方法
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
// 将exprots对象定义个 Symbol属性 值为Module
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
// 将exprots对象定义个 __esModule属性 值为true
Object.defineProperty(exports, '__esModule', { value: true });
};
// 创建一个独立的命名空间对象
// 模式1 :值(value)是module id ,需要迭代器it对象
// 模式2 : 合并值(value)所有属性进入命名空间
// 模式4 : 如果value是对象直接返回
// 模式8|1 : 行为类似require
__webpack_require__.t = function (value, mode) {
if (mode & 1) value = __webpack_require__(value); // export default 1
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
// 如果到这里 export const a = 1 这种情形
var ns = Object.create(null);
// 给模块打上标签
__webpack_require__.r(ns);
// 定义 export default = null
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) {
// 判断是否为es6模块
var getter = module && module.__esModule ?
// 返回default
function getDefault () { return module['default']; } :
// 返回commonJS的模块
function getModuleExports () { return module; };
// 为getter函数 通过a属性 反向代理自身
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// 判断对象内部是否有某个属性值
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
// 公共路径
__webpack_require__.p = "";
// 加载入口函数并返回exports对象 Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
}
const moduleArr = {
// 模块1
"./src/a.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
// 打标签
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = { name: '看到了点心做的屋子' };
}),
// 模块2
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
console.log(_a_js__WEBPACK_IMPORTED_MODULE_0__["default"], '草泥马')
})
}
init(moduleArr)
四、变量解析
可以看到,它是由 init函数与moduleArr组成
moduleArr是由一个字典组成,也就是建值对的形式,其中他的键值为相对于webpack.config.js文件的路径,值我称它为一个模块函数字典
继续观察init函数以及它对应的词法作用域下的变量,真正意义上的变量只有两个
变量 | 说明 |
---|---|
installedModules | 闭包模块缓存 |
__webpack__require | (function)接受一个模块id执行相应的模块函数,返回其结果 |
要注意的是,installedModules存放的模块的数据结构(模块缓存对象) :
var module = {
i: moduleId, // 模块id
l: false, // 模块是否已经加载
exports: {} // 模块函数返回结果
}
对于__webpack__require,它拥有很多静态方法与属性
变量 | 说明 | |
---|---|---|
installedModules | 模块缓存,闭包 | |
webpack_require.m | 模块函数字典的引用 | |
webpack_require.c | 闭包模块缓存引用 | |
webpack_require.t | (function)创建一个独立的命名空间对象 1.mode 1 |
8 : value是model 的id值,这个id值即为路径,直接去调用主函数 2.value是对象并且支持es6 直接返回 3. 合并value的所有所有属性进入命名空间 |
webpack_require.r | (function)为模块缓存对象的exports属性打上标签 | |
webpack_require.n | (function)处理commonJS或者es6模块差异函数,返回一个getter函数 | |
webpack_require.o | (function)判断对象内部是否有某个属性值 | |
webpack_require.p | 公共路径(存疑) | |
webpack_require.s | entry路径('./src/index.js') |
五、函数行为分析
(1). 来看主菜,__webpack__require
- 如果在模块缓存中找到相应的模块id,则直接返回模块缓存对应的对象
- 缓存中没有该对象,则注册一个模块缓存对象 newModule
- 执行模块函数,入参为 newModule.exprots newModule, newModule.exports,webpack_require(函数自身)
- 模块函数执行完毕,将newModule状态设为已经加载状态
- 返回模块内容
(2). 再来看moduleArr的各个模块函数
对于路径为./src/a.js
而言
- 声明严格模式
- 为模块newModule的exports打上标签
- 为exprots的default赋值为
{ name : '看到了点心做的屋子'}
对于路径为 ./src/index.js
而言
- 声明严格模式
- 为模块newModule的exports打上标签
- 使用_webpack_require调用a模块,并将其返回值赋值给a
- 执行index.js的内容
cosnole.log(a,'草泥马')
六、总结
与AMD同步模式类似,首先为对webpack.config.js入口的js文件解析,对于import导入的js文件,则递归调用__webpack__require方法,询问缓存,如果缓存没有,则执行对应得模块函数
七、疑问
bundle分析到这里了,在这里继续留下更多的疑问.
- 对于异步加载import('') 是如何实现的 ?
- webpack_require.t 、webpack_require.d 这些函数是用来干什么的 ?
- 如果分chunk,webpack会对其怎么处理呢 ?
网友评论