浅谈在 vscode 中调试 typescript 的小坑
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
概述
使用 typescript 写 node ,本来以为在 vscode 上调试是件很容易的事情,结果还是踩了一些小坑。下文记录一下踩坑的过程和具体的解决方案。
下文整理使用两套方案在 vscode 中调试 typescript :
PS,vscode、tsc、node 版本如下:
- vscode:
1.71.2
- tsc:
4.8.3
- node:
16.13.1
方案一:tsc
demo 演示项目:demo-ts-debugger,可以 clone 下来然后跟着下文步骤尝试
这个方案是 vscode 官网文档——Debugging TypeScript 中的方案,但官网文档中提到的内容感觉不太全,有些坑和细节没细讲( 也许是官网大大觉得这些都是常识吧 (〒︿〒)
)
完整效果
如下图 gif,演示使用 tsc 编译 typescript 代码,然后使用 vscode 调试的效果:
达到调试的预期效果:
- 直接定位到
ts
源文件,而不是跳去编译出来的js
文件- 在代码中使用
debugger
或者直接在 vscode 中打断点都可以
- 在代码中使用
- 支持源文件中使用别名(alias)
launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/out/**/*.js"]
}
]
}
launch.json
的详细字段说明可以参考:官网文档,部分字段中文含义可以参考:vscode调试学习
挑三个对这次调试任务来说比较重要的字段说明一下:
program
- “启动调试器时要运行的可执行文件或文件”
- 我猜:这个应该是调试器加载需要调试代码的入口文件吧
- 试了把这个字段的值改成
"${workspaceFolder}/out/index.js"
也可以正常运行,可能是有 map 吧,暂时没找到详细解说,希望有了解里面细节的朋友评论区告知一下 🤝 - 另外,如果把这个字段删掉就有问题,没法正常调试
preLaunchTask
- “要在调试会话开始之前启动任务,请将此属性设置为
tasks.json
中指定的任务标签(在工作区的.vscode
文件夹中)。或者,这可以设置${defaultBuildTask}
为使用您的默认构建任务” - 翻译一下就是在调试启动之前会先执行
tasks.json
指定的任务,至于指定的是哪个任务呢?就看preLaunchTask
的值和tasks.json
中label
的对应关系,如下图:- 这样说明,调试启动之前会先执行
tasks.json
中label
标签是tsc: build - tsconfig.json
的任务 - PS:这里有一个坑,如果一开始没有
task.json
文件的话,直接启动调试会弹出一个对话框,提示并帮助你生成task.json
文件,如下图:- 但如果 vscode 使用了中文版(中文语言包)的话,自动生成的
task.json
文件中的label
的值是"tsc: 构建 - tsconfig.json"
,要手动换成"tsc: build - tsconfig.json"
- 反正记住
preLaunchTask
的值和tasks.json
中label
的值向对应就准没错
- “要在调试会话开始之前启动任务,请将此属性设置为
outFiles
- 按照官网的说法:Mapping the output location
- If generated (transpiled) JavaScript files do not live next to their source, you can help the VS Code debugger locate them by setting the
outFiles
attribute in the launch configuration. Whenever you set a breakpoint in the original source, VS Code tries to find the generated source by searching the files specified by glob patterns inoutFiles
.
- If generated (transpiled) JavaScript files do not live next to their source, you can help the VS Code debugger locate them by setting the
- 但好像去掉这个字段也是可以正常调试的,暂时不确定详细原因和用法
- 按照官网的说法:Mapping the output location
另外,配置中如:${workspaceFolder}
之类的特殊变量,具体可参考:官方文档 —— variables-reference,这里就不一一翻译里,文档里面还挺详细的 (~ ̄▽ ̄)~
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"problemMatcher": [
"$tsc"
],
"group": "build",
"label": "tsc: build - tsconfig.json"
}
]
}
关于 vscode 中 tasks 的用法和介绍,可以参考:官方文档 —— tasks
关于 tasks.json
文件中各个字段的解析,可以参考:官方文档 —— tasks-appendix
上述这份 tasks.json
文件中的内容作用就是使用 tsc
命令行工具,将 typescript 代码编译成 javascript 代码
如何在源码中使用“别名”(alias)?
用过 webpack 别名 alias,应该不陌生,可以非常优雅地解决两个问题:
- 相对路径太长
- 使用相对路径 import 的文件,一旦移动了文件,要重新修改相对路径
比如,在文档: webpack 别名 alias 中的案例,定义了 src/utilities/
路径的别名为:Utilities
后,就可以使用 import Utility from 'Utilities/utility';
替换原本的相对路径的写法:import Utility from '../../utilities/utility';
,灰常方便
但 tsc
并不支持这种做法,需要借助第三方库:module-alias 才能实现上述效果。
要想使用 module-alias 一共有三步:
- 在
tsconfig.json
文件中添加baseUrl
和paths
两个字段baseUrl
字段指代当前根目录paths
里面则用于定义自己想定义的别名(alias)- 如上图,我们把
src
目录下的所有路径都起一个别名,叫:@
- 这样,后续 demo 源码 中
import sum from './utils/sum'
的写法就可以替换成import sum from '@/utils/sum'
- 如上图,我们把
- 安装 module-alias 依赖,并且在
package.json
文件中定义_moduleAliases
字段- 细心的朋友可能已经发现,为什么
_moduleAliases
中定义的别名跟tsconfig.json
文件中的paths
不一样?下面我们一起看看编译出来的out
目录下的js
文件内容就知道:- 可以看到
import sum from '@/utils/sum'
编译出来的内容是var sum_1 = require("@/utils/sum")
- 即:在编译阶段,
tsc
并没有因为多了module-alias
就把@
这个自定义别名“翻译”出来。实际上我猜module-alias
应该是在js
运行时才完成的这步“翻译”操作
- 即:在编译阶段,
- 在项目的入口文件中引入
import 'module-alias/register';
失败记录
一开始查找 tsc
文档时,发现了这个编译选项:--paths (在 compiler-options 可以查看所有 tsc
的编译选项)
一开始以为可以实现预期的别名效果,但实际上并不行,会报一下错误:
error TS6064: Option 'paths' can only be specified in 'tsconfig.json' file or set to 'null' on command line.
暂时没搞懂,就没走这条路了,换成了上述成功的道路
方案二:ts-node
demo 演示项目:demo-father-ts-debugger,可以 clone 下来然后跟着下文步骤尝试
这个方案是使用 ts-node 库协助编译,然后再使用 vscode 调试
完整效果
如下图 gif,演示使用 ts-node 库协助编译,然后再使用 vscode 调试
达到调试的预期效果:(跟上面 tsc
的方案的预期效果一致)
- 直接定位到
ts
源文件,而不是跳去编译出来的js
文件- 在代码中使用
debugger
或者直接在 vscode 中打断点都可以
- 在代码中使用
- 支持源文件中使用别名(alias)
安装 ts-node 和 tsconfig-paths
这个方案依赖 ts-node 和 tsconfig-paths 这两个第三方库
- ts-node,用来将
ts
编译成js
- tsconfig-paths,用来实现上述别名(alias)的效果
launch.json
launch.json
文件内容字段就不赘述了,详细可以参考上述 “方案一:tsc” 小节的内容
这份配置基本是直接照抄: tsconfig-paths 中的使用文档
对于这个方案来说,最核心的应该就是上图中的 runtimeArgs
和 args
两个字段
我猜:(对又是合理性猜测)
- 上面
runtimeArgs
中的内容,是传给启动调试程序时的参数,表示使用ts-node
编译以及使用tsconfig-paths
“翻译” 别名 - 上面
args
中的内容,表示从"${workspaceFolder}/src/index.ts"
文件作为入口开始编译ts
代码
为什么会有上述猜想?可以看看下图:
这个是启动调试程序后,vscode 中 “调试控制台” 打印出来的内容
/Users/nicholas/.nvm/versions/node/v16.13.1/bin/node -r /Users/nicholas/Nicholas/my-pro/demo/demo-father-ts-debugger/node_modules/ts-node/register -r /Users/nicholas/Nicholas/my-pro/demo/demo-father-ts-debugger/node_modules/tsconfig-paths/register /Users/nicholas/Nicholas/my-pro/demo/demo-father-ts-debugger/src/index.ts
很明显,是调用了一个 node 程序的命令,其他内容都作为命令的参数传了进去
如何在源码中使用“别名”(alias)?
这个方案使用别名(alias)的方法比较简单,只需要修改 tsconfig.json
文件即可
相对来说,这个方案简单一下,需要修改的地方也不多
One More Thing
按理来说,“方案一:tsc” 应该是可以扩展的,比如不用 tsc
编译代码之类的
比如,在 “方案二:ts-node” 中使用了 Umi 的 father 作为项目的脚手架,那么,应该是可以修改“方案一:tsc”中的 tasks.json
,不用 tsc
任务,换成是 father
的命令行工具,执行编译任务,应该也是可以的,各位看官有兴趣可以尝试一下 (~ ̄▽ ̄)~
转载自:https://juejin.cn/post/7144238328655642637