模块配置与装饰器

共 59 题
📑 题目列表 59 题
#
★★★

1. isolatedModules、verbatimModuleSyntax(TS 5.0+)对打包器(Babel/esbuild/SWC/Oxc)的协作

isolatedModules 与 verbatimModuleSyntax 对打包器(Babel/esbuild/SWC/Oxc)的协作有什么影响?

  • isolatedModules 的"单文件转译"语义
  • verbatimModuleSyntax 强制 import type/export type
  • 与 Babel/esbuild 等转译器的协作边界

Babel/esbuild 等转译器按文件独立转译、不做跨文件类型分析,因此 const enum、namespace 等需要整体信息的语法无法正确转译。isolatedModules 在类型层面约束"每个文件可独立编译":禁止 const enum、禁止无 export 的全局增强等,并强制导出类型时使用 export type;verbatimModuleSyntax(TS 5.0+)更进一步,要求 import/export 的类型修饰符按原样保留,编译器不再自动擦除或合并类型导入,保证产物中的 import 语句与打包器的 ESM 语义一致。协作要点:使用这些转译器时必须开启 isolatedModules,verbatimModuleSyntax 提供最严格的语义对齐,类型导入统一写 import type,枚举改用 as const 对象或联合字面量,从而让 tsc 检查与打包器产物行为一致。

考察"转译器 + 类型检查器"的职责分工:isolatedModules/verbatimModuleSyntax 把"可独立转译"与"类型导入显式化"变成强制约束,回答应说明语法限制与 import type 规范。

#
★★★

2. skipLibCheck 的开启代价(第三方类型 bug 隐藏)与治理

skipLibCheck 开启的代价是什么?如何治理其隐藏的第三方类型问题?

  • skipLibCheck 跳过 .d.ts 的类型检查
  • 隐藏第三方声明 bug 与本地声明冲突
  • 治理手段(显式声明覆盖、升级依赖)

skipLibCheck 跳过 node_modules 与 .d.ts 文件的类型检查,能大幅加快编译(尤其依赖多时),但代价是:第三方声明的内部类型错误、重复声明冲突(两个包对同一全局/模块声明不一致)、@types 版本不匹配等问题全部被隐藏,错误只在使用处间接暴露且难以定位。治理手段:其一,用本地的 module augmentation 或 declare module 对问题声明做显式修正,而不是依赖跳过;其二,锁定 @types 与依赖的版本矩阵,升级时回归类型测试;其三,对核心依赖单独开启检查(把声明拷贝到本地检查或写类型测试);其四,CI 中定期全量检查(临时关闭 skipLibCheck 跑一遍)发现隐藏问题。

考察 skipLibCheck 的"性能换可见性"权衡:跳过检查的代价是隐藏第三方类型问题,回答应说明代价机制与系统化治理手段。

#
★★★

3. erasableSyntaxOnly(TS 5.8+)禁止 enum/namespace/参数属性的纯转译器兼容性

erasableSyntaxOnly(TS 5.8+)是什么?它如何保证纯转译器兼容性?

  • "类型可擦除"(erasable syntax)的定义
  • 禁止 enum、namespace、参数属性等运行时语法
  • 与 Babel/esbuild/tsgo 的协作

erasableSyntaxOnly 是 TS 5.8 新增选项:只允许"可擦除语法"——即仅含类型信息、擦除后不产生运行时语义的语法(类型标注、接口、类型别名、类型参数等),禁止 enum、namespace(带运行时对象)、构造函数参数属性(constructor(private x))等"非可擦除语法",因为这些语法在转译时需要生成额外代码,纯转译器(Babel/esbuild/swc/tsgo 的 native 实现)无法或不愿一致处理。开启后编译器直接报错引导改写:enum 换 as const + 联合、namespace 换模块、参数属性换显式字段赋值。工程价值:让"tsc 只做类型检查、转译器做产物生成"的链路完全成立,产物行为与类型语义零漂移,是 tsgo 等原生编译器路线的配套约束。

考察"可擦除语法"概念与纯转译链路的关系:该选项把非可擦除语法挡在编译期,回答应说明被禁止的语法类别与改写方向。

#
★★★

4. @ts-ignore/@ts-expect-error/@ts-nocheck 的滥用治理

@ts-ignore、@ts-expect-error、@ts-nocheck 的语义区别是什么?如何治理滥用?

  • 三个注释指令的语义与适用场景
  • @ts-expect-error 的"未使用即报错"特性
  • 滥用治理与 lint 配置

@ts-ignore 抑制下一行所有错误(无论是否真的报错,错误消失后无感);@ts-expect-error 期望下一行报错,若下一行没有错误反而报"未使用的指令",能自检"错误是否已被修复";@ts-nocheck 关闭整个文件的检查。治理:@ts-expect-error 是三者中最安全的,适合"已知第三方类型缺失"的临时绕过;@ts-ignore 与 @ts-nocheck 会静默掩盖回归,应配合 ESLint 的 ban-ts-comment 规则限制(如禁止 ignore/nocheck,允许 expect-error 且要求注释理由);数量上设预算(lint 统计),新代码禁止新增,存量逐步清理;修复方向是类型守卫、声明文件与升级依赖而非无限压制。

考察三个抑制指令的语义差异与工程治理:expect-error 的可自检性是其核心优势,回答应说明指令区别与 lint/预算治理。

#
★★★

5. lib 配置 在 IDE 智能提示与重构的工程价值

tsconfig 的 lib 配置在 IDE 智能提示与重构中有哪些工程价值?

  • lib 决定可用的内置类型(DOM、ES 版本 API)
  • target 与 lib 的分离配置
  • 环境差异(Node/浏览器/混合)的 lib 组合

lib 声明编译期可用的标准库类型集合(ES5/ES2015...ESNext、DOM、DOM.Iterable、WebWorker 等),它与 target(产物语法版本)解耦:即使 target 为低版本,也可配置高版本 lib 使用新 API 类型(配合 polyfill)。工程价值:其一,IDE 智能提示与补全严格按 lib 过滤——配置了 DOM 才有 document/window 类型,配置 WebWorker 才有 worker API,避免"类型上存在但运行时没有"的错觉;其二,重构时 lib 决定 API 可用性检查(如 String.replaceAll 需要 ES2021 的 lib),缺失时直接报错提醒 polyfill;其三,环境混合(Node + 浏览器同构代码)用 lib 组合与 types 区分全局类型,保证类型环境与运行环境一致。

考察 lib 配置对"类型环境"的塑造:lib 定义全局类型面,直接影响补全、提示与可用性检查,回答应说明与 target 的解耦及环境组合。

#
★★★

6. @deprecated JSDoc 在库发布与消费的类型一致性

@deprecated JSDoc 注释在库发布与消费场景中如何保证类型一致性?

  • @deprecated 的标注方式与 IDE 提示
  • 库版本演进中的废弃管理
  • 消费端对废弃 API 的可见性

@deprecated 是 JSDoc 标签,TS 会识别并在 IDE 中显示废弃提示(删除线 + 警告),配合 TS 5.0+ 的 @deprecated 校验(strictDeprecation 类检查或 ESLint)可在使用处直接告警。库发布场景:废弃 API 用 @deprecated 标注并附替代建议(@deprecated use newApi instead),类型签名保留以兼容旧消费端,实现上标记 JSDoc 后 IDE 自动提示迁移路径;消费端升级时 lint 的 deprecation 检查把"废弃用法"变成可量化的迁移清单。一致性保障:废弃状态(是否 deprecated、替代 API)需要类型与文档同步,API Extractor 的 api-report 可审查废弃标注,保证发布物中的废弃信息与类型一致。

考察废弃元数据在类型层的表达:@deprecated 是"类型 + 文档"的双重契约,回答应说明标注方式、IDE 提示与迁移治理。

#
★★★

7. TypeScript 模块解析策略(classic、node16、nodenext、bundler)

TypeScript 的模块解析策略(classic、node16、nodenext、bundler)有什么区别?如何选择?

  • 各解析策略的查找规则与适用场景
  • node16/nodenext 对 ESM/CJS 的区分
  • bundler 模式与打包器的匹配

moduleResolution 决定 import 的解析规则:classic 是旧版逐目录向上查找,已废弃;node(node10)模拟旧 Node CJS 解析(扩展名补全、目录 index);node16/nodenext 按文件扩展名与最近的 package.json type 字段区分 ESM/CJS,强制 ESM 导入带扩展名,尊重 exports 条件导出;bundler(TS 5.0+)模拟打包器语义:允许无扩展名、支持 exports 的 import/browser 条件,但要求 module 为 esnext/preserve 且不可用于纯 Node 运行时。选择:Node 库/服务端代码用 nodenext(或 node16);打包器工程(Vite/Webpack)用 bundler;两者混合的同构代码分别配置;legacy 项目逐步迁移到现代模式。

考察模块解析模式的演进与选型:node16/nodenext 对齐 Node 真实运行语义,bundler 对齐打包器语义,回答应说明各模式要点与选型依据。

#
★★★

8. .d.ts 声明文件在第三方库类型扩展(declare module)的工程应用

.d.ts 声明文件如何通过 declare module 扩展第三方库类型?

  • declare module 的模块增强语法
  • 扩展现有模块的导出与全局类型
  • 与 module augmentation 的边界

declare module 用于给无类型或类型不全的模块补充声明:declare module 'lib' { export function x(): void } 声明模块整体,或对已有类型库做增量增强(module augmentation)——同名的 declare module 内只写新增导出,与原类型自动合并;扩展全局(declare global)给 window 等全局对象补成员。工程应用:为 JS 依赖补类型、扩展第三方库的配置选项(如给某插件包补充它不导出的类型)、在应用层为库声明"环境专属"的类型。边界:declare module 只能出现在外部模块文件(含 import/export)中或全局 .d.ts,增强的成员需与库实际导出兼容,否则运行时缺失;对绝对路径子路径增强用通配 declare module 'lib/*'

考察模块增强机制:declare module 是"第三方类型修补"的主要手段,回答应说明整体声明与增量增强的区别及全局扩展方式。

#
★★★

9. tsconfig.json 的 strict 系列选项在大型项目的渐进启用策略

tsconfig.json 的 strict 系列选项在大型项目如何渐进启用?

  • strict 家族各选项的语义
  • 渐进启用的策略与工具
  • 迁移成本与收益的平衡

strict 是一个总开关,包含 strictNullChecks、strictFunctionTypes、strictPropertyInitialization、noImplicitAny、noImplicitThis、useUnknownInCatchVariables、exactOptionalPropertyTypes(5.0 起入列)等。大型项目渐进启用:其一,先开风险可控的(noImplicitAny、strictNullChecks)并配合 lint 清理存量;其二,用"分区开关"策略——tsconfig 继承链中 base 开 strict,对遗留目录用独立的宽松 tsconfig 覆盖(或按文件 whitelist 引入),新代码强制 strict;其三,利用 tsc --noEmit 全量检查输出错误清单,按模块分批修复;其四,strictNullChecks 迁移可借助 TS 4.x 的 auto fixer(--strictNullChecks 相关建议)或第三方 codemod。收益:strict 下运行时错误显著减少(null/undefined 类 bug 提前暴露),是长期类型安全的根基。

考察严格模式的组合语义与渐进迁移工程:strict 不是单选项而是一族,回答应说明选项清单、分批启用策略与成本收益。

#
★★★

10. moduleResolution: bundler 在 Vite、Webpack 等打包器下的类型解析行为

moduleResolution: bundler 在 Vite、Webpack 等打包器下的类型解析行为是怎样的?

  • bundler 模式的解析规则(无扩展名、exports 条件)
  • 与打包器 resolve.alias/conditionNames 的对应
  • 类型解析与运行时解析的一致性

bundler 模式把 TS 的类型解析对齐打包器:允许无扩展名导入(.ts/.tsx 与目录 index)、支持 package.json exports 的 import 条件、支持 import.meta、.ts 后缀导入等;与 Vite/Webpack 的 resolve.alias、resolve.conditionNames 对应时,需保证 tsconfig 的 paths/自定义条件与打包器配置同步,否则"类型能解析而运行时失败"或反之。要点:其一,paths 别名与 resolve.alias 必须一致(Vite 可读 tsconfig paths);其二,exports 中浏览器/客户端条件(import、browser、default)与打包器 conditionNames 顺序对齐;其三,打包器支持的无扩展名目录解析在 bundler 模式下同样生效;其四,环境变量导入(vite/client 类型)用 triple-slash 或 types 声明。类型解析与运行时解析的一致性检验可用"类型检查 + 打包产物冒烟"双保险。

考察 bundler 模式与打包器解析的对应关系:类型解析规则模拟打包器,关键是与 alias、conditionNames 同步,回答应说明一致性保障。

#
★★★

11. exports/imports 条件导出(含 import/require/types/browser)

package.json 的 exports/imports 条件导出(import/require/types/browser)如何工作?

  • exports 字段的结构与条件顺序
  • types 条件与 import/require 条件的配合
  • 双格式包(ESM/CJS)的类型分发

exports 字段替代 main/module/types 的分发逻辑,按条件选择入口:"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } }——解析器按顺序匹配条件(types 常放最前保证类型优先、import/require 区分 ESM/CJS 入口、browser 供浏览器打包器)。关键点:其一,条件顺序敏感,先匹配者胜出,types 必须前置否则打包器可能先吃到 js 入口;其二,子路径导出用 "./sub" 键并控制通配符 "./*",未导出路径在 Node 下解析失败(封装性);其三,imports 字段为包内私有别名(#alias)提供映射;其四,双格式包需分别提供 .d.mts/.d.cts 或按条件分发 types,保证 ESM/CJS 消费者都拿到正确类型。版本兼容:Node 12.7+/打包器支持 exports,老工具回退 main。

考察现代包分发协议:exports 条件决定"谁拿到哪个入口与类型",回答应说明条件顺序、types 前置与双格式分发。

#
★★★

12. 类型声明合并(declaration merging)在全局扩展与库作者的应用

类型声明合并(declaration merging)在全局扩展与库作者场景中如何应用?

  • 声明合并的语法(interface、namespace、enum 合并)
  • 全局对象与模块的扩展
  • 库作者扩展现有类型的能力设计

声明合并指多个同名声明自动合并:interface 同名合并成员、namespace 与同名 class/function/enum 合并、declare global 扩展全局命名空间。应用场景:其一,全局扩展——给 window/globalThis 添加自定义属性(declare global { interface Window { myFlag?: boolean } }),给第三方接口补字段;其二,库作者——设计"可扩展接口"(如插件系统的 PluginContext 留 interface 供消费者增强)、用 namespace 合并补充函数对象的静态成员类型;其三,JS 库声明——declare function + namespace 合并表达"函数带静态属性"。边界:类型与同名变量/函数合并会生成运行时对象(namespace 转译产物),纯类型库应避免;合并冲突(同名属性类型不同)报错。

考察声明合并机制的两种主力场景:全局类型扩展与库的可扩展性设计,回答应说明合并语法与运行时存在性边界。

#
★★★

13. 环境声明(ambient declarations)

环境声明(ambient declarations)是什么?有哪些应用?

  • declare 关键字与 ambient 上下文
  • .d.ts 文件中声明的特殊性
  • 声明运行时存在的全局/模块内容

环境声明用 declare 关键字描述"运行时已存在、无需生成实现"的内容:declare functiondeclare constdeclare classdeclare moduledeclare namespace 等,位于 .d.ts(ambient 上下文)中时无需额外 declare 也可省略实现。应用:声明全局变量/函数(第三方 script 引入的全局)、声明模块(无类型 npm 包、CSS/JSON 导入 declare module '*.css')、声明环境变量(import.meta.env 类型扩展)、Node 全局等。关键:ambient 声明只提供类型,不产生任何运行时代码;声明必须与运行时真实存在一致,否则类型通过而运行时报 undefined;全局 ambient 文件的类型默认全局可见(除非有 import/export 变模块),配合 tsconfig include 管理。

考察 declare/ambient 的"只声明不实现"语义:环境声明描述运行时既存内容,回答应说明语法形态、.d.ts 特殊性与一致性要求。

#
★★★

14. TypeScript 5.x 实验装饰器(@dec)与 Stage 3 标准装饰器的转译器差异

TypeScript 5.x 的实验装饰器与 Stage 3 标准装饰器在转译器上有哪些差异?

  • 实验装饰器(legacy)的语义与参数形态
  • Stage 3 装饰器(access/context)的新形态
  • Babel/SWC 对两套装饰器的转译差异

TS 5.x 的 experimentalDecorators(legacy,对应 TS 早期与 ES decorators 旧提案)参数形态是 (target, propertyKey, descriptor),类装饰器收构造器;Stage 3 装饰器(TS 5.0+ 原生支持,无需 experimentalDecorators)改为 (value, context) 二元形态,context 携带 kind、name、addInitializer、static/private 等元信息,语义上"装饰器返回值替换成员"且元数据机制(Symbol.metadata)不同。转译器差异:Babel/SWC 对 legacy 用 legacy-decorators 插件(babel-plugin-proposal-decorators 的 legacy 模式),对 Stage 3 用 2023-05/2023-11 版本插件,两套语义不可混用(同一文件只能用一套);useDefineForClassFields 也会影响装饰器下字段初始化顺序。工程建议:新项目用 Stage 3 装饰器(对齐标准、ESM 友好),遗留项目明确锁定 legacy 并保持转译器版本一致。

考察两套装饰器语义与转译链路的对应:参数形态与元数据机制不同,转译器按模式选择插件,回答应说明差异与选型。

#
★★★

15. ECMAScript 装饰器(Stage 3,access/context 二元形态)

ECMAScript 装饰器(Stage 3)的 access/context 二元形态是什么?有什么能力?

  • 装饰器函数 (value, context) 签名
  • context 的 kind/name/metadata/addInitializer
  • 类、方法、访问器、字段的装饰能力

Stage 3 装饰器是 (value, context) => newValue | undefined 的普通函数:value 为被装饰目标(类、方法函数、访问器、字段值、参数),context 是描述上下文的对象,含 kind('class'|'method'|'getter'|'setter'|'field'|'accessor')、name、static、private、addInitializer(注册实例/静态初始化钩子)、access(get/set 访问器对象,供字段装饰器外部读取)等;类装饰器也可访问 context.metadata 与 Symbol.metadata 共享元数据。能力:方法装饰器可替换或包装函数(AOP)、字段装饰器配合 access 实现代理读写、accessor 关键字定义自动访问器、addInitializer 注入初始化逻辑、metadata 支持依赖注入与 ORM 的元数据收集。TypeScript 5.0+ 原生支持,无需 experimentalDecorators。

考察标准装饰器的新形态:二元签名 + context 元信息是核心,回答应说明 context 字段与各类成员的装饰能力。

#
★★★

16. 装饰器在类、方法、访问器、字段的元数据应用

装饰器在类、方法、访问器、字段上如何实现元数据应用?

  • 各类目标的装饰器签名与返回值语义
  • 元数据收集模式(依赖注入、ORM、校验)
  • 与 Reflect.metadata/Symbol.metadata 的配合

装饰器可在四类目标上附加元数据并改写行为:类装饰器可替换构造器或注册类级元数据(组件声明、路由注册);方法装饰器可包装方法(日志、权限、重试)并在原型上打标记;访问器装饰器包装 get/set(响应式依赖收集);字段装饰器记录字段元数据(ORM 列映射、校验规则)并可配合 access 代理读写。元数据应用范式:装饰器把"声明式配置"写入元数据表(WeakMap 或 Symbol.metadata),框架在实例化时读取执行——Nest.js 的 DI 注入、TypeORM 的实体列映射、class-validator 的校验、Angular 的组件元数据均基于此。TS 提供 emitDecoratorMetadata 辅助(设计时类型信息),标准路线上 Symbol.metadata 承担跨目标共享元数据;注意装饰器求值顺序(按目标类型与书写顺序)对元数据组装的影响。

考察装饰器的元数据编程范式:声明式注解 + 运行时读取是框架设计基石,回答应说明四类目标的改写/标记能力与元数据读取时机。

#
★★★

17. auto-accessor(随装饰器 Stage 3)

auto-accessor 是什么?它与装饰器 Stage 3 的关系是什么?

  • accessor 关键字声明自动访问器
  • 字段与访问器的无缝装饰
  • 与私有字段、静态字段的组合

auto-accessor(TS 4.9 原生支持)用 accessor x = 0 声明"自动访问器":编译器生成后备私有字段 + get/set 对,开发者无需手写样板;配合 Stage 3 装饰器时,@dec accessor x 会把装饰器应用于"访问器"而非"字段",使字段语义的装饰(如响应式、序列化代理)得以统一——装饰字段时只能看到初始值,装饰访问器时可通过 context.access 读写实际值。组合能力:私有 auto-accessor(#x)、静态 auto-accessor、access 对象注入(供外部读取装饰后的值)、addInitializer 中初始化。工程价值:让"字段级 AOP"以标准方式实现,同时保持字段声明简洁,是装饰器时代字段增强的推荐形态。

考察 accessor 声明与装饰器语义的衔接:自动访问器把字段升级为可装饰的访问器,回答应说明生成机制与装饰行为。

#
★★★

18. 装饰器在 Angular、Lit、Aurelia 的核心地位与设计哲学差异

装饰器在 Angular、Lit、Aurelia 中处于什么地位?它们的设计哲学有什么差异?

  • Angular 的装饰器驱动组件/DI 设计
  • Lit 的装饰器与响应式属性
  • Aurelia 的装饰器可选哲学

三个框架都深度使用装饰器但哲学不同:Angular 以装饰器为骨架——@Component/@Injectable/@Input 等声明组件元数据与依赖注入,类定义即配置,装饰器承载框架契约(当前以 legacy 形态为主,Angular 17+ 探索标准装饰器兼容);Lit 用装饰器做响应式属性声明——@property/@state 定义元素属性与变更触发,与 reactive 更新机制绑定,装饰器是"少样板 + 类型友好"的桥梁;Aurelia 主张"装饰器可选"——同能力提供 decorator 与 plain 两种形态,理念是"TypeScript 装饰器是语法糖,可随时移除",降低框架锁定感。差异本质:装饰器在 Angular 是"框架必须的配置面",在 Lit 是"声明增强",在 Aurelia 是"可替换的便捷层";工程选型需考虑团队对装饰器形态与转译链路的容忍度。

考察框架对装饰器的依赖程度与设计哲学:Angular 依赖最深、Lit 作为声明增强、Aurelia 可选,回答应对比三者并说明工程含义。

#
★★★

19. Stage 3 装饰器元数据(context.metadata 与 Symbol.metadata)的工程应用

Stage 3 装饰器的 context.metadata 与 Symbol.metadata 在工程中有哪些应用?

  • context.metadata 的共享元数据对象
  • Symbol.metadata 的收集与读取
  • DI、校验、ORM 的元数据驱动

Stage 3 装饰器中每个装饰器的 context.metadata 指向该类共享的元数据对象(类级 Symbol.metadata 的访问入口),装饰器可写入(如 context.metadata[key] = value),类装饰器可读取汇总;Symbol.metadata 是元数据对象在类上的标准存储位置,外部可用 Class[Symbol.metadata] 读取。工程应用:依赖注入——用 metadata 记录"注入哪些依赖"而非 Reflect.defineMetadata 的手工管理;校验框架——记录字段校验规则供实例化时执行;ORM——记录列/关系映射;日志/序列化——记录成员顺序与排除项。优势:标准协议、跨转译器一致、支持继承链合并(子类继承父类元数据)。相比 legacy 的 emitDecoratorMetadata 设计时类型,Symbol.metadata 是显式、跨目标的标准通道。

考察标准元数据协议:context.metadata 写入、Symbol.metadata 读取的闭环,回答应说明元数据驱动框架的工程模式。

#
★★★

20. module: Node16/NodeNext 与 moduleResolution: bundler 在 ESM/CJS 互操作的工程取舍

module: Node16/NodeNext 与 moduleResolution: bundler 在 ESM/CJS 互操作上有什么工程取舍?

  • node16/nodenext 的模块模式与真实运行语义
  • bundler 模式的打包器假设
  • 双格式互操作(import 引入 CJS、require 引入 ESM)

module: node16/nodenext 让"模块模式由文件上下文决定"(package.json type 与扩展名),类型检查严格模拟 Node 的 ESM/CJS 互操作规则:ESM 文件 import CJS 包时命名导出按 cjs-module-lexer 检测、default 导出规则受限;CJS 不能静态 import ESM(Node 下需动态 import);这保证类型与 Node 实际运行行为一致。moduleResolution: bundler 则假设打包器统一处理互操作(interop 帮 ESM/CJS 互相导入),不检查这些 Node 原生限制,类型更宽松但与纯 Node 运行存在差异。取舍:库/服务端代码用 node16/nodenext 获得真实语义校验;纯打包器应用(Vite 等)用 bundler 减少噪音;同构工程可拆分配置——应用代码 bundler、Node 脚本 nodenext,避免"类型允许但 Node 报错"。

考察模块模式与运行语义的对齐:nodenext 校验真实互操作限制、bundler 放行打包器语义,回答应说明取舍与拆分配置策略。

#
★★★

21. exactOptionalPropertyTypes 在 API 边界严格区分 T | undefined 与 T? 的语义差异

exactOptionalPropertyTypes 如何严格区分 T | undefinedT? 的语义差异?在 API 边界有什么影响?

  • 可选属性(T?)与显式 undefined(T | undefined)的语义
  • 开启后的赋值限制
  • API 边界(参数/响应)的精确建模

关闭 exactOptionalPropertyTypes 时 prop?: T 允许赋值 undefined,语义上"缺省"与"显式 undefined"混同;开启后 prop?: T 只接受"属性缺失",显式 undefined 被拒绝,需要 undefined 时须声明 prop: T | undefined。API 边界影响:其一,请求参数建模更精确——"不传"(缺失)与"传 undefined"(显式)是两个不同契约(如 JSON 序列化时前者键消失、后者键存在值为 null),开启后类型强制区分;其二,库的默认值逻辑可依赖"缺失即取默认",不必处理显式 undefined;其三,消费端赋值必须用条件判断或 ??,代码更显式但略繁琐。工程建议:库作者开启以获得严格契约,团队内统一"可选即缺省、显式 undefined 用 T | undefined"的规范。

考察可选属性精确语义对 API 契约的塑造:开启后"缺失"与"显式 undefined"在类型层分离,回答应说明语义差异与边界建模影响。

#
★★★

22. 声明文件(.d.ts)与双格式包发布的消费者兼容

双格式包(同时发布 ESM/CJS)如何组织声明文件以保证消费者兼容?

  • .d.ts/.d.mts/.d.cts 的格式分发
  • exports 中 types 条件与格式配对
  • 双格式包的互操作与类型一致

双格式包发布需让 ESM 与 CJS 消费者都拿到正确类型:常见方案是按格式分别产出声明——ESM 产物配 .d.mts、CJS 产物配 .d.cts(或统一 .d.ts 配合 module 字段);exports 条件里把 types 与 import/require 配对且 types 置前:"exports": { ".": { "import": { "types": "./dist/index.d.mts", "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }。消费者兼容要点:TS 的 moduleResolution node16/nodenext 下自动按格式选声明(.mts 对应 ESM、.cts 对应 CJS);bundler 模式按条件取 types;缺失类型时消费者回退 any 或报错;构建链路(tsc 双配置或打包器 + dts 工具)需分别生成两套声明并保证 API 一致,用类型测试(tsd/expectTypeOf)在两种格式下双跑验证。

考察双格式发布的声明分发协议:types 条件与 import/require 配对、.d.mts/.d.cts 区分格式,回答应说明 exports 结构与双格式验证。

#
★★★

23. TypeScript 项目引用(Project References)

TypeScript 项目引用(Project References)是什么?在大型工程中如何应用?

  • references 与 composite 的关系
  • tsc --build 的增量构建与依赖图
  • Monorepo 中的模块拆分与边界

Project References 让多个 tsconfig 项目互相引用(references 字段指向被引用项目),被引用项目需开 composite(自动启用 declaration/rootDir/增量),主项目通过 tsc --build 按依赖图增量构建:只重建变更项目,利用 .tsbuildinfo 缓存,构建时间显著下降。应用模式:Monorepo 按包/层拆分子项目(types、utils、app),依赖方向显式声明,杜绝循环引用;每个子项目独立 include/outDir/composite,输出自己的 .d.ts;类型检查跨项目边界时以声明文件为准(先构建依赖);tsc --build 支持 --force 全量、--watch 监听。工程价值:增量编译、并行构建(--build 可并行)、类型边界清晰(跨包只见声明);注意依赖包必须先构建,CI 中按拓扑序执行。

考察项目引用的构建模型:composite + references + tsc --build 构成增量依赖图,回答应说明配置要素、构建流程与 Monorepo 拆分价值。

#
★★★

24. tsconfig.json 的 target、module、moduleResolution、strict 核心配置影响

tsconfig.json 的 target、module、moduleResolution、strict 四个核心配置分别影响什么?

  • target 决定语法转译目标与默认 lib
  • module/moduleResolution 决定模块语义
  • strict 决定类型检查严格度

target 决定输出语法级别(ES2015/ES2020/ES2022 等):新语法(async/await、class fields、可选链)被降级转译,且默认隐式影响 lib(未显式配置时按 target 选标准库类型);module 决定模块系统(ESNext/CommonJS/Node16/Preserve 等)与 import 的产物形态;moduleResolution 决定 import 路径解析规则(node16/nodenext/bundler),须与 module 搭配(如 module: node16 需 moduleResolution: node16,module: esnext + bundler);strict 聚合一组严格检查(strictNullChecks、noImplicitAny 等)决定空值、隐式 any、函数变体等检查的严格度。四者协作示例:target: ES2020 + module: Node16 + moduleResolution: Node16 + strict: true 是"现代 Node 库"的标准配置;打包器应用用 module: ESNext + moduleResolution: bundler。配置组合直接影响产物兼容性、解析行为与类型安全基线。

考察 tsconfig 核心配置的职责划分:target 管语法、module/moduleResolution 管模块语义、strict 管检查强度,回答应说明各自的独立影响与组合惯例。

#
★★★

25. allowImportingTsExtensions(TS 5.0+)与 --rewriteRelativeImportExtensions 的边界

allowImportingTsExtensions 与 --rewriteRelativeImportExtensions 的边界是什么?

  • allowImportingTsExtensions 允许 .ts/.tsx 后缀导入
  • 与 noEmit 的绑定约束
  • rewriteRelativeImportExtensions 的改写与适用场景

allowImportingTsExtensions(TS 5.0+)允许导入路径带 .ts/.tsx/.mts/.cts 后缀,但要求 noEmit(或 emitDeclarationOnly)——因为 tsc 不改写导入路径,若同时 emit 产物会保留 .ts 后缀导致运行失败。--rewriteRelativeImportExtensions(TS 5.7+)解除这个限制:相对导入的 .ts 后缀在 emit 时改写为 .js(.mts→.mjs、.cts→.cjs),使"源码写 .ts 后缀 + tsc 直接产出可运行 JS"成立,适合无打包器的原生 ESM Node 项目;边界:rewrite 只处理相对路径(./、../),非相对导入(裸包名)不改写;别名路径(paths)不属于相对导入不受影响;配合 moduleResolution nodenext/bundler 与模块内 .ts 后缀规则使用。打包器项目(Vite)通常不需要(打包器自身解析 .ts 后缀),原生 Node ESM 是主要受益场景。

考察 .ts 后缀导入的配置边界:allowImportingTsExtensions 绑定 noEmit,rewriteRelativeImportExtensions 为 emit 场景改写相对路径,回答应说明适用场景与处理范围。

#
★★★

26. useDefineForClassFields 与 target ES2022+ 在 class field 语义的协作

useDefineForClassFields 与 target ES2022+ 在 class field 语义上如何协作?

  • class field 的 [[Define]] 与 [[Set]] 语义
  • useDefineForClassFields 的默认行为变化
  • 与装饰器、继承的相互作用

class field 按 ES 标准使用 Object.defineProperty([[Define]])语义而非赋值([[Set]]),useDefineForClassFields 控制 tsc 转译 class field 的方式:target ES2022+ 时默认 true(原生 ESM 语义),低 target 默认 false([[Set]] 赋值语义)。差异影响:其一,声明 x = 1 时若父类有 setter([[Set]] 会触发 setter、[[Define]] 直接定义实例字段覆盖);其二,字段声明会"遮蔽"原型同名访问器;其三,与装饰器配合(装饰器在字段定义后执行、accessor 生成后备字段)与 extends 继承的初始化顺序都受影响。工程协作:target 升级到 ES2022+ 时自动切换为 define 语义,需回归测试继承 setter 与字段遮蔽场景;装饰器项目(experimental 或 Stage 3)必须确认 useDefineForClassFields 与装饰器求值顺序匹配,避免字段被覆盖或丢失初始化。

考察 class field 语义与转译目标的联动:define/set 语义差异影响继承与装饰器,回答应说明默认行为变化与兼容性注意点。

#
★★★

27. isolatedDeclarations(TS 5.5+)的显式返回类型约束与 .d.ts 生成加速

isolatedDeclarations(TS 5.5+)是什么?它的显式返回类型约束与 .d.ts 生成加速如何运作?

  • isolatedDeclarations 的"单文件生成声明"语义
  • 显式返回类型/参数类型的强制
  • 与打包器/并行构建的加速协作

isolatedDeclarations(TS 5.5+)要求"每个文件可独立生成 .d.ts",即声明所需信息全部在文件内可见:函数导出必须显式标注返回类型(不能依赖跨文件推断)、参数与属性需显式类型、模板字面量等推断受限,否则报错"requires explicit annotation"。收益:其一,.d.ts 生成不再需要全量类型推断,可由并行/增量工具(如 TypeScript 的 API、tsgo 等)逐文件快速产出;其二,与 allowImportingTsExtensions、打包器链路配合,声明生成与打包并行;其三,库作者获得"可读、稳定"的显式签名(API 更清晰)。代价:显式标注增加书写成本、部分推断(如复杂条件类型结果)需手写或改造;适合发布型库与大型 Monorepo 的构建加速场景,业务应用内可不开。

考察声明生成的可并行化约束:isolatedDeclarations 用显式类型换取独立生成能力,回答应说明强制内容、加速原理与适用场景。

#
★★

28. 命名空间(namespace)与 ES Module 在工程化代码组织的取舍

命名空间(namespace)与 ES Module 在工程化代码组织上如何取舍?

  • namespace 的合并与全局特性
  • ES Module 的静态分析与 tree-shaking
  • 现代工程中 namespace 的适用边界

namespace 是 TS 早期的代码组织机制:同名合并、可嵌套、可同时容纳类型与值,转译后生成运行时命名空间对象;ES Module 是标准模块机制:静态可分析、支持 tree-shaking、循环依赖处理明确、被打包器与 Node 原生支持。取舍:现代工程默认 ES Module——模块边界清晰、摇树友好、生态标准;namespace 的残留价值在于:声明文件中组织"全局+类型"(declare namespace)、库的 UMD 全局形态、以及 .d.ts 中表达"函数带静态成员"的合并模式;工程化上不建议新代码用 namespace 组织业务代码(erasableSyntaxOnly 已禁之),合并需求用模块 + 显式导出替代。迁移注意:namespace 嵌套引用转 module 时需重构为导入导出,且注意全局污染问题。

考察两类组织机制的定位:ES Module 是现代标准(静态分析、摇树),namespace 退守声明文件与全局场景,回答应说明取舍与迁移。

#
★★

29. TypeScript 的 declare 与 ambient 上下文在大型 monorepo 的工程价值

TypeScript 的 declare 与 ambient 上下文在大型 monorepo 中有什么工程价值?

  • ambient 声明文件(全局 .d.ts)的组织
  • declare global 扩展全局类型
  • monorepo 中共享类型声明的管理

大型 monorepo 的全局类型面需要统一管理:环境声明文件(ambient .d.ts,无 import/export)集中声明全局类型(环境变量类型、全局配置接口、第三方脚本全局变量、自定义全局工具函数);declare global 在模块文件中扩展现有全局(Window、NodeJS.ProcessEnv、Express.Request 等)。工程价值:其一,把"跨包共享的全局契约"集中定义,各包 include 同一份声明,避免各包重复声明导致冲突;其二,环境变量、polyfill、CSS/资源模块(declare module '*.css')等类型面统一入口;其三,第三方库类型修正用 declare module 增强集中在"类型补丁"包,按依赖顺序加载;注意:ambient 文件相互可见性依赖 tsconfig include/类型引用,monorepo 中应显式组织"类型入口"(如 types 包或根 include),避免隐式全局冲突。

考察环境声明的组织价值:集中式全局类型面与模块增强在 monorepo 中减少重复与冲突,回答应说明组织方式与 include 管理。

#
★★

30. @ts-check 在 JSDoc 注释启用类型检查的渐进迁移策略

@ts-check 如何在 JSDoc 注释启用类型检查?渐进迁移策略是什么?

  • @ts-check 与 checkJs 的关系
  • JSDoc 类型注释(@type/@param/@returns/@typedef)的能力
  • 从 JS 到 TS 的渐进迁移路径

@ts-check 在 JS 文件顶部开启类型检查(等价于 tsconfig 的 checkJs 对单文件生效),配合 JSDoc 注释提供类型信息:@type 标注变量/表达式类型、@param/@returns 描述函数签名、@typedef/@template 定义类型与泛型、@type {import('./types').X} 跨文件引用。渐进迁移策略:第一步全量开启 @ts-check 暴露隐患(配合 allowJs + noEmit);第二步对核心模块补 JSDoc 类型,用 @ts-expect-error 处理存量错误;第三步局部文件重命名 .ts,利用 TS 4.5+ 支持 import 类型引用、verbatimModuleSyntax 前的兼容写法;第四步整体切 .ts 并开 strict。此路径让 JS 项目在不重写的前提下获得类型收益,是"JS→TS"的低风险迁移路线。

考察 JSDoc 类型检查的渐进路径:@ts-check 开检查、JSDoc 给类型、分步迁移 .ts,回答应说明每步操作与迁移顺序。

#
★★

31. TypeScript 配置文件继承(extends)在多项目共享配置的工程实践

TypeScript 配置文件继承(extends)在多项目共享配置中如何实践?

  • extends 的继承语义与覆盖规则
  • 共享 base 配置的组织(paths、compilerOptions 合并)
  • 多项目覆盖的常见坑(files/include 不继承等)

extends 允许 tsconfig 继承另一个配置:被继承配置的 compilerOptions 浅合并,子配置同名项覆盖;关键点:files/include/exclude 不继承(子配置必须自己声明),references 不继承,baseUrl/paths 相对路径以"继承链中各自文件"为基准解析(paths 需在子配置重写或用相对根路径)。工程实践:根目录 base.json 定义共享 compilerOptions(strict 家族、moduleResolution、verbatimModuleSyntax 等),各子项目(packages/*/tsconfig.json)extends 根 base 并覆盖 outDir、rootDir、include、references;为不同场景派生配置(base、base.build、base.test:继承 build 再改 noEmit/include);工具链(Vite/eslint)读取的是实际生效配置,用 tsc --showConfig 验证合并结果。注意 extends 链深度与覆盖时机,避免"根配置被覆盖静默失效"。

考察配置继承的合并规则与组织模式:extends 只合并 compilerOptions、files 不继承、paths 相对性,回答应说明 base 拆分与验证手段。

#
★★

32. emitDeclarationOnly 与 declaration 在类库发布的工程取舍

emitDeclarationOnly 与 declaration 在类库发布中有什么工程取舍?

  • declaration 生成 .d.ts、emitDeclarationOnly 只产声明
  • 与打包器(esbuild)分工的发布链路
  • 声明产物的一致性与调试

declaration: true 让 tsc 输出 .d.ts;emitDeclarationOnly 则只输出声明、不输出 JS——典型搭配:esbuild/tsup 负责 JS 产物(快),tsc --emitDeclarationOnly 负责 .d.ts(类型检查 + 声明),两套命令并行或串联完成发布。取舍:其一,构建速度——JS 用打包器、声明用 tsc,比纯 tsc 全量 emit 快;其二,一致性——声明由 tsc 从源码生成,与 JS 产物(转译器生成)需保证语法语义一致(装饰器、class field 语义配置要对齐);其三,declarationMap 可附带,帮助消费端跳转源码;其四,isolatedDeclarations 可进一步加速声明生成。注意:tsc 生成声明时本身仍会执行类型检查,与打包器产 JS 的分工需保证两边语义一致(装饰器、class field 配置对齐);发布前用"双格式 + 类型测试"验证声明与产物匹配。

考察类库发布链路的职责拆分:打包器产 JS、tsc 产声明,回答应说明 emitDeclarationOnly 的角色、分工配置与一致性保障。

#
★★

33. JSDoc @typedef 与 @template 在无 TypeScript 项目的轻量类型化

JSDoc 的 @typedef 与 @template 如何在无 TypeScript 项目实现轻量类型化?

  • @typedef 定义对象类型与联合类型
  • @template 定义泛型
  • checkJs 下 JSDoc 类型化的能力边界

@typedef 用对象字面量或类型表达式定义命名类型:/** @typedef {{ id: number, name: string }} User */@typedef {'a'|'b'} Mode@typedef {import('./types').X} X 跨文件引用;@template 给函数/类定义泛型:/** @template T @param {T[]} arr @returns {T} */。配合 @ts-check/checkJs,无 TypeScript 项目(纯 JS)可获得:参数/返回值检查、对象形状校验、泛型推导与 IDE 补全。边界:JSDoc 类型化弱于 .ts——复杂条件类型、模板字面量类型、映射类型等不可用或需借助 import 类型;类型标注冗长;推断精度有限。适合"渐进类型化":给核心模块补 JSDoc,其余保持 JS,逐步向 .ts 迁移。

考察 JSDoc 类型系统的能力与边界:@typedef/@template 提供命名类型与泛型,回答应说明检查收益与迁移路径。

#
★★

34. import type 与 export type 在构建产物与 tree-shaking 的工程取舍

import type 与 export type 在构建产物与 tree-shaking 上有什么工程取舍?

  • import type 的纯类型导入语义
  • 对产物与摇树的影响
  • verbatimModuleSyntax 下的强制显式

import type 声明"只导入类型",擦除后不产生任何运行时代码;export type 同理。工程价值:其一,明确表达"该导入仅用于类型",防止被误用为值;其二,隔离副作用——类型导入不会触发模块执行,避免"仅类型引用却执行了模块副作用";其三,配合 verbatimModuleSyntax/isolatedModules 时,混合导入必须拆分(import { type A, b } 或分别写),让打包器清楚哪些是类型可安全移除,提升摇树精度与产物确定性;其四,.d.ts 与实现分离的库,消费端用 import type 可避免拉取运行时实现(配合 exports 条件)。取舍:不写 type 时 tsc 默认也能擦除类型导入,但显式化提高可读性与转译器语义对齐;规则上 ESLint 的 consistent-type-imports 可统一风格。

考察纯类型导入的语义与收益:import type 明确"类型仅编译期",回答应说明副作用隔离、摇树精度与 verbatimModuleSyntax 的联动。

#
★★

35. Monorepo 中共享类型包(types package)

Monorepo 中共享类型包(types package)如何组织与管理?

  • 类型包的包结构与导出设计
  • 与项目引用/构建的配合
  • 类型包演进的版本与依赖治理

共享类型包把跨包复用的类型(领域模型、API 响应类型、事件契约、工具类型)集中发布:结构上 package.json 的 types/exports 指向聚合 .d.ts,内部按域拆分子模块(models/routes/events)用 barrel 导出;构建可用 tsc --emitDeclarationOnly 或 isolatedDeclarations 快速产出,配合 Project References 让依赖包直接消费;源码开发时用 TS paths 或 workspace 引用直接解析源码(HMR 类型即时生效)。管理要点:其一,类型包应"无运行时实现"(或仅含常量),避免依赖放大;其二,演进用显式导出 + API Extractor 报告管控破坏性变更;其三,类型包不宜过大(拆域包),防止全量加载与编译变慢;其四,版本跟随语义化,类型破坏性变更发 major。

考察共享类型包的组织范式:域拆分导出、声明构建、引用方式与演进治理,回答应说明包结构与管理要点。

#
★★

36. TypeScript package.json 的 exports 字段在子路径导出的类型解析

package.json 的 exports 字段在子路径导出时如何影响类型解析?

  • exports 子路径键与通配符
  • 子路径的 types 条件
  • 类型解析失败与回退行为

exports 的子路径导出用键声明:"exports": { ".": {...}, "./utils": {...}, "./*": "./dist/*.js" }——精确子路径显式声明、通配符控制通用子路径;每个子路径可独立配置条件(types/import/require),TS 解析 import 'pkg/utils' 时按该子路径的条件取类型。要点:其一,未在 exports 声明的子路径在 node16/nodenext 与 bundler 模式下解析失败(封装性),类型与运行时行为一致地报错;其二,通配符类型要配对:"./*": { "types": "./dist/*.d.ts", "default": "./dist/*.js" } 且实际文件存在;其三,types 条件在前,避免打包器先吃到 JS;其四,老版本 TS(<4.7)不支持 exports,需 main/types 兜底。工程上子路径导出配合目录规范(utils/components 等)为消费者提供稳定入口,用 arethetypeswrong 等工具校验类型解析。

考察 exports 子路径与类型解析的配对:键声明、通配符、types 条件顺序决定类型可见性,回答应说明解析规则与校验手段。

#
★★

37. tsc --build 与 Project References 增量构建在大型代码库的应用

tsc --build 与 Project References 的增量构建在大型代码库中如何应用?

  • tsc --build 的缓存与失效机制
  • 依赖图驱动的构建顺序
  • 与 CI、IDE 的配合

tsc --build 以项目引用依赖图为构建单位:构建某项目前先构建其依赖(拓扑序),利用 .tsbuildinfo 与输出文件时间戳判断"是否需重建"——只有变更项目及其下游受影响项目被重建,全仓构建时间显著下降。应用要点:根 tsconfig 用 references 聚合全部子项目,CI 执行 tsc --build(可加 --force 全量);--watch 模式增量监听;干净构建用 --clean;项目内部分文件变更时按"依赖链"传播重建,跨包类型以声明文件为边界(依赖项目需先生成 .d.ts)。与 IDE 配合:TS Server 识别 references 并自动加载依赖项目,跨包跳转/诊断无需手工构建;CI 与本地用同一构建命令保证一致性;增量缓存失效的坑:tsconfig 变更、依赖项目改动、--force 缺失时缓存未刷新,需在 CI 定期 --force 校验。

考察增量构建的工程化运用:依赖图 + 缓存失效模型,回答应说明构建命令、缓存机制与 CI 配合。

#
★★

38. ts-node、tsx 与 SWC 在 Node.js 运行时类型执行的工程取舍

ts-node、tsx 与 SWC 在 Node.js 运行时执行 TypeScript 有什么工程取舍?

  • ts-node 的编译器模式(tsc/SWC)与配置
  • tsx 的 esbuild 转译与 ESM 支持
  • 类型检查与执行分离的取舍

ts-node 是经典方案:默认用 tsc 转译(可配 transpileOnly 跳过类型检查提速、swc 模式用 SWC 转译),支持 tsconfig、project references 与 ESM 实验;tsx(node --import tsx)基于 esbuild 转译,启动快、原生支持 ESM/CJS、无需配置,是脚本与开发工具的轻量选择;SWC 作为转译核心可被 ts-node 等复用(@swc/core),性能高但语义与 tsc 有差异(不检查类型)。工程取舍:开发/测试脚本追求启动速度用 tsx 或 ts-node transpileOnly;需要类型检查把关用"先 tsc --noEmit 再执行"的分步模式(CI 中必做);生产运行建议编译为 JS(打包或 tsc emit),运行时执行器仅作开发辅助;注意 ESM 项目需配 module nodenext 与 loader 参数,且装饰器等语法要转译器支持。

考察 TS 运行时执行器的分工:转译速度与类型安全的权衡,回答应说明各工具定位与"检查/执行分离"的最佳实践。

#
★★

39. 声明文件生成(dts-bundle-generator、rollup-plugin-dts)在类库发布的取舍

dts-bundle-generator、rollup-plugin-dts 等声明文件生成工具在类库发布中有什么取舍?

  • 打包声明 vs tsc 原生多文件声明
  • 外部类型的内联与隔离
  • 与双格式产物、sourcemap 的配合

tsc 默认按源码结构产出多文件 .d.ts;dts-bundle-generator、rollup-plugin-dts 把声明"打包"成单文件:内联内部模块、处理路径重写、可外置指定依赖(external 名单)或内联第三方类型,解决"发布单入口声明、消费者免看内部结构"的需求。取舍:其一,单文件便于 types/exports 指向与双格式分发;其二,内联第三方类型可避免消费者安装额外 @types(但版本漂移风险),外置保持类型依赖最小化;其三,打包工具需处理类型别名重名、命名空间合并、模板字面量等边缘,偶发声明失真需测试兜底;其四,rollup-plugin-dts 与 rollup 构建链集成(同一 bundle 管线),dts-bundle-generator 独立命令更轻。工程上:小库单文件友好,大库保持 tsc 多文件 + exports 子路径更稳,用 arethetypeswrong/tsd 双重校验发布物。

考察声明分发形态的选择:单文件打包与多文件原生各自的成本收益,回答应说明工具差异与校验保障。

#
★★

40. verbatimModuleSyntax(TS 5.0+)与 importsNotUsedAsValues 在强制 type 导入与打包器 tree-shaking 的现代取舍

verbatimModuleSyntax 与 importsNotUsedAsValues 在强制 type 导入与 tree-shaking 上有什么现代取舍?

  • 两个选项的语义与演进(importsNotUsedAsValues 已废弃)
  • verbatimModuleSyntax 的严格显式化
  • 与打包器摇树、isolatedModules 的协作

importsNotUsedAsValues(TS 4.5 引入、5.0 废弃)控制"仅类型引用"导入的处理:remove(默认擦除)、preserve(保留 import 副作用)、error(强制 import type);verbatimModuleSyntax(5.0+)取代并更严格:导入/导出中混用的类型必须显式 type 修饰或拆行,否则报错,且保留的 import 语句原样输出——类型导入零产物、值导入原样传递,语义完全交由打包器/运行时处理。现代取舍:verbatimModuleSyntax 是推荐配置(与 isolatedModules 同开):显式 type 让摇树可精确移除类型导入、防止"仅类型依赖却保留副作用"、保证 esbuild/Babel 产物与 tsc 理解一致;importsNotUsedAsValues 已废弃,仅存量项目过渡用(error 模式引导改写)。

考察类型导入显式化的选项演进:verbatimModuleSyntax 取代旧选项并强制显式,回答应说明语义差异与摇树协作。

#
★★

41. .d.ts 与 .d.mts/.d.cts 在 ESM-only/CJS-only 包的声明分发的现代工程实践

.d.ts 与 .d.mts/.d.cts 在 ESM-only/CJS-only 包中如何分发声明?

  • .d.mts/.d.cts 的格式语义
  • ESM-only 包的声明配置(module: nodenext、verbatim)
  • 双格式与单格式的工程取舍

.d.mts 声明对应 ESM 模块、.d.cts 对应 CJS 模块,格式由扩展名决定(TS 按扩展名应用模块语义);单格式包的实践:ESM-only 包(package.json type: module,产物 .js)声明用 .d.ts 或 .d.mts,exports 中 types 指向且 import/require 都指向 ESM 声明;CJS-only 包同理用 .d.cts(或 .d.ts)并声明 main/exports。关键点:其一,声明格式必须与产物格式匹配——ESM 产物配 ESM 语义声明,否则默认导出/命名导出的互操作类型错误(如 ESM 包被 CJS require 时的 interop 类型);其二,moduleResolution node16 下按格式选声明,bundler 模式按条件;其三,ESM-only 包注意"default 导出 + 命名导出"的类型形态、import.meta 类型支持;其四,双格式包才需要 .d.mts + .d.cts 双份,单格式保持简单。用 arethetypeswrong 校验"类型解析与运行时解析一致"。

考察声明格式与模块格式的匹配原则:单格式包声明与产物一致,双格式才需双声明,回答应说明配置与校验。

#
★★

42. paths 与 baseUrl 在 TS 路径别名与 Vite/Rspack resolve.alias 的协作关系

paths 与 baseUrl 在 TS 路径别名与 Vite/Rspack resolve.alias 之间如何协作?

  • paths/baseUrl 的类型解析别名
  • 打包器 resolve.alias 的运行时别名
  • 双端别名一致性维护

tsconfig 的 paths(配合 baseUrl 或相对路径)定义类型层别名:"baseUrl": ".", "paths": { "@/*": ["src/*"] }——tsc/IDE 按此解析模块;Vite/Rspack 的 resolve.alias 定义打包器运行时别名。二者必须保持一致,否则"类型解析到 A、运行时解析到 B"。协作要点:其一,Vite 可直接读 tsconfig paths(vite-tsconfig-paths 插件),Rspack 类似;其二,paths 的通配与别名顺序会影响子路径匹配(@/*@/utils/* 的优先级),打包器配置需对应;其三,paths 支持多目标数组(回退解析);其四,monorepo 中 paths 指向源码目录可让类型直达源码(配合 references);维护上用"单一别名配置源"(根 tsconfig + 插件生成打包器配置),避免手工双写漂移;注意 paths 别名不改变产物路径,打包器才负责最终替换。

考察类型别名与运行时别名的双端协作:paths 管类型、resolve.alias 管打包,回答应说明一致性维护与插件方案。

#
★★

43. Reflect Metadata 在依赖注入与 ORM 映射的应用

Reflect Metadata 在依赖注入与 ORM 映射中如何应用?

  • Reflect.metadata 的设计时元数据
  • emitDecoratorMetadata 的自动生成
  • DI 容器与 ORM 的元数据读取模式

Reflect Metadata(reflect-metadata polyfill)提供 Reflect.defineMetadata/getMetadata 的类/成员级元数据存储;TS 的 emitDecoratorMetadata 在装饰器执行时自动记录"设计时类型"元数据:design:type(成员类型)、design:paramtypes(构造函数参数类型)、design:returntype(返回类型)。DI 应用:容器读取构造函数的 design:paramtypes 得知依赖类型并实例化注入(Nest.js、Inversify 模式),配合 @Injectable 元数据注册提供者;ORM 应用:实体类属性上的 @Column 装饰器写列元数据、design:type 提供 JS 类型映射,映射器读取构建 SQL 与反序列化。现代演进:Stage 3 装饰器的 Symbol.metadata 逐步替代 Reflect Metadata(标准协议、无需 polyfill、继承合并更清晰);reflect-metadata 仍是存量框架主路径,新框架(如 typia/type-graphql 类)转向标准元数据或代码生成。

考察设计时元数据在框架层的应用机制:emitDecoratorMetadata 自动生成类型元数据供 DI/ORM 读取,回答应说明读取模式与标准元数据演进。

#
★★

44. 装饰器在 Nest.js、TypeORM、tsoa 等框架的元数据驱动设计

装饰器在 Nest.js、TypeORM、tsoa 等框架中如何支撑元数据驱动设计?

  • 各框架的装饰器元数据用途
  • 声明式配置到运行时/代码生成的能力
  • 装饰器生态的设计范式

三个框架展示装饰器的三种元数据驱动范式:Nest.js 用 @Controller/@Injectable/@Module 装饰类声明依赖与路由元数据,容器启动时扫描装饰器元数据构建 DI 图与路由表(运行时元数据驱动);TypeORM 用 @Entity/@Column/@ManyToOne 标注实体结构与关系,启动时读取元数据生成/校验表结构与 SQL(运行时 ORM 映射);tsoa 用 @Route/@Get/@Query 装饰控制器与参数,构建期把装饰器元数据转换为 OpenAPI 文档与路由处理器(编译期代码生成)。共同范式:"装饰器声明 → 元数据收集(Reflect.metadata/设计时类型)→ 框架消费(运行时注入或构建期生成)";差异在消费时机与产物。工程启示:装饰器把"配置"与"代码"同位书写,可读性与类型友好度高,但元数据隐式依赖要求框架版本与装饰器语义(legacy/Stage 3)对齐。

考察装饰器元数据的三种消费模式:运行时 DI/ORM 与构建期代码生成,回答应说明各框架的声明-收集-消费链路。

#
★★

45. TypeScript Roadmap(持续演进)

TypeScript Roadmap(路线图)的演进方向是什么?如何跟踪与评估新特性?

  • TS 迭代节奏(约 3 个月一个 minor)与治理(GitHub/迭代计划)
  • 近期方向:性能(tsgo/native)、装饰器、推断改进
  • 团队升级评估(--explainFiles、breaking changes 清单)

TypeScript 以约 3 个月为节奏发布 minor(5.x),方向由 GitHub issues 与迭代计划(iterations)驱动,社区提案经设计会议评审进入版本;近期主线:性能革命(tsgo——基于 Go 的原生编译器原型、约 10 倍提速目标)、Stage 3 装饰器与元数据完善、推断行为改进(const 类型参数、NoInfer、条件分支收窄、类型谓词推断)、编译选项收敛(erasableSyntaxOnly、isolatedDeclarations)与工具链(API Extractor 集成、Language Service 增强)。工程评估方法:升级前读 breaking changes 清单与"升级指南"博客、用 --explainFiles/tsc --build --force 全量检查对比错误面、用 CI 双版本矩阵灰度(先 nightly 试跑)、跟踪官方 TS 博客与 DefinitelyTyped 生态适配;对实验特性(装饰器、tsgo)评估风险后再引入。

考察对语言演进的理解与治理:迭代节奏、技术方向与升级评估手段,回答应体现"跟踪-评估-灰度"的工程方法。

#
★★

46. TS 5.5/5.7/5.8 的新特性(--rewriteRelativeImportExtensions、--noUncheckedSideEffectImports、条件分支收窄、类型谓词推断)对工程体验的改善

TS 5.5/5.7/5.8 的新特性(--rewriteRelativeImportExtensions、--noUncheckedSideEffectImports、条件分支收窄、类型谓词推断)如何改善工程体验?

  • 各版本新特性的语义
  • 对构建、检查与开发体验的提升
  • 升级收益评估

各版本特性分别改善不同环节:TS 5.5 的条件分支收窄(基于正则/表达式常量推断)、类型谓词推断(过滤器函数自动生成 is 谓词)与 isolatedDeclarations——推断更准、声明生成更稳、过滤函数无需手写谓词;TS 5.7 的 --rewriteRelativeImportExtensions 使 .ts 后缀导入可在 emit 时改写为 .js,配合 noUncheckedSideEffectImports(5.6)对 CSS/SVG 等副作用导入做检查(避免类型误解析);TS 5.8 的 erasableSyntaxOnly 强化纯转译兼容、Node 专属条件类型等。工程体验改善:其一,少写"手动谓词/断言"——类型谓词推断让 filter 后类型自动收窄;其二,模块边界更清晰——rewrite 打通原生 ESM 链路、noUncheckedSideEffectImports 消除资源导入的类型噪音;其三,构建链路更统一——erasableSyntaxOnly + isolatedDeclarations 支撑快速声明与原生编译器。升级评估按项目痛点选特性验证,配合 nightly 试跑。

考察新特性的收益面:推断收窄、模块处理、构建约束三线改善,回答应说明各特性解决的问题与升级验证方式。

#
★★

47. using/await using 声明(TS 5.2+ / Stage 3 提案 Explicit Resource Management)

using/await using 声明是什么?在资源管理上有什么工程价值?

  • Explicit Resource Management 提案语义
  • Symbol.dispose/Symbol.asyncDispose 协议
  • 资源生命周期(文件、连接、监听)的类型化管理

using 声明(TS 5.2+,Stage 3 提案 Explicit Resource Management)引入"作用域结束自动释放"语义:using res = createResource() 在离开作用域时自动调用 resSymbol.dispose,await using 用于异步释放(Symbol.asyncDispose);TS 5.2 起原生支持 using 与 await using 声明(await using 需在 async 上下文中使用)。工程价值:其一,文件句柄、数据库连接、事件订阅、定时器等资源"声明即管理",异常路径也保证释放,替代 try/finally 样板;其二,类型层面可用 Symbol.dispose 协议约束资源类型(自定义资源实现 dispose);其三,与 React/框架的解绑逻辑、测试夹具清理等场景结合,减少泄漏;注意:需要 target ES2022+ 与显式导入 polyfill(或运行时支持),转译产物依赖 helpers,Node/浏览器支持度需特性检测。

考察资源管理的声明式语法:using 自动释放 + dispose 协议,回答应说明语义、协议与工程价值。

#
★★

48. TypeScript 项目使用 native decorators 与 useDefineForClassFields 的取舍

TypeScript 项目使用 native decorators(Stage 3)与 useDefineForClassFields 之间有什么取舍?

  • native decorators 的字段处理语义
  • useDefineForClassFields 与装饰器的交互
  • 字段初始化顺序与访问器生成

native decorators(Stage 3,TS 5.0+ 默认不依赖 experimentalDecorators)与 useDefineForClassFields 强相关:Stage 3 装饰器按"字段定义([[Define]])→ 装饰器执行 → addInitializer"顺序工作,useDefineForClassFields: true(target ES2022+ 默认)下 class field 用 define 语义,与装饰器语义天然对齐;若为兼容 legacy 关闭 define 语义([[Set]]),字段先赋值再被装饰,可能发生"装饰器替换值被初始赋值覆盖"或 setter 触发差异。取舍:新项目统一 target ES2022+ + useDefineForClassFields: true + native decorators,语义与标准对齐;legacy 装饰器项目保持 experimentalDecorators + 旧字段语义组合,不要混用两套装饰器;auto-accessor(accessor 关键字)在 define 语义下生成后备字段,与装饰器组合更一致。测试重点:继承 + 字段遮蔽 + 装饰器顺序。

考察装饰器与字段语义的配置协同:native decorators 对齐 define 语义,回答应说明配置组合与混用风险。

#
★★

49. TC39 装饰器提案时间线与 Babel/SWC 转换兼容性

TC39 装饰器提案的时间线如何?与 Babel/SWC 的转换兼容性如何?

  • 装饰器提案的演进阶段(旧提案 → Stage 3)
  • Babel 插件版本(legacy/2023-05/2023-11)的语义差异
  • SWC 的装饰器配置兼容

装饰器提案经历多年演进:早期 TS experimentalDecorators 对应"旧提案"(descriptor 形态),后重设计为 (value, context) 形态进入 Stage 3(2022 年起稳定,2023-05/2023-11 更新元数据与访问器语义),尚未 Stage 4。转译器兼容:Babel 的 @babel/plugin-proposal-decorators 按版本选择语义——legacy(旧形态)、2023-05、2023-11(当前主流,含 Symbol.metadata);SWC 用 decoratorVersion 或 legacy 开关对应,配置须与源码使用的语义一致;TS 5.x 原生支持 Stage 3(无需转译器转换,仅需 target ES2022+ 及 helper)。兼容要点:同一源码只能按一套语义转译,混用报错或行为异常;库发布时声明其装饰器语义(legacy 或标准),消费者转译器版本需匹配;polyfill(reflect-metadata vs Symbol.metadata)也要对应。

考察装饰器提案演进与转译器版本矩阵:语义阶段决定转换方式,回答应说明时间线、插件版本与配置对齐。

#
★★

50. 装饰器与 Class Field 的执行顺序(类 > 字段 > 方法)

装饰器与 Class Field 的执行顺序是什么?为什么重要?

  • 装饰器按"类-字段/方法-类装饰器"的执行次序
  • 类装饰器最后执行(可替换类)的原因
  • 字段初始化与装饰器 addInitializer 的时机

Stage 3 装饰器执行顺序:先执行成员装饰器(字段、方法、访问器、参数按源码顺序),最后执行类装饰器——因为类装饰器可能替换整个类,成员装饰先跑以收集元数据。具体次序:类的静态字段/方法按声明顺序装饰,实例成员随后,参数装饰器最内层(先于成员);装饰器执行后,类的字段初始化发生在实例化时(构造器 super 之后、字段按序 [Define]),addInitializer 注册的初始化钩子按注册顺序在实例/静态初始化阶段执行。重要性:其一,框架元数据收集依赖"成员装饰先于类装饰"(类装饰器读取已收集的成员元数据);其二,字段初始化时机影响装饰器改写的值(装饰器返回替换值 vs 初始化覆盖);其三,混用 legacy(顺序不同:类装饰器对方法按不同次序)与标准装饰器会导致行为错乱,理解顺序是调试元数据类 bug 的前提。

考察装饰器求值时序:成员先于类、初始化在实例化期,回答应说明次序原因与对框架元数据的影响。

#
★★

51. 升级 TypeScript 大版本前用 --explainFiles 与 CI 类型差异评估破坏性变化

升级 TypeScript 大版本前如何使用 --explainFiles 与 CI 类型差异评估破坏性变化?

  • --explainFiles 的模块解析诊断
  • 升级前评估流程(错误清单、双版本矩阵)
  • 破坏性变化分类(推断变化、解析变化、弃用)

--explainFiles 输出"每个文件为何被包含、从哪个配置/引用解析、包含原因",升级前用它核对:include/exclude 漂移、意外引入的全局类型文件、路径解析差异,避免升级后错误面因"文件集合变化"而失真。评估流程:第一步升级前基线——tsc --noEmit 全量错误清单 + 构建产物基线;第二步灰度——新版本在 CI 双版本矩阵(旧/新)跑同一命令对比错误面,用 nightly 或 beta 提前暴露推断变化(严格空检查、模板推断收紧、lib 更新导致的 API 类型变化);第三步分类处理——破坏性变化清单(ts 官网 breaking changes)逐项核对,区分"真错误"(修复)与"类型收紧"(改写);第四步渐进——大版本升级先放开 noEmit 检查不阻塞发布,批量修复后收紧。风险点:声明文件(第三方)在新版本下的兼容、--explainFiles 显示解析行为差异。

考察大版本升级的方法论:基线、灰度、分类、渐进四步,回答应说明 --explainFiles 的作用与 CI 双版本对比。

#
★★

52. TypeScript 5.0+ 装饰器 在类型测试(tsd、expectTypeOf)

TypeScript 5.0+ 装饰器如何在类型测试(tsd、expectTypeOf)中验证?

  • 装饰器改写类型后的测试断言
  • 装饰器工厂与泛型装饰器的类型测试
  • 类型测试与运行时测试的配合

装饰器会改写成员/类的类型,类型测试需验证改写结果:tsd/expectTypeOf 对"装饰后的类型"断言——如 @log 包装后方法签名是否保留(expectTypeOf(instance.method).toEqualTypeOf<(x: number) => number>());装饰器工厂(@withOptions(opts))返回的装饰器类型用泛型断言验证参数与返回值关系;accessor 装饰后经 context.access 读写值的类型;类装饰器替换后实例类型(InstanceType 变化)。要点:类型测试文件里"装饰器 + 类型断言"成对出现,验证声明面与实现面一致;配合运行时测试(装饰行为)双覆盖;装饰器本身是泛型函数时,用 expectTypeOf 断言其推断(如 context.kind 收窄);对 Stage 3 元数据(Symbol.metadata 的键类型)也做断言。tsd 的 @ts-expect-error 可验证"不允许的装饰组合"。

考察装饰器类型面的测试方法:对改写后签名、工厂泛型与元数据做断言,回答应说明断言对象与双覆盖策略。

#

53. TS 的 module: "preserve"(5.4+)在原样输出 import 语句与 Bundler 解析的工程价值

TS 的 module: "preserve"(5.4+)在原样输出 import 语句与 Bundler 解析上有什么工程价值?

  • preserve 模式下 import 原样保留
  • 与 moduleResolution: bundler 的搭配
  • 转译职责完全交给打包器

module: "preserve"(TS 5.4+)让 tsc emit 时"原样保留 import/export 语句",不做模块系统转换(不生成 require、不改写路径),配合 moduleResolution: bundler 使用:解析语义(exports 条件、无扩展名)由 bundler 模式处理,产物中的模块语句原样交给 Vite/Webpack 等打包器处理。工程价值:其一,避免 tsc 与打包器对模块语义的重复转换与冲突(如 tsc 把 ESM 转 CJS 后打包器再转换);其二,noEmit 时代(打包器自己转译)类型检查与产物链路分离更干净;其三,配合 isolatedModules/verbatimModuleSyntax 语义完全透明。注意:preserve 只适合"打包器做转译"的场景,Node 直接运行或需要 tsc 产物的项目不适用;preserve 下仍做类型解析校验(bundler 模式)。

考察模块输出的"零转换"模式:preserve 把模块语义完全交给打包器,回答应说明适用场景与搭配约束。

#

54. declaration: true 与 declarationMap(source map for d.ts)在 IDE 跳转与库用户体验的现代取舍

declaration: true 与 declarationMap 在 IDE 跳转与库用户体验上有什么取舍?

  • declaration 生成 .d.ts、declarationMap 生成映射
  • 消费端"跳转到源码"的体验
  • 发布包大小与调试成本

declaration: true 让 tsc 产出 .d.ts(库发布的必要前提);declarationMap 额外生成 .d.ts.map,映射声明到源码位置,消费端 IDE 从 .d.ts 成员"跳转到源码"(而非仅到声明),显著改善调试与可读性——VSCode 中 go-to-definition 直达实现。取舍:其一,体验收益——库用户排错、读源码更顺,尤其 monorepo 内部包;其二,成本——包体积增加(map 文件)、发布清单需包含 map 与源码(sourceMap 指向),隐私场景(闭源库)需谨慎或去掉 map;其三,构建配置——declarationMap 依赖 declaration 与 sourceMap 相关的 sources 字段,双格式包需分别配置;现代实践:开源与内部库默认开启,闭源商业库权衡后关闭;配合 isolatedDeclarations 与声明打包工具时 map 路径要重写正确。

考察声明映射的体验与成本:declarationMap 实现"声明跳源码",回答应说明收益、包体积与隐私取舍。

#

55. globalAugmentations 与 declare global 在扩展第三方库类型(如 Express、Vue)的边界

global augmentations 与 declare global 在扩展第三方库类型(如 Express、Vue)时有什么边界?

  • declare global 的模块内全局增强
  • 全局增强文件的模块边界(须是模块)
  • 对 Express Request、Vue 组件全局属性的扩展

declare global 在"模块文件"(含 import/export)中扩展全局作用域:declare global { namespace Express { interface Request { userId?: string } } }——Express 的 Request 扩展是典型场景(中间件挂载的字段类型化);Vue 扩展:declare module 'vue' { interface ComponentCustomProperties { $api: Api } } 用模块增强而非全局声明;全局属性(window 扩展)用 declare global 的 interface Window。边界:其一,declare global 必须位于模块文件,纯脚本文件用顶层声明(ambient .d.ts 直接写 interface Window 扩展);其二,全局增强文件必须被 include 且避免被 tree-shaken 忽略(用 import '扩展文件' 引一次或放 types 入口);其三,增强与库原生声明合并依赖同名 interface/namespace 合并机制,库若用 type 定义则无法合并(只能改库或再封装);其四,多个包同时增强同名接口会冲突,需规范化。

考察全局增强的机制与边界:declare global 模块内扩展全局、模块增强扩展库导出,回答应说明合并前提与组织规范。

#

56. TS 5.x 的 noUncheckedSideEffectImports(5.6+)在 CSS、SVG 等副作用导入的检测能力

noUncheckedSideEffectImports(TS 5.6+)在 CSS、SVG 等副作用导入上有哪些检测能力?

  • 副作用导入(无变量绑定)的类型检查
  • CSS/SVG 等资源导入的路径解析
  • 与打包器/Vite 类型声明的配合

noUncheckedSideEffectImports(TS 5.6+)对"纯副作用导入"(import './styles.css' 这类无绑定导入)做检查:若解析不到对应类型(如路径不存在或缺少声明),报 TS 错误,避免资源路径拼错静默失败;默认行为是无副作用导入不校验(旧版容忍任意路径)。检测能力:其一,路径拼写与大小写错误被捕获;其二,配合环境声明(declare module '*.css'、vite/client 的 '*.svg' 等)让合法资源导入通过;其三,对"看起来像副作用实则有导出"的导入区分处理(带绑定的导入仍走正常检查)。配合注意:Vite 项目需引入 vite/client 类型(含资源模块声明),否则合法导入也被误报;Node 项目导入 JSON 等需 resolveJsonModule 或声明;该选项对"无声明资源"采用"有声明才放行"的严格策略,是工程质量增强项。

考察副作用导入的类型校验:无绑定导入也纳入路径/声明检查,回答应说明检测行为与资源声明的配合。

#

57. tsconfig.json 的 include/exclude/files 三者优先级与大型 Monorepo 的工程实践

tsconfig.json 的 include/exclude/files 三者的优先级是什么?在大型 Monorepo 中如何实践?

  • files 精确指定、include 通配、exclude 排除
  • 优先级与默认行为
  • Monorepo 中的 include 组织(覆盖范围控制)

三者的语义与优先级:files 显式列出文件(最精确),include 用 glob 模式纳入文件集合,exclude 从(files 之外的)集合中排除——exclude 只作用于 include 的结果,不排除 files 显式列出的文件;三者为"并集 - 排除"关系:生效集合 = (files ∪ include) - exclude。默认行为:未配置 files/include 时默认包含目录下所有支持的源文件,exclude 默认排除 node_modules、bower_components、outDir 等。Monorepo 实践:子项目 tsconfig 用 include 限定自身源码目录(["src"])+ 用 references 指向共享包,exclude 排除测试/构建产物;根配置用 references 聚合;注意 include 的 glob 相对 tsconfig 所在目录,跨包 glob(../)会扩大检查面导致重复检查,应避免;outDir 目录默认被排除防止产物被二次编译。

考察文件集合的三元关系:files/include 并集再被 exclude 排除,回答应说明优先级与 Monorepo 的 include 控制。

#

58. allowImportingTsExtensions(TS 5.0+)在搭配 bundler 时的 "noEmit": true 工程边界

allowImportingTsExtensions 在搭配 bundler 与 noEmit: true 时有什么工程边界?

  • allowImportingTsExtensions 与 noEmit 的绑定
  • 打包器解析 .ts 后缀的能力
  • Vite 等工程的实际配置建议

allowImportingTsExtensions(TS 5.0+)允许导入路径带 .ts/.tsx/.mts/.cts 后缀,但强制要求 noEmit: true(或 emitDeclarationOnly)——因为 tsc emit 不会改写这些后缀,产物中的 .ts 路径在 Node 下无法运行(除非 5.7+ 的 rewriteRelativeImportExtensions)。搭配 bundler 的边界:Vite/esbuild 等打包器原生支持解析 .ts 后缀导入(源码级解析),因此"源码写 .ts 后缀 + noEmit 类型检查 + 打包器转译"的链路成立;注意点:其一,noEmit 是硬前提,若需要 tsc 产物(Node 库)需换 rewrite 方案或改后缀写法;其二,paths 别名与 .ts 后缀同时使用时,打包器别名解析需能命中(别名后仍带后缀);其三,IDE 与 lint 的类型解析在 bundler 模式下同样接受 .ts 后缀;其四,声明文件生成(emitDeclarationOnly)时 .d.ts 的导入路径也会保留 .ts 后缀,消费端解析需支持。

考察 .ts 后缀导入在打包器链路的边界:noEmit 硬约束与打包器原生解析,回答应说明配置组合与 emit 场景的差异。

#

59. package.json 的 types/typings/exports.types 字段在类型入口分发与 ESLint 解析的工程价值

package.json 的 types/typings/exports.types 字段在类型入口分发与 ESLint 解析中有什么工程价值?

  • types/typings 主类型入口与 exports.types 条件
  • TS 与 ESLint(typescript-eslint)的类型解析路径
  • 多入口包的类型分发一致性

types(旧名 typings)声明包的主类型入口(相对路径 .d.ts),exports 的 types 条件声明各条件分支的类型入口(现代推荐,优先级高于 types);TS 解析 import 'pkg' 时按"exports.types → types → main 旁同名 .d.ts"顺序找类型。工程价值:其一,类型入口分发——多格式/多条件包按 import/require/node 分别指向对应声明;其二,ESLint 解析——typescript-eslint 的 parserOptions.project 加载类型信息做类型感知规则(no-floating-promises 等),其模块解析复用 TS 配置,types 字段错误会导致"规则无类型信息可用"或误报;其三,缺失 types 时 TS 尝试 main 旁同名声明,找不到即 any 化,类型安全失效。实践:统一用 exports.types(+ 兼容 types 兜底)、用 arethetypeswrong 校验入口、lint 与 IDE 用同一解析配置验证。

考察类型入口字段的分发机制:exports.types 优先于 types,影响 TS 与 ESLint 的类型解析,回答应说明解析顺序与校验手段。