将仅依赖于 ESM 库的包编译为 CommonJS 包

HUI*_*ANG 14 commonjs node.js typescript es6-modules

我正在开发一个依赖于仅 ESM 库的包:unified,并且我将我的 npm 包公开为 CommonJS 库。

当我在应用程序中调用我的包时,节点给出了以下错误消息:

不支持 ES 模块 node_modules\unified\index.js 的 require()

错误消息很明显,因为我们不允许使用requireESM 模块,但我不是已经告诉 Typescript 将源代码编译为 CommonJS 格式吗?


参考:

  1. ESM 与 CommonJS
  2. 如何为 ESM 和 CommonJS 创建混合 NPM 模块

jse*_*ksn 36

概括

\n

您不能在 CJS 中使用静态导入语句:没有办法解决它。

\n

但是,如果您只需要在异步上下文中使用模块,则可以通过动态导入语句使用 ES 模块。然而,TypeScript 的当前状态给这种方法带来了一些复杂性。

\n
\n

如何

\n

考虑这个示例,其中我使用您提到的模块设置了 CJS TS 存储库,并且配置了 npmtest脚本来编译和运行输出。我已将以下文件放入一个空目录(我so-70545129以该 Stack Overflow 问题的 ID 命名):

\n

文件

\n

./package.json

\n
{\n  "name": "so-70545129",\n  "version": "1.0.0",\n  "description": "",\n  "type": "commonjs",\n  "main": "dist/index.js",\n  "scripts": {\n    "compile": "tsc",\n    "test": "npm run compile && node dist/index.js"\n  },\n  "author": "",\n  "license": "MIT",\n  "devDependencies": {\n    "@types/node": "^17.0.5",\n    "typescript": "^4.5.4"\n  },\n  "dependencies": {\n    "unified": "^10.1.1"\n  }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

./tsconfig.json

\n
{\n  "compilerOptions": {\n    "exactOptionalPropertyTypes": true,\n    "isolatedModules": true,\n    "lib": [\n      "ESNext"\n    ],\n    "module": "CommonJS",\n    "moduleResolution": "Node",\n    "noUncheckedIndexedAccess": true,\n    "outDir": "dist",\n    "strict": true,\n    "target": "ESNext",\n  },\n  "include": [\n    "./src/**/*"\n  ]\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

./src/index.ts

\n
import {unified} from \'unified\';\n\nfunction logUnified (): void {\n  console.log(\'This is unified:\', unified);\n}\n\nlogUnified();\n\n
Run Code Online (Sandbox Code Playgroud)\n
\n

现在,运行npm install并运行test脚本:

\n
$ npm install\n--- snip ---\n\n$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\n/so-70545129/dist/index.js:3\nconst unified_1 = require("unified");\n                  ^\n\nError [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.\nInstead change the require of /so-70545129/node_modules/unified/index.js in /so-70545129/dist/index.js to a dynamic import() which is available in all CommonJS modules.\n    at Object.<anonymous> (/so-70545129/dist/index.js:3:19) {\n  code: \'ERR_REQUIRE_ESM\'\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

作为参考,这里是输出./dist/index.js::

\n
$ npm install\n--- snip ---\n\n$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\n/so-70545129/dist/index.js:3\nconst unified_1 = require("unified");\n                  ^\n\nError [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.\nInstead change the require of /so-70545129/node_modules/unified/index.js in /so-70545129/dist/index.js to a dynamic import() which is available in all CommonJS modules.\n    at Object.<anonymous> (/so-70545129/dist/index.js:3:19) {\n  code: \'ERR_REQUIRE_ESM\'\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

上面的错误解释了问题(我在这个答案的顶部总结了这一问题)。TypeScript 已将静态import语句转换为调用,require因为模块类型是“CommonJS”。让我们修改./src/index.ts为使用动态导入:

\n
"use strict";\nObject.defineProperty(exports, "__esModule", { value: true });\nconst unified_1 = require("unified");\nfunction logUnified() {\n    console.log(\'This is unified:\', unified_1.unified);\n}\nlogUnified();\n\n
Run Code Online (Sandbox Code Playgroud)\n

test再次运行脚本:

\n
$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\nnode:internal/process/promises:246\n          triggerUncaughtException(err, true /* fromPromise */);\n          ^\n\nError [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.\nInstead change the require of /so-70545129/node_modules/unified/index.js in /so-70545129/dist/index.js to a dynamic import() which is available in all CommonJS modules.\n    at /so-70545129/dist/index.js:11:52\n    at async getUnified (/so-70545129/dist/index.js:11:17)\n    at async logUnified (/so-70545129/dist/index.js:16:21) {\n  code: \'ERR_REQUIRE_ESM\'\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

路障

\n

嗯,我们不是刚刚解决了这个问题吗?让我们看一下输出./dist/index.js::

\n
import {type Processor} from \'unified\';\n\n/**\n * `unified` does not export the type of its main function,\n * but you can easily recreate it:\n *\n * Ref: https://github.com/unifiedjs/unified/blob/10.1.1/index.d.ts#L863\n */\ntype Unified = () => Processor;\n\n/**\n * AFAIK, all envs which support Node cache modules,\n * but, just in case, you can memoize it:\n */\nlet unified: Unified | undefined;\nasync function getUnified (): Promise<Unified> {\n  if (typeof unified !== \'undefined\') return unified;\n  const mod = await import(\'unified\');\n  ({unified} = mod);\n  return unified;\n}\n\nasync function logUnified (): Promise<void> {\n  const unified = await getUnified();\n  console.log(\'This is unified:\', unified);\n}\n\nlogUnified();\n\n
Run Code Online (Sandbox Code Playgroud)\n

解决方案

\n

为什么呼叫require仍然在那里?这个 GitHub 问题ms/TS#43329解释了为什么 TS 仍然以这种方式编译,并提供了两种解决方案:

\n
    \n
  1. 在您的 TSConfig 中,设置compilerOptions.module"node12"(或nodenext)。

    \n
  2. \n
  3. 如果 #1 不是一个选项(你在问题中没有说),请使用eval作为解决方法

    \n
  4. \n
\n

让我们探讨一下这两个选项:

\n

方案一:修改TSConfig

\n

让我们修改compilerOptions.module一下中的值./tsconfig.json

\n
$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\nnode:internal/process/promises:246\n          triggerUncaughtException(err, true /* fromPromise */);\n          ^\n\nError [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.\nInstead change the require of /so-70545129/node_modules/unified/index.js in /so-70545129/dist/index.js to a dynamic import() which is available in all CommonJS modules.\n    at /so-70545129/dist/index.js:11:52\n    at async getUnified (/so-70545129/dist/index.js:11:17)\n    at async logUnified (/so-70545129/dist/index.js:16:21) {\n  code: \'ERR_REQUIRE_ESM\'\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

并再次运行:

\n
$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\ntsconfig.json:8:15 - error TS4124: Compiler option \'module\' of value \'node12\' is unstable. Use nightly TypeScript to silence this error. Try updating with \'npm install -D typescript@next\'.\n\n8     "module": "node12",\n                ~~~~~~~~\n\n\nFound 1 error.\n\n
Run Code Online (Sandbox Code Playgroud)\n

又一个编译器错误!让我们按照诊断消息中的建议来解决这个问题:将 TS 更新到不稳定版本typescript@next

\n
$ npm uninstall typescript && npm install --save-dev typescript@next\n--- snip ---\n\n$ npm ls\nso-70545129@1.0.0 /so-70545129\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 @types/node@17.0.5\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 typescript@4.6.0-dev.20211231\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 unified@10.1.1\n
Run Code Online (Sandbox Code Playgroud)\n
\n

现在安装的版本typescript"^4.6.0-dev.20211231"

\n
\n

让我们再次运行:

\n
$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\nnode:internal/process/promises:246\n          triggerUncaughtException(err, true /* fromPromise */);\n          ^\n\nError [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.\nInstead change the require of /so-70545129/node_modules/unified/index.js in /so-70545129/dist/index.js to a dynamic import() which is available in all CommonJS modules.\n    at /so-70545129/dist/index.js:30:65\n    at async getUnified (/so-70545129/dist/index.js:30:17)\n    at async logUnified (/so-70545129/dist/index.js:35:21) {\n  code: \'ERR_REQUIRE_ESM\'\n}\n
Run Code Online (Sandbox Code Playgroud)\n

还是同样的错误。这是用于检查的输出./dist/index.js::

\n
"use strict";\nObject.defineProperty(exports, "__esModule", { value: true });\n/**\n * AFAIK, all envs which support Node cache modules,\n * but, just in case, you can memoize it:\n */\nlet unified;\nasync function getUnified() {\n    if (typeof unified !== \'undefined\')\n        return unified;\n    const mod = await Promise.resolve().then(() => require(\'unified\'));\n    ({ unified } = mod);\n    return unified;\n}\nasync function logUnified() {\n    const unified = await getUnified();\n    console.log(\'This is unified:\', unified);\n}\nlogUnified();\n\n
Run Code Online (Sandbox Code Playgroud)\n

尽管我们已遵循所有诊断消息建议并正确配置了项目,但TS 仍在将动态转换import为对的调用。require目前这似乎是一个错误。

\n

让我们尝试一下解决方法,但首先,让我们撤消刚刚所做的更改:

\n

首先,卸载不稳定版本typescript并重新安装稳定版本:

\n
$ npm uninstall typescript && npm install --save-dev typescript\n--- snip ---\n\n$ npm ls\nso-70545129@1.0.0 /so-70545129\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 @types/node@17.0.5\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 typescript@4.5.4\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 unified@10.1.1\n
Run Code Online (Sandbox Code Playgroud)\n
\n

现在安装的版本typescript"^4.5.4"

\n
\n

然后,将compilerOptions.module值修改回"CommonJS"in ./tsconfig.json

\n
{\n  "compilerOptions": {\n    ...\n    "module": "node12",\n    ...\n  },\n...\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

解决方案 2:解决方法使用eval

\n

让我们修改./src/index.ts,特别是函数getUnified(第 16-21 行):

\n

目前,它看起来像这样:

\n
$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\ntsconfig.json:8:15 - error TS4124: Compiler option \'module\' of value \'node12\' is unstable. Use nightly TypeScript to silence this error. Try updating with \'npm install -D typescript@next\'.\n\n8     "module": "node12",\n                ~~~~~~~~\n\n\nFound 1 error.\n\n
Run Code Online (Sandbox Code Playgroud)\n

而TS拒绝停止变形的有问题的说法在第18行:

\n
$ npm uninstall typescript && npm install --save-dev typescript@next\n--- snip ---\n\n$ npm ls\nso-70545129@1.0.0 /so-70545129\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 @types/node@17.0.5\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 typescript@4.6.0-dev.20211231\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 unified@10.1.1\n
Run Code Online (Sandbox Code Playgroud)\n

让我们将其移入字符串文字并在运行时使用它进行评估,eval以便 TS 不会对其进行转换:

\n
$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\nnode:internal/process/promises:246\n          triggerUncaughtException(err, true /* fromPromise */);\n          ^\n\nError [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.\nInstead change the require of /so-70545129/node_modules/unified/index.js in /so-70545129/dist/index.js to a dynamic import() which is available in all CommonJS modules.\n    at /so-70545129/dist/index.js:30:65\n    at async getUnified (/so-70545129/dist/index.js:30:17)\n    at async logUnified (/so-70545129/dist/index.js:35:21) {\n  code: \'ERR_REQUIRE_ESM\'\n}\n
Run Code Online (Sandbox Code Playgroud)\n

所以整个函数现在看起来像这样:

\n
"use strict";\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n    if (k2 === undefined) k2 = k;\n    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\n}) : (function(o, m, k, k2) {\n    if (k2 === undefined) k2 = k;\n    o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n    Object.defineProperty(o, "default", { enumerable: true, value: v });\n}) : function(o, v) {\n    o["default"] = v;\n});\nvar __importStar = (this && this.__importStar) || function (mod) {\n    if (mod && mod.__esModule) return mod;\n    var result = {};\n    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n    __setModuleDefault(result, mod);\n    return result;\n};\nObject.defineProperty(exports, "__esModule", { value: true });\n/**\n * AFAIK, all envs which support Node cache modules,\n * but, just in case, you can memoize it:\n */\nlet unified;\nasync function getUnified() {\n    if (typeof unified !== \'undefined\')\n        return unified;\n    const mod = await Promise.resolve().then(() => __importStar(require(\'unified\')));\n    ({ unified } = mod);\n    return unified;\n}\nasync function logUnified() {\n    const unified = await getUnified();\n    console.log(\'This is unified:\', unified);\n}\nlogUnified();\n\n
Run Code Online (Sandbox Code Playgroud)\n

保存文件并再次运行:

\n
$ npm run test\n\n> so-70545129@1.0.0 test\n> npm run compile && node dist/index.js\n\n\n> so-70545129@1.0.0 compile\n> tsc\n\nThis is unified: [Function: processor] {\n  data: [Function: data],\n  Parser: undefined,\n  Compiler: undefined,\n  freeze: [Function: freeze],\n  attachers: [],\n  use: [Function: use],\n  parse: [Function: parse],\n  stringify: [Function: stringify],\n  run: [Function: run],\n  runSync: [Function: runSync],\n  process: [Function: process],\n  processSync: [Function: processSync]\n}\n
Run Code Online (Sandbox Code Playgroud)\n

最后!达到了预期的结果。让我们最后一次比较一下输出./dist/index.js::

\n
$ npm uninstall typescript && npm install --save-dev typescript\n--- snip ---\n\n$ npm ls\nso-70545129@1.0.0 /so-70545129\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 @types/node@17.0.5\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 typescript@4.5.4\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 unified@10.1.1\n
Run Code Online (Sandbox Code Playgroud)\n

这就是我们想要的:动态import语句没有转换为require调用。

\n

现在,当您需要使用该unified函数时,只需在程序中使用以下语法:

\n
{\n  "compilerOptions": {\n    ...\n    "module": "CommonJS",\n    ...\n  },\n...\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n


Yua*_*ang 6

我们在尝试导入仅 ESM 的包 (ChatGPT) 时遇到了这个问题,甚至尝试动态失败,因为import已被转译。我们在这个文件中找到了解决方案:

export const importDynamic = new Function('modulePath', 'return import(modulePath)');

async function fn {
  // use the dynamic import:
  const { ChatGPTAPI } = await importDynamic('chatgpt');

  // do whatever you need to do:
  api = new ChatGPTAPI();
}
Run Code Online (Sandbox Code Playgroud)

进一步思考,它所做的似乎只是使用 Javascript 的能力来评估基于字符串的函数,因此转译器无法触及它。天才!