环境
-
Koa.js ( Node.js )
-
TypeScript
-
ESM
背景
最近做毕设用到了 y-websocket 这个包,它将 Yjs 的能力和 WebSocket 服务封装在一起,使用的时候需要额外开启一个node服务。我希望把它里面的函数整合进我后端现有的 WebSocket 中,并增加功能。因为它只提供了 CJS 方式编写的js文件,所以如果要优雅地融入我用 ts+ESM 编写的项目中,我目前能想到的就是重构它的代码。
ESM和CJS的区别主要在于导出和引入的语法
-
ESM使用export和import
-
CJS使用module.exports和require
正好这次重构把ESM的几种引入方式都写了个遍,记录一下当编辑器无法自动或正确地引入依赖时,应该如何正确地书写import。
我们先看看重构前后的对比:
从图中可以看到 ESM 引入依赖的多种写法:
- import dep from 'module'
- import * as dep from 'module'
- import { dep } from 'module'
- import dep from 'module/child'
- import type dep from 'module' (在ts中只作为类型使用)
如何正确地书写 import ?
首先在 node_modules 中找到要引入的包和函数,检查依赖的文件。
1. 查看文件中是否有默认导出 export default dep
- 有:使用 import dep from 'module'
- 无:使用 import * as dep from 'module'
2. 查看依赖函数的导出方式
-
export dep
:使用 import dep from 'module' -
export { dep, .... }
:使用 import { dep, ... } from 'module'
在 tsconfig.json 中配置
"esModuleInterop": true
,也可以用第二种方式引入export dep
导出的函数
3. 检查包的package.json,查看 exports
字段
下面是 lib0 包 package.json 文件中的 exports
字段 和其 ./index.js 文件的内容。
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"require": "./dist/index.cjs"
},
"./decoding.js": "./decoding.js",
"./dist/decoding.cjs": "./dist/decoding.cjs",
"./decoding": {
"import": "./decoding.js",
"require": "./dist/decoding.cjs"
},
"./encoding.js": "./encoding.js",
"./dist/encoding.cjs": "./dist/encoding.cjs",
"./encoding": {
"import": "./encoding.js",
"require": "./dist/encoding.cjs"
}
// ......
}
/**
* ./index.js
* @module lib0
*/
import * as decoding from './decoding.js'
import * as encoding from './encoding.js'
// ......
export { decoding, encoding, /** ...... */ }
在exports
字段中写了'.'路径,提供ESM("import")和CJS("require")的引入方式;
在./index.js
文件中看到函数的导出写法是export { ... }
。
因此可以使用以下两种写法:
import { dep } from 'module'
import dep from 'module/child'
import { encoding, decoding, /** ...... */} from 'lib0'
// 或
import * as encoding from 'lib0/encoding'
import * as decoding from 'lib0/decoding'
有一些包不提供'.'路径的导出,如y-protocols
它 package.json 文件中的 exports
字段长这样:
"exports": {
"./package.json": "./package.json",
"./sync.js": "./sync.js",
"./dist/sync.cjs": "./dist/sync.cjs",
"./sync": {
"import": "./sync.js",
"require": "./dist/sync.cjs"
},
"./awareness.js": "./awareness.js",
"./dist/awareness.cjs": "./dist/awareness.cjs",
"./awareness": {
"import": "./awareness.js",
"require": "./dist/awareness.cjs"
}
// ......
}
那么引入的时候必须写出具体的路径:
import * as awarenessProtocol from 'y-protocols/awareness'
import * as syncProtocol from 'y-protocols/sync'
结尾
最近一年,我开发的前端项目基本使用Vite构建,必须用 ESM 的方式导出、引入,所以在写 Node.js 后端的时候也选择了ESM的写法(既然可以,为什么不呢)。虽然现在在 Node.js 使用 ESM 还是没有像写 CJS 这么稳定,但是留意最近的发展趋势,相信 Node.js 对 ESM 的支持会越来越好。
以上就是我在将 require 重构为 import 引入时总结的方法,如有错误、遗漏或疑问,欢迎在评论区留言,感谢你的阅读!