附带 .mjs 和 .d.ts 但没有 .d.mts 的 Typescript 包 - 如何在启用 ESM 的情况下导入?

Ser*_*tin 14 node.js typescript es6-modules

介绍

随着Typescript 4.7 中添加的ECMAScript 模块支持,在 TS 构建过程中可能会涉及几个新的文件扩展名.mjs,包括.d.mts. 如果项目启用了此功能,TS 编译器在进行模块解析(定位要导入的文件)时管理起来会更加复杂。新的 ESM 文件扩展名有两种简单的模块:

  1. 模块有.js实现、.d.ts声明文件
  2. 模块有.mjs实现、.d.mts声明文件

问题

并非所有包都符合上述类别。有些软件包附带了.js.mjs版本的实现,但只有.d.ts声明文件,没有 .d.mts

本案的解决规则是什么?它似乎.mjs被优先考虑.js,但拒绝工作,.d.mts如果您不拥有导入的模块,那么如果没有它,就会出现问题。不修改包就可以解决这个问题吗?


例子

对于通过以下配置启用 ESM 的项目

// package.json
"type": "module"

// tsconfig.json
"module": "Node16",
"moduleResolution": "node16"
Run Code Online (Sandbox Code Playgroud)

这取决于一个软件包(例如js-base64),该软件包附带.js, .mjs.d.ts但没有.d.mts

$ ls -l node_modules/js-base64
base64.d.ts
base64.js
base64.mjs
Run Code Online (Sandbox Code Playgroud)

然后当我尝试导入它时

// myfile.ts
import { Base64 } from 'js-base64'
Run Code Online (Sandbox Code Playgroud)

我收到错误:

找不到模块“js-base64”的声明文件。'/myproj/node_modules/js-base64/base64.mjs' 隐式具有 'any' 类型

但是,如果我这样做

$ ln -s node_modules/js-base64/base64.d.ts node_modules/js-base64/base64.d.mts
Run Code Online (Sandbox Code Playgroud)

然后错误就消失了,这对我来说表明它.d.ts被故意忽略了。

JΛY*_*ÐΞV 23

让未来的读者达成共识

\n

上面的问题询问一个package.json文件,即 AToW (在撰写本文时)是这样配置的。

\n
js-base64/package.json:
\n
{\n  "main": "base64.js",\n  "module": "base64.mjs",\n  "types": "base64.d.ts",\n\n  "files": [\n    "base64.js",\n    "base64.mjs",\n    "base64.d.ts"\n  ],\n\n  "exports": {\n    ".": {\n      "import": "./base64.mjs",\n      "require": "./base64.js"\n    },\n    "./package.json": "./package.json"\n  },\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

问题:

\n
\n

当问题的作者将包文件作为 ESM 模块导入时,他会使用 import 语句来尝试访问它,但是,他的“TypeScript 编译器”又名 )每次TSC都会抛出错误(如下所示)尝试使用导入的模块。

\n

Could not find a declaration file for module: "js-base64" \'/myproj/node_modules/js-base64/base64.mjs\' implicitly has an \'any\' type

\n
\n

重申问题

\n
\n

模块可以在不重新配置包的情况下解析吗?为什么创建符号链接(如谢尔盖的问题所示)似乎可以修复正在发生的错误?

\n
\n
\n\n

更详细的问题

\n
\n

这是一个双模块 Node 包,这意味着它包含两个版本:一个 CJS 版本和一个 ESM 版本。它被配置为仅在模块的入口点使用不同的文件,包的其余部分(理论上)应该是相同的。

\n

为两个不同入口点配置的文件可以在package.json我在此答案最顶部发布的代码片段中看到。

\n

在我们深入讨论之前,我想澄清一下,package.json文件配置有一个问题,但是,在大多数情况下,它的配置是正确的。&入口点 & 构建都运行良好,唯一的问题是,当 TypeScript 用户将模块导入为ECMAS-Module \xe2\x80\x94又名ESM\ xe2\x80\x94 时,它不会解析类型,因此,当问题作者尝试导入和使用包时,TypeScript 编译器会抛出类型错误。CJS ESM

\n
\n

当前配置的含义

\n

不将 with 解析为类型化模块会产生一些明显的影响。虽然它们对大多数人来说都是显而易见的,但最好将它们涵盖在内,以确保所有读者在讨论中都站在同一点上。

\n
\n
    \n
  1. 如果您使用纯 JavaScript,您可能不会注意到任何问题。JS 不使用类型,因此,将包导入到纯 JavaScript 代码库中,应该能够作为“ES-Module”或“Common-JS Module”来完成 \xe2\x80\x94 ” \xe2\x80\x94 没有遇到任何问题。

    \n
  2. \n
  3. import { ... } from \'js-base64\'使用 TypeScript 的人应该能够通过&/或 a来解析模块require(\'js-base64\') 语句解析模块,并且所有内容都应该解析为键入的内容。

    \n
  4. \n
  5. 尝试在 ESM 环境中使用 TypeScript \xe2\x80\x94 导入包的人将收到类型错误,并且他们应该注意到该包未使用类型进行解析。

    \n
  6. \n
\n
\n

为什么类型无法解析?他们不包括在base64.d.ts文件中吗?

\n

因此,包是用 TypeScript 编写的,通常双模块包将用 TypeScript 编写,或者至少使用某些转译器进行转译。在这种情况下,该包无疑是 TS 创作的包。我没有彻底检查它的代码,但如果它曾经是纯 JS,并且在某个时候转换为 TS,我不会感到惊讶。不管怎样,它确实发出了正确的 TSC.d.ts声明文件。

\n
\n
那么为什么它不能解决呢?
\n

由于配置方式的原因,它无法解决。默认情况下,该包配置为解析为“Common-JS Module”。基础package.json文件不包含“类型”字段,因此它默认为CJS模块。文件package.json"type"字段可以设置为...

\n
    \n
  1. "commonjs" (又名 CJS) \xc2\xa0 或,
  2. \n
  3. "module" (又名 ESM)
  4. \n
\n
如上所述,省略该"type"字段会导致包解析为CJS模块。
\n
\n
\n

为 ESM 配置模块,尤其是在尝试同时支持 CJS 时,已成为一项非常复杂的考验,并且目前在涉及 TypeScript 编写的包时,许多人并不理解它。这是因为,尽管 ESM 标准早在 2015 年就已包含在 ECMA-262 规范中,但对 ESM 的大部分支持都是新的。

\n

需要注意的重要一点是,包维护者使用 package.json 文件的“exports”字段来为两种模块类型定义单独的入口点。

\n
这是一段 JSON 代码:
\n
  "types": "base64.d.ts",\n  "exports": {\n    ".": {\n      "import": "./base64.mjs", // ESM entry point\n      "require": "./base64.js" // CJS entry point\n    },\n    "./package.json": "./package.json"\n  },\n
Run Code Online (Sandbox Code Playgroud)\n

他本可以反过来做,但他没有,他按照你在上面看到的方式做了。

\n
\n
\n\n

解决方案是什么样的

\n
\n

问题可以在上面的代码片段中看到,类型字段是他告诉 TSC 当包作为模块导入时声明文件在哪里的地方,问题是,正如我上面指出的,包默认解析为CJS 模块,尽管定义了 ESM 入口点。

\n

为了解决这个问题,包需要有一个声明文件,该文件的名称和文件扩展名与设置为 ESM 入口点的文件具有相同的名称和文件扩展名。解决问题的另一种方法是,base64.d.ts需要显式配置已存在的声明文件,以将文件集设置为 ESM 入口点(即base64.mjs)进行解析。

\n

我进去调整了一些东西,以验证我现在写的东西是大炮。我在使用这个包时做的第一件事就是更改文件中的配置package.json"exports"字段中的配置集。

\n
\n###### 这就是“导出”字段应该的样子\n
// jD3V\'s adjusted package configuration\n\n{\n  "main": "base64.js",\n  "module": "base64.mjs",\n  "types": "base64.d.ts",\n\n  "files": [\n    "base64.js",\n    "base64.mjs",\n    "base64.d.ts"\n  ],\n\n  "exports": {\n    ".": {\n      "import": "./base64.mjs",\n      "require": "./base64.js",\n      "types": "./base64.d.ts"\n    },\n  },\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在上面您可以看到该包的配置使得TypeScript现在可以推断(从包含在“exports”字段中的“types”字段)base64.d.ts 文件应该通过所有导出来解析。

\n
\n
他可以exports这样写该字段:
\n
下面是我从“TypeScript v4.7 发行说明”中摘录的一些内容,片段中的注释非常有帮助。请尝试理解评论想要表达的内容。
\n
    "exports": {\n        ".": {\n            // Entry-point for `import "my-package"` in ESM\n            "import": {\n\n\n            // Where TypeScript will look.\n            "types": "./base64.d.ts",\n\n\n            // Where Node.js will look.\n            "default": "./base64.mjs"\n        },\n\n        "require": "./base64.js",\n    },\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

package.jsonJD3V文件和 TS v4.7 代码片段之间的区别在于,"import"字段(在"exports"字段中)将对象作为其分配的值,因此;当types在“TS v4.7”中设置位置时,它会为“base64.mjs”设置一个特定文件,就像在 JD3V 片段中一样,它会为所有导出设置一个特定的“类型”位置。

\n
\n
请注意:“就包而言,使用本答案顶部显示的配置,如果不修复 \'base64.d.ts\' 文件的解析方式,则无法将该模块作为 ESM 模块导入到 TypeScript 项目中.因为当前在将包导入 ESM 模块时无法解析。”
\n
\n
\n
此外,这都是官方记录的,可以在以下两个链接中找到:
\n

TS v4.7:“Node.js 中的 ECMAScript 模块支持”

\n

TS-Lang:package.json导出、导入和自引用

\n

  • 这回答了问题的第一部分——“本案的解决规则是什么?” 这对我来说非常有价值,谢谢。既然你投入了时间来帮助我,我认为这足以向你授予赏金。然而,答案是从**包作者**的角度写的,这意味着我可以修改“js-base64”等库的源代码。所以它不会回答“这个问题可以在不修改包的情况下解决吗?” 从**包裹消费者**的角度提出的问题的一部分。也许我应该将其提取到一个单独的问题中。 (2认同)