介绍了在线上使用 SourceMap 进行调试的方法和常见问题。常见的使用姿势是通过浏览器的开发者工具进行本地调试,而在线上使用 SourceMap 则需要手动添加 SourceMap 地址。针对线上无法自动加载 SourceMap 的问题,可以尝试使用浏览器插件、Charles 进行转发或者私有静态服务托管 SourceMap。
正文从这开始~~
1. 前言
1.1 常见调试手段
平日开发过程中,大家是如何调试线上问题的呢?采样后的众生相如下:
等一下,还有没有人在用 SourceMap 调试啊?通过 SourceMap,我们可以在浏览器内,直接看到源码,而不是编译、压缩、混淆后的部署产物。
1.2 SourceMap 的常规用法
为了保证后续内容的理解,这里简单阐述下常见的使用姿势。
1.2.1 本地调试
本地启动项目后,可以在 Source 标签页看到三块面板:
通过这些面板,我们可以直接找到希望调试的源码文件,打断点单步调试,查看上下文信息等。如果你是纯粹的 debugger 选手或 console.log 选手,只好表示 understand & respect。
【第3020期】解读SourceMap
1.2.2 线上排查
线上使用 SourceMap 则困难不少。通常我们的使用方式是:
1\ 发现控制台中报异常,根据 Chrome 提供的堆栈定位到报错文件
2\ 在代码编辑面板中,右键 - Reveal in sidebar,在文件树中定位到部署文件
3\ 右键部署文件,点击 Copy link address,在控制台中查看资源的地址
4\ 确定构建产物对应的 SourceMap 资源地址(以下为内部发布平台示意)
5\ 在代码编辑面板,右键 - Add source map,添加 SourceMap 地址
6\ 从已经映射到源码的堆栈信息再次进入,即可看到报错在源码中的位置
从上面的说明我们可以感受到:
-
Chrome DevTools 很强大
-
手动添加 SourceMap 很麻烦
2. 问题探索
我们需要先将以下问题研究清楚:
-
浏览器是如何识别并加载 SourceMap 的?
-
为什么本地可以自动加载而线上不可以?
-
如何感知浏览器确实加载了 SourceMap?
2.1 浏览器是如何识别并加载 SourceMap 的?
如果我们让构建工具开启了 SourceMap,例如 Webpack 的 devtools,源码经过构建过程(编译、混淆、压缩等)生成的部署代码会在底部增加一行注释,如下图所示:
sourceMappingURL 告诉我们,当前资源文件 ConsoleSiteList.57ca29c2.chunk.js 对应的 SourceMap 文件的路径是 ConsoleSiteList.57ca29c2.chunk.js.map。这是相对路径的写法,也就是说,在本地启用的服务中,构建后的 chunk 和对应的 SourceMap 文件的地址分别为:
这样一来,浏览器就可以根据 sourceMappingURL 去自动加载 SourceMap,而不用苦哈哈的手动添加。
2.2 为什么本地可以自动加载而线上不可以?
一般来讲,线上产物中会把 SourceMap 去除,除了为了加速构建过程,更重要的是避免有开发经验的人直接在浏览器中「阅读源码」。除了直接去除,企业内也常常利用内部的存储能力,将构建好的 SourceMap 资源转存到其他地方,这样一来,构建产物中的 sourceMappingURL 将无法正确指向 SourceMap 的资源地址,从而实现与直接去除接近的效果。
这样一来,在生产环境下 Chrome 根据 sourceMappingURL 相对路径的写法就只能寻址到不存在的 404,浏览器会加载不到需要的资源。
2.3 如何感知浏览器确实加载了 SourceMap?
我们可以打开 DevTools Network 标签页,使用过滤器过滤 .map 文件,我们发现什么都没有:
难道是 Chrome 加载 SourceMap 不需要通过网络请求?这显然不会,如果你有兴趣可以查看 issue,这其实是有意为之。不过我们仍然有其他手段看到
.map
文件的请求,打开 Charles 抓一把(涉及证书安装等操作本文不再赘述),就可以看到一堆迷途的请求:
你可能会困惑,既然 Chrome 实际发出了请求,Chrome 本身没提供可以查看的入口吗?打开 Developer Resources 标签页过滤出 SourceMap 相关的请求即可(也可以使用 net-internals):
2.4 总结
浏览器根据构建产物中的注释 sourceMappingURL 尝试加载 SourceMap 文件,但线上构建时往往会将 SourceMap 删除(或上传到了其他地方),因此我们无法直接在线上使用 SourceMap。
3. 解决方案
我们本质论一把:在线上加载到正确的 SourceMap 资源地址。
3.1 尝试一:基于浏览器插件 Redirect
我们可以尝试使用 XSwitch 等工具重定向一把。假设我们的 SourceMap 资源转存到了 http://sourcemap.def.alibaba-inc.com 下,规则可以书写如下:
{
"proxy": [
[
"https://g.alicdn.com/(.*).map",
"https://sourcemap.def.alibaba-inc.com/sourcemap/$1.map",
]
],
}
很快我们就会发现没有任何卵用。为了验证,笔者自行实现了基于 Manifest V3 的浏览器插件(使用 chrome.declarativeNetRequestAPI)和基于 Manifest V2 的浏览器插件(使用 chrome.webRequestAPI),也同样没有卵用。无论是 Charles 还是 Developer Resources 都会告诉你 SourceMap 的加载是 404,不过如果我们转发 Network 标签页中可见的请求是可以生效的。
补充:关于为什么如此,目前猜测是因为 Chrome Extension 无法感知浏览器级别的活动(可感知方式)。