在上周的周刊(Weekly 第 50 期[1])中我们有介绍到 TypeScript 中的 isolatedDeclarations 配置项。起初决定深入研究的来源是一篇快报文章中[2]的:

文章的名称翻译下:“加速 JavaScript 生态系统 - 独立声明”,简单明了。
但是这篇文章中的作者少说了一个 API,而且该文章作者使用的是 deno,天然支持 TypeScript 。

文章中有这么一句话:“免责声明,我的环境在 deno”,说明并未在 Node 环境中真实的体验过这个特性以及背后产生的动机。
好了,我们步入正题。这里聊一个辅车相依,唇亡齿寒的问题,那就是 isolatedDeclarations 和 transpileDeclaration 配对使用才可以在上层编译打包工具开发中发挥最大的优势。
或者可以摒弃 transpileDeclaration ,借助 孤立声明 的信任度 自己写解析器也不是不可以(刚好社区就有一个用 rust 写的 oxc-transform[3])。
当然对于使用者而言是无感的,这部分用户是无所谓的(假如这时候社区的绝大部分工具已经相当完善了,比如 @rollup/rollup-plugin-dts 等工具)
本篇文章的环境信息为
Mac OS m2,相关工具版本为TypeScript v5.5.3、Node v21.1.0
对 TypeScript 代码库进行类型检查可能会很慢,尤其是对于包含大量项目的 monorepos,每个项目都需要使用类型检查器来生成类型声明文件。“隔离声明”的 TypeScript 新功能,该功能允许在完全不使用类型检查器的情况下生成 DTS 文件!
isolatedDeclarations 出现的动机几个维度:用户、声明文件底层工具创作者、声明文件的在上层工具中生成的创作者
isolatedDeclarations ,并且知道这个配置项需要你的文件在导出时充分注释,否则它会让 TypeScript 报告错误。(必须充分注释类型,而不是常规情况下的自动推导 (如 any))transpileDeclaration 来构建声明文件。语法 check 、编译 ts 为 js,很纯粹的快。DX 体验升级 或者 CI/CD 的速度加快。monorepo 的仓库维护者,可以让构建和 DTS 的声明相关的任务并行执行。具体的介绍以及讨论详见 `Issue 58944`[4]
isolatedDeclarations 的使用{
"extends": ["../../tsconfig.json"],
"compilerOptions": {
"baseUrl": "./",
"declarationDir": "./lib",
"isolatedDeclarations": true,
"composite": true
},
"include": ["src"]
}
如果有语法错误,我们大概会在 IDE 中看到如下错误:

transpileDeclaration 的使用关于此 API 的介绍以及讨论详见`Issue 58261`[5]
import { transpileDeclaration } from 'typescript';
const dts = transpileDeclaration(code, {
});
我们就以第三视角 声明文件的在上层工具中生成的创作者,以 Monorepo 为例,这里借助 rollup v4 进行打包,自定义一个插件,姑且就叫 rollup-plugin-dts 吧。
import { transpileDeclaration } from 'typescript';
import { createFilter } from '@rollup/pluginutils';
import { consola } from "consola";
function generateDTS(options = {}) {
return {
name: "generateDTS",
buildStart() {
this.DTSTimeStart = Date.now();
consola.info("Generate DTS Start");
},
buildEnd() {
consola.success("Generate DTS End,Time Delay: ", Date.now() - this.DTSTimeStart, "ms");
},
transform(code, id) {
const filter = createFilter(options.include, options.exclude);
if (!filter(id)) return;
const [path, ext] = id.split(".");
const [fileName] = path.split('/').slice(-1)
if(ext === 'ts') {
const dts = transpileDeclaration(code, {});
this.emitFile({
type: 'asset',
fileName: `${fileName}.d.ts`,
source: dts.outputText
})
}
return {
code: code,
map: null
};
}
}
}
export default generateDTS
执行 pnpm run build 命令,最终可以观察到。

最终输出时间为:80+ms
源码来自:Source Code: `src/services/transpile.ts`[6]
/*
* This function will create a declaration file from 'input' argument using specified compiler options.
* If no options are provided - it will use a set of default compiler options.
* Extra compiler options that will unconditionally be used by this function are:
* - isolatedDeclarations = true
* - isolatedModules = true
* - allowNonTsExtensions = true
* - noLib = true
* - noResolve = true
* - declaration = true
* - emitDeclarationOnly = true
* Note that this declaration file may differ from one produced by a full program typecheck,
* in that only types in the single input file are available to be used in the generated declarations.
*/
export function transpileDeclaration(input: string, transpileOptions: TranspileOptions): TranspileOutput {
return transpileWorker(input, transpileOptions, /*declaration*/ true);
}
源码来自:Source Code: `src/services/transpile.ts`[7]
const result = program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ declaration, transpileOptions.transformers, /*forceDtsEmit*/ declaration);
我对 TypeScript 底层的源码研究不多,但是看下来基本是换汤不换药,没做底层的大改动,基于原来的底层方法,扩展了配置,分隔了一个方法出来,专门做声明文件的生成。
TypeScript 官网提供了两个 Case:
相关地址:https://devblogs.microsoft.com/typescript/announcing-typescript-5-5-beta/#isolated-declarations
举个例子:
// util.ts
export let one = "1";
export let two = "2";
// add.ts
import { one, two } from "./util";
export function add() { return one + two; }
对于咱们应用而言,add.ts 作为主入口,那么,只需要声明 add() 的返回类型即可,这样就不用递归解析去推导类型了。
我个人认为这种模式是相当合理的,特别是作为库开发者,动动手就能解决一些痛点。

如上:fronted 和 backend 都依赖 core,通过拓扑依赖的解析顺序而言,先要解析 core ,再解析 fronted 和 fronted。
这个时候,虽然可以并行执行 fronted & backend,但是必须同步等 core,且这个解析动作包含了 类型检查 和 类型声明的生成以及推导,也就有了痛点。
所以这就体现了孤立声明的好处,类型检查和文件声明生成并行执行,不用等 tsc 等工具的 检查 和 生成 同时 OK。
isolatedDeclarations 和 transpileDeclaration 确实是一次对工具层的革新,期待在 TypeScript 5.6 版本中更加成熟。
最后,我是不换,本期探究到此结束啦,如有勘误可以在博客下方留言板留言,期待下篇博客再见 👋
参考资料
[1]
Weekly 第 50 期: https://weekly.binlin.wang/docs/2024/07/50/
[2]
快报文章中: https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-10/
[3]
oxc-transform: https://github.com/oxc-project/oxc/blob/main/npm/oxc-transform/scripts/generate-packages.mjs
[4]
Issue 58944: https://github.com/microsoft/TypeScript/issues/58944
[5]
Issue 58261: https://github.com/microsoft/TypeScript/pull/58261
[6]
Source Code: src/services/transpile.ts: https://github.com/microsoft/TypeScript/pull/58261/files#diff-8b9f3ad376989ea0de53dc29981105e71f6f3ba51c72ab811f267dc414621808R64
[7]
Source Code: src/services/transpile.ts: https://github.com/microsoft/TypeScript/pull/58261/files#diff-8b9f3ad376989ea0de53dc29981105e71f6f3ba51c72ab811f267dc414621808R64