webpack源码分析—ESModule规范模块的导入与导出详解
前言
上一章分析了CommonJS规范模块的导入与导出,这一章我们来分析ESModule规范模块的导入与导出。
ESModule规范模块的导入与导出
示例代码
// index.js
import data, { param1, param2 } from './module.js'
console.log(data)
console.log(param1)
console.log(param2)
// module.js
export default 'this is default export'
export let param1 = 'export param1'
export let param2 = 'export param2'
导入分析
先分析左边的代码,我们希望导入module模块,并取出其中的默认导出变量default
和变量param1
、param2
这三个变量,并打印
再分析右边的代码,他调用了一个__webpack_require_.r
方法,然后使用__webpack_require__
方法导入module模块,并使用一个变量保存,最后打印了这个变量的default
,param1
,param2
属性
可以看出,实际上它基本执行了左边代码的操作,只是进行了一些额外处理
差异:
- 调用了
__webpack_require_.r
方法 - 使用
__webpack_require__
方法进行导入 - 使用变量接收导出内容,并使用
变量[key]
方式替换原本模块变量的使用
可以看到,不管是commonjs规范,还是esmodule规范,最终导入的方法都是使用__webpack_require__
来实现,并且都是通过将变量挂载到exports上来实现模块变量的导出。
但esmodule规范多了一个__webpack_require_.r
方法调用,这个方法有什么用呢?
r方法—模块类型标记
来看看r方法的内部逻辑
__webpack_require__.r = function (exports) {
// 判断当前环境是否支持Symbol语法和Symbol.toStringTag方法
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
// 存在则向exports添加一个唯一属性Symbol.toStringTag,值为Module
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
// 给exports添加一个属性__esmodule,值为true
Object.defineProperty(exports, '__esModule', { value: true });
};
可以看到,r方法其实就是为了给exports去添加一些标识,我个人理解,就是为了去标识导入规范类型和当前环境是否支持es6语法(因为Symbol是es6的新的数据类型)
那么,r方式是给谁打标识呢?r方法接收一个exports参数,从字面意义上我们也可以猜测出来,这其实就是我们需要导出的变量module.exports
,且exports应该是一个对象。
实际来看看
r方法传入的参数是__webpack_exports__,而第二个参数接收的刚好就是module.exports变量(这里不懂的可以看上一章Commonjs规范的导入和导出)
所以,r方法其实就是给当前模块的导出变量打标识
后面的其实就比较常规了,使用__webpack_require__
进行导出,使用变量接收导出的内容,最后使用导出的变量。
导出分析
看完导入,我们再来看看导出,看看它是如何将变量挂载上去的。
我们可以看到,导出的代码和源代码有很大的不同,有一个r方法,还多了一个d方法,我们来简单分析一下
- 执行r方法,上面说过,这个是给导出变量做标记
- 执行了d方法,并传递了一些参数
- 又执行了一次d方法,传递了其他参数
- 往
__webpack_exports_
上挂载了一个default属性 - 声明变量param1,param2并赋值
d方法分析
其中只有d方法我们是不知道干什么的,我们再进入到d方法去看看
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
}
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
代码并不复杂,d方法接收三个参数exports
,name
,getter
。从代码可以分析,exports
是一个对象。首先通过o方法判断exports
是否存在name
变量值的属性,如果没有,则再exports上挂载一个name
变量值的属性,并设置其访问器为getter
所以d方法其实就是为传入的exports
对象挂载一个name
变量值的属性,并设置其访问器为getter
。
我们再看会原来打包后的代码
先看第一个d方法的执行
它传递了__webpack_exports__
,"param1"
,function () { return param1 }
这三个参数:
其中__webpack_exports__
就是将来导出变量的一个对象,
"param1"
正好就是源代码中exports let param1 = "export param1"
中的变量名称,
最后的方法返回了一个param1变量,而param1变量的值对应源代码中exports let param1 = "export param1"
导出的值
结论:对于由exports var param = value
导出类型的变量,我们通过d方法来将对于的属性名和属性值挂载到导出对象__webpack_exports__
上,而对于export default param
导出类型的变量则通过__webpack_exports__['default'] = param
来实现导出。
所以最终__webpack_export__
对象上就有default
,param1
,param2
这三个需要导出的属性了。
至于为什么d方法中要使用getter,而不是value,我个人猜测是因为要实现其导出变量实际是导出变量的地址,而不是导出他的值的原因。
以上就是本章的内容了
以上内容仅供学习参考