commonJS和ES Module

94 阅读3分钟

直接在node中使用ES6的模块化语法会直接报错。

例子:

 // 01.js
 import a from "./02";
 console.log(a)
 ​
 // 02.js
 let a = { a: 1 };
 export default a;

image.png

在node中使用es6模块化规范的解决方法:

  1. 在项目的package.json文件中指定type为module,则开启使用ESM;默认不写和指定type为commonjs,则都是使用CJS模块化规范; 当指定了type字段后,如果被引用的文件没有加文件类型后缀,会报错。

image.png 高版本Node在默认情况下,对import命令的文件后缀存在强制性,因此import "./02"并不等于import ./02.js。其次CJS的自动后缀处理行为可通过--es-module-specifier-resolution=node开启,但模块主入口并不会受到ESM的影响,例如import Path from "path"照样可正常运行。在命令中加上--es-module-specifier-resolution=node就能解决显示文件名称的问题。

```
 node --es-module-specifier-resolution=node 01.js
```

0. 当不指定package.json文件中指定type为module,则需要采用.mjs作为文件的类型后缀;

在node中使用es6模块化语法的其他问题: ES6模块化不再提供Node某些特性,不能灵活引用json文件,dirname、 filename、require、module和exports这几个特性将无法使用。

解决方法:

  • filename与dirname可用import.meta对象重建
  • require、module和exports可用import与export代替
  • json文件的引用可用Fs模块的readFileSync与JSON.parse()代替
 import { readFileSync } from "fs";
 import { dirname } from "path";
 import { fileURLToPath } from "url";
 ​
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = dirname(__filename);
 console.log(__filename, __dirname);
 ​
 const json = readFileSync("./info.json");
 const info = JSON.parse(json);

image.png CJS的循环依赖关系已通过缓存各个模块的module.exports对象解决,但ESM用了所谓的绑定。简而言之,ESM模块不会导出值而是引用值。

  • 导入引用模块可访问该引用但无法修改它。
 // 01.js
 import a, {b}from "./02.js";
 ​
 b = 2
 console.log(a)
 ​
 // 02.js
 let a = { a: 1 };
 ​
 export let b = 1
 export default a;

image.png

  • 导出引用模块可为引用该模块的模块重新分配值且该值由导入引用模块使用
 // 01.js
 import a, {b}from "./02.js";
 ​
 setInterval(()=>{
     console.log(b,'01.js');  
 },1000)
 ​
 console.log(a)
 ​
 // 02.js
 let a = { a: 1 };
 ​
 export let b = 1
 ​
 setInterval(()=>{b+=1},1000)
 ​
 export default a;

image.png

  • 在CJS中, 导出的如果是基本数据类型的变量,则导出的是值,如果导出的是引用类型的变量,则导出的是引用值的内存地址;允许在任何时间点将引用分配给模块的module.exports对象,让这些改动仅部分反映在其他模块。
 //  04.js
 let b = 1
 ​
 setInterval(()=>{
     b+=1
 },1000)
 ​
 module.exports  = { b }
 ​
 // 03.js
 let b = require('./04')
 ​
 setInterval(()=>{
     console.log(b)
 },1000)

image.png

 // 03.js
 let b = require('./04')
 ​
 setInterval(()=>{
     b.b = b.b+1
 },1000)
 ​
 // 04.js
 let b = 1
 ​
 setInterval(()=>{
     console.log(b)
     console.log(module.exports,'module.exports');
 },1000)
 ​
 module.exports  = { b }

image.png

在node中使用第三方模块时,可能很多第三方模块都使用CJS编码,因为同时使用require与export/import会报错,所以单个模块可能无法切换到ESM。 可用babel将代码从ESM转换为CJS,因此使用babel编译ESM代码是低版本Node支持ESM最稳定的方案。 在Node v8.9.0前的版本无法使用--experimental-modules支持ESM,也就更需babel解决该问题了。 当然在任何版本中,babel都能让新语法转换为与旧环境兼容的代码,因此在高版本Node中也同样适用。

 npm i @babel/cli @babel/core @babel/node @babel/preset-env -D
  • @babel/cli:提供支持@babel/core的命令运行环境
  • @babel/core:提供转译函数
  • @babel/node:提供支持ESM的命令运行环境
  • @babel/preset-env:提供预设语法转换集成环境

安装完毕,在package.json中指定babel相关配置,node xxx.js替换为babel-node xxx.js。

该方案无需在package.json中指定engines,毕竟其目的还是将代码的模块方案从ESM转换为CJS。若需兼容更低版本Node,可在package.json中指定babel的targets。