深入聊一聊__esModule

    当ECMAScript module(简称es module) 的模块化语法import/export产生的时候,commonjs的require/module.exports已经在nodejs的环境中被广泛使用,特别是es module中的default语法,让es module和commonjs之间的转换变得复杂。本文结合自己在es module和commonjs两种模块化互相转换的实践,聊聊会遇到的问题以及如何解决。

  • es module中export default带来的问题
  • __esModule
  • __esModule存在的问题

一、es module中export default带来的问题

    es module模块间的互相引用不会有任何的问题,随着nodejs环境和浏览器环境都陆续开始支持es module, 一些主流的npm包的最新版本,都会提供es module的源文件,一般通过指定package.json中的module或者exports字段来提供esm形式的源文件。但是,如果在es module中引用一个commonjs中的模块,就可能会出现引用问题。

出问题的本质就是esm中export default的语法,在commonjs中是无法对应的。

举例来说:

//commonjs模块 a.js
module.exports.name = 'Jony'
module.exports.hobby = 'Play'
复制代码
//main.js引用a.js
const person = require("./a.js") //这样引没用问题
复制代码
//main.js引用a.js,用esm的形式引用commonjs
import person from './a.js'

person = ???
复制代码

上述的例子中,如果我们需要以es module的形式来引用a.js。因为a.js是commonjs形式的,我们在a.js中找不到与export default相对应的属性。

二、__esModule

    在问题一中的两个例子,本质都说明了esm 中export default的语法,在commonjs中很难找到对应。babel首先在commonjs模块的文件中引入__esModule标识,如果__esModule为true,那么将commonjs转化为es module,export default导出的是module.exports.default。如果__esModule为false,那么commonjs转化为es module后,export default到处的是整个module.exports。

    我们同样拿上述章节中的例子,来说明:

//commonjs模块 a.js
module.exports.name = 'Jony'
module.exports.hobby = 'Play'
复制代码

这里没用__esModule属性,默认__esModule为false

//main.js引用a.js
const person_cjs = require("./a.js") //这样引没有问题

person_cjs = {name,hobby}
复制代码

因为__esModule为false或者不存在,那么export default导出的是整个module.exports对象。

//main.js引用a.js,用esm的形式引用commonjs
import person_esm from './a.js' 

person_esm = {name,hobby}

复制代码

再来看一个例子,如果包含commonjs中包含了module.exports.default属性,以及__esModule位true。

//commonjs 模块a.js
module.exports.default = "aa"
module.exports.name = "Jony"
module.exports.hobby = 'Play'
module.exports.__esModule = true

复制代码

这里__esModule为true,且存在module.exports.default属性,那么以es module的方式引用上述文件得到的结果为:

//main.js引用a.js,用esm的形式引用commonjs
import person_esm from './a.js' 
person_esm = "aa"

复制代码

三、__esModule存在的问题

    __esModule首先由Babel提出,其他的构建工具或者系统在commonjs和es module的转换中都遵循了这个规则。但是在使用在转换和引用的过程中需要注意几个细节。

(1)如果commonjs的模块中存在__esModule为false,到处的是整个Module.exports对象,如果设置了__esModule=false,这个对象中可能会多一个属性__esModule.

因此如果__esModule为false,可以不设置。否则到处的对象会多一个__esModule属性

//commonjs a.js
module.exports.a = 2
module.exports.b = 3
module.exports.__esModule = false

//main.js

import x from './a.js'
x = {a:2,b:3,__esModule:false}
复制代码

(2)如果commonjs的模块中存在__esModule为true,但是不存在module.exports.default属性。

这种情况下,不同的构建工具或者系统可能有不同的表现,以esbuild的最新版本为例子,对于这种情况esbuild构建后认为default = undefined.

//commonjs a.js
module.exports.a = 2
module.exports.b = 3
module.exports.__esModule = true

//main.js

import x from './a.js'
x = undefined 
复制代码

此外,也有一些工具的处理,如果__esModule为true,但是不存在module.exports.default不存在,就把这种情况当成__esModule为false来处理。

(3)如果在.mjs文件中引用

//commonjs a.js
module.exports.default = "aa"
module.exports.a = 2
module.exports.b = 3
module.exports.__esModule = true

//main.mjs

import x from './a.js'
x = {
   default:'aa',
   a:2,
   b:2,
   __esModule:true
}
复制代码

    以.mjs后缀结尾的文件,是nodejs中支持了es module的形式,此时如果在.mjs后缀结尾的文件中引用commonjs,一般不会做特殊处理。需要__esModule的场景本身就是为了兼容nodejs环境中,无法直接运行es module。

    因此这里__esModule不会生效,所有属性都被当成普通属性。

Guess you like

Origin juejin.im/post/7063002055308214302