作用
在package.json中,type字段是决定了node环境执行js文件使用的模块系统。
所以type有两种值:
- commonjs
- module
即node执行文件的时候,要么使用commonjs来解释文件的导入导出,要么使用ESM模块规范
两种模块系统
commonjs
使用module.exports,或者exports来导出需要被其他模块引用的值;使用require来导入其他模块
esm
使用export,或者export default来导出需要被其他模块引用的值;使用import来导入其他模块;
小练习
下面我们一起练习一下,在不同的模块系统中运行js文件。
尝试commonjs
// package.json
{
"type": "commonjs",
}
//add.js
exports.add = (a, b) => {
return a + b;
};
//index.js
const { add } = require("./add");
console.log("add: ", add(1, 2));
运行结果:
成功
注: type字段默认缺省,node在没有type字段的情况下,也是用commonjs的规则运行文件
尝试esm
// package.json
{
"type": "module",
}
//add.js
export const add = (a, b) => {
return a + b;
};
//index.js
import { add } from "./add.js"; //这是一个小细节
console.log("add: ", add(1, 2));
运行结果:
成功
如果type的值不是module,就会报下面这个错误
其实,除了通过type去告诉node使用哪个模块系统,还有一种方式,就是通过js文件的后缀。
文件后缀为.cjs, node就会用commonjs来解析,若后缀为.mjs,node就会用esm来解析。
下面来做个实验:
// package.json
{
"type": "commonjs",
}
//add.mjs
export const add = (a, b) => {
return a + b;
};
//index.mjs
import { add } from "./add.js"; //这是一个小细节
console.log("add: ", add(1, 2));
这里只改变type的值,为commonjs。但js文件还是esm的语法,且文件后缀改成了.mjs
运行结果:
成功
读者也可以试试.cjs后缀的文件执行
两种模块系统的混用
在使用esm语法的文件中,导入使用commonjs导出的文件。或者倒过来。这可行吗?
前者可行的,但是后者不可行。因为import导入是异步的,require是同步的;用异步来模拟同步是可以的,但是反过来却不行。虽然commonjs中不支持import语法,但却支持import()语法,通过动态载入的方式加载其他模块。
下面我们来看个例子
// package.json
{
"type": "commonjs",
}
//add.mjs
export const add = (a, b) => {
return a + b;
};
//increment.cjs
exports.increment = async (num) => {
const { add } = await import("./add.mjs");
return add(num, 1);
};
//index.mjs
import { increment } from "./increment.cjs";
console.log("increment: ", await increment(2));
其中add模块和index模块都是esm语法,increment是commonjs。而package中的type值是commonjs
其实我们已经通过文件后缀告诉node使用哪个模块系统,type已经不起作用了
先用increment模块引用add模块,即实验commonjs模块文件引用esm模块文件,而后在index模块中引用increment,即实验esm模块文件引用commonjs模块文件。因为import()动态载入,所以需要promise,而后导致increment函数的返回结果也是一个promise;
还有一个细节需要注意,在esm模块中,支持在最顶层使用await语法,不需要async函数的包裹。而commonjs不支持
运行结果:
成功
总结:
文章的开头通过package.json中的type字段,来引出两种模块文件系统。而后讲了两种模块系统的混用。
done😊