编译依赖 ESM only 库的包成 CommonJS 包
Compile a package that depends on ESM only library into a CommonJS package
我正在开发一个依赖于仅 ESM 库的包:unified 并且我将我的 npm 包公开为 CommonJS 库。
当我在应用程序中调用我的包时,node 给我这个错误消息:
require() of ES Module node_modules\unified\index.js not supported
错误消息很明显,因为我们不允许 require
ESM 模块,但我不是已经告诉 Typescript 将源代码编译成 CommonJS 格式吗?
参考文献:
总结
你不能在 CJS 中使用 static import statements:没有办法绕过它。
然而,如果您只需要在异步上下文中使用模块,则可以通过 dynamic import statements 使用 ES 模块。但是,TypeScript 的当前状态在这种方法方面引入了一些复杂性。
操作方法
考虑这个示例,其中我使用您提到的模块设置了一个 CJS TS 存储库,并且我已经配置了 npm test
脚本来编译和 运行 输出。我已将以下文件放入一个空目录(我在这个 Stack Overflow 问题的 ID 之后将其命名为 so-70545129
):
文件
./package.json
{
"name": "so-70545129",
"version": "1.0.0",
"description": "",
"type": "commonjs",
"main": "dist/index.js",
"scripts": {
"compile": "tsc",
"test": "npm run compile && node dist/index.js"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^17.0.5",
"typescript": "^4.5.4"
},
"dependencies": {
"unified": "^10.1.1"
}
}
./tsconfig.json
{
"compilerOptions": {
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"lib": [
"ESNext"
],
"module": "CommonJS",
"moduleResolution": "Node",
"noUncheckedIndexedAccess": true,
"outDir": "dist",
"strict": true,
"target": "ESNext",
},
"include": [
"./src/**/*"
]
}
./src/index.ts
import {unified} from 'unified';
function logUnified (): void {
console.log('This is unified:', unified);
}
logUnified();
现在,运行 npm install
和 运行 test
脚本:
$ npm install
--- snip ---
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
/so-70545129/dist/index.js:3
const unified_1 = require("unified");
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.
Instead 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.
at Object.<anonymous> (/so-70545129/dist/index.js:3:19) {
code: 'ERR_REQUIRE_ESM'
}
作为参考,输出如下:./dist/index.js
:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const unified_1 = require("unified");
function logUnified() {
console.log('This is unified:', unified_1.unified);
}
logUnified();
上面的错误解释了问题(我在这个答案的顶部总结了)。 TypeScript 已将静态 import
语句转换为 require
的调用,因为模块类型是“CommonJS”。让我们修改 ./src/index.ts
以使用动态导入:
import {type Processor} from 'unified';
/**
* `unified` does not export the type of its main function,
* but you can easily recreate it:
*
* Ref: https://github.com/unifiedjs/unified/blob/10.1.1/index.d.ts#L863
*/
type Unified = () => Processor;
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified: Unified | undefined;
async function getUnified (): Promise<Unified> {
if (typeof unified !== 'undefined') return unified;
const mod = await import('unified');
({unified} = mod);
return unified;
}
async function logUnified (): Promise<void> {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
运行 test
脚本再次:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.
Instead 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.
at /so-70545129/dist/index.js:11:52
at async getUnified (/so-70545129/dist/index.js:11:17)
at async logUnified (/so-70545129/dist/index.js:16:21) {
code: 'ERR_REQUIRE_ESM'
}
障碍
嗯,我们不是刚刚解决了这个问题吗?让我们看一下输出:./dist/index.js
:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified;
async function getUnified() {
if (typeof unified !== 'undefined')
return unified;
const mod = await Promise.resolve().then(() => require('unified'));
({ unified } = mod);
return unified;
}
async function logUnified() {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
解决方案
为什么对 require
的调用仍然存在?这个 GitHub issue ms/TS#43329 解释了为什么 TS 仍然这样编译,并提供了两个解决方案:
在您的 TSConfig 中,set compilerOptions.module
to "node12"
(或 nodenext
)。
如果#1 不是一个选项(你没有在你的问题中说),使用 eval
as a workaround
让我们探讨一下这两个选项:
方案一:修改TSConfig
让我们修改./tsconfig.json
中的compilerOptions.module
值:
{
"compilerOptions": {
...
"module": "node12",
...
},
...
}
又是运行:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
tsconfig.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'.
8 "module": "node12",
~~~~~~~~
Found 1 error.
另一个编译器错误!让我们按照诊断消息中的建议来解决它:将 TS 更新到不稳定版本 typescript@next
:
$ npm uninstall typescript && npm install --save-dev typescript@next
--- snip ---
$ npm ls
so-70545129@1.0.0 /so-70545129
├── @types/node@17.0.5
├── typescript@4.6.0-dev.20211231
└── unified@10.1.1
The version of typescript
now installed is "^4.6.0-dev.20211231"
让我们再运行:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.
Instead 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.
at /so-70545129/dist/index.js:30:65
at async getUnified (/so-70545129/dist/index.js:30:17)
at async logUnified (/so-70545129/dist/index.js:35:21) {
code: 'ERR_REQUIRE_ESM'
}
还是一样的错误。这是检查的输出:./dist/index.js
:
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified;
async function getUnified() {
if (typeof unified !== 'undefined')
return unified;
const mod = await Promise.resolve().then(() => __importStar(require('unified')));
({ unified } = mod);
return unified;
}
async function logUnified() {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
TS 仍在将动态 import
转换为对 require
的调用,即使我们已遵循所有诊断消息建议并正确配置了项目。在这一点上这似乎是一个错误。
让我们尝试解决方法,但首先,让我们撤消刚刚所做的更改:
首先卸载不稳定版typescript
,重新安装稳定版:
$ npm uninstall typescript && npm install --save-dev typescript
--- snip ---
$ npm ls
so-70545129@1.0.0 /so-70545129
├── @types/node@17.0.5
├── typescript@4.5.4
└── unified@10.1.1
The version of typescript
now installed is "^4.5.4"
然后,将compilerOptions.module
的值修改回./tsconfig.json
中的"CommonJS"
:
{
"compilerOptions": {
...
"module": "CommonJS",
...
},
...
}
解决方案 2:使用 eval
的解决方法
让我们修改 ./src/index.ts
,特别是函数 getUnified
(第 16-21 行):
目前看起来是这样的:
async function getUnified (): Promise<Unified> {
if (typeof unified !== 'undefined') return unified;
const mod = await import('unified');
({unified} = mod);
return unified;
}
而TS拒绝停止转换的问题语句在第18行:
const mod = await import('unified');
让我们将其移动到字符串文字中并在 运行 时使用 eval
对其进行评估,以便 TS 不会对其进行转换:
// before:
const mod = await import('unified');
// after:
const mod = await (eval(`import('unified')`) as Promise<typeof import('unified')>);
所以整个函数现在看起来像这样:
async function getUnified (): Promise<Unified> {
if (typeof unified !== 'undefined') return unified;
const mod = await (eval(`import('unified')`) as Promise<typeof import('unified')>);
({unified} = mod);
return unified;
}
保存文件并再次运行:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
This is unified: [Function: processor] {
data: [Function: data],
Parser: undefined,
Compiler: undefined,
freeze: [Function: freeze],
attachers: [],
use: [Function: use],
parse: [Function: parse],
stringify: [Function: stringify],
run: [Function: run],
runSync: [Function: runSync],
process: [Function: process],
processSync: [Function: processSync]
}
终于!达到了预期的结果。让我们最后一次比较输出:./dist/index.js
:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified;
async function getUnified() {
if (typeof unified !== 'undefined')
return unified;
const mod = await eval(`import('unified')`);
({ unified } = mod);
return unified;
}
async function logUnified() {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
这就是我们想要的:动态 import
语句未转换为 require
调用。
现在,当您需要使用unified
函数时,只需在您的程序中使用这个语法:
const unified = await getUnified();
我正在开发一个依赖于仅 ESM 库的包:unified 并且我将我的 npm 包公开为 CommonJS 库。
当我在应用程序中调用我的包时,node 给我这个错误消息:
require() of ES Module node_modules\unified\index.js not supported
错误消息很明显,因为我们不允许 require
ESM 模块,但我不是已经告诉 Typescript 将源代码编译成 CommonJS 格式吗?
参考文献:
总结
你不能在 CJS 中使用 static import statements:没有办法绕过它。
然而,如果您只需要在异步上下文中使用模块,则可以通过 dynamic import statements 使用 ES 模块。但是,TypeScript 的当前状态在这种方法方面引入了一些复杂性。
操作方法
考虑这个示例,其中我使用您提到的模块设置了一个 CJS TS 存储库,并且我已经配置了 npm test
脚本来编译和 运行 输出。我已将以下文件放入一个空目录(我在这个 Stack Overflow 问题的 ID 之后将其命名为 so-70545129
):
文件
./package.json
{
"name": "so-70545129",
"version": "1.0.0",
"description": "",
"type": "commonjs",
"main": "dist/index.js",
"scripts": {
"compile": "tsc",
"test": "npm run compile && node dist/index.js"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^17.0.5",
"typescript": "^4.5.4"
},
"dependencies": {
"unified": "^10.1.1"
}
}
./tsconfig.json
{
"compilerOptions": {
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"lib": [
"ESNext"
],
"module": "CommonJS",
"moduleResolution": "Node",
"noUncheckedIndexedAccess": true,
"outDir": "dist",
"strict": true,
"target": "ESNext",
},
"include": [
"./src/**/*"
]
}
./src/index.ts
import {unified} from 'unified';
function logUnified (): void {
console.log('This is unified:', unified);
}
logUnified();
现在,运行 npm install
和 运行 test
脚本:
$ npm install
--- snip ---
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
/so-70545129/dist/index.js:3
const unified_1 = require("unified");
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.
Instead 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.
at Object.<anonymous> (/so-70545129/dist/index.js:3:19) {
code: 'ERR_REQUIRE_ESM'
}
作为参考,输出如下:./dist/index.js
:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const unified_1 = require("unified");
function logUnified() {
console.log('This is unified:', unified_1.unified);
}
logUnified();
上面的错误解释了问题(我在这个答案的顶部总结了)。 TypeScript 已将静态 import
语句转换为 require
的调用,因为模块类型是“CommonJS”。让我们修改 ./src/index.ts
以使用动态导入:
import {type Processor} from 'unified';
/**
* `unified` does not export the type of its main function,
* but you can easily recreate it:
*
* Ref: https://github.com/unifiedjs/unified/blob/10.1.1/index.d.ts#L863
*/
type Unified = () => Processor;
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified: Unified | undefined;
async function getUnified (): Promise<Unified> {
if (typeof unified !== 'undefined') return unified;
const mod = await import('unified');
({unified} = mod);
return unified;
}
async function logUnified (): Promise<void> {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
运行 test
脚本再次:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.
Instead 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.
at /so-70545129/dist/index.js:11:52
at async getUnified (/so-70545129/dist/index.js:11:17)
at async logUnified (/so-70545129/dist/index.js:16:21) {
code: 'ERR_REQUIRE_ESM'
}
障碍
嗯,我们不是刚刚解决了这个问题吗?让我们看一下输出:./dist/index.js
:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified;
async function getUnified() {
if (typeof unified !== 'undefined')
return unified;
const mod = await Promise.resolve().then(() => require('unified'));
({ unified } = mod);
return unified;
}
async function logUnified() {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
解决方案
为什么对 require
的调用仍然存在?这个 GitHub issue ms/TS#43329 解释了为什么 TS 仍然这样编译,并提供了两个解决方案:
在您的 TSConfig 中,set
compilerOptions.module
to"node12"
(或nodenext
)。如果#1 不是一个选项(你没有在你的问题中说),使用
eval
as a workaround
让我们探讨一下这两个选项:
方案一:修改TSConfig
让我们修改./tsconfig.json
中的compilerOptions.module
值:
{
"compilerOptions": {
...
"module": "node12",
...
},
...
}
又是运行:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
tsconfig.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'.
8 "module": "node12",
~~~~~~~~
Found 1 error.
另一个编译器错误!让我们按照诊断消息中的建议来解决它:将 TS 更新到不稳定版本 typescript@next
:
$ npm uninstall typescript && npm install --save-dev typescript@next
--- snip ---
$ npm ls
so-70545129@1.0.0 /so-70545129
├── @types/node@17.0.5
├── typescript@4.6.0-dev.20211231
└── unified@10.1.1
The version of
typescript
now installed is"^4.6.0-dev.20211231"
让我们再运行:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /so-70545129/node_modules/unified/index.js from /so-70545129/dist/index.js not supported.
Instead 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.
at /so-70545129/dist/index.js:30:65
at async getUnified (/so-70545129/dist/index.js:30:17)
at async logUnified (/so-70545129/dist/index.js:35:21) {
code: 'ERR_REQUIRE_ESM'
}
还是一样的错误。这是检查的输出:./dist/index.js
:
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified;
async function getUnified() {
if (typeof unified !== 'undefined')
return unified;
const mod = await Promise.resolve().then(() => __importStar(require('unified')));
({ unified } = mod);
return unified;
}
async function logUnified() {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
TS 仍在将动态 import
转换为对 require
的调用,即使我们已遵循所有诊断消息建议并正确配置了项目。在这一点上这似乎是一个错误。
让我们尝试解决方法,但首先,让我们撤消刚刚所做的更改:
首先卸载不稳定版typescript
,重新安装稳定版:
$ npm uninstall typescript && npm install --save-dev typescript
--- snip ---
$ npm ls
so-70545129@1.0.0 /so-70545129
├── @types/node@17.0.5
├── typescript@4.5.4
└── unified@10.1.1
The version of
typescript
now installed is"^4.5.4"
然后,将compilerOptions.module
的值修改回./tsconfig.json
中的"CommonJS"
:
{
"compilerOptions": {
...
"module": "CommonJS",
...
},
...
}
解决方案 2:使用 eval
的解决方法
让我们修改 ./src/index.ts
,特别是函数 getUnified
(第 16-21 行):
目前看起来是这样的:
async function getUnified (): Promise<Unified> {
if (typeof unified !== 'undefined') return unified;
const mod = await import('unified');
({unified} = mod);
return unified;
}
而TS拒绝停止转换的问题语句在第18行:
const mod = await import('unified');
让我们将其移动到字符串文字中并在 运行 时使用 eval
对其进行评估,以便 TS 不会对其进行转换:
// before:
const mod = await import('unified');
// after:
const mod = await (eval(`import('unified')`) as Promise<typeof import('unified')>);
所以整个函数现在看起来像这样:
async function getUnified (): Promise<Unified> {
if (typeof unified !== 'undefined') return unified;
const mod = await (eval(`import('unified')`) as Promise<typeof import('unified')>);
({unified} = mod);
return unified;
}
保存文件并再次运行:
$ npm run test
> so-70545129@1.0.0 test
> npm run compile && node dist/index.js
> so-70545129@1.0.0 compile
> tsc
This is unified: [Function: processor] {
data: [Function: data],
Parser: undefined,
Compiler: undefined,
freeze: [Function: freeze],
attachers: [],
use: [Function: use],
parse: [Function: parse],
stringify: [Function: stringify],
run: [Function: run],
runSync: [Function: runSync],
process: [Function: process],
processSync: [Function: processSync]
}
终于!达到了预期的结果。让我们最后一次比较输出:./dist/index.js
:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* AFAIK, all envs which support Node cache modules,
* but, just in case, you can memoize it:
*/
let unified;
async function getUnified() {
if (typeof unified !== 'undefined')
return unified;
const mod = await eval(`import('unified')`);
({ unified } = mod);
return unified;
}
async function logUnified() {
const unified = await getUnified();
console.log('This is unified:', unified);
}
logUnified();
这就是我们想要的:动态 import
语句未转换为 require
调用。
现在,当您需要使用unified
函数时,只需在您的程序中使用这个语法:
const unified = await getUnified();