Debugging in VSCode(看源码利器)

描述了在VS Code中怎样充分利用其强大的debug功能来调试web项目和源码调试(TypeScript)javascript

原文在此java

背景

以前在看一些github源码的时候看调试数据是很是很是吃力,不知道多少时间消耗在了console,rebuild,switch tab的这几个频率最高的步骤中,着实烦躁。首先回顾一下我本身以前在看一些库源码的过程,以rollup这个打包库为例,首先git clone下来,npm install后发现它也自动执行了打包过程,看了下package.json文件中的scripts字段,其中有prepare脚本命令,npm在执行npm install后就会自动执行这个script,这个命令又指向了npm run build,进而进行了自动打包过程。一般我都会在这个project下执行npm link而后从新重建个文件夹写本身的一些例子项目link到前面的project,这样看project下的源码或者console以后从新build,就能在例子项目实时可以调试到,其中执行npm run watch能够不用从新build。node

这样真的很累!...须要开两个项目频繁切换,消耗时间,还一点都不灵活!下面就研究了下VSCode中强大的Debug功能,让我犹如坐上了火箭🚀,并在此作个总结。react

初识Debug Panel(牛刀小试)

下面是vscode中的debug panelgit

image

最左边一列的第四个tab,以前的我看到过无数遍这个小蜘蛛,却历来没有打开过...,相信我,它会让你打开新世界的大门。(command+shife+D快捷打开此面板)github

方便这节后面的演示,用VSCode打开一个空目录取名Hello,下面新建一个app.js,写个最简单的代码以下:web

var hello = "Hello World"
console.log(hello)
复制代码

而后点击小蜘蛛进入到debug面板,点击👇下面的小齿轮图标chrome

若是你的项目根目录下有.vscode目录且下面有launch.json配置文件,则点击上面的小齿轮按钮会直接打开此 launch.json配置文件shell

这时会出现以下,让你选择调试环境,VSCode内置了Node.js环境,能够在插件tab安装其余语言的扩展,VSCode支持各类语言的调试,eg:PHP,Python,go,C/C++...咱们直接选择Node.js环境npm

选择后会直接打开一个launch.json文件并有以下配置:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "program": "${file}"
        }
    ]
}
复制代码

command+shift+E回到目录列表会看到多了一个.vscode目录,下面就是该launch.json文件,能够点击右下角的Add Configuration按钮,会弹出以下配置供选择,在这个配置文件里能够充分利用它的智能感知功能,咱们选择Node.js: Launch Program

它会自动帮咱们生成该配置的经常使用项,咱们将其中的"program": "${file}"改成"program": "${workspaceRoot}/app.js",在这个配置有挺多的内置变量能够直接使用,${file}就是当前所活跃的文件,${workspaceRoot}表示当前项目的工做根目录,完整的替换变量的列表可参考这里

在上面的配置字段中有个request字段,有两个值能够选择:"launch""attach",它表示VS Code中核心的两种调试模式。当时搞清这两种模式区别的时候也是有点晕,这也是取决与你项目中的工做流是怎样的,假如你调试的是web项目,一般你会在浏览器中已经直接打开项目了,这个时候咱们可使用attach模式,正如字面的意思,将debugger附加到你已经运行到的web项目,而launch就像字面意思是直接由编辑器启动这个程序,好比启动一个服务端项目或者上面咱们的小例子,这个启动模式编辑器会自动把debugger附加到这个程序中。launch config 就像是做为一种debug模式直接启动app,而attach config 就像是debugger链接到正在运行的app。下面咱们在web项目调试一节会有这两种模式的直观印象

在开始debug以前,咱们先在app.js的第二行最左边打一个断点

configurations里面的name字段会显示在左边Debug一栏最上面的下拉列表里,点击小齿轮左边的框就能够选择刚才添加的配置,对应与configurations里面的name字段,选择Launch Program,而后点击左边的小绿色三角启动debug(F5),而后就能够看到程序暂停在刚才打的断点这行了

能够看到最上面的工具栏就是全部的调试操做:

image

  • 继续/暂停 F5
  • 单步跳过 F10
  • 单步调试 F11
  • 单步跳出 ⇧F11
  • 重启 ⇧⌘F5
  • 中止 ⇧F5

左边的调试栏也显示出了程序里运行的全部变量,能够看到commonjs的模块变量也显示出来了__dirname,__filename,module,require...global下面也有全部的全局变量,调用堆栈也显示了程序中函数的调用顺序,这样你就能够在任何你想调试的地方打个断点,这个地方的全部信息就所有暴露出来了。下面咱们能够看看怎样来调试web项目

调试web项目

咱们用create-react-app官方脚手架建立一个简单app项目,在终端运行以下命令:

npx create-react-app debug-react  # npm 版本5.2以上
cd debug-react
npm start
复制代码

能够看到浏览器自动打开并运行了这个简单web项目,为了方便调试看到效果,咱们将App.js代码稍加改动以下:

// App.js
// ...
function App(props) {
  const {
    link,
    text,
  } = props  // 这行打个断点
  return (
    <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href={link} target="_blank" rel="noopener noreferrer" > {text} </a> </header> </div> ); } // ... 复制代码

相应的index.js改动以下:

// index.js
// ...
ReactDOM.render(
    <App link="https://reactjs.org" text="hello world" />, document.getElementById('root') ); // ... 复制代码

保存后浏览器自动刷新了,能够看到只是将渲染数据提取出来以props形式从父组件传入,接下来咱们进行debug的配置

调试web项目须要安装一个利器,打开VS Code左边的扩展栏 ⇧⌘X ,而后输入chrome,选择并点安装 Debugger for Chrome 扩展,安装完后进入左边debug栏点击小齿轮 F5 在弹出的选择环境的下拉列表框中选择 chrome ,而后会自动打开Launch.json配置文件并有以下配置:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:3000",
            "webRoot": "${workspaceFolder}"
        }
    ]
}
复制代码

你必须指定一个 url 或者 file 配置项启动浏览器,以上配置的url就是指向咱们最上面的debug-react项目的本地服务url。若是指定了url,就要设置webRoot字段,其表示一个绝对路径指示本地服务的文件来自哪里,好比以上的webRoot配置会解析http://localhost:3000/index.js成为相似/Users/me/debug-react/app.js因此确保它设置正确

在启动调试以前确保以前的本地服务是跑着的,而后按 F5 启动debug调试,能够看到VS Code启动了个新的浏览器窗口,若是程序并无停在以前的断点处,能够在刷新一下调试 ⇧⌘F5 ,能够看到以下效果了:

相应的props和相关变量均可以在debug栏清晰的看到,都不用去看浏览器的调试了。

注意到上面的chrome调试配置的 request 类型是 launch,咱们还能够尝试另外一种调试方式,也就是"request": "attach"的调试方式,打开Launch.json配置文件,点击右下角的Add Cogfiguration选择Chrome: attach, VS Code自动生成了系列配置以下:

{
   "type": "chrome",
   "request": "attach",
   "name": "Attach to Chrome",
   "port": 9222,
   "webRoot": "${workspaceFolder}"
}
复制代码

要进行远程调试必须的打开chrome的远程调试功能,并设置以上默认配置的远程调试端口,以下:

  • Mac
    在终端运行/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222, 会直接运行一个新的浏览器的窗口,而后将以前的debug-react项目重启一下npm start

  • Windows
    右击桌面的chrome的快捷方式,点属性=>目标框的后面添加--remote-debugging-port=9222,或者在命令行下执行<path to chrome>/chrome.exe --remote-debugging-port=9222,重启一下debug-react项目

启动调试 F5 ,可能会让你选一个本地服务,选择debug-react便可,能够看到已经开始了debug调试:

image

点击以上绿色按钮重启一下,程序停在了断点的位置上了。注意attach并无启动一个新的浏览器窗口,而是在你原有启动的窗口下开始了调试。将鼠标移到上图橙色正方图标上,显示的是断开链接,这也和以前咱们所说的attach模式是把debugger链接到了已经启动的app上的说法一致,它没有像launch模式那样每次启动都会启一个新的浏览器窗口。

在调试react项目的时候会常常遇到render阶段数据还没出来,这跟react的生命周期有关,每每咱们会多点几下单步调试或者单步跳过,其实打断点的时候支持多种断点,好比打条件断点,日志断点,函数断点等等...能够自行研究下~至此,调试web项目就到这了,下面咱们来看一下怎样用此debugger科学的看那些库源码

源码调试

正如背景里面所提到的,在未接触VS Code的debugger以前,我看源码的过程真的很苦逼...并且时常搞不清源码中各个函数的调用栈顺序,效率着实慢的着急,由此每每没耐心往下看,也体会不到源码里的思想精髓...

废话很少说,这节咱们以rollup源码为例。建议花半分钟能够去官网看看这是个什么,不过看不看无所谓也不是这节的重点。建议把此项目clone下来跟着步骤一块儿走。

打开项目,发现整块源码都是用TypeScript写的(实践你会发如今VS Code中用debugger配合TypeScript看源码有多爽),VS Code能够调试任何能够编译成javascript的语言,更不用说亲儿子TS了。

iamge

看到项目目录中是有本身的.vscode目录中,打开看下launch.json是有关于debug test的配置的,咱们先点右下角添加一个Node.js: Launch配置,先放在这。而后看下package.json文件的scripts字段下的命令,在背景里面也说了在install完成后会执行build脚本,咱们能够看看build中有一段rollup -c命令,这个命令就是典型的rollup打包命令,也就是说它以上一版本已经打包后的自已为依赖把本身的源码进行了打包,那咱们就去看下rollup.config.js文件(-c 是指根据根目录下的rollup.config.js文件进行配置打包)。

打开rollup.config.js文件,主要是这三项配置:

/* Rollup core node builds */
{
    input: 'src/node-entry.ts',
    ...,
    output: [
        { file: 'dist/rollup.js', format: 'cjs', sourcemap: true, banner },
        { file: 'dist/rollup.es.js', format: 'esm', banner }
    ]
},
/* Rollup CLI */
{
    input: 'bin/src/index.ts',
    ...,
    output: {
        file: 'bin/rollup',
        format: 'cjs',
        banner: '#!/usr/bin/env node',
        paths: {
            rollup: '../dist/rollup.js'
        }
    }
}
/* Rollup core browser builds */
{
    input: 'src/browser-entry.ts',
    ...,
    output: [
        { file: 'dist/rollup.browser.js', format: 'umd', name: 'rollup', banner },
        { file: 'dist/rollup.browser.es.js', format: 'esm', banner }
    ]
}
复制代码

这是根据input文件打包到output的输出路径,能够看看package.json里面的files字段就对应与这里的output的输出路径,也就是最终发布到npm包里的全部代码。咱们先从Rollup CLI打包过程看起,它的input输入文件是 bin/src/index.ts,这里的入口文件应该就是rollup -c对应的执行源文件了,咱们先在const command = minimist(...)这行打个断点。

image

既然bin/src/index.ts是最开始的rollup -c开始的执行源文件,那咱们将此添加至刚刚在launch.json新添加的配置里

{
    "type": "node",
    "request": "launch",
    "name": "Launch Program",
    "program": "${workspaceFolder}/bin/src/index.ts",  // 更换这里的路径
    "args": ["-c"]  // 传给program的参数
}
复制代码

注意咱们加➕了个args字段,这样就模拟了rollup -c命令,这时候如果按F5启动debug,会报个错 没法启动程序.../src/index.ts,提示设置"outFiles"属性

就是说咱们是以ts文件为入口的文件,VS Code须要编译后的js文件对源ts文件的映射,也就是须要sourcemap文件的路径,这样才能对应与源码的位置,但是咱们看到只有dist目录下的rollup.js文件有对应的sourcemap文件,这从上面的三项配置就能够看到只有Rollup core node builds下的output下的file: 'dist/rollup.js',设置了sourcemap: true,那咱们给Rollup CLI也设置一下:

/* Rollup CLI */
{
    input: 'bin/src/index.ts',
    ...,
    output: {
        file: 'bin/rollup',
        format: 'cjs',
        banner: '#!/usr/bin/env node',
        paths: {
            rollup: '../dist/rollup.js'
        },
        sourcemap: true    // 设置sourcemap
    }
}
复制代码

这样给launch.json也要设置对应的 outFiles 属性outFiles: ["${workspaceFolder}/bin/*"], 这样就能找到源文件的映射了。

咱们改了rollup.config.js文件,那就意味着须要从新 run build 一下,在命令行执行npm run build,能够看到bin目录下有了新的rollup.map文件

而后F5启动debug,你会发现程序停在最开始打的command断点那个地方了,这样你就能够寻着debug的脚步 👣 探寻源码之旅了,大功告成!

彩蛋 🥚🥚


上面的源码调试虽然成功了,但是它调试的是原有的npm run build命令,也就是仓库本身的一些rollup配置,我想调试本身写的一些rollup配置该咋办勒。。。?

其实前面的背景已经提到了这个问题,在clone的rollup目录下执行npm link,而后你在你本身写的rollup配置目录里执行npm link rollup,这样你在node_modules(先安装好rollup依赖的其余依赖,能够先npm install rollup,而后删除node_modules里的rollup仓库,link你clone的rollup)里就能够看到整个rollup的连接仓库,在连接仓库里打断点,而后在按上面的步骤在你的例子项目里配置launch.json,这样启动你的debug,你会发现打的断点没用...😓,程序没有暂停过,这也是由于node_modules里的是symlinks,要使断点起做用的话,得给launch.json加上运行参数:

{
    "runtimeArgs": [
        "--preserve-symlinks",
        // "--preserve-symlinks-main"
    ]
}
复制代码

若是你的program指向的也是个连接文件就也要加上"--preserve-symlinks-main"参数,这样就能正常调试你本身写的配置了。(须要Node.js 10+)


在你调试的过程当中进行单步调试的时候时常会进入一些node_modules里不想看的库,或者会进入到node的内置模块里,这些其实有时候不必看,咱们就能够跳过这些文件,例如:

{
    "skipFiles": [
        "${workspaceFolder}/node_modules/**/*.js",
        "${workspaceFolder}/lib/**/*.js",
        "<node_internals>/**/*.js"   // node内部核心模块 设置的话在正常调试的时候可能有些问题。。。
    ]
}
复制代码

在前面调试源码一节中咱们修改了rollup源码的配置文件并从新手动 run build 了一下,其实咱们能够直接在launch.json配置文件中配置一个"preLaunchTask": "rollup",它表示在启动debug调试以前运行一个任务,能够在.vscode文件夹下新建一个task.json,针对源码调试那节咱们能够写以下配置:

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "rollup",
            "type": "shell",
            "command": "npm run build"
        }
    ]
}
复制代码

而后在launch.json中配置"preLaunchTask": "rollup",这样,咱们就不用在debug以前手动去执行build,直接F5启动debug就行。相应的还有postDebugTask钩子在debug结束后执行一些任务,关于任务的具体设置信息能够看这里


end

相关文章
相关标签/搜索