Appearance
基础
代码在平台运行时会通过backend对外通过调式协议暴露出运行时各种状态,各种调试工具通过解析调试协议实现UI界面(frontend)。chrome使用的是CDP(chrome devtools protocol)。
谷歌浏览器中有些devtool插件,比如vue devtools 和 react devtool。浏览器插件分为两个部分,一个是可以注入到当前页面的content script,一个是常驻的background部分。此外如果是扩展的devtool部分,还可以有一个devtool page部分。devtool插件通过content script向当前页面注入backend.js(负责收集运行时数据),页面的backend.js 通过background通道暴露调试协议,devtool page解析调试协议形成页面。
vscode也可以调试chrome和nodejs中运行时代码,同理vscode也实现了一个frontend,只不过vscode在解析协议上还做了一层adapter,因为vscode不止可以调试js代码。
chrome devtool protocol
chrome通过ws暴露协议,vscode中配置文件如下,主要有两种方式启动chrome调试,request: "launch" 表示启动一个新的chrome示例,默认配置好ws端口。 request: "attach"表示关联到现有的浏览器示例,这个需要在启动浏览器时指定参数(--remote-debugging-port=9222 --user-data-dir=你自己创建的某个目录)
需要注意的是如果我们手动启动浏览器需要指定--user-data-dir,这个目录保存了当前浏览器的所有配置缓存插件等,而且一个浏览器实例只能同时使用一个数据目录。所以我们在使用launch模式时需要注意,useDataDir的指定,false 表示使用默认(如果已经开了其他实例,启动会失败),true使用临时目录(默认值)或者指定一个目录
通过下载金丝雀版本的chrome可以解决这个问题
json
// .vscode/launch.json
{
"configurations": [
{
"name": "Launch Chrome",
"request": "launch", // attach
"type": "chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}", // 相当于指定pathMapping: {"/":"${workspaceFolder}/"}
"port": 9222, // 如果type: attach需要指定
"runtimeExecutable": "canary", // type修改成 pwd-chrome
"useDatadir": false, // 使用默认用户目录
// "type": "pwd-chrome"
"runtimeArgs": "--auto-open-devtools-for-tabs", // 额外的启动参数 --auto-open-devtools-for-tabs 自动打开控制台 --incognito 匿名启动
// 某些情况下sourcemap解析的文件地址和本地目录结构不一致,需要重新修正
"sourceMapPathOverrides": { // bunld.js => source => localfile
"webpack://?:*/*": "${workspaceFolder}/*" // ?:* 表示匹配不映射, * 表示匹配并映射。 该规则表示
},
// 针对静态文件重新调整
"pathMapping": {
"static/js/": "src/js/"
}
},
]
}sourcemap
sourcemap是对源码的描述,大部分dev tool都实现了sourcemap的解析,格式如下
js
{
version : 3, // 版本
file: "out.js",
sourceRoot : "",
sources: ["foo.js", "bar.js"], // 多个源文件
names: ["a", "b"],
mappings: "AAgBC,SAAQ,CAAEA;AAAEA", // 最重要的描述文件
sourcesContent: ['const a = 1; console.log(a)', 'const b = 2; console.log(b)'] // 原始代码
}存在一个问题,webpack中开发环境可能会使用eval函数模拟require,eval函数中无法针对字符串实现断点。如果在eval中添加sourceUrl=xxx,会直接添加eval函数的内容到根目录下的xxx,可以实现对eval执行断点。 同时,sourceMap可以递归,eval映射出的文件还可以再次指定sourceMapUrl映射到其他目录。
针对线上环境,打包时生成sourceMap文件,但是放在本地,在浏览器调试时使用filesystem将对线上的sourcemap映射到本地,实现本地调试线上环境(待实验),或者重定向到本地代码
sourceMap配置参考这个正则表达式 /^(eval-|inline-|hidden-)?(noresources-)?(cheap-(module-)?)?source-map?$/ eval:在eval函数中添加sourceUrl在调试环境中添加文件。 inline:在bundle.js 中sourceMapUrl直接跟sourceMap内容。 hidden: 表示不在bundle在关联sourceMap,但是要生成。 noresource: 不在sourcemap中包含原始代码 cheap: sourcemap只定位到行错误,不到列错误 module: sourcemap生成时会包含每个loader处理的sourceMap(关键配置)
vue中sourceMap存在hash问题
使用eval会使映射添加hash后缀,最好的办法是不使用hash, 修改devtool: 'source-map',或者使用sourceMapPathOverrides重新映射(未成功实现)
线上问题如何调试
目前有几种方案解决
线上环境使用noresources构建sourcemap,通过vscode的detools连接到浏览器实现调试
把本地的sourcemap文件添加到filesystem,然后对出错的代码文件右键添加sourcemap,选择filesystem中对应sourcemap文件。这种方式不要设置hidden-source-map
打包后服务器手动删除所有sourcemap文件,本地保留一份。这样请求map时总是会404, 然后把本地的sourcemap文件添加到filesystem
生产环境正常打包sourcemap,但是在请求.map文件时无权限人员加载404,有权限人员能请求,这样既可以在vscode调试,也能在浏览器调试。不过也有问题,每个bundle中会存在sourceUrl,会增大一点包的体积
在打包时对每个chunk和map生成一个映射表(hidden方案),在请求chunk时,通过代理对每个chunk后面添加sourcemap语句,sourcemap指向对应的服务(本地或者远端,或者filesystem),这种方式可以在vscode和浏览器。
每次打包时总是生成两份dist,一份配置(hidden方案),另一份正常配置sourcemap(输出目录配置成线上域地址),通过overrides将线上的请求代理到本地,这样请求的bundle文件就是本地的带有sourcemap的文件,filesystem添加带有sourcemap的文件夹到调试环境中。这种方式不能在vscode中实现调试
针对客户浏览器出现的问题,搜集客户代码报错的行和列,通过sourcemap文件反向查找出错误地址,如果sourcemap配置了sourceContent,还可以直接定位具体的文件。
目前最优的方案还是针对不同的用户,展示不同bundle文件。
pathMapping 和 sourceMapPathOverrides的区别
前者是对network中是在的请求资源的映射(会自动忽略查询参数),后者是对sourcemap生成的源文件的映射(sourcemap生成文件在请求中是不存在的)。
filesystem 和 overrides区别
浏览器请求时总是先对overrides进行查找,然后是服务端,再对filesystem查找。
