CJS和ESM的区别

982 阅读2分钟

本章解释一下Node的两种规范,CJSESM的区别

13.2.0版本后,Nodejs不但保留了原有的CommonJS(CJS)语法前提下,还新增了对于ESModule(ESM)语法的支持。 虽然说NodeJs新增了ESM的支持,但其实并没有想象中的那么友好。

CJS和ESM的不同点

1. 不同的加载逻辑

  • 在CJS模块中,require()是同步接口,会直接从磁盘(或网络)读取依赖模块并立即执行对应的脚本
  • ESM标准模块加载器则是异步的,读取到脚本后不会立即执行,而是先进入编译阶段进行模块解析检查模块上调用了import和export的地方,并以此类推的把依赖模块一个个异步、并行地进行下载。在此阶段ESM加载器不会执行任何依赖模块代码,只检查语法错误、确定模块依赖关系、确定模块输出和输入的变量。最后ESM会进入执行阶段,按照顺序执行各模块脚本。
    所以`CJS``ESM`的加载区别是,CommonJS模块是`运行时加载`ESM模块是`编译时输出接口`

2. 不同的模式

ESM默认使用严格模式(use strict),因此在ES模块中的this不再指向全局window对象(global object),而是undefined,且变量在声明前无法使用

这也是为何在浏览器中,<script/>标签如要启用原生引入ES模块的能力,必须加上type=module这个属性来告知浏览器应该把它和常规JS区分开来处理。

3. 对于await的支持度

ESM模式支持顶级await,但CJS不支持,即在ES模块中,无需async函数内部就能直接使用await:

    //index.mjs
    const { convat } = await import ('./demo.js');
    convat();

在CJS模块中是没有这种能力的(即使用了动态的import导入接口),这也是为何require无法加载ESM的原因之一。

假设一个场景,一个CJS模块中的reqiure加载器同步加载一个ES模块,但是该ES模块里异步import了一个CJS模块,然后这个CJS模块又去同步加载一个ES模块,这种复杂的嵌套引入逻辑处理起来会变得非常麻烦。

4. __filename 和 __dirname的支持度

在CJS模式中,模块的执行需要用函数包起来,并指定一些常用的值,这样我们才可以在CJS模块中直接使用_filename_dirname

ESM标准不包含这方面的实现,即无法再Node的ESM里使用_filename_dirname

从上方几点可以表述,在目前的NodeJs中,如果将默认的CJS模式切换到ESM模式,那么会存在巨大的问题,比如兼容性。这也是NodeJs目前、甚至未来很长一段时间都难以解决的一个规范问题。