Skip to content

基础

代码在平台运行时会通过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重新映射(未成功实现)

线上问题如何调试

目前有几种方案解决

  1. 线上环境使用noresources构建sourcemap,通过vscode的detools连接到浏览器实现调试

  2. 把本地的sourcemap文件添加到filesystem,然后对出错的代码文件右键添加sourcemap,选择filesystem中对应sourcemap文件。这种方式不要设置hidden-source-map

  3. 打包后服务器手动删除所有sourcemap文件,本地保留一份。这样请求map时总是会404, 然后把本地的sourcemap文件添加到filesystem

  4. 生产环境正常打包sourcemap,但是在请求.map文件时无权限人员加载404,有权限人员能请求,这样既可以在vscode调试,也能在浏览器调试。不过也有问题,每个bundle中会存在sourceUrl,会增大一点包的体积

  5. 在打包时对每个chunk和map生成一个映射表(hidden方案),在请求chunk时,通过代理对每个chunk后面添加sourcemap语句,sourcemap指向对应的服务(本地或者远端,或者filesystem),这种方式可以在vscode和浏览器。

  6. 每次打包时总是生成两份dist,一份配置(hidden方案),另一份正常配置sourcemap(输出目录配置成线上域地址),通过overrides将线上的请求代理到本地,这样请求的bundle文件就是本地的带有sourcemap的文件,filesystem添加带有sourcemap的文件夹到调试环境中。这种方式不能在vscode中实现调试

  7. 针对客户浏览器出现的问题,搜集客户代码报错的行和列,通过sourcemap文件反向查找出错误地址,如果sourcemap配置了sourceContent,还可以直接定位具体的文件。

目前最优的方案还是针对不同的用户,展示不同bundle文件。

pathMapping 和 sourceMapPathOverrides的区别

前者是对network中是在的请求资源的映射(会自动忽略查询参数),后者是对sourcemap生成的源文件的映射(sourcemap生成文件在请求中是不存在的)。

filesystem 和 overrides区别

浏览器请求时总是先对overrides进行查找,然后是服务端,再对filesystem查找。