sourcemap学习笔记

sourcemap产生的背景

  前端代码开发已经实现了模块化,生产环境中的前端代码(js和css文件)是经过前端构建工具编译压缩后的bundle。这样会存在一个问题:当线上前端代码出现bug时,我们无法很方便地调试线上代码。sourcemap就是为了解决这种问题而被设计出来的。sourcemap在编译压缩后的bundle与源代码之间建立了一个映射关系,通过sourcemap可以很快地找到bundle中的代码在源代码中对应的位置。目前主流的浏览器已经支持sourcemap,通过sourcemap,我们可以像在开发环境中调试代码一样调试生产环境的代码。

sourcemap格式

  sourcemap是一个JSON对象,典型的数据结构如下:

1
2
3
4
5
6
7
8
9
{
"version" : 3,
"file": "bundle.js",
"sourceRoot": "",
"sources": ["foo.js", "bar.js"],
"sourcesContent": [null, null],
"names": ["src", "maps", "are", "fun"],
"mappings": "A,AAAB;;ABCDE;"
}

  sourcemap的属性介绍如下:

  • version: sourcemap文件的版本号,取值为正整数。
  • file: sourcemap文件对应的bundle文件参数。
  • sourceRoot: sourcemap文件对应的源代码所在根目录。
  • sources: 源文件列表。
  • sourcesContent: 源文件内容列表,顺序与sources元素顺序对应。
  • names: 源代码中所有变量名和属性名列表。
  • mappings: 存储映射关系。

sourcemap如何存储映射关系

  sourcemap文件中的mappings属性值是将位置映射关系采用Base 64 VLQ编码后生成的字符串。mappings属性值包含的信息可以按照下面的方式进行拆分。
  (1)生成的bundle文件中的每一行依次对应于mappings属性值中的一个组,每个组之间使用字符”;”分隔。上一节sourcemap示例中mappings属性值包含三个组”A,AAAB”、”空”和”ABCDE”,分别对应于bundle文件的第1、2、3行;
  (2)每个组可以使用字符”,”分隔为多个段,如(1)中的第1组分为两个段,对应的字符序列分别为”A”和”AAAB”;
  (3)每个段可以看做是由5个可变长度字段组成。段与可变长度字段的关系可由图1表示。

段与可变长度字段的关系

图 1 段与可变长度字段的关系

  字段取值是可变长度的字符串的原因在于字段值是经过Base 64 VLQ编码后生成的字符串并且VLQ是变长编码。
  段中每个可变长度字段的包含的信息如下:
  (1)第一个字段表示的是段对应于bundle文件的第几列(从第0列开始计算);
  (2)第二个字段表示的是段对应的源文件在sources属性对应的源文件列表中的index;
  (3)第三个字段表示的是段对应于源文件的第几行(从第0行开始计算);
  (4)第四个字段表示的是段对应于源文件的第几列(从第0列开始计算);
  (5)第五个字段表示的是段对应的变量名或属性在names属性对应的变量名或属性列表中的index。

Base 64 VLQ编码

  VLQ编码是一种采用任意长度的二进制位来表示任意大小的整数的通用编码方式。Base 64编码是将二进制序列分成多个由6个bit组成的数据块(不足补0)并将6bit数据映射成常见字符的编码方式。Base 64 VLQ编码结合了VLQ编码和Base 64编码的特点,它的编码特点如下:
  (1)对任意整数编码后生成的结果是一个由Base 64字母表中的字符组成的任意长度的字符串。
  (2)每一个字符对应6bit的二进制位。最高位取1时,表示该6bit后面的6bit和该6bit表示的是同一个整数,最高位取0时,表示整数的编码到该6bit结束。起始6bit的最低位起到标志整数符号的作用,0表示正数,1表示负数,其余的bit组的最低位当做数据位。

sourcemap mappings示例

  假设某一个sourcemap的内容如下:

1
2
3
4
5
6
7
8
{
"version" : 3,
"file": "index.min.js",
"sourceRoot": "",
"sources": ["page1.js", "page2.js"],
"names": ["a", "b", "c", "d"],
"mappings": "ABCDE;"
}

  上述示例中的mappings解读如下:
  (1)index.min.js只有一行,只对应于mappings中的一个组。
  (2)组只包含一个段,段中可变长度字段取值经过Base 64 VLQ编码后的结果为”ACCaE”。
  (3)将”ABCDE”逐个进行Base 64解码,可以得到如下二进制序列:000000 000010 000010 011010 000100。
  (4)上述解码后得到的5个6bit二进制序列的最高位都为0,表示每一个整数只对应一个6bit单元。每个6bit单元的最低位都是0,表示每一个整数都是正数。上述二进制序列解码后得到的数字序列为0 1 1 13 2。
  (5)根据段中可变字段的含义,mappings展示出的信息如下:index.min.js的第1行第0列对应于page2.js文件的第1行第13列,这个位置对应的变量名为c。

如何关联bundle文件和sourcemap文件

  有两种方法可以关联bundle文件和sourcemap文件:
  (1)在bundle文件的http响应头部添加如下字段:

1
SourceMap: <url>

  (2)在bundle文件内部添加一行注释:

1
2
3
4
5
对于js文件而言添加如下内容:
//# sourceMappingURL=<url>
对于css文件而言添加如下内容:
/*# sourceMappingURL=<url> */

参考文献

  1. https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US&pli=1&pli=1#
  2. https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/
  3. http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html
  4. https://en.wikipedia.org/wiki/Variable-length_code
  5. https://en.wikipedia.org/wiki/Variable-length_quantity
  6. http://blog.allenm.me/2012/12/base64-vlq-encoding/