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.ts
从 index.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
5条答案
按热度按时间mspsb9vt1#
你认为呢?看起来合理。
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"/>
,用户可以通过添加它们来显式序列化该依赖项,如果你认为这很重要的话。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
声明文件导入时也反映出声明合并。ewm0tg9j4#
关于您关于排序的评论,请注意,直接处理
.ts
文件时没有任何问题。为什么在以与相应.ts
文件相同的顺序导入.d.ts
文件时会出现问题?cotxawn75#
我是否正确理解了TypeScript 5'v new
verbatimModuleSyntax
选项,当我假设它解决了这个问题时?