跟着调试 TS 源码

1,433 阅读4分钟

今天刚好看到一个视频讲解如何调试 ts 源码,感觉很有趣,虽然只学过 ts 语法,没用过 ts 做项目。但是并不影响我的“好奇心”,于是跟着试了一下,可以成功,所以做了个笔记纪录一下步骤(细节我也不懂,能跑就行,这样我也算是 “看过底层源码,调试过源码了😁😎😉”)。

视频来源:公众号博主“神光的编程秘籍” 2022年 5 月 11 日 11:08pm 的视频

注意:以下内容不保证正确性(名词 / 解释)

下载 ts 源码

git clone --depth=1 https://github.com/microsoft/TypeScript.git
# 注意这里最好带上 --depth=1,代表只克隆最新版本的代码
# 因为 github 本身下载就慢,而且 ts 源代码完整仓库特别特别特别庞大。
# 可能我的工具比较垃圾(免费的),所以即使用了工具下载也是很慢

git clone https://gitee.com/linhieng/msts.git
# 我将浅克隆下来仓库上传到了 gitee 上(这一块又是一个坑),方便下次克隆。

单单一次版本体积就达到了 30 M。完整仓库的话,有 60万+ 文件.....,我下载到 100 M (差不多25%) 的时候就放弃了 image.png

安装和编译

# 安装依赖
npm i
# 构建,创建 built 文件夹
npm run build:compiler

也可以直接界面化操作

image.png

配置调试

在根目录的 .vscode 下创建 launch.json 文件,然后写入一下内容:

{
  "configurations": [
    {
      "name": "调试 ts 源码",
      "program": "${workspaceFolder}/test.js",
      "request": "launch",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "type": "node"
    }
  ]
}

说明:以上代码可通过 “添加配置” 快速添加的,如图:

image.png

image.png

往 .eslintignore 文件中添加两个忽略文件:

input.ts
test.js

编写代码

在根目录下创建 input.ts,内容如下:

// input.ts
type Test<T> = T extends 1 ? 'a' : 'b'
// 将要调试的是下面这条语句的解析过程,即 res 的类型是如何计算的
type res = Test<1 | 2>

查看 input.ts 的 AST 树结构

我们要调式的是源码是如何实现对 res 类型的计算的,所以我们需要过滤出对应的 AST 结构

AST 这块知识和 ts 是如何转换成 js 有关,很多工具都和这个 AST 有关(ESlint)

进入网站 AST explorer ,这个网站可以查看代码的 AST 结构。

设置语言为 javascript,paternity,然后鼠标点击 res

image.png

此时可以在右边看到对应的 AST 结构

image.png

image.png

看了上面两张图后,基本上对于 AST 结构是什么也有了一个认识了。建议自己随便粘贴几个代码上去试试,应该会很有意思。

编写测试代码

同样在根目录下创建 test.js,内容如下:(我这种注释很不建议使用,特别是当你熟悉调试后,会感觉我的注释都是“废话”)

// test.js

// 引入 API 入口文件
const ts = require('./built/local/typescript')

const filename = './input.ts'
// 编译 filename 文件,在此处断点,查看 program 数据是什么样的
/* 断点 */const program = ts.createProgram(filename, {
  allowJs: false,
  strictFunctionTypes: false,
})

// 获取文件的 AST 结构,在此处断点,查看 sourceFile 的数据是怎么样的
/* 断点 */const sourceFile = program.getSourceFile([filename])
const typeChecker = program.getTypeChecker()

function visitNode(node) {
  // 在遍历过程中,过滤出我们想要的结点(这里的条件是通过查看 AST 树来得到的,即未扑先知)
  // 通过在网站上看 AST 结构,我们这里的过滤条件就是:取出 “语句” 中 “变量名” 为 “res” 的结点
  if (node.kind === ts.SyntaxKind.TypeAliasDeclaration && node.name.escapedText === 'res') {
    // 这里可以获取节点元素的数据类型,
    // 在此处进行断点,然后进入 getTypeFromTypeNode 函数中
    // 就可以了解到 ts 解析类型的过程了
    /* 断点 */const type = typeChecker.getTypeFromTypeNode(node.name)

    debugger
  }

  node.forEachChild(child => visitNode(child))
}

// 遍历 AST
visitNode(sourceFile)

开始调试

前面已经配置好了 launch.json ,也编写好了 test.js 和 input.ts,剩下的工作就是 debugger,也就是理解源码的思维。前面的工作只是告诉了我们,这条路在这里,现在,我们要做的就是沿着这条路走,然后明白 “这条路” 是怎么被 “走” 出来的。而这里,就是靠理解的地方了。这篇文章,主要是纪录一下如何调试,而具体的思维,很多 “剖析源码” 的文章讲的更加详细。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

TypeParameter 指的就是 T (是吧)

下一步博主就直接通过搜索 isDistributive 然后就定位到了“是在哪里用到了这个变量”。这一步我是不知道怎么来的,反正我搜索后看见那么多个地方又,我是不知道为什么就是这里用到了 😭

image.png

image.png

image.png

image.png

image.png

image.png

这里,刚开始,我就没有打 map 的断点,我想着跟上面调试一样,在前面打两个断点就好了.结果发现找不找博主进入的代码,然后才发现,原来还需要在 map 打个断点。不然当你在 23807 停住的时候,此时是已经跑完了 map 函数。

image.png

image.png

image.png

image.png

好了,调试到此结束。看视频 5 分钟,做笔记 5 小时(应该有吧)

总结

如果会读源码,真的好爽呀