一 Source Map是什么?

Source Map,顾名思义,是保存源代码映射关系的文件,相信用过webpack的开发者对它应该不会陌生。在项目开发完进行打包后,在打包的文件夹里通常除了js,css,图片字体等资源文件外,大家一定还见过xxx.js.map的文件。这种带map后缀的文件就是Source Map文件——它保存了源代码和转换之后代码(通常经过压缩混淆和其他转换)的关系。 下图展示了部分打包之后生成的Source Map文件:

下面是一个典型的Source Map文件的格式:

{

"version": 3,

"sources": [

"log.js",

"main.js"

],

"names": [

"sayHello",

"name",

"length",

"substr",

"console",

"log"

],

"mappings": "AAAA,SAASA,SAAUC,MACjB,GAAIA,KAAKC,OAAS,EAAG,CACnBD,KAAOA,KAAKE,OAAO,EAAG,GAAK,MAE7BC,QAAQC,IAAI,QAASJ,MCJvBD,SAAS"

}

复制代码

二 为什么使用Source Map?

明白了什么是Source Map之后,大家肯定有个疑问,我们为什么需要Source Map? 由于现代前端项目的发展,前端代码变得越来越庞大和复杂。大部分源码都需要经过转换,才能投入到生产环境中使用。 常见的转换过程包括但不限于:

  • 压缩混淆(UglifyJS)

  • 编译(TypeScript, CoffeeScript)

  • 转译(Babel)

  • 合并多个文件,减少带宽请求。

经过上述转换过程的代码,往往都会变得面目全非,就像下面这样:

这样虽然对带宽很友好,但是调试起来就不是那么轻松了。我们在代码出错的时候,肯定最希望能定位其在源码中的位置。 比如下面这两个错误提示: 对于大多数开发者来说,希望看到的应该是第二种提示方式,而这就是Source Map能够输出的能力。

三 Source Map是怎么实现映射的?

在探索这个问题之前,可以先想想真实世界里对这种语言转换是怎么做的?

I AM CHRIS ——> Map ——> CHRIS I AM

复制代码

现在我们要从CHRIS I AM还原到I AM CHRIS,Map里应该存储哪些信息呢?

3.1 最简单粗暴的方法

将输出文件中每个字符位置对应在输入文件名中的原位置保存起来,并一一进行映射。上面的这个映射关系应该得到下面的表格:

字符

输出位置

在输入中的位置

输入的文件名

c

行1,列1

行1,列6

输入文件1.txt

h

行1,列2

行1,列7

输入文件1.txt

r

行1,列3

行1,列8

输入文件1.txt

i

行1,列4

行1,列9

输入文件1.txt

s

行1,列5

行1,列10

输入文件1.txt

i

行1,列7

行1,列1

输入文件1.txt

a

行1,列9

行1,列3

输入文件1.txt

m

行1,列10

行1,列4

输入文件1.txt

备注: 由于输入信息可能来自多个文件,所以这里也同时记录输入文件的信息。

将上面表格整理成映射表的话,看起来就像这样(使用"|"符号分割字符)

mappings: "1|1|输入文件1.txt|1|6,1|2输入文件1.txt|1|7,1|3|输入文件1.txt|1|8,1|4|输入文件1.txt|1|9,1|5|输入文件1.txt|1|10,1|7|输入文件1.txt|1|1,1|9|输入文件1.txt|1|3,1|10|输入文件1.txt|1|4"(长度:144)

复制代码

这种方法确实能将处理后的内容还原成处理前的内容,但是随着内容的增加,转换规则的复杂,这个编码表的记录将飞速增长。目前仅仅10个字符,映射表的长度已经达到了144个字符。如何进一步优化这个映射表呢?

备注: mappings: "输出文件行位置|输出文件列位置|输入文件名|输入文件行号|输入文件列号,....."

3.2 优化手段1:不要输出文件中的行号

在经历过压缩和混淆之后,代码基本上不会有多少行(特别是JS,通常只有1到2行)。这样的话,就可以在上节的基础上移除输出位置的行数,使用";"号来标识新行。 那么映射信息就变成了下面这样

mappings: "1|输入文件1.txt|1|6,2|输入文件1.txt|1|7,3|输入文件1.txt|1|8,4|输入文件1.txt|1|9,5|输入文件1.txt|1|10,7|输入文件1.txt|1|1,9|输入文件1.txt|1|3,10|输入文件1.txt|1|4; 如果有第二行的话"(长度:129)

复制代码

备注: mappings: "输出文件列位置|输入文件名|输入文件行号|输入文件列号,....."

3.3 优化手段2:提取输入文件名

由于可能存在多个输入文件,且描述输入文件的信息比较长,所以可以将输入文件的信息存储到一个数组里,记录文件信息时,只记录它在数组里的索引值就好了。 经过这步操作后,映射信息如下所示:

sources: ['输入文件1.txt'],

mappings: "1|0|1|6,2|0|1|7,3|0|1|8,4|0|1|9,5|0|1|10,7|0|1|1,9|0|1|3,10|0|1|4;" (长度:65)

复制代码

经过转换后mappings字符数从129下降到了65。

备注: mappings: "输出文件列位置|输入文件名索引|输入文件行号|输入文件列号,....."

3.4 优化手段3: 可符号化字符的提取

经过上一步的优化,mappings字符数有了很大的下降,可见提取信息是一个很有用的简化手段,那么还有什么信息是能够提取的么? 当然。已输出文件中的Chris字符为例,当我们找到了它的首字符C在源文件中的位置(行1,列6)时,就不需要再去找剩下的hris的位置了,因为Chris可以作为一个整体来看待。想想源码里的变量名,函数名,都是作为一个整体存在的。 现在可以把作为整体的字符提取并存储到一个数组里,然后和文件名一样,在mapping里只记录它们的索引值。这样就避免了每一个字符都要记的窘境,大大缩减mappings的长度。

添加一个包含所有可符号化字符的数组:

names: ['I', 'am', 'Chris']

复制代码

那么之前Chris的映射就从

1|0|1|6,2|0|1|7,3|0|1|8,4|0|1|9,5|0|1|10

复制代码

变成了

1|0|1|6|2

复制代码

最终的映射信息变成了:

sources: ['输入文件1.txt'],

names: ['I', 'am', 'Chris'],

mappings: "1|0|1|6|2,7|0|1|1|0,9|0|1|3|1" (长度: 29)

复制代码

备注: 1. “I am Chris"中的"I"抽出来放在数组里,其实意义不大,因为它本身也就只有一个字符。但是为了演示方便,所以拆出来放在数组里。 2. mappings: "输出文件列位置|输入文件名索引|输入文件行号|输入文件列号|字符索引,....."

3.5 优化手段4: 记录相对位置

前面记录位置信息(主要是列)时,记录的都是绝对位置信息,设想一下,当文件内容比较多时,这些数字可能会变的很大,这个问题怎么解决呢? 可以通过只记录相对位置来解决这个问题(除了第一个字符)。 来看一下具体怎么实现的,以之前的mappings编码为例:

mappings: "1(输出列的绝对位置)|0|1|6(输入列的绝对位置)|2,7(输出列的绝对位置)|0|1|1(输入列的绝对位置)|0,9(输出列的绝对位置)|0|1|3(输入列的绝对位置)|1"

复制代码

转换成只记录相对位置

mappings: "1(输出列的绝对位置)|0|1|6(输入列的绝对位置)|2,6(输出列的相对位置)|0|1|-3(输入列的相对位置)|0,2(输出列的相对位置)|0|1|-2(输入列的绝对位置)|1"

复制代码

从上面的例子可能看不太出这个方法的好处,但是当文件慢慢大起来,使用相对位置可以节省很多字符长度,特别是对于记录输出文件列信息的字符来说。

3.6 优化手段5: VLQ编码

经过上面几步操作之后,现在最应该优化的地方应该就是用来分割数字的"|"号了。 这个优化应该怎么实现呢? 在回答之前,先来看这样一个问题——如果你想顺序的记录4组数字,最简单的就是用"|"号进行分割。

1|2|3|4

复制代码

如果每个数字只有1位的话,可以直接表示成

1234

复制代码

但是很多时候每个数字不止有1位,比如

12|3|456|7

复制代码

这个时候,就一定得用符号把各个数字分割开,像我们上面例子中一样。还有好的方法嘛? 通过VLQ编码的方式,你可以很好的处理这种情况,先来看看VLQ的定义:

3.6.1 VLQ定义

A variable-length quantity (VLQ) is a universal code that uses an arbitrary number of binary octets (eight-bit bytes) to represent an arbitrarily large integer. 翻译一下:VLQ是用任意个2进制字节组去表示一个任意数字的编码形式。

VLQ的编码形式很多,这篇文章中要说明的是下面这种:

  • 一个组包含6个二进制位。

  • 在每组中的第一位C用来标识其后面是否会跟着另一个VLQ字节组,值为0表示其是最后一个VLQ字节组,值为1表示后面还跟着另一个VLQ字节组。

  • 在第一组中,最后1位用来表示符号,值为0则表示正数,为1表示负数。其他组的最后一位都是表示数字。

  • 其他组都是表示数字。

这种编码方式也称为Base64 VLQ编码,因为每一个组对应一个Base64编码。

3.6.2 小例子说明VLQ

现在我们用这套VLQ编码规则对12|3|456|7进行编码,先将这些数字转换为二进制数。

12  ——> 1100

3   ——> 11

456 ——> 111001000

7   ——> 111

复制代码

  • 对12进行编码

12需要1位表示符号,1位表示是否延续,剩下的4位表示数字

B5(C)

B4

B3

B2

B1

B0

0

1

1

0

0

0

  • 对3进行编码

B5(C)

B4

B3

B2

B1

B0

0

0

0

1

1

0

  • 对456进行编码

从转换关系中能够看到,456对应的二进制已经超过了6位,用1组来表示肯定是不行的,这里需要用两组字节组来表示。先拆除最后4个数(1000)放入第一个字节组,剩下的放在跟随字节组中。

B5(C)

B4

B3

B2

B1

B0

B5(c)

B4

B3

B2

B1

B0

1

1

0

0

0

0

0

1

1

1

0

0

  • 对7进行编码

B5(C)

B4

B3

B2

B1

B0

0

0

1

1

1

0

最后得到下列VLQ编码:

011000 000110 110000 011100 001110

复制代码

通过Base64进行转换之后:

最终得到下列结果:

YGwcO

复制代码

3.6.3 转换之前的例子

通过上面这套VLQ的转换流程转换之前的例子,先来编码1|0|1|6|2. 转换成VLQ为:

1 ——> 1(二进制) ——> 000010(VLQ)

0 ——> 0(二进制) ——> 000000(VLQ)

1 ——> 1(二进制) ——> 000010(VLQ)

6 ——> 110(二进制) ——> 001100(VLQ)

2 ——> 10(二进制) ——> 000100(VLQ)

复制代码

合并后编码为

000010 000000 000010 001100 000100

复制代码

转换成Base64

BABME

复制代码

其他也是按这种方式编码,最后得到的mapping文件如下:

sources: ['输入文件1.txt'],

names: ['I', 'am', 'Chris'],

mappings: "BABME,OABBA,SABGB" (长度: 17)

复制代码

和第一节的mappings文件对比一样,是不是一样呢?

3.6.4 one more thing

在真实场景中,我们在mappings中经常可以看到不是5位的字符。大于5位好理解,可能表示的数字太大。比如123456789转换成Base64 VLQ编码就是qxmvrH。而少于5位的情况在于mappings的编码片段中可能不需要那么多信息就能进行映射,比如不需要Names属性,这样只通过4位信息也就能获取到映射关系了。一个编码片段可能会有以下几种长度:

  • 5 - 包含全部五个部分:输出文件中的列号,输入文件索引,输入文件中的行号,输入文件中的列号,符号索引

  • 4 - 输出文件中的列号,输入文件索引,输入文件中的行号,输入文件中的列号

  • 1 - 输出文件中的列号

通过上面的讲解,相信大家一定对Source Map是如何映射源码转换后代码之间的位置关系有所了解。在了解了Source Map原理之后,日后再去使用它肯定能够得心应手。

作者:淼淼真人

链接:https://juejin.cn/post/6844903869928079373

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

什么是 SourceMap?相关推荐

  1. Webpack中的sourcemap

    Webpack中sourcemap的配置 sourcemap是为了解决开发代码与实际运行代码不一致时帮助我们debug到原始开发代码的技术.尤其是如今前端开发中大部分的代码都经过编译,打包等工程化转换 ...

  2. sentry 命令_sentry(二)集成sourcemap

    上一篇介绍了sentry的基本用法,任意门:sentry(一)初探 今天我们再接着熟悉sentry,按照上一篇的教程我们可以在自己的项目里面安装sentry的sdk,而且写了一个异常的例子来触发异常, ...

  3. sentry使用webpack上传sourceMap源文件定位错误到更详细具体的代码片段

    配置文件 sentry上传sourMap的前提是先设置webpack的配置文件 1.在项目文件的根目录新建一个.sentryclirc文件 sentry会自动检测并使用.sentryclirc文件中的 ...

  4. Angular 项目里和 sourceMap 相关的设置

    生产环境里,sourceMap 为 false:source source-map 和 source-map-loader 是专门的包: tsconfig.json 里,sourceMap 为 tru ...

  5. [react] 在React中如果去除生产环境上的sourcemap?

    [react] 在React中如果去除生产环境上的sourcemap? GENERATE_SOURCEMAP=false react-scripts build If you use the Crea ...

  6. vue 解决: *!!vue-style-loader!css-loader?{“sourceMap“:true}!../../../../vue-loader

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 问题描述 *!!vue-style-loader!css-loader?{"sourceM ...

  7. sourcemap总结

    sourcemap在线上压缩文件调试中很重要,在此总结如下: 1. 开启sourcemap (1). 浏览器要开启source-map支持 (2). 压缩文件底部要有source-map的URL,压缩 ...

  8. 用webstorm自动编译less产出css和sourcemap

    css产出sourcemap有什么用呢,可能大家要问这个问题了. 请移步这里 https://developers.google.com/chrome-developer-tools/docs/css ...

  9. webpack Plugin常用 optimization splitChunks UglifyJsPlugin sourceMap

    很多东西容易忘记,做个简单笔记 1.webpack loader 加载顺序 从后到前 例子 ['style-loader', 'css-loader','sass-loader'] 先sass最后st ...

  10. bundle文件解压_通过sourcemap解压缩webpack 实战

    现在许多网站都使用webpack对网站打包,许多前端框架也默认配置好webpack.这会在渗透测试或挖洞过程中带来一些麻烦.这让我们极其痛苦.但是开发者忽视起潜在风险,在线上环境使用了开发环境的配置, ...

最新文章

  1. mongodb安装_MongoDB索引策略和索引类型
  2. 【FFmpeg】FFmpeg 相关术语简介 ( 容器 | 媒体流 | 数据帧 | 数据包 | 编解码器 | 复用 | 解复用 )
  3. pythonapi是什么意思_python api是什么
  4. 【SQL编程】Greenplum 与 MySQL 数据库获取周几函数及函数结果保持一致的方法
  5. djl和ljl_使用Spring Boot和DJL进行深度学习
  6. 两家大型网贷平台竟在借款人审核问题上“偷懒”?
  7. 3123称重显示控制器说明书_失重秤在自动化配料系统中的应用 - 工业自动化称重仪表...
  8. 传统socket的编程实现
  9. vue ---- 组件
  10. (三)pscc学习笔记
  11. 声波的时域和频域Python实现示例
  12. Mugeda(木疙瘩)H5案例课—快闪制作-岑远科-专题视频课程
  13. ncnn发布20220420版本,让Vulkan神经网络推理得更快
  14. Java中常见的5种WEB服务器介绍以及性能配置要点总结
  15. Windows安装MySql
  16. 论文笔记:Dynamic GCN: Context-enriched Topology Learning for Skeleton-based Action Recognition
  17. 简单介绍迪杰斯拉Dijkstra算法步骤
  18. portal服务器认证系统有哪些,portal服务器认证过程问题
  19. 用代码实现一场烟花盛宴,提前祝大家2022新春快乐
  20. java设计模式之模板

热门文章

  1. Catia幕墙BIM阵列优化培训视频教程
  2. 一文讲透图像分割经典网络:FCN、Unet、DeepLabV3+、Vnet、Unet++
  3. python循环构建多个类_Python高级特性:Python迭代、生成器、列表生成式
  4. 如何把设计问题转化为数学问题,方法论
  5. docker 部署es 集群 elasticsearch
  6. ECNU_OJ_1026
  7. 银行供应链金融业务的数字化转型方案探析 | 金融科技时代
  8. 一键批量修改零部件名称,这款工具你值得拥有!
  9. Linux下文本文件合并和去除重复
  10. 《FLUENT 14.0超级学习手册》——第2章 FLUENT软件介绍2.1 FLUENT软件特点简介