介绍 webpack的source-map原理
为什么要有source-map?
答:因为目前我们开发时候的源码跟通过webpack构建混淆压缩后的生产环境部署的代码不一样,sourceMap就是一个文件,里面储存着位置信息。
source-map,怎么来的 ?它在哪儿?
答:通过webpack等工具,我们可以使用 sourceMap,它跟构建后的文件同在一个目录下~
source-map,它怎么用?
答:在webpack的development模式下,会自动开启source-map
例如: 下面的代码没有声明过log函数,结果调用了
componentDidMount() { log(this.props); }
最终精准报错,代码所处的文件,还有行数,这是source-map的最重要作用
可是我们的代码经过webpack打包后,即便是development模式,也会被混淆。真正的内存中的代码,已经是下面这种形式了
那么是它是怎么给我们精准报错定位到源文件地址和行数的呢?
我们通过source面板打开了文件,全局搜索了source
此时发现原来开启source-map后的路径映射,是直接保存在文件中的,但是我看到在老版本的webpack中,是以base64的形式保存在文件中的
⚠️:在webpack配置中设置devtool: 'none',那么就会关闭source-map
development模式会自动开启source-map,是为了让我们方便调试,定位错误,但是production模式,会自动关闭
众所周知,代码加密,也是有代价的,会降低代码运行效率,所以前端安全就很难做。特别是Electron、安卓这种客户端,都是可以反编译的,只要有编译,就基本上都有反编译。所以即便是whatsApp这种,也是大部分代码裸奔。当然,还是有很多手段可以提高反编译成本的
下面是source-map各种不同配置的速度
当我在production模式下,加入 devtool:"source-map"选项,打包构建后,代码里多了很多个.map文件,这些个map文件里,就包含了源码路径
这里可以看到,每个文件模块,都有一个对应的.map文件,里面存放着他们的源码路径
sourcemap文件的格式:
{ version : 3, //SourceMap的版本,目前为3 sources: ["***.js,***.js"], //转换前的文件,该项是一个数组,表示可能存在多个文件合并 names: ["***", "***", "**", "***"], //转换前的所有变量名和属性名 mappings: "AACvB,gBAAgB,EAAE;AAClB;", //记录位置信息的字符串 file: "out.js", //转换后的文件名 sourcesContent: " \t// The module cache\n", //转换后的代码 sourceRoot : "" //转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空}
其他的应该都很好解释,重点是mappings
以"AACvB,gBAAgB,EAAE;AAClB;"
为例:
AACvB
代表该位置转换前的源码位置,以VLQ
编码表示;位置关系的保存经历了诸多步骤和优化,这个不详细说了,想看的可以看这里,我们只说最后的结果。
在每个位置中:
假设现在有a.js
,内容为feel the force
,处理后为b.js
,内容为the force feel
以the
为例,它在输出中的位置是(0,0),a.js
是sources
的第1个(这里只是举例),输入中的位置是(0,5),the
是names
的第2个(这里只是举例)。
那么映射关系为: 0 1 0 5 2
最后将 01052 表示为 Base64 VLQ 即可。
说明:
names
属性中的变量,可以省略第五位VLQ
编码表示,由于VLQ
编码是可变长的,所以每一位可以由多个字符构成相对位置是啥呢,看示意图:
第一次记录的输入位置和输出位置是绝对的,往后的输入位置和输出位置都是相对上一次的位置移动了多少,例如the
的输出位置为(0,-10),因为the
在feel
的左边数10下才能到这个位置。
VLQ
是Variable-length quantity
的缩写,是一种通用的、使用任意位数的二进制来表示一个任意大的数字的一种编码方式。这种编码最早用于MIDI文件,后来被多种格式采用,它的特点就是可以非常精简地表示很大的数值,用来节省空间。
这种编码需要用最高位表示连续性,如果是1,代表这组字节后面的一组字节也属于同一个数;如果是0,表示该数值到这就结束了。
这样干巴巴说不太容易懂,还是举个栗子说明一下吧。
如何对数值137进行VLQ编码:
步骤 | 结果 |
---|---|
将137改写成二进制形式 | 10001001 |
七位一组做分组,不足的补0 | 0000001 0001001 |
最后一组开头补0,其余补1 | 10000001 00001001 |
所以,137的VLQ编码形式为10000001 00001001
与一般的VLQ
的区别:
Base64
字符只能表示 6bit(2^6)
的数据Base64 VLQ
需要能够表示负数,于是用最后一位来作为符号标志位。[-15,15]
,如果超过了就要用连续标识位了。表示正负的方式:
VLQ
编码的第一组字节,那它的最后一位代表"符号",0为正,1为负;我们再来举个栗子说明下使用方法。
如何对数值137进行Base64 VLQ
编码:
步骤 | 结果 |
---|---|
将137改写成二进制形式 | 10001001 |
127是正数,末位补0 | 100010010 |
五位一组做分组,不足的补0 | 01000 10010 |
将组倒序排序 | 10010 01000 |
最后一组开头补0,其余补1 | 110010 001000 |
转64进制 | y和I |
所以 137 通过Base64 VLQ
表示为yl
可以看出:
Base64 VLQ
中,编码顺序是从低位到高位VLQ
中,编码顺序是从高位到低位对于做前端异常监控来说,source-map是很有必要的,但是对于性能要求极高的项目,那么可能还是要自己去实现一套独特的监控方式。
部分内容来自参考文章:
https://segmentfault.com/a/1190000020213957