Webpack中Source Maps的底层原理与使用

1,444 阅读5分钟

目录

1 Source Maps的意义

当我们使用webpack打包源代码的时候,可能很难追踪到原始的报错位置。比如:我们把多个js源文件打包到一个bundle.js里,当其中某一个源文件出错时,浏览器的报错只会指向bundle.js,这样很不利于代码调试。

Webpack中的Source maps,正是为了解决这个问题。

2 Source Maps的结构

简单来说,Source maps就是一个保存着位置信息的文件。它可以把已编译代码映射到原始源代码,从而让我们可以准确地获知是哪个源文件出错。

Source maps文件中包含一个JSON对象,这个对象存储着映射信息,格式如下:

{
    version: 3, 
    file: "script.js.map",
    sources: [
        "app.js",
        "content.js",
        "widget.js"
    ],
    sourceRoot: "/", 
    names: ["slideUp""slideDown""save"], 
    mappings: "AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO..." 
}

各个字段的含义:

  • version:Source maps的版本

  • file:Source maps的文件名

  • sources:原始文件的URL

  • sourceRoot:(可选项)原始文件所在的目录

  • names:原始文件中所有变量和函数名

  • mappings:包含实际代码映射的Base64 VLQ字符串。

3 位置映射

我们试想一种方案——可以用一个字段来保存映射关系,在这个字段里面有六个位置,分别代表:

  • 输出代码的所在行

  • 输出代码的所在列

  • 输入代码的所在文件的名称

  • 输入代码的所在行

  • 输入代码的所在列

  • 代码的内容(比如函数名、变量名。但是,这个位置如果没有可以对应的内容的时候,这种情况下可以省略)

我们来看一个例子:a.js为原始文件(输入文件),而b.js为处理后的文件(输出文件)。

(注:图片来自文末资料4)

其中the这个字符串,用前面的映射方式,就可以记录为:

我们可以做一些优化,把输入文件名存储到JSON对象的sources字段中,把字符组合存储到names字段中。

优化后:

在实际的Source maps中,做了更近一步的优化:

  1. 省略输出代码的所在行,直接用mappings字段中的分号(;),来区分每一行。

  2. 除了第一个代码使用绝对位置之外,后面的代码都是用相对位置来记录。这样可以避免行/列号过大的问题。

最终优化的结果:

但我们可以看到,实际的mappings字段值似乎跟这种记录映射的方式有些不同。原因在于,mappings还将这种映射进行了Base64 VLQ编码。

4 Base64-VLQ编码

以映射关系[10|0|0|0|0]为例,在这里我们用|划分每个数。但使用这种方法的话,其中的|需要占一定的字符。于是,连续位的概念被提出了,用来解决这个问题。

VLQ(Variable-length quantity)编码可变长,它使用六个二进制位表示一个字符,其中:

  • 第一位:连续位。如果是1,则代表后面的6个二进制位跟这里属于同一个数,如果是0,则代表这个数到这里结束了。

  • 最后一位:符号位。0代表正数,1代表负数。

  • 中间的四位:用来表示具体的数值。因此,它可以表示-15~15范围内的数字。如果数字超过这个范围,就需要多几个字符来表示。

那么,VLQ具体是如何编码的呢?

我们以数值16为例,演示VLQ编码:

  1. 将16改写成二进制形式10000。

  2. 在最右边补充符号位。因为16大于0,所以符号位为0,整个数变成100000。

  3. 从右边的最低位开始,将整个数每隔5位,进行分段,即变成1和00000两段。如果最高位所在的段不足5位,则前面补0,因此两段变成00001和00000。

  4. 将两段的顺序倒过来,即00000和00001。

  5. 在每一段的最前面添加一个"连续位",除了最后一段为0,其他都为1,即变成100000和000001。

  6. 将每一段转成Base64编码。

Base 64对应表:

比如位置映射如下:

[0|0|0|0 , 9|0|0|9|0 , 9|0|0|9|1 , 6|0|1|-14|1 , 8|0|0|8|1 , 4|0|0|4 , 9|0|0|10|-2]

则Base64-VLQ编码之后为:

"mappings":"AAAA,SAASA,SAASC,MACdC,QAAQC,IAAI,SAAUF"

5 Source Map的开启方式

在Chrome和Firefox的开发者工具中都内置了对Source maps的支持。

下图为Chrome开发者工具的对应截图:

让我们来看一下如何启用Source Maps。

第一种方式,可以通过在优化后的文件尾部添加特殊的注释,格式如下:

//# sourceMappingURL=/path/to/script.js.map

第二种方式,可以通过HTTP header中的X-SourceMap,格式如下:

SourceMap/path/to/script.js.map

6 Webpack中的Source Maps

在Webpack中配置Source Map有两种方式:

  1. Devtool

  2. Plugin:SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin

二者不可混用,只能择一。

Webpack中提供了多种Source Maps的可选值,在webpack/examples/source-map/显示了每种Source Maps的案例。

那么我们应当如何选择呢?

对于开发模式,相比打包的体积大小,我们更加注重打包速度。而在生产模式下,我们则更在意准确性和打包体积。

7 归纳总结

  1. 为了方便对打包后的代码进行调试,Source Maps提供了一种代码位置映射机制。

  2. 位置映射策略和Base64-VLQ编码,二者在Source Maps中起到了关键作用。

资料