TypeScript 声明合并可能会不一致

bkhjykvo  于 2个月前  发布在  TypeScript
关注(0)|答案(5)|浏览(36)

TypeScript版本: 4.0.3
搜索词:

  • module
  • declare
  • merging
  • declaration
    代码
  • 源文件 index.ts :
import { foo } from "./foo.ts"
console.log(foo({ fooField: "hello" }))
  • 源文件 foo.ts :
import { Bar } from "bar"

declare module "bar" {
  interface Bar {
    fooField?: string
  }
}

export const foo = (e: Bar) => e.fooField
  • 编译后的 index.d.ts :
export {};
  • 编译后的 foo.d.ts :
import { Bar } from "bar"
declare module "bar" {
    interface Bar {
        fooField?: string;
    }
}
export declare const foo: (e: Bar) => string | undefined;

预期行为:

  • 编译后的 index.d.ts 应该看起来像:
import "./foo"
export {};

当导入 index.d.ts 时,模块 "bar" 的声明合并应该发生。更准确地说:
当生成 .d.ts 文件时,如果没有使用/暴露 foo.d.ts 中的类型,但导入该文件会导致模块声明合并,则编译器应自动将 import { x } from "./foo" 替换为 import "./foo"

实际行为:

index.d.ts 没有导入 ./foo.d.ts ,这意味着模块 "bar" 的声明合并并未发生。
现在,如果我们决定从 foo.ts 导出一些内容,那么一切都“正常工作”( ./foo.d.tsindex.d.ts 中被引用),这是错误的,原因如下:

  • 我们可能不希望从 foo 中导出任何内容
  • 如果导入文件依赖于 Bar.fooField (如果它依赖于 Bar.fooField ),则可能会开始出现编译错误(因为一个无关的导出被删除了)
  • 在开发 index.ts 时,开发者会“看到” Bar.fooField 属性,并错误地认为任何使用该文件的用户都会“看到”它。

目前支持这种方式的方法是在 index 的开头手动添加 x1m29n1x (除了 x1m29n2x 之外)。但是,这意味着您需要了解每个依赖项的内部情况(您只需要显式导入声明要合并的模块)。
编译器应在 x24n3x 文件中自动用 x24n4x 替换 x24n5x,即使没有使用来自 x25n6x 的类型,如果导入该文件会导致声明合并。

Playground链接:

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgMwhOBfFUIjgIgDoB6VCfAWACgBjCAOwGcIAbAU0JYgHMAKM3ggwBKYdWrFicYMjgkyhKIzh16MAIbAmcZiDZwAJmxot1UdTGAM4eqNy3cANHBgALNgE84AdwYByeAAjfXUwMBZgNgMfd3ppcGhLem4XV2BlZGB2IA

mspsb9vt

mspsb9vt1#

你认为呢?看起来合理。

jecbmhm3

jecbmhm32#

嗯,我的意思是,虽然我们可以,从技术上讲
编译器应该自动将 import { x } from "./foo" 替换为在 .d.ts 文件中导入 "./foo",即使没有使用 foo.d.ts 中的类型
这是一个简单的转换,但它有一个明显的缺点。你将失去在实现中使用 import {x} from "lodash" 的能力,并且如果你不使用它们,就不会依赖于 lodash 的类型(因为我们在声明文件中会发出 import "lodash")。这是一个真正的、明显的缺点。(我认为在大型项目中会有较大的性能损失。)如果导入该文件会导致声明合并。
这部分甚至更困难——这取决于在加载时首先遇到的文件的导入顺序,这在增量场景(或循环)下可能不稳定,因此不太可靠。我认为鉴于这个限制,我们甚至无法做到这一点。
我不是很喜欢这种做法。如果你的深度导入是包括该文件的 API 合同的重要组成部分(例如,包括 X 意味着 Y 依赖项以及隐式包含类型空间副作用),我们有两个 import "lib"/// <reference types="lib"/>,用户可以通过添加它们来显式序列化该依赖项,如果你认为这很重要的话。

n53p2ov0

n53p2ov03#

感谢您的回复@weswigham。我同意您所说的一切。以下是我遇到的使用案例,这使得您的最后一段解决方案不太方便。
当涉及到子模块时,“问题”变得非常烦人。在我之前的示例中,我们确实可以从 /index.ts 导入 /// <reference types="./foo"/> 或 x1m1m1x 。但请想象一下,./index.ts 实际上是私有子模块(实际文件位置:./sub-module/index.ts)的入口点。在这种情况下,当从根模块导入该子模块时,开发人员期望 import * from './sub-module' 能够正常工作。在构建过程中,tsc 不会抱怨关于根 ./index.ts 中合并声明的任何引用类型。然而,生成的根声明文件(./index.d.ts)既不包含 import "./sub-module/foo" 也不包含 /// <reference types="./sub-module/foo"/> 。这意味着如果您有一个结构,其中子模块使用声明合并,任何导入它的模块都必须显式地要么 import "./sub-module" 要么 /// <reference types="./sub-module/foo"/> 。传递性地。这有点破坏了关注点的分离,以及团队能够透明地协作的能力。
我不是说我建议的解决方案是正确的方法(我真的不了解TS的内部足够假装那样)。但我真的认为从概念上讲,导入一个直接或间接导致声明合并的 .ts 模块应该在通过 .d.ts 声明文件导入时也反映出声明合并。

ewm0tg9j

ewm0tg9j4#

关于您关于排序的评论,请注意,直接处理 .ts 文件时没有任何问题。为什么在以与相应 .ts 文件相同的顺序导入 .d.ts 文件时会出现问题?

cotxawn7

cotxawn75#

我是否正确理解了TypeScript 5'v new verbatimModuleSyntax选项,当我假设它解决了这个问题时?

相关问题