Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入聊一聊__esModule #77

Open
forthealllight opened this issue Feb 10, 2022 · 1 comment
Open

深入聊一聊__esModule #77

forthealllight opened this issue Feb 10, 2022 · 1 comment

Comments

@forthealllight
Copy link
Owner

forthealllight commented Feb 10, 2022

深入聊一聊__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不会生效,所有属性都被当成普通属性。

@yangnaiyue
Copy link

yangnaiyue commented Feb 10, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants