随着JavaScript脚本越来越复杂,大部分代码都需要经过一系列转换才能投入生产。而转换后的代码位置、变量名都已改变,那么如何定位转换后的代码?这就是source-map要解决的问题。
source-map 存储了压缩后代码相对于源代码的位置信息,常用于JavaScript代码调试;一般以.map结尾的json文件存储。
source-map的构成
version:source-map的版本,目前为3
sources:转换前的文件url数组
names:转换前可以用于映射的变量名和属性名
file:源文件的文件名
sourceRoot:源文件的目录前缀(url)
sourcesContent: sources对应的源文件内容
mappings:记录位置信息的
VLQ编码
字符串,下文讲解
JSON示例(webpack)
# @param {
Number
} source-map的版本,目前为
3
"version"
:
3
,
# @param {
Array
<
String
>} 转换前的文件url数组
"sources"
: [
"webpack://utils/webpack/universalModuleDefinition"
,
"webpack://utils/./src/utils.js"
# @param {
Array
<
String
>} 转换前可以用于映射的变量名和属性名
"names"
: [
"add"
,
"a"
,
"b"
,
"Error"
],
# @param {
String
} base64的
VLQ
编码
"mappings"
:
"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;;;;;;;;;;ACVO,SAASA,GAAT,CAAcC,CAAd,EAAiBC,CAAjB,EAAoB;AACvB,QAAMC,KAAK,CAAC,MAAD,CAAX;AACA,SAAOF,CAAC,GAAGC,CAAX;AACH,C"
,
# @param {
Array
<
String
>} sources对应的源文件内容
"sourcesContent"
: [
"(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"utils\"] = factory();\n\telse\n\t\troot[\"utils\"] = factory();\n})(self, function() {\nreturn "
,
"export function add (a, b) {\r\n throw Error('test')\r\n return a + b\r\n}"
],
# @param {
String
} 源文件的目录前缀(url)
"sourceRoot"
:
"/path/to/static/assets"
,
# @param {
String
} 源文件的文件名
"file"
:
"utils.js"
【注】JSON没有注释语句,代码中#仅作解释说明
mappings属性
VQL编码后
VQL编码后的mappings字符串划分为三层:
每行用分号(;)划分,行内每个位置信息用逗号(,)划分,具体的行列位置记录用VLQ编码存储,即
mappings:"AAAAA,BBBBB;CCCCC"
表示转换后的源码有两行,第一行记录有两个变量名和属性名位置,第二行只有一个两个位置记录
通过以上可知基于位置的映射关系是模糊映射,即仅能指出具体某个(行列)位置所在的局域内
VLQ编码后的字符串是变长,如上文JSON示例(webpack)
中的mapping属性所示:GAAGC,CAAX;AACH,C
VLQ编码前
编码前的位置信息由五位组成:
第一位,表示这个位置在(转换后的代码的)的第几列。
第二位,表示这个位置属于sources属性中文件(名)的序列号。
第三位,表示这个位置属于转换前代码的第几行。
第四位,表示这个位置属于转换前代码的第几列。
第五位,表示这个位置属于names属性中(变量名和属性名)数组的序列号。
第五位不是必需的,如果该位置没有对应names属性中的变量,可以省略第五位
在实际应用中,每行的VLQ编码前映射信息是相对前一位置的相对位置(节省空间?)
const mappings = "AACA;AACA,CAAC;"
const decodedMappings = decode(mappings)
VLQ解码
应用vlq库可对mapping信息进行解码,但由于行内的位置编码是相对位置,所以获取每行的标志位置的绝对定位需要累计位置信息