在上周的周刊(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