Deno 中使用 @typescript/vfs 生成 DTS 文件
背景
前段时间开源的 STC 工具,这是一个将 OpenApi 规范的 Swagger/Apifox 文档转换成代码的工具。可以在上一篇(《OpenApi(Swagger)快速转换成 TypeScript 代码 - STC》)随笔里面查看这个工具的介绍和使用。
为了支持生成 Javascript,近期添加了 JavaScript 插件,并且生成 DTS 文件。实现它有两个设想:
- 重新写一遍解析 OpenApi 规范的文档数据。
- 基于 TypeScript 插件生成的 TypeScript 代码字符串,通过编译工具转换成 JavaScript。
最终选择第二种实现方式,原因也很简单,TypeScript 是 JavaScript 的超集,有着丰富的编译工具(tsc、esbuild、swc、rome 等等)。相比第一种方式起来更简单,出现问题时只需要修改 TypeScript 的转译部分,还能减少多次修改的情况。通过实践,选择 swc 编译 TypeScript 代码 DTS 文件则由 tsc 生成。

代码实现
首先在 Deno 文档找了一遍,是否有满足需求我们的 Api 提供。看到文档上写着:

于是,开始另寻他路。
在尝试了 esbuild 失败后,决定使用 swc 将 TypeScript 编译成 JavaScript 代码,可是不支持生成 DTS 文件,这还需要用 tsc 来实现。其中比较棘手是 tsc 在 Deno 里面实现(应该是对 TypeScript compiler Api 不熟的原因)。
通过在网上查阅 TypeScript compiler Api 的使用资料,同时还借助 ChatGPT 的协助,对 TypeScript compiler Api 有了个初步的认识。
摘自 TypeScript wiki 的示例(从 JavaScript 文件获取 DTS):
import * as ts from "typescript";
function compile(fileNames: string[], options: ts.CompilerOptions): void {
// 创建一个带有内存发射的编译程序
const createdFiles = {}
const host = ts.createCompilerHost(options); // 创建编译器主机
host.writeFile = (fileName: string, contents: string) => createdFiles[fileName] = contents // 覆盖写入文件的方法
// 准备并发射类型声明文件
const program = ts.createProgram(fileNames, options, host);
program.emit();
// 遍历所有输入文件
fileNames.forEach(file => {
console.log("### JavaScript\n")
console.log(host.readFile(file))
console.log("### Type Definition\n")
const dts = file.replace(".js", ".d.ts")
console.log(createdFiles[dts])
})
}
// 运行编译器
compile(process.argv.slice(2), {
allowJs: true,
declaration: true,
emitDeclarationOnly: true,
});
我想法是直接是用代码字符串生成的方式,不是文件,所以这段示例不能直接应用到我们的代码里面来。结合 ChatGPT 的一些回答和网上的资料,改造如下:
import ts from "npm:typescript";
const generateDeclarationFile = () => {
const sourceCode = `
export type TypeTest = 1 | 0 | 3;
export interface ISwagger {
name: string;
age: number;
test: string; // Array<string>;
}
/**
* Adds two numbers.
* @param a The first number.
* @param b The second number.
* @returns The sum of a and b.
*/
export function add(a: any, b: any): number {
return a + b;
}
`;
const filename = "temp.ts";
// 创建一个编译选项对象
const compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ESNext,
declaration: true,
emitDeclarationOnly: true,
lib: ["ESNext"],
};
let declarationContent = "";
const sourceFile = ts.createSourceFile(
filename,
sourceCode,
ts.ScriptTarget.ESNext,
true,
);
const defaultCompilerHost = ts.createCompilerHost(compilerOptions);
const host: ts.CompilerHost = {
getSourceFile: (fileName, languageVersion) => {
if (fileName === filename) {
return sourceFile;
}
return defaultCompilerHost.getSourceFile(fileName, languageVersion);
},
writeFile: (_name, text) => {
declarationContent = text;
// console.log(text);
},
getDefaultLibFileName: () => "./registry.npmjs.org/typescript/5.1.6/lib/lib.d.ts"
useCaseSensitiveFileNames: () => true,
getCanonicalFileName: (fileName) => fileName,
getCurrentDirectory: () => "",
getNewLine: () => "\n",
getDirectories: () => [],
fileExists: () => true,
readFile: () => "",
};
// 创建 TypeScript 编译器实例
const program = ts.createProgram(
[filename],
compilerOptions,
host,
);
// 执行编译并处理结果
const emitResult = program.emit();
if (emitResult.emitSkipped) {
console.error("Compilation failed");
const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
allDiagnostics.forEach((diagnostic) => {
if (diagnostic.file) {
const { line, character } = ts.getLineAndCharacterOfPosition(
diagnostic.file,
diagnostic.start!,
);
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
"\n",
);
console.log(
`${diagnostic.file.fileName} (${line + 1},${
character + 1
}): ${message}`,
);
} else {
console.log(
ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
);
}
});
}
return declarationContent;
};
运行结果,一切正常,DTS 内容也拿到了。

这就结束了吗?修改一下 sourceCode 的内容,test: string; 改成 Array<string>;,出错了。

这个问题是由于 lib.d.ts 文件的找不到导致的,比较棘手的是,尝试了几种修改 lib.d.ts 文件的路径方式,结果都以是吧告终。

不愿妥协的我,又开始另辟蹊径了,在网上开始搜索一番。可谓皇天不负有心人,于是找到了 @typescript/vfs 这个 npm 库。@typescript/vfs 是一个基于映射的 TypeScript 虚拟文件系统。这对于我们在 Deno 环境中很有用,它可以运行虚拟的 TypeScript 环境,其中文件不是来源于真实磁盘上的。按照文档开始改造,最终核心的实现:
import vfs from "npm:@typescript/vfs";
const generateDeclarationFile = async (sourceCode: string) => {
// ...
// 创建一个编译选项对象
const compilerOptions: ts.CompilerOptions = {
declaration: true,
emitDeclarationOnly: true,
lib: ["ESNext"],
};
// 创建一个虚拟文件系统映射,并加载 lib.d.ts 文件
const fsMap = await vfs.createDefaultMapFromCDN(
compilerOptions,
ts.version,
true,
ts,
);
fsMap.set(filename, sourceCode);
// 创建虚拟文件系统
const system = vfs.createSystem(fsMap);
// 创建虚拟 TypeScript 环境
const env = vfs.createVirtualTypeScriptEnvironment(
system,
[filename],
ts,
compilerOptions,
);
// 获取 TypeScript 编译输出
const output = env.languageService.getEmitOutput(filename);
// 将输出的声明文件内容拼接起来
const declarationContent = output.outputFiles.reduce((prev, current) => {
prev += current.text;
return prev;
}, "");
// 创建虚拟编译器主机
const host = vfs.createVirtualCompilerHost(system, compilerOptions, ts);
// 创建 TypeScript 程序
const program = ts.createProgram({
rootNames: [...fsMap.keys()],
options: compilerOptions,
host: host.compilerHost,
});
// 执行编译并获取输出结果
const emitResult = program.emit();
// ...
}
看一下输出结果,符合我们的结果期望了,并且没有错误。

总结
问题最终是得到了很好的解决,是值得庆祝的。使用了 @typescript/vfs 库提供的虚拟文件系统,达到了我们需要的结果。期间想通过解析 AST,来生成 DTS 文件,这个工程有点大,最终被劝退了。本文介绍了在 Deno 中使用 @typescript/vfs 库生成 DTS 文件的步骤和方法,希望对你有所帮助。
参考资料:
Deno 中使用 @typescript/vfs 生成 DTS 文件的更多相关文章
- delphi中单独编译pas生成dcu文件
delphi中单独编译pas生成dcu文件 在网上下载了一个带源码的组件,结果碰到提示说缺少xxx.dcu.一看它的目录下确实没有,那能不能生成一个呢? 当然可以! 方法是使用delphi的安装目录\ ...
- JNI中使用cl命令生成DLL文件
问题描述: 在使用JNI调用DLL时,首先需要生成DLL文件 问题解决: (1)现在使用VS2008的cl.exe程序,生成DLL文件 (1.1)cl.exe环境搭建 注: cl. ...
- spark中saveAsTextFile如何最终生成一个文件
原文地址: http://www.cnblogs.com/029zz010buct/p/4685173.html 一般而言,saveAsTextFile会按照执行task的多少生成多少个文件,比如pa ...
- Python中使用dom模块生成XML文件示例
在Python中解析XML文件也有Dom和Sax两种方式,这里先介绍如何是使用Dom解析XML,这一篇文章是Dom生成XML文件,下一篇文章再继续介绍Dom解析XML文件. 在生成XML文件中,我们主 ...
- Java中使用DOM4J来生成xml文件和解析xml文件
一.前言 现在有不少需求,是需要我们解析xml文件中的数据,然后导入到数据库中,当然解析xml文件也有好多种方法,小编觉得还是DOM4J用的最多最广泛也最好理解的吧.小编也是最近需求里遇到了,就来整理 ...
- 在myEclipse中根据图表自动生成Hibernate文件
1.新建一个Java Project项目,在scr中创建两个包:Com.hibernate.po 和com.hibernate.dao 2. 3.点击ok 4. 5.选中MyElipse Derby, ...
- 读取Excel二进制写入DB,并从DB中读取生成Excel文件
namespace SendMailSMSService { class Program { static void Main(string[] args) { var connString = Sq ...
- JAVA中使用freemark生成自定义文件(json、excel、yaml、txt)
原文:http://blog.csdn.net/jinzhencs/article/details/51461776 场景:在我们工作中,有时需要生成一些文件,可能它不是一种标准的格式,比如JSON. ...
- 在spring boot 中使用itext和itextrender生成pdf文件
转载请注明出处 https://www.cnblogs.com/majianming/p/9539376.html 项目中需要对订单生成pdf文件,在第一版本其实已经有了比较满意的pdf文档,但是还是 ...
- 【Android Studio安装部署系列】二十五、Android studio使用NDK生成so文件和arr文件
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 Android Studio使用ndk的简单步骤. NDK环境搭建 下载NDK 下载链接:https://developer.and ...
随机推荐
- 2022-01-13:K 个不同整数的子数组。 给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定不同的子数组为好子数组。 (例如,[1,2,3,1
2022-01-13:K 个不同整数的子数组. 给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续.不一定不同的子数组为好子数组. (例如,[1,2,3,1 ...
- HCIP- ICT实战进阶ex1-MPLS
HCIP- ICT实战进阶ex1-MPLS 0 前言 由于BGP设备之间是通过TCP协议实现的跨设备互联, 所以在两台BGP设备之间的其他设备是没有配置BGP协议的, 因此中间的其他设备很可能无法学习 ...
- [SWPUCTF 2021 新生赛]PseudoProtocols
[SWPUCTF 2021 新生赛]PseudoProtocols 一.题目 二.WP 1.打开题目,发现提示我们是否能找到hint.php,并且发现URL有参数wllm.所以我们尝试利用PHP伪协议 ...
- c++的前世今生
C++ 语言是本贾尼·斯特劳斯特卢普 在1982 年发明的,早期版本被称为C with Classes,之后在1983年更名为C++. C++语言在发明后很快就获得了广泛的应用,由于其具有高效.灵活和 ...
- doo 13 之11 :开发之看板视图和用户端 QWeb
QWeb 是 Odoo 使用的模板引擎,它基于 XML 来生成 HTML 片断和页面.通过 QWeb可生成内容丰富的看板(Kankan)视图.报表和 CMS 网页.本文中我们将学习QWeb 语法以及如 ...
- AntV L7 快速入门示例
1. 引言 L7 地理空间数据可视分析引擎是一种基于 WebGL 技术的地理空间数据可视化引擎,可以用于实现各种地理空间数据可视化应用.L7 引擎支持多种数据源和数据格式,包括 GeoJSON.CSV ...
- 读文献先读图——主成分分析 PCA 图
上周五彩斑斓的气泡图 有让你眼花缭乱吗? 本周,化繁为简的PCA图 你值得拥有! 数据分析| 科研制图﹒PCA 图 关键词:主成分分析.降维 1665 年的鼠疫 牛顿停课在家提出了万有引力 ;183 ...
- 生物信息培训之WGCNA-权重基因共表达网络分析
本文分享自微信公众号 - 生信科技爱好者(bioitee).如有侵权,请联系 support@oschina.cn 删除.本文参与"OSC源创计划",欢迎正在阅读的你也加入,一起分 ...
- Spring事件监听机制使用和原理解析
你好,我是刘牌! 前言 好久没有更新Spring了,今天来分享一下Spring的事件监听机制,之前分享过一篇Spring监听机制的使用,今天从原理上进行解析,Spring的监听机制基于观察者模式,就是 ...
- 关于VS2022使用EF生成实体模型报错的问题:运行转换:System.NullReferenceException:对象引用未设置为对象的示例。
起因: 之前版本vs2022生成EF模型一直没有问题,在更新了最新的vs2022之后,版本号17.6+,出现此问题: 运行转换:System.NullReferenceException:对象引用未设 ...