source-map我相信大家平时接触的也比较多,但是它的具体实现原理可能比较模糊。接下来我带大家一步步解析source-map原理。
基础配置
module.exports = {
entry: {
index: './src/index.js',
},
mode: 'production',
output: {
clean: true
},
devtool: 'source-map'
}
// index.js
console.log('I', 'am', 'Chris');
打包后生成的文件:
// 打包后生成的文件index.js
console.log("I","am","Chris");
//# sourceMappingURL=testxx.js.map
// 打包后生成的文件index.js.map
{
"version": 3,
"file": "testxx.js",
"mappings": "AAAAA,QAAQC,IAAI,IAAK,KAAM",
"sources": [
"webpack://test-webpack/./src/index.js"
],
"sourcesContent": [
"console.log('I', 'am', 'Chris');"
],
"names": [
"console",
"log"
],
"sourceRoot": ""
}
生成具体步骤如下:
每一行都代表一个连续的字段:
1. 第一个字1表示生成的字段在第1行
2. 第二个字0表示生成的字段在第0列
3. 第三个字0表示生成的字段在sources文件中下标为0
4. 第四个字1表示原始的字段在第1行
5. 第五个字0表示原始的字段在第0列
1|0|0|1|0
1|8|0|1|8
1|12|0|1|12
1|17|0|1|16
1|21|0|1|23
下面开始优化:
// 1. 去掉输出行,用;来表示换行
0|0|1|0
8|0|1|8
12|0|1|12
17|0|1|16
21|0|1|23
// 2. 加入映射 "names": [ "console", "log" ],names 是转换前的变量名。这样就可以通过下标来索引了,mapping 里面就不用保存变量名,只保留 names 的索引就行。
0|0|1|0|0
8|0|1|8|1
12|0|1|12|-1
16|0|1|17|-1
21|0|1|23|-1
// 3. 输出列、输出行、输入列、输入行、sources等做相对位置(后面的绝对位置-前面的绝对位置)
// 其中names中没有的用-1代替
0|0|0|0|0
8|0|0|8|1
4|0|0|4|-1
4|0|0|5|-1
5|0|0|6|-1
// 4. vlq编码然后转换为base64(如果6位中间的四位(不超过15)可以显示,那么只用6位,第一位表示是否有连接,最后一位表示正负)。如果names索引为负数那么不显示,只显示4位。
0|0|1|0|0 000000 000000 000000 000000 000000 AAAAA
8|0|1|8|1 010000 000000 000000 010000 000010 QAAQC
4|0|1|4|-1 001000 000000 000000 001000 IAAI
4|0|1|5|-1 001000 000000 000000 001010 IAAK
5|0|1|6|-1 001010 000000 000000 001100 KAAM
最后我们和webpack打包出来的对比下得出的是一样的。
base64图表:
下面我带大家看看webpack中源码实现,和我们上面说的是一致的。
// webpack-sources\lib\helpers\createMappingsSerializer.js
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(
""
);
const CONTINUATION_BIT = 0x20;
const createFullMappingsSerializer = () => {
let currentLine = 1;
let currentColumn = 0;
let currentSourceIndex = 0;
let currentOriginalLine = 1;
let currentOriginalColumn = 0;
let currentNameIndex = 0;
let activeMapping = false;
let activeName = false;
let initial = true;
return (
generatedLine,
generatedColumn,
sourceIndex,
originalLine,
originalColumn,
nameIndex
) => {
if (activeMapping && currentLine === generatedLine) {
// A mapping is still active
if (
sourceIndex === currentSourceIndex &&
originalLine === currentOriginalLine &&
originalColumn === currentOriginalColumn &&
!activeName &&
nameIndex < 0
) {
// avoid repeating the same original mapping
return "";
}
} else {
// No mapping is active
if (sourceIndex < 0) {
// avoid writing unneccessary generated mappings
return "";
}
}
let str;// 当前行小于生成后的行添加;
if (currentLine < generatedLine) {
str = ";".repeat(generatedLine - currentLine);
currentLine = generatedLine;
currentColumn = 0;
initial = false;
} else if (initial) {
str = "";
initial = false;
} else {
str = ",";
}
const writeValue = value => {
const sign = (value >>> 31) & 1;
const mask = value >> 31;
const absValue = (value + mask) ^ mask;
let data = (absValue << 1) | sign;
for (;;) {
const sextet = data & 0x1f;
data >>= 5;
if (data === 0) {
str += ALPHABET[sextet];
break;
} else {
str += ALPHABET[sextet | CONTINUATION_BIT];
}
}
};// 当前绝对列减去前一个绝对列获取相对列
writeValue(generatedColumn - currentColumn);
currentColumn = generatedColumn;
if (sourceIndex >= 0) {
activeMapping = true;// 设置文件索引
if (sourceIndex === currentSourceIndex) {
str += "A";
} else {
writeValue(sourceIndex - currentSourceIndex);
currentSourceIndex = sourceIndex;
}// 获取相对行
writeValue(originalLine - currentOriginalLine);
currentOriginalLine = originalLine;
if (originalColumn === currentOriginalColumn) {
str += "A";
} else {// 设置相对列
writeValue(originalColumn - currentOriginalColumn);
currentOriginalColumn = originalColumn;
}
if (nameIndex >= 0) {// 如果name>=0设置相对name,否则不记录,此时为4个字母
writeValue(nameIndex - currentNameIndex);
currentNameIndex = nameIndex;
activeName = true;
} else {
activeName = false;
}
} else {
activeMapping = false;
}
return str;
};
};