source map原理分析

sourcemap文件就是一个记录了压缩后的代码和源代码的映射关系的一个文件,本文将分析其映射原理

source map文件结构

1
2
3
4
5
6
7
8
9
{
"version" : 3,
"file": "out.js",
"sourceRoot": "",
"sources": ["foo.js", "bar.js"],
"sourcesContent": [null, null],
"names": ["src", "maps", "are", "fun"],
"mappings": "A,AAAB;;ABCDE;"
}
  • version:表示现在是第三个版本
  • file:表示sourcemap对应的源代码文件名(可选)
  • sourceRoot:当有sources字段有多个值时,并且都是在同一个远程地址上时,就可以用sourceRoot,避免在sources内重复的写前缀(可选)
  • sources:mapping内用到的源文件
  • sourcesContent:当源代码文件不能被访问时,可以将源代码放在这个字段内,是null表示通过names即可映射,不需要源代码了(可选)
  • names:源代码中的所有符号
  • mappings:映射代码;通过”,” 分割成段,通过”;”分割行,每段可能有1、4、5个字符;

原理

简单来说,就是通过建立映射关系,把压缩后的代码中各个字符的位置信息和压缩前代码符号的位置关系建立映射,从而在浏览器控制台中可以把源代码展示出来,并且断点调试可以断到正确的地方。

VLQ编码原理

对于VLQ编码原理可以参考这篇文章

  • base64编码表
    base64

简单示例

源代码

1
2
/* 注释 */
var name = "abc";

压缩后的代码

1
2
var name="abc";
//# sourceMappingURL=a.js.map

对应的source-map

1
2
3
4
5
6
7
8
{
"version":3,
"sources":["a.js"],
"names":["name"],
"mappings":";AACA,IAAIA,KAAO",
"file":"a.js",
"sourcesContent":["/* 注释 */\nvar name = \"abc\";"]
}

压缩编码过程(位置映射关系)

  • 注意:记录位置时,使用相对位置信息,相对上一个字符的位置信息,这样可以避免数字过大,比如
  • var 是压缩后的0列,对应第0个源文件的1行0列,不需要映射字符,所以可以用0010表示映射关系,0010对其每个数字再、用二进制编码表示就是

    1
    0 0 0 0   0 0 0 0    0 0 0 1   0 0 0 0

    上面的二进制编码变成用VLQ编码就是

    1
    0 0 0 0 0 0  0 0 0 0 0 0   0 0 0 0 1 0   0 0 0 0 0 0

    对应的Base64码就是

    1
    AACA
  • name是在输出结果中的第4列(+4),对应第0(+0)个源文件的第1(+0)行,第4(+4)列,符合映射关系是第0(+0)个字符,所以得到的数字编码就是

    1
    2
    4 0 0 4 0
    40源码 +0行 +4列 第0个字符

    二进制->VLQ编码->Base64编码

    1
    2
    3
    4
    5
    0100 -> 001000 -> 8 -> I
    0000 -> 000000 -> 0 -> A
    0000 -> 000000 -> 0 -> A
    0100 -> 001000 -> 8 -> I
    0000 -> 000000 -> 0 -> A
  • 同理我们可以得到”abc”的位置映射二进制编码是第9列(+5),第0(+0)个源文件,第1行(+0)第11(+7)列,没有符号映射关系,所以得到的数字编码就是:

    1
    2
    5 0 0 7
    +50文件 +0行 + 7

    二进制->VLQ编码->Base64编码

    1
    2
    3
    4
    0101 -> 001010 -> 10 -> K
    0000 -> 000000 -> 0 -> A
    0000 -> 000000 -> 0 -> A
    0111 -> 001110 -> 14 -> O

来一个复杂示例

源代码

1
2
3
4
function a() {
var b = "01234567890123456789";var d=1
};
a();

压缩后的代码

1
function a(){var b="01234567890123456789";var d=1}a();

对应的source map

1
2
3
4
5
6
{
"version":3,
"sources":["./a.js"],
"names":["a","b","d"],
"mappings":"AAAA,SAASA,IACP,IAAIC,EAAI,uBAAuB,IAAIC,EAAE,EAEvCF"
}

各个符号的映射关系建立,位置信息 -> 二进制表示 -> VLQ二进制 -> BASE64编码

  • function

    1
    2
    3
    4
    0 -> 0000 -> 000000 -> A
    0 -> 0000 -> 000000 -> A
    0 -> 0000 -> 000000 -> A
    0 -> 0000 -> 000000 -> A
  • a

    1
    2
    3
    4
    5
    +9 -> 1001 -> 010010 -> S
    +0 -> 0000 -> 000000 -> A
    +0 -> 0000 -> 000000 -> A
    +9 -> 1001 -> 010010 -> S
    +0 -> 0000 -> 000000 -> A
  • var

    1
    2
    3
    4
    +4 -> 0100 -> 001000 -> 8  -> I
    +0 -> 0000 -> 000000 -> 0 -> A
    +1 -> 0001 -> 000010 -> 2 -> C
    -7 -> 0111 -> 001111 -> 15 -> P
  • b

    1
    2
    3
    4
    5
    +4 -> 0100 -> 001000 -> 8 -> I
    +0 -> 0000 -> 000000 -> 0 -> A
    +0 -> 0000 -> 000000 -> 0 -> A
    +4 -> 0100 -> 001000 -> 8 -> I
    +1 -> 0001 -> 000010 -> 2 -> C
  • “01234567890123456789”

    1
    2
    3
    4
    +2 -> 0010 -> 000100 -> 4 -> E
    +0 -> 0000 -> 000000 -> 0 -> A
    +0 -> 0000 -> 000000 -> 0 -> A
    +4 -> 0100 -> 001000 -> 8 -> I
  • var

    1
    2
    3
    4
    +23 -> 10111 -> 101110 000001 -> 46 1 -> uB
    +0 -> 0000 -> 000000 -> 0 -> A
    +0 -> 0000 -> 000000 -> 0 -> A
    +23 -> 10111 -> 101110 000001 -> 46 1 -> uB
  • d

    1
    2
    3
    4
    5
    +4 -> 0100 -> 001000 -> 8 -> I
    +0 -> 0000 -> 000000 -> 0 -> A
    +0 -> 0000 -> 000000 -> 0 -> A
    +4 -> 0100 -> 001000 -> 8 -> I
    +1 -> 0001 -> 000010 -> 2 -> C
  • 1

    1
    2
    3
    4
    +2 -> 0010 -> 000100 -> 4 -> E
    +0 -> 0000 -> 000000 -> 0 -> A
    +0 -> 0000 -> 000000 -> 0 -> A
    +2 -> 0010 -> 000100 -> 4 -> E
  • a

    1
    2
    3
    4
    5
    +2  -> 0010   -> 000100         -> 4     -> E
    +0 -> 0000 -> 000000 -> 0 -> A
    +2 -> 0010 -> 000100 -> 4 -> E
    -39 -> 100111 -> 101111 000010 -> 47 2 -> vC
    -2 -> 0010 -> 000101 -> 5 -> F

参考文章

source map 的原理探究
source map 规范