从 require 转换 import 总结 ESM 依赖正确的引入方式

1,730 阅读2分钟

环境

  • 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。

我们先看看重构前后的对比: CJS重构ESM前后对比.png

从图中可以看到 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

  1. 有:使用 import dep from 'module'
  2. 无:使用 import * as dep from 'module'

2. 查看依赖函数的导出方式

  1. export dep :使用 import dep from 'module'

  2. 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 引入时总结的方法,如有错误、遗漏或疑问,欢迎在评论区留言,感谢你的阅读!