ESM 和 CJS 模块在前端模块引用中存在互操作性,总结一下ESM 和CJS 模块如何相互引用。
1. ESM模块 引入CJS
- 具名引入 默认引入 import
- 动态引入 import('xxx').then((res)=>res.default)
import 引入 cjs 模块
// cjs.cjs commonjs 模块
module.exports.bb = "hh";
module.exports = {
test: "ooooooooo",
world: function () {
console.log("world");
},
};
这里后面的module.exports 导出 会默认覆盖之前的 module.exports 导出 导致之前导出的bb 为undefined 。
使用es module 导入 cjs 模块时 导入的是默认最后一个module.exports 导出的值
import cjs,{bb} from './cjs.cjs'
// const bb = require('./cjs.js')
console.log(cjs);
console.log(bb);
使用esmodule 默认导入的是 module.exports 导出的值 且无法实现具名导入cjs 模块 只能使用默认导入的方式导入cjs模块。
如果想要实现 具名导出 和默认导出都可以访问到的情况 使用exports.xxx 属性和exports.default 属性 可以实现
exports.bb = "hh";
exports.default = {
test: "ooooooooo",
world: function () {
console.log("world");
},
};
esm 模块引入上述cjs模块
import cjs from './cjs.cjs'
console.log(cjs);
result:
该处的esm模块 默认引入 可以同时访问到 cjs模块的具名导出 和默认导出。 也可以使用具名引入 引入cjs 模块的具体某一个属性
import cjs,{bb} from './cjs.cjs'
// console.log(cjs);
console.log(bb);
也可以使用动态import的方式导入cjs 模块
import('./cjs.cjs').then((res)=>{
console.log(res,'cjs模块');
})
可以看到 通过这种方式引入进来的cjs 模块 默认的所有属性都绑定在default属性上
import('./cjs.cjs').then((res)=>{
console.log(res.default,'cjs模块');
})
注意:
exports
是module.exports
的别名 在cjs模块的导出中 以 module.exports的导出为准
eg: exports 的覆盖导出:
exports = {
xxx
}
eg: module.exports 的覆盖导出
module.exports = {
xxx
}
module.exports的覆盖导出可以覆盖exports的导出 ,但是exports的覆盖导出无法覆盖module.exports的导出
具体以module.exports的导出为准
eg:
// cjs.cjs
exports.bb = "hh";
exports.default = {
test: "ooooooooo",
world: function () {
console.log("world");
},
};
module.exports = {
b:400
}
esm导入:
import('./cjs.cjs').then((res)=>{
// console.log(res,'cjs模块');
console.log(res.default,'cjs模块2');
})
eg2: exports 的覆盖导出 无法覆盖module.exports.xxx 的导出。
exports = {
aa:8888
}
module.exports.bb = "module hh";
module.exports.ff = {
test: "module ooooooooo",
moduleworld: function () {
console.log("module world");
},
};
esm导入:
import('./cjs.cjs').then((res)=>{
// console.log(res,'cjs模块');
console.log(res.default,'cjs模块2');
})
因此实际业务中尽量使用module.exports导出
2. CJS中如何导入ESM模块
- 动态import的方式 import('xxxx').then((xxx)=>xxx)
- 或者 await的方式引入
由于require只能引入同步模块 cjs模块 不能引入esm 异步模块 因此使用如下方式引用 必须修改文件名为.mjs 后缀结尾的 标志这个文件是一个esmodule文件 或者 单独开一个文件夹 初始化package.json文件 标识 type:'module' 这样也可以实现 将以.js后缀为结尾的文件识别为esmodule格式。
async function main() {
const mod = await import('./esm.mjs');
const { a, b, foo } = mod;
console.log(a);
console.log(b);
console.log(foo());
}
main();
- 使用IIFE 立即执行函数引入esm模块
type = 'module' 这里可以使用.js 后缀结尾文件标识
(async function main() {
const mod = await import('./esm.js');
const { a, b, foo } = mod;
console.log(a);
console.log(b);
console.log(foo());
})()
最新Node23 版本 新增支持require esm 模块
// demo.mjs 模块
export const a = '5555555555555555'
export default function TestEsm() {
console.log('999999999999999999');
}
// test.cjs模块
const TestEsm = require('./demo.mjs')
console.log(TestEsm);
最终结果:
参考: