前端最常用的两种模块化规范分别是esm
和cjs
ESM 和 CJS 两种模块化规范混用
浏览器环境下
首先,在浏览器环境下,不存在这esm
和cjs
混用的情况,因为浏览器只支持esm
模块化。你使用任何脚手架的项目,在开发时可能使用了cjs
模块化对反,但是最终它的打包产物必须是esm
的才可以运行在浏览器环境下
打包后浏览器运行的代码html
与js
文件一般是这样的:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="module" src="./m1.js"></script>
<script type="module" src="./m2.js"></script>
</head>
<body>
</body>
</html>
m1.js
import { m2 } from './m2.js';
console.log("引入了m2", m2);
m2.js
export const m2 = "m2"
当然,一般不会直接使用es6
,而是会去做一些pollyfill
来适配各种浏览器
Node 环境下
在node
环境下,我给出的结论是:在同一个包内,是没有办法实现esm
和cjs
两种模块化规范混用的
但是,你在你当前开发的项目下配置了package.json
中的type = module
,然后使用esm import
去引入一个只有cjs
产物的node_modules
包中的导出是完全可以的
我们在node
项目中自建一个local-test
的本地测试的npm
包,然后这个包只有cjs
的导出,其结构如下:
index.js
module.exports = {
name: "local-test"
}
package.json
{
"name": "local-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
然后我们在我们自己的node
项目中使用esm
进行引入:
index.js
import localTest from 'local-test';
console.log(localTest.name);
发现它是可以正确地运行的:
但是这里要注意,我们使用esm
导入cjs
包时,使用的是默认导入,因为cjs
的module.exports
对于esm
来说就是默认导出,即export default
这种格式的,所以我们不能使用import { xxx } from "xxx"
这样的具名导入来引入cjs
模块
在 Typescript 下的模块化
如果你用的是ts
来开发项目,它同样也支持esm
和cjs
两种模块化规范
浏览器环境下
在浏览器环境中,想要使用ts
就必须要经过编译,而且在你使用框架的情况下,肯定要结合打包工具,要么vite
,要么webpack
当使用vite
打包的时候,对你的代码进行语法树解析时,是在node
环境下运行的,并且vite
只支持使用esm
模块化,这就意味着你的vite
项目不能使用require
进行导入
Node 环境下
由于ts
没有运行时,所以在node
环境中要跑ts
首先要把ts
编译为js
,所以我们直接用ts-node
来帮助我们完成这个工作,使用ts-node
时的体验就好像使用js
的node
运行时一样;ts-node
实际上就是在node
环境下适配了ts
并帮你编译好
使用node-ts
开发项目时,package.json
中的type = module
字段对ts
模块化规范的影响同样生效,你使用require
进行引入,就不能添加type = module
;否则你使用import
进行引入,那么你就要添加该字段,不然都会报错