绝了,没想到一个 source map 竟然涉及到那么多知识盲区

Source map 想必你们都不陌生。线上的代码可能是压缩后的,若是线上有报错却只能调试那个代码多半是个噩梦。所以咱们须要有一个桥梁帮助咱们搭建起源代码及压缩后代码的联系,source map 就是起了这个做用。html

如下是 MDN 对于 source map 的解释:html5

调试原始源代码会比浏览器下载的转换后的代码更加容易。 source map 是从已转换的代码映射到原始源的文件,使浏览器可以重构原始源并在调试器中显示重建的原始源。webpack

可是不知道各位读者有没有对 source map 的原理产生过疑问?笔者列出了四个疑问,不知道各位是否是也存在过这样的问题:git

Source map 四问

接下来的内容会逐步为读者解答这四问。github

source map 文件是否影响网页性能

这个答案确定是不会影响,不然构建相关的优化就确定会涉及到对于 source map 的处理了,毕竟 source map 文件也不小。web

其实 source map 只有在打开 dev tools 的状况下才会开始下载,相信大部分用户都不会去打开这个面板,因此这也就不是问题了。json

这时可能会有读者想说:哎,可是我好像历来没有在 Network 里看到 source map 文件的加载呀?其实这只是浏览器隐藏了而已,若是你们使用抓包工具的话就能发如今打开 dev tools 的时候开始下载 source map 了。数组

source map 存在标准嘛?

source map 是存在一个标准的,为 Google 及 Mozilla 的工程师制定,文档地址。正是由于存在这份标准,各个打包器及浏览器才能生成及使用 source map,不然就乱套了。浏览器

各个打包器基本都基于该库来生成 source map,固然也存在一些魔改的方案,可是标准都是统一的。markdown

经过上面的库生成出来的 source map 格式大体以下,你们也能够对比各个打包器的产物,格式及内容大部分都是一致的:

{
  version: 3,
  file: "min.js",
  names: ["bar", "baz", "n"],
  sources: ["one.js", "two.js"],
  sourceRoot: "http://example.com/www/js/",
  mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
}
复制代码

接下来笔者介绍下重要字段的做用:

  • version:顾名思义,指代了版本号,目前 source map 标准的版本为 3,也就是说这份 source map 使用的是第三版标准产出的
  • file:编译后的文件名
  • names:一个优化用的字段,后续会在 mappings 中用到
  • sources:多个源文件名
  • mappings:这是最重要的内容,表示了源代码及编译后代码的关系,可是先略过这块,下文中会详细解释

另外大部分应用都是由 webpack 来打包的,可能有些读者会发现 webpack 的 source map 产出的字段于上面的略微有些不一致。

这是由于 webpack 魔改了一些东西,可是底下仍是基于这个库实现的,只是变更了一些不涉及核心的字段,具体代码

浏览器怎么知道源文件和 source map 的关系?

这里咱们以 webpack 作个实验,经过 webpack5 对于如下代码进行打包:

// index.js
const a = 1
console.log(a);
复制代码

当咱们开启 source map 选项之后,产物应该为两个文件,分别为 bundle.js 以及 bundle.js.map

查看 bundle.js 文件之后咱们会发现代码中存在这一一段注释:

console.log(1);
//# sourceMappingURL=bundle.js.map
复制代码

sourceMappingURL 就是标记了该文件的 source map 地址。

固然除此以外还有别的方式,经过查阅 MDN 文档 发现还能够经过 response header 的 SourceMap: <url> 字段来代表。

source map 是如何对应到源代码的?

这是 source map 最核心的功能,也是最涉及知识盲区的一块内容。

你们应该还记得上文中没介绍的 mapping 字段吧,接下来咱们就来详细了解这个字段的用处。

咱们仍是以刚才打包的文件为例,来看看产出的 source map 长啥样(去掉了可有可无的):

{
  sources:["webpack://webpack-source-demo/./src/index.js"],
  names: ['console', 'log'],
  mappings: 'AACAA,QAAQC,IADE',
}
复制代码

首先 mappings 的内容实际上是 Base64 VLQ 的编码表示。

内容由三部分组成,分别为:

  • 英文,表示源码及压缩代码的位置关联
  • 逗号,分隔一行代码中的内容。好比说 console.log(a) 就由 consoleloga 三部分组成,因此存在两个逗号。
  • 分号,表明换行

逗号和分号想必你们没啥疑问,可是对于这几个英文内容应该会很困惑。

其实这就是一种压缩数字内容的编码方式,毕竟源代码可能很庞大,用数字表示行数及列数的话 source map 文件将也会很庞大,所以选用 Base 64 来表明数字用以减小文件体积。

好比说 A 表明了数字 0,C 表明了数字 1 等等,有兴趣的读者能够经过该网站了解映射关系。

了解了这层编码的映射关系,咱们再来聊聊这一串串英文到底表明了什么。

其实这每串英文中的字母都表明了一个位置:

  1. 压缩代码的第几列
  2. 哪一个源代码文件,毕竟能够多个文件打包成一个,对应 sources 字段
  3. 源代码第几行
  4. 源代码第几列
  5. names 字段里的索引

这时读者可能有个疑惑,为啥没有压缩代码的第几行表示?这是由于压缩后的代码就一行,因此只须要表示第几列就好了。


更新:有读者询问 Base64 表达的数字是有上限的,若是须要表示的数字很大的话该怎么办。实际上除了每一个分号中的第一串英文是用来表示代码的第几行第几列的绝对位置以外,后面的都是相对于以前的位置来作加减法的。


了解完以上知识之后,咱们就来根据上文的内容解析下 AACAA 的具体含义吧,经过该网站咱们能够知道 AACAA 对应了 [0,0,1,0,0],这里须要注意的是数字都从 0 开始,笔者表述的时候会自动加一,毕竟代码第零行听起来怪怪的。

  1. 压缩代码的第一列
  2. 第一个源代码文件,也就是 index.js 文件了
  3. 源代码第二行了
  4. 源代码的第一列
  5. names 数组中的第一个索引,也就是 console

经过以上的解析,咱们就能知道 console 在源代码及压缩文件中的具体位置了。

可是为何 source map 会知道编译后的代码具体在什么位置呢?这里就要用到 AST 了。让咱们打开网站输入 console.log(a) 后观察右边的内容,你应该会发现如图所示的数据:

image-20210516214636867

由于 source map 是由 AST 产出的,因此咱们能用上 AST 中的这个数据。

source map 的应用

通常来讲 source map 的应用都是在监控系统中,开发者构建完应用后,经过插件将源代码及 source map 上传至平台中。一旦客户端上报错误后,咱们就能够经过该库来还原源代码的报错位置(具体 API 看文档便可),方便开发者快速定位线上问题。

最后

source map 是咱们平常中常常用到的东西,可是直到学习这块内容的时候才知道竟然涉及到了那么多的知识盲区。

你们若是有什么疑问欢迎在评论区交流。

相关文章
相关标签/搜索