前文介绍了 Nodejs 调试代码的原理,本文讲解几个调试案例加深下理解,使用的是 Vscode 调试器客户端。
调试 Express 代码
初始化 express 工程。
$ mkdir express-demo
$ cd express-demo
$ npm init -y
$ npm install express
创建 index.js 文件,内容如下:
const express = require('express')
const app = express()
app.get('/', (req, res) => {
const a = 1
const b = 2
const c = a + b
res.end(`a + b = ${c}`)
})
app.listen('3000', function () {
console.log('服务器已启动,端口', 3000)
})
方法一:request: launch
参考前文 Vscode 调试,添加配置文件如下:
// .vscode/launch.json
{
"type": "node",
"request": "launch",
"name": "Express launch",
"program": "${workspaceFolder}/index.js"
}
express 项目有个入口文件 index.js,平时启动服务需要输入 $ node index.js,调试的时候使用内置的调试器程序启动 index.js。
request: "launch" 表示使用调试器程序启动 index.js 文件。
方法二:request: attach
正常启动 Express 服务。
此时突然想调试某个接口,如果使用 launch 方式,需要 Ctrl + C 停掉当前服务,然后用调试器程序重新执行 index.js。但是现在不想重启服务,又要对代码进行调试,可以使用 attach 方式。
添加调试配置文件如下。
// .vscode/launch.json
{
"type": "node",
"request": "attach",
"name": "Express attach",
"processId": "${command:PickProcess}",
"port": 9229
}
- request: "attach" 表示 Nodejs 程序已经在运行着,待会启动调试器程序,去关联这个已经运行的 Nodejs 程序;
- processId: "${command:PickProcess}" 进程ID,后面花括弧里的东东不用管,意思当你点击 Vscode 调试面板绿色箭头(下图红字1)时会弹出一个下拉框让你选择:调试器程序关联哪一个正在运行的 Nodejs 程序(下图红字2)。
我的机器上起了好几个 Nodejs 程序,跑 express-demo 的是第二个 Nodejs 程序,点击之后再看看终端,会打印一些新的消息,其中 Debugger attached 表示调试器程序已经和正在运行的 Nodejs 程序关联起来了。
此时在 Vscode 中的源码里打个断点,在浏览器中访问 localhost:3000 可以看到进入断点了。
调试 nodemon 代码
还是👆上面 Express 的例子,你会发现改动代码不会立即生效,需要重启服务才行。
第三方包 nodemon 可以监听文件变化,自动重启服务。(supervisor 包作用相同)
全局安装 nodemon:
$ npm install nodemon -g
修改 package.json 的 scripts 属性
"scripts": {
"dev": "nodemon index.js"
}
此时执行 npm run dev 即可启动服务,修改 index.js 代码,会发现服务自动重启。
对于 Nodemon 启动的 Express 如何调试呢?
Vscode 打开调试配置,点击 Add Configuration(红字1),选择 Nodemon Setup(红字2)。
Vscode 会自动生成一份调试配置文件。(program 属性默认入口文件为 app.js,这里改成我们的 index.js)
{
"type": "node",
"request": "launch",
"name": "nodemon",
"program": "${workspaceFolder}/index.js",
"runtimeExecutable": "nodemon",
"restart": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
上面配置文件中前四个属性都有介绍过,这里主要介绍后四个属性的含义。
runtimeExecutable
使用哪个命令运行脚本,这里是 nodemon,启动调试器相当于执行 nodemon index.js
。如果没有写 runtimeExecutable 这个属性,它的默认值为 "node"。
这里的 nodemon 需要全局安装,这样在环境变量 PATH 中才能找到该命令。
restart
加上这个配置,修改代码,调试器才会重启。
console
启动调试器后,会打印一些信息,如:调试器服务端监听哪个端口,调试器客户端是否连接上等。
console 配置有三个值,分别设置在什么地方打印这些信息:
- integratedTerminal:Vscode 集成终端中打印信息,下图 Terminal 选项窗;
- internalConsole:Vscode 里的调试控制台中打印信息,下图 Debug Console 选项窗;(改成这个吧~)
- externalTerminal:打开系统自带的终端打印信息;
internalConsoleOptions
internalConsole 即 Vscode 中的 Debug Console,在👆介绍 console 配置时已经介绍过了,internalConsoleOptions 设置是否自动打开 Debug Console 选项窗。
- neverOpen:不会自动跳转到 Debug Console 选项窗,但是可以手动点击切换到 Debug Console 选项窗;
- openOnSessionStart:即便当前在 Terminal 选项窗,当进行调试时,会自动跳转到 Debug Console 选项窗。
- openOnFirstSessionStart:下同;
调试 Typescript 代码
上面👆Express 代码都是用原生 js 写的,如果用 Typescript 来写 Express 代码,如何调试呢?
调试之前,先要搭建 Typescript + Express 开发环境,参见 环境搭建。
src 目录放 ts 写的代码;
dist 目录放编译后的 js 代码。
前面已经介绍过在 Vscode 中调试 js 代码,但是 ts 编译成的 js 代码丑陋无比,如下:
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var express_1 = __importDefault(require("express"));
var app = express_1.default();
app.get('/', function (req, res) {
res.send('Hello Ts Express dd ee ff');
});
app.listen(3000, function () {
console.log('Server is running.');
});
//# sourceMappingURL=app.js.map
这导致我们想直接在 ts 文件上打断点,而非在 js 文件上打断点,如何做到这一点,是接下来的重点。
打开 Vscode 调试配置文件:
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/src/app.ts",
"runtimeExecutable": "nodemon",
"restart": true,
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
前面 6 个属性都介绍过,只有最后两个属性是陌生的。
preLaunchTask
在调试器启动入口文件之前执行这个任务,这里的任务是 tsc: build,即编译操作,将 src 目录下的 *.ts 文件编译成 *.js 文件;最终,调试器执行的还是 js 文件而非 ts 文件。
问题来了,调试器咋知道你编译后的 js 文件放在那里呢?下面👇属性 outFiles 告诉它的。
outFiles
该属性告诉调试器执行的 js 文件在哪个目录下,上面例子编译后的 js 文件都放在 dist 目录下。
既然调试器执行的是 js 代码,为什么我们可以在 ts 文件中打断点呢?
tsconfig.json 中有个配置项 sourceMap,当设置为 true 时,在 *.ts 文件编译成 *.js 文件时,会顺带生成 *.js.map 文件,这个文件将 *.ts 和 *.js 文件关联起来了,所以可以在 ts 文件打断点。