接 Webpack 打包 commonjs 和 esmodule 模块的产物对比 继续,这篇文章来测试下 commonjs
模块和 esmodule
混用的情况,也就是 import
导入 commonjs
的模块,require
导入 esomodule
的模块,看一下它们在 Webpack
下的产物。
import 导入 commonjs 模块
commonjs
模块会为我们预设一个 module = {exports: {}}
的对象,导出模块的话我们可以直接给 module.exports.xxx = xxxx
或者 exports.xxx = xxx
加属性,也可以给 module.exports = xxx
赋值为一个新对象或者函数。
下边看下这两种情况的异同:
exports 添加属性
两个文件的代码:
1 | // src/commonjs/add.js |
如果还记得 Webpack 打包 commonjs 和 esmodule 模块的产物对比 这里总结的,我们的 import
会导入整个对象,然后执行的时候再通过 xxx.add
的形式调用。
1 | var _add__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( |
而 _add__WEBPACK_IMPORTED_MODULE_0__
就是我们导出的整个 module.exports
对象。
1 | "./src/commonjs/add.js": (module, exports) => { |
因此这种情况两种模式是完全契合的,不会有问题。
全部代码如下:
1 | (() => { |
exports 赋值为新对象
这次我们将 module.exports
整个赋值为一个新对象,这种情况我们一般是直接赋值为一个新的函数。
1 | // src/commonjs/add.js |
index.js
我们可以直接导入函数,不需要这样子 import { xxx } from 'yyy'
再进行对象解构。
1 | // src/commonjs/index.js |
我们知道,对于直接 import
导入的话, esmodule
相当于导入 default
属性,事实上 commonjs
并没有导出 default
,但 webpack
帮我们进行了兼容。
1 | var _add__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( "./src/commonjs/add.js"); |
看一下 __webpack_require__.n
方法
1 | __webpack_require__.n = (module) => { |
如果不是 esmodule
模块的话,我们会将整个模块作为 default
属性返回,但为什么在模块内又加了个 a
属性,这里没太懂,谁知道的话可以和我交流一下哈。
再看下整个的代码:
1 | (() => { |
require 导入 esmodule 模块
esmodule
模块除了正常的 export
,我们把 export default
也加一下:
1 | // src/esmodule/add.js |
然后 index.js
通过 require
来导入:
1 | // src/esmodule/index.js |
因为 require
是直接导入整个对象,没有专门导入 default
的形式,所以调用 default
方法的时候,我们需要通过 m.default
来调用。
运行起来是没有问题的:
让我们回忆下 Webpack 打包 commonjs 和 esmodule 模块的产物对比 这里介绍的 esmodule
模块的导出产物:
1 | var __webpack_modules__ = { |
相当于导出了一个 module.exports
大的对象,包含 sub
和 default
属性。
然后是 index.js
1 | (() => { |
需要注意的是虽然导出的是整个对象,但对于 index.js
我们不可以通过对象解构来拿到 default
方法。
1 | // src/esmodule/index.js |
default
会在这里被认为是一个关键字,直接抛错:
来看下整体代码:
1 | (() => { |
同时导出 commonjs 和 esmodule
我们在一个文件同时使用 module.exports
和 export
。
1 | const add = (a, b) => { |
浏览器会直接抛错:
如果直接重写 module.exports
呢?
1 | const add = (a, b) => { |
同样会抛错,让我们看一下 webpack
的产物。
1 | "./src/commonjs/add.js": ( |
调用了 __webpack_require__.hmd
方法拿到 module
。
1 | __webpack_require__.hmd = (module) => { |
hmd
方法重新定义了 exports
属性,没有定义 get
属性,所以 module.exports
返回的是 undefined
,module.exports.add
就直接抛错了。
重新定义了 set
函数,所以 module.exports = xxx
,重新赋值属性的时候走到 set
后直接抛错。
同时导入 commonjs 和 esmodule
定义一个 commonjs
模块:
1 | // src/commonjs/add.js |
定义一个 esmodule
模块:
1 | // src/esmodule/sub.js |
然后在 index.js
同时引入:
1 | const add = require("./add") ; |
没什么问题,正常运行:
因为导入的话它们是互不影响的,各自导入自己的即可,可以看下完整代码:
1 | (() => { |
总
不管是 esmodule
还是 commonjs
模块,最终都转换成了 module = {exports: {}}
形式的模块,所以它们之间的混用成为了可能。
import
commonjs
模块的话,import
拿到的就是整个 module.exports
对象,正常使用即可。如果我们直接改写 module.exports
对象,webpack
会认为等同于 export default
,进行兼容处理。
require
esmodule
模块的话,如果之前 esmodule
模块中有 export default
,那么使用的时候需要显示的调用 xxx.default
,对于其他的 export
正常使用即可。
虽然可以混用,但一般情况下能不混用就不混用,以免遇到未知问题,目前更推荐 esmodule
模块。
如果遇到奇怪问题的话,可以考虑直接去查看 webpack
的产物,能更快的排查出问题。
讨论
对产物其实有两个问题,然后去请教了下 Tecvan
,杰哥。
第一个问题就是上边提到,当使用 imports
导入 commonjs
模块的时候,会调用 n
方法。
1 | __webpack_require__.n = (module) => { |
这里会挂一个 a
属性,原因的话如下:
主要是兼容 webpack
混用的情况,场景可能如下:
第二个问题,还是 import
导入 commonjs
模块的时候,打包产物如下:
1 | var _add__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( |
这里会触发 __webpack_require__.n
方法去生成 _add__WEBPACK_IMPORTED_MODULE_0___default
变量。触发这个逻辑的原因并不是因为我们使用了 import xxx from 'yyy'
的格式,而是因为导出 commonjs
模块的时候直接使用 module.exports = xxx
进行了覆盖,这种情况 webpack
就会认为等效于 export default
的情况。
但对于代码,因为 _add__WEBPACK_IMPORTED_MODULE_0__
和 _add__WEBPACK_IMPORTED_MODULE_0___default
是同一个值,我们不处理 default
逻辑其实也是通的:
webpack
为什么会这样处理,具体原因就不知道了,欢迎大家一起来讨论,下边是杰哥的猜测:
后记
算上这篇,总结了三篇 webpack
的产物的文章 Webpack 打包 commonjs 和 esmodule 模块的产物对比 、Webpack 打包 commonjs 和 esmodule 动态引入模块的产物对比,可以加深平常开发中对于模块之间的理解。
大家如果还对 Webpack
原理感兴趣的话,可以去看杰哥的 Webpack 原理系列。目前我没有总结这个系列的计划了,哈哈。