前言
最近运用Rollup
打包输出CJS
模块的代码,时不时会提示导入的模块是ESM
标准,需求运用动态导入的方法引进这个模块,这个问题引起了我的留意,所以决议深入研究一下。
下面我会围绕这两个点讲解一波。
- 简略介绍
ESM
和CJS
的原理 -
ESM
和CJS
的相互引证问题
CJS和ESM的历史演化
JavaScript一开始诞生是在浏览器上作为脚本语言运用的,其时在浏览器上并不存在模块的概念。它就像日本动漫里的男主角,刚转生到异世界时没有任何的兵器和装备(不是龙傲天),所以说一开始的JavaScript也是不完善的,需求时刻的历练,打怪晋级学技能。
直到Node.JS的诞生,让JavaScript能在服务端运转,一起Node.js社区的发展给JavaScript带来了模块化的概念,运用module.exports
导出和require
引进模块,这个标准叫做CommonJS
。它的呈现使得服务端的JavaScript应用程序的开发更加标准化和标准化。
因为CommonJS
的引进模块原理是同步加载的方法,要是在浏览器端运用这个标准,则需求等待所有的模块加载完才干履行后边的代码。因为浏览器中的JavaScript引擎是单线程的,假如加载JavaScript模块的过程中,代码履行的时刻过长,就会导致其他代码无法履行,从而影响整个页面的渲染和交互,最终影响了用户体验。因而,浏览器需求一种异步加载模块方法。
在2015年的6月,ES6
标准正式推出,在这个标准里带来了JavaScript异步加载模块的ESM
标准。
ESM
,也叫做 ECMAScript modules
或许ES modules
,是来自ES6
标准里的模块化概念,运用import
和export
关键字,来完成js的模块化导入和导出。
ESModule和CommonJS相互引证
彻底不同的两个标准,相互导入的话会导致什么情况呢?
在持续之前,咱们需求了解Node.js支撑运转ESM
标准的最低版别是:v13.2.0
,在之前的版别中,想要在node中运用ESM
,需求增加--experimental-module
参数,在v13.2.0
版别之后可以直接运用。
我将会运用Node.jsv16.18.0
版别进行下面的试验,话不多说,立马进入。
CJS导入ESM模块
- 先新建一个
ESM
模块,文件名为:esm.js
,代码如下:
// esm.js
export const a = 1;
export const b = 2;
export function foo () {
return 3;
}
- 另外新建一个
CJS
模块,文件名为:cjs.js
,代码如下:
// cjs.js
const { a, b, foo } = require('./esm.js')
console.log(a);
console.log(b);
console.log(foo());
- 运转
cjs.js
文件,成果提示以下的过错信息:
export const a = 1;
^^^^^^
SyntaxError: Unexpected token 'export'
经过来说,假如在CJS
上经过相对路径方法引进.js
结束的ESM
模块,就会遇到上面的问题。根本原因Node.js底层在加载这个esm.js
文件时,把它辨认成了CJS
标准,最后运用cjs的loader进行处理时抛出的反常:
那么问题来了,Node.js是如何辨认引进的文件是ESM
仍是CJS
标准的?其实Node.js有一个判断的机制叫做:Determining module system。用图例简略解析这个机制的判断逻辑:
为了Determining module system
可以把esm.js
正确辨认成ESM
模块,咱们有2种计划:
第一种:修正文件名后缀的方法。
过程如下:
- 咱们修正一下
esm.js
文件名为esm.mjs
,然后调整一下cjs.js
文件的内容如下:
// cjs.js
const { a, b, foo } = require('./esm.mjs');
console.log(a);
console.log(b);
console.log(foo());
- 履行指令
node cjs.js
,又抛出反常了,不过这次的报错略微不太一样:
node:internal/modules/cjs/loader:1031
throw new ERR_REQUIRE_ESM(filename, true);
^
Error [ERR_REQUIRE_ESM]: require() of ES Module esm.mjs not supported.
Instead change the require of esm.mjs to a dynamic import() which is available in all CommonJS modules.
- 大致的意思是
ESM
模块不支撑经过require()
函数引进,需求替换成经过import()
函数动态引进ESM
模块,那咱们就改一下cjs.js
文件中代码,经过动态import的方法引进:
async function main() {
const mod = await import('./esm.mjs');
const { a, b, foo } = mod;
console.log(a);
console.log(b);
console.log(foo());
}
main();
- 成功输出:
1
2
3
第二种:在文件同级或许父级目录加一个package.json
文件,并且加上”type”字段,值为”module”。
为了让cjs.js
和esm.js
别离辨认成正确的标准。咱们改形成下图的结构:
其中lib/package.json
的文件内容如下,这样esm.js
就会被辨认成ESM
模块。
// lib/package.json
{
"type": "module"
}
然后cjs.js
文件导入esm.js
咱们可以改写成:
// cjs.js
async function main() {
// 这儿改成导入./lib/esm.js 留意看后缀是 .js
const mod = await import('./lib/esm.js');
const { a, b, foo } = mod;
console.log(a);
console.log(b);
console.log(foo());
}
main();
最后运转指令:node ./cjs.js
,成功!!!
1
2
3
经过以上的试验,咱们了解到Node.js的底层机制现已支撑CJS
模块导入ESM
模块,不过需求留意以下的几个点:
- 根据
Determining Module System
机制,需求修正ESM
模块的文件后缀名为.mjs
或许经过同级或许最近父级的package.json
加上"type": "module"
。 -
CJS
模块只能经过import()
函数异步导入ESM
模块。
ESM导入CJS模块
在持续之前,咱们需求了解Node.js支撑运转
ESM
标准的最低版别是:v13.2.0
,所以首要保证本机的Node.js版别大于等于v13.2.0
。
首要新建一个文件夹,结构如下:
- 编辑根目录下的
package.json
文件,加上type
字段,值为”module”,如下所示:
// package.json
{
...,
"type": "module",
...,
}
这样esm.js
就会被作为ESM
模块处理。
- 编辑
lib/package.json
,内容如下:
// lib/package.json
{
// 或许可以加上"type": "commonjs"
}
-
esm.js
和cjs.js
文件内容别离为:
// ./lib/cjs.js
module.exports = {
a: 1,
b: 2,
foo: function () {
return 3
}
}
// esm.js
import pkg from './lib/cjs.js'
const { a, b, foo} = pkg;
console.log(a);
console.log(b);
console.log(foo());
- 运转
node esm.js
,输出了以下信息:
1
2
3
ESM
成功引进CJS
模块!!!
在Node.js中ESM
模块是支撑引进CJS
模块的,可是留意以下几点:
- 为了让Node.js正确辨认这是一个
CJS
模块,根据Determining Module System
机制,需求修正ESM
模块的文件后缀名为.cjs
,或许经过同级或许最近父级文件夹下的package.json
加上"type": "commonjs"
字段。 - 转化时,
CJS
模块的module.exports
与ESM
模块的export default
对应。
总结
经过以上的章节,咱们了解到CJS
和ESM
模块各自的特色,也知道了Node.js的Determining module system机制时是如何辨认代码属于哪种模块,再经过不同的loader去转化代码。
当你需求封装一些类库,引进不同标准依赖的时分也可以挥洒自如地处理,也可以合作一些打包东西例如Rollup
的运用。
本来两种模块标准是别离用在不同的端,可是因为ESM
标准的异步加载优势,天然生成支撑Tree Shaking
,以及各种JavaScript打包东西的盛行,例如Webpack
,Rollup
等等,越来越多的开发者在封装各类东西库时运用ESM
标准。
后边我将会写一篇文章讲解运用Rollup
东西的简略运用,来解决打包成CJS
标准时处理ESM
。不过或许在不远的将来,Node.js默认运用ESM
标准履行js代码也说不定。
感谢你阅读到这儿,如有发现文章中存在过错的当地也请纠正。