纯 ESM 包
链接里的包现在是纯 ESM。它不能使用 CommonJS 的 require()
引入。
这意味着你有以下选择:
- 自己使用 ESM。 (首选)
使用import foo from 'foo'
而不是const foo = require('foo')
来导入包。你还需要在你的 package.json 中加入"type": "module"
等等。请遵循以下指南。 - 如果是在异步上下文中使用包,你可以在 CommonJS 中使用
await import(...)
,而不是require(...)
。 - 继续使用该软件包的现有(CommonJS)版本,直到你可以迁移到 ESM。
你还需要确保你使用的是最新的 Node.js 次要版本。至少 Node.js 16。
我强烈建议迁移到 ESM。 ESM 仍然可以导入 CommonJS 包,但是 CommonJS 包不能同步地导入 ESM 包。
我的仓库不是问 ESM/TypeScript/Webpack/Jest/ts-node/CRA 支持问题的地方。
常问问题
如何将我的 CommonJS 项目移动到 ESM?
- 将
"type": "module"
添加到你的 package.json。 - 将 package.json 中的
"main": "index.js"
替换为"exports": "./index.js"
。 - 将 package.json 中的
"engines"
字段更新为 Node.js 16:"node": ">=16"
。 - 从所有 JavaScript 文件中删除
'use strict';
。 - 将所有
require()
/module.export
替换为import
/export
。 - 仅使用完整的相对文件路径进行导入:
import x from '.';
→import x from './index.js';
。 - 如果你有 TypeScript 类型定义(例如,
index.d.ts
),请将其更新为使用 ESM 导入/导出。 - 使用
node:
协议导入 Node.js 内置模块。
旁注:如果你正在寻找有关如何向 JavaScript 包添加类型的指导,查看我的指南。
我可以在我的 TypeScript 项目中导入 ESM 包吗?
可以,但你需要将项目转换为输出 ESM。见下文。
如何让我的 TypeScript 项目输出 ESM?
快速步骤:
- 确保你使用的是 TypeScript 4.7 或更高版本。
- 将
"type": "module"
添加到你的 package.json。 - 将 package.json 中的
"main": "index.js"
替换为"exports": "./index.js"
。 - 将 package.json 中的
"engines"
字段更新为 Node.js 16:"node": ">=16"
。 - 将
"module": "node16", "moduleResolution": "node16"
添加到你的 tsconfig.json。 (示例) - 仅使用完整的相对文件路径进行导入:
import x from '.';
→import x from './index.js';
。 - 删除
namespace
的使用并改用export
。 - 使用
node:
协议导入 Node.js 内置模块。 - 即使你正在导入
.ts
文件,你也必须在相对导入中使用.js
扩展名。
如何在 Electron 中导入 ESM?
Electron 从 Electron 28 开始支持 ESM(在撰写本文时尚未推出)。请读这篇文档。
使用 ESM 和 Webpack 时遇到问题
这个问题出在 Webpack 或你的 Webpack 配置上。首先,确保你使用的是最新版本的 Webpack。请不要在我的仓库中提 issue。尝试在 Stack Overflow 上提问或 open an issue the Webpack repo。
使用 ESM 和 Next.js 时遇到问题
升级到 Next.js 12 将完全支持 ESM.
使用 ESM 和 Jest 时遇到问题
使用 ESM 和 TypeScript 时遇到问题
如果你决定让你的项目使用 ESM(将 "type": "module"
设置在你的 package.json 中),请确保 "module": "node16"
在你的 tsconfig.json 文件中,并且你对本地文件的所有导入语句都使用 .js
扩展名,而不是 .ts
或不使用扩展名。
ts-node
的问题
遇到了 ESM 和 遵循本指南 并确保你使用的是最新版本的 ts-node
。
使用 ESM 和 Create React App 时遇到问题
Create React App 还不完全支持 ESM。我建议在他们的仓库中提你遇到的问题的 issue。一个已知问题是 #10933。
如何将 TypeScript 与 AVA 一起用于 ESM 项目?
遵循本指南。
如何确保不会意外使用 CommonJS 特定的约定?
__dirname
和 __filename
?
如何代替 import { fileURLToPath } from "node:url";
import path from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
但是,在大多数情况下,这样会更好:
import { fileURLToPath } from "node:url";
const foo = fileURLToPath(new URL("foo.js", import.meta.url));
许多 Node.js API 直接接受 URL,所以你可以这样做:
const foo = new URL("foo.js", import.meta.url);
如何在测试时导入模块并绕过缓存?
目前还没有很好的方法来做到这一点。直到我们可以使用 ESM 加载程序挂钩。现在,这个段代码可能很有用:
const importFresh = async (modulePath) =>
import(`${modulePath}?x=${new Date()}`);
const chalk = (await importFresh("chalk")).default;
注意:这会导致内存泄漏,所以只能用于测试,不能用于生产。此外,它只会重新加载导入的模块,而不是它的依赖项。
如何导入 JSON?
JavaScript 模块最终将原生支持 JSON,但现在,你可以这样做:
import fs from "node:fs/promises";
const packageJson = JSON.parse(await fs.readFile("package.json"));
什么时候应该使用默认导出或命名导出?
我的一般规则是,如果某个东西导出一个主要的东西,它应该是默认导出。
请记住,你可以在有意义的情况下将默认导出与命名导出相结合:
import readJson, { JSONError } from "read-json";
在这里,我们导出了主要的 readJson
,但我们也导出了一个错误作为命名导出。
异步和同步 API
如果你的包同时具有异步和同步主 API,我建议使用命名导出:
import { readJson, readJsonSync } from "read-json";
这使读者清楚地知道该包导出了多个主要 API。我们还遵循 Node.js 的约定,即使用 Sync
为同步 API 加后缀。
可读的命名导出
我注意到对命名导出使用过于通用的名称的包的错误模式:
import { parse } from "parse-json";
这迫使消费者要么接受不明确的名称(这可能导致命名冲突),要么重命名它:
import { parse as parseJson } from "parse-json";
相反,这样更易于使用:
import { parseJson } from "parse-json";
例子
使用 ESM,我现在更喜欢描述性命名导出而不是命名空间默认导出:
CommonJS(之前):
const isStream = require('is-stream');
isStream.writable(…);
ESM(现在):
import {isWritableStream} from 'is-stream';
isWritableStream(…);
原文地址:Pure ESM package
此翻译由 zjffun/translated-document 提供,在 GitHub 上完善此翻译。