Node的CJS与ESM有哪些不同点


今天小编给大家分享一下Node的CJS与ESM有哪些不同点的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.1 在 Node 中使用 ESMNode 默认只支持 CJS 语法,这意味着你书写了一个 ESM 语法的 js 文件,将无法被执行。如果想在 Node 中使用 ESM 语法,有两种可行方式:⑴ 在 package.json 中新增 "type": "module" 配置项。⑵ 将希望使用 ESM 的文件改为 .mjs 后缀。对于第一种方式,Node 会将和 package.json 文件同路径下的模块,全部当作 ESM 来解析。第二种方式不需要修改 package.json,Node 会自动地把全部 xxx.mjs 文件都作为 ESM 来解析。同理,如果在 package.json 文件中设置 "type": "commonjs",则表示该路径下模块以 CJS 形式来解析。
如果文件后缀名为 .cjs,Node 会自动地将其作为 CJS 模块来解析(即使在 package.json 中配置为 ESM 模式)。我们可以通过上述修改 package.json 的方式,来让全部模块都以 ESM 形式执行,然后项目上的模块都统一使用 ESM 语法来书写。如果存在较多陈旧的 CJS 模块懒得修改,也没关系,把它们全部挪到一个文件夹,在该文件夹路径下新增一个内容为 {"type": "commonjs"}package.json 即可。Node 在解析某个被引用的模块时(无论它是被 import 还是被 require),会根据被引用模块的后缀名,或对应的 package.json 配置去解析该模块。1.2 ESM 引用 CJS 模块的问题ESM 基本可以顺利地 import CJS 模块,但对于具名的 exports(Named exports,即被整体赋值的 module.exports),只能以 default export 的形式引入:
1.3 CJS 引用 ESM 模块的问题假设你在开发一个供别人使用的开源项目,且使用 ESM 的形式导出模块,那么问题来了 —— 目前 CJS 的 require 函数无法直接引入 ESM 包,会报错:按照上述错误陈述,我们不能并使用 require 引入 ES 模块(原因会在后续提及),应当改为使用 CJS 模块内置的动态 import 方法:
开源项目当然不能强制要求用户改用这种形式来引入,所以又得借助 rollup 之类的工具将项目编译为 CJS 模块……由上可见目前 Node.js 对 ESM 语法的支持是有限制的,如果不借助工具处理,这些限制可能会很糟心。对于想入门前端的新手来说,这些麻烦的规则和限制也会让人困惑。截至我落笔书写本文时, Node.js LTS 版本为 16.14.0,距离开始支持 ESM 的 13.2.0 版本已过去了两年多的时间。那么为何 Node.js 到现在还无法打通 CJS 和 ESM?答案并非 Node.js 敌视 ESM 标准从而迟迟不做优化,而是因为 —— CJS 和 ESM,二者真是太不一样了。2.1 不同的加载逻辑在 CJS 模块中,require() 是一个同步接口,它会直接从磁盘(或网络)读取依赖模块并立即执行对应的脚本。ESM 标准的模块加载器则完全不同,它读取到脚本后不会直接执行,而是会先进入编译阶段进行模块解析,检查模块上调用了 importexport 的地方,并顺腾摸瓜把依赖模块一个个异步、并行地下载下来。在此阶段 ESM 加载器不会执行任何依赖模块代码,只会进行语法检错、确定模块的依赖关系、确定模块输入和输出的变量。最后 ESM 会进入执行阶段,按顺序执行各模块脚本。所以我们常常会说,CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。在上方 1.2 小节,我们曾提及到 ESM 中无法通过指定依赖模块属性的形式引入 CJS named exports:这是因为 ESM 获取所指定的依赖模块属性(花括号内部的属性),是需要在编译阶段进行静态分析的,而 CJS 的脚本要在执行阶段才能计算出它们的 named exports 的值,会导致 ESM 在编译阶段无法进行分析。2.2 不同的模式ESM 默认使用了严格模式(use strict),因此在 ES 模块中的 this 不再指向全局对象(而是 undefined),且变量在声明前无法使用。这也是为何在浏览器中, 标签如要启用原生引入 ES 模块能力,必须加上 type="module" 告知浏览器应当把它和常规 JS 区分开来处理。查看 ESM 严格模式的更多限制:https://es6.ruanyifeng.com/#docs/module#%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F2.3 ESM 支持“顶级 await”,但 CJS 不行。ESM 支持顶级 await(top-level await),即 ES 模块中,无须在 async 函数内部就能直接使用 await:到 Github 获取示例代码(test3):https://github.com/VaJoy/BlogDemo3/tree/main/220220/test3在 CSJ 模块中是没有这种能力的(即使使用了动态的 import 接口),这也是为何 require 无法加载 ESM 的原因之一。试想一下,一个 CJS 模块里的 require 加载器同步地加载了一个 ES 模块,该 ES 模块里异步地 import 了一个 CJS 模块,该 CJS 模块里又同步地去加载一个 ES 模块…… 这种复杂的嵌套逻辑处理起来会变得十分棘手。查阅关于更多“如何实现 require 加载 ESM”的讨论:https://github.com/nodejs/modules/issues/4542.4 ESM 缺乏 __filename 和 __dirname在 CJS 中,模块的执行需要用函数包起来,并指定一些常用的值:所以我们才可以在 CJS 模块里直接用 __filename__dirname。而 ESM 的标准中不包含这方面的实现,即无法在 Node 的 ESM 里使用 __filename__dirname
从上方几点可以看出,在 Node.js 中,如果要把默认的 CJS 切换到 ESM,会存在巨大的兼容性问题。这也是 Node.js 目前,甚至未来很长一段时间,都难以解决的一场模块规范持久战。如果你希望不借助工具和规则,也能放宽心地使用 ESM,可以尝试使用 Deno 替代 Node,它默认采用了 ESM 作为模块规范(当然生态没有 Node 这么完善)。借助构建工具可以实现 CJS 模块、ES 模块的混用,甚至可以在同一个模块同时混写两种规范的 API,让开发不再需要关心 Node.js 上面的限制。另外构建工具还能利用 ESM 在编译阶段静态解析的特性,实现 Tree-shaking 效果,减少冗余代码的输出。这里我们以 rollup 为例,先做全局安装:接着再安装 rollup-plugin-commonjs 插件,该插件可以让 rollup 支持引入 CJS 模块(rollup 本身是不支持引入 CJS 模块的):我们在项目根目录新建 rollup 配置文件 rollup.config.jsplugin免费云主机域名-commonjs 默认会跳过所有含 import/export 的模块,如果要支持如 import + require 的混合写法,需要带 transformMixedEsModules 属性。接着执行 rollup --config 指令,就能按照 rollup.config.js 进行编译和打包了。示例
打包后的 bundle.js 文件如下:可以看到,rollup 通过 Tree-shaking 移除掉了从未被调用过的 c 模块的 deadCode 方法,但 a、b 两模块中的 deadCode 代码段未被移除,这是因为我们在引用 a.js 时使用了 require,在 b.js 中使用了 CJS named exports,这些都导致了 rollup 无法利用 ESM 的特性去做静态解析。常规在开发项目时,还是建议尽量使用 ESM 的语法来书写全部模块,这样可以最大化地利用构建工具来减少最终构建文件的体积。以上就是“Node的CJS与ESM有哪些不同点”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注百云主机行业资讯频道。

相关推荐: vue中怎么给data里面的变量增加属性

这篇“vue中怎么给data里面的变量增加属性”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue中怎么给data里面的变量增加属性”文章吧。里…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

Like (0)
Donate 微信扫一扫 微信扫一扫
Previous 09/29 16:28
Next 09/29 16:28

相关推荐