聊聊 SourceMap 的原理

本文详细解释了Sourcemap的工作原理,包括其作用、文件结构、浏览器如何关联源代码和Sourcemap,以及对性能的影响。重点介绍了Sourcemap在压缩和调试中的应用,以及Webpack中Sourcemap的生成和浏览器识别机制。
摘要由CSDN通过智能技术生成

前言

Source map 想必大家都不陌生。线上的代码多是压缩后的,如果线上有报错却只能调试那个代码多半是个噩梦。因此我们需要有一个桥梁帮助我们搭建起源代码及压缩后代码的联系,source map 就是起了这个作用。

Source Map 是一种 .map 结尾的文件类型,主要的作用是记录和源码有关的位置信息。
JavaScript 从最开始的简单变得越来越复杂,大部分源码都要经过转换才能放到生产环境。一般情况下在 压缩文件合并语言转义 方面可以看到实际的运行代码不同于源码,这时候如果要调试就会毫无头绪,Source Map 就是来解决这个问题的。

以下是 MDN 对于 source map 的解释:

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

但是不知道大家有没有对 source map 的原理产生过疑问?

这里先列出了四个疑问,不知道各位是不是也存在过这样的问题:

  • source map 那么大,不会影响性能吗?
  • 那么多打包器及浏览器,是存在什么标准才让它们通用吗?
  • 浏览器是怎么知道哪个文件对应哪个 source map 的?
  • source map 是如何一一对应到代码的?

接下来的内容会逐步解答这四问。

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

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

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

但是我们不会在 Network 里看到 source map 文件的加载,这只是因为浏览器隐藏了而已,如果大家使用抓包工具的话就能发现在打开 dev tools 的时候开始下载 source map 了。

source map 存在标准嘛?

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

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

通过上面的库生成出来的 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) 就由 console 、log 及 a 三部分组成,所以存在两个逗号。
  • 分号,代表换行

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

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

比如说 A 代表了数字 0,C 代表了数字 2 等等,有兴趣的读者可以通过该网站了解映射关系。

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

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

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

这时读者可能有个疑惑,为啥没有压缩代码的第几行表示?

这是因为压缩后的代码就一行,所以只需要表示第几列就行了。

除了每个分号中的第一串英文是用来表示代码的第几行第几列的绝对位置之外,后面的都是相对于之前的位置来做加减法的。

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

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

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

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

因为 source map 是由 AST 产出的,所以我们能用上 AST 中的这个数据。

source map 的应用

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

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值