source map 的原理探究
线上产品代码一般是编译过的,前端的编译处理过程包括不限于
这里提及的都是可生成source map 的操作。 经过这一系列骚气的操作后,发布到线上的代码已经面目全非,对带宽友好了,但对开发者调试并不友好。于是就有了 source map。顾名思义,他是源码的映射,可以将压缩后的代码再对应回未压缩的源码。使得我们在调试线上产品时,就好像在调试开发环境的代码。 来看一个工作的示例准备两个测试文件,一个 log.js function sayHello(name) { 一个 sayHello('世界') 我们使用 npm install uglify-js -g 安装并执行后,我们得到了一个输出文件 output.js function sayHello(name){if(name.length>2){name=name.substr(0,1)+"..."}console.log("hello,",name)}sayHello("世界");sayHello("第三世界的人们"); output.js.map {"version":3,"sources":["log.js","main.js"],"names":["sayHello","name","length","substr","console","log"],"mappings":"AAAA,SAASA,SAASC,MACd,GAAIA,KAAKC,OAAS,EAAG,CACjBD,KAAOA,KAAKE,OAAO,EAAG,GAAK,MAE/BC,QAAQC,IAAI,SAAUJ,MCJ1BD,SAAS,MACTA,SAAS"} 为了能够让 source map 能够被浏览器加载和解析,
<!DOCTYPE html>
python3 -m http.server
在浏览器中开启 source map 最后,就可以访问 在压缩过的代码中打断点 从截图中可以看到,开启 source map 后,除了页面中引用的 为了测试,我们将 output.js 在调试工具中进行格式化,然后在 刷新页面后,我们发现,断点正确定位到了 代码的还原 会否觉得很赞啊! 下面我们来了解它的工作原理。 我们所想象的 source map将现实中的情况简化一下无非是以下的场景:
上面,输出无疑就是需要发布到产品线上的浏览器能运行的代码。这里只讨论 js,所以输出是 js 代码,当然,其实source map 也可以运用于其他资源比如 LESS/SASS 等编译到的 CSS。 而 source map 的功能是帮助我们在拿到输出后还原回输入。如果我们自己来实现,应该怎么做。 最直观的想法恐怕是,将生成的文件中每个字符位置对应的原位置保存起来,一一映射。请看来自这篇文章中给出的示例:
一个简单的文本转换输出,其中
这里之所以将输入文件也作为映射的必需值,它可以告诉我们从哪里去找源文件。并且,在代码合并时,生成输出文件的源文件不止一个,记录下每处代码来自哪个文件,在还原时也很重要。 上面可以直观看出,生成文件中 (1,0) 位置的字符对应源文件中 (1,5)位置的字符,...
这样确实能够将处理后的文件映射回原来的文件,但随着内容的增多,转换规则更加地复杂,这个记录映射的编码将飞速增长。这里源文件 省去输出文件中的行号大多数情况下处理后的文件行数都会少于源文件,特别是 js,使用 UglifyJS 压缩后的文件通常只有一行。基于此,每必要在每条映射中都带上输出文件的行号,转而在这些映射中插入
可符号化字符的提取这个例子中,一共有三个单词,拿输出文件中 所以,首先我们将文件中可符号化的字符提取出来,将他们作为整体来处理。
于是得到一个所有包含所有符号的数组:
在记录时,只需要记录一个索引,还原时通过索引来这个 所以
可以简化为
同时,考虑到代码经常会有合并打包的情况,即输入文件不止一个,所以可以将输入文件抽取一个数组,记录时,只需要记录一个索引,还原的时候再到这个数组中通过索引取出文件的位置及文件名即可。
所以上面
于是我们得到了完整的映射为:
记录相对位置当文件内容巨大时,上面精简后的编码也有可能会因为数字位数的增加而变得很长,同时,处理较大数字总是不如处理较小数字容易和方便。于是考虑将上面记录的这些位置用相对值来记录。比如(1,1001)第一行第999列的符号,如果用相对值,我们就不用每次记录都从0开始数,假如前一个符号位置为 (1,999),那后面这个符号可记录为(0,2),类似这样的相对值帮我们节省了空间,同时降低了数据的维度。 具体到本例中,看看最初的表格中,记录的输出文件中的位置:
对应到整个表格则是:
然后我们得到的编码为:
注意
VLQ (Variable Length Quantities)进一步的优化则需要引入一个新的概念了,VLQ(Variable-length quantity)。 VLQ 以数字的方式呈现如果你想顺序记录4个数字,最简单的办法就是将每个数字用特殊的符号隔开:
如果如果提前告诉你这些被记录的数字都是一位的,那这个分隔线就没必要了,只需要简单记录成如下样子也能被正确识别出来:
此时这个记录值的长度是原来的1/2,省了不少空间。 但实际上我们不可能只记录个位数的数字,使用 VLQ 方式时,如果一个数字后面还跟有剩余数字,将其标识出来即可。假设我们想记录如下的四个数字:
我们使用下划线来标识一个数字后跟有其他数字: 1234567 所以解读规则为:
VLQ 以二进制方式的方式呈现上面的示例中,引入了数字系统外的符号来标识一个数字还未结束。在二进制系统中,我们使用6个字节来记录一个数字(可表示至多64个值),用其中一个字节来标识它是否未结束(正文 C 标识),不需要引入额外的符号,再用一位标识正负(下方 S),剩下还有四位用来表示数值。用这样6个字节组成的一组拼起来就可以表示出我们需要的数字串了。
第一个字节组(四位作为值) 这样一个字节组可以表示的数字范围为:
* -0 没有实际意义,但技术上它是存在的 任意数字中,第一个字节组中已经标明了该数字的正负,所以后续的字节组中无需再标识,于是可以多出一位来作表示值。
未结束的字节组(五位作为值) 现在我们使用上面的二进制规则来重新编码之前的这个数字序列 先看每个数字对应的真实二进制是多少:
1需要一位来表示,还好对于首个字节组,我们有四位来表示值,所以是够用的。
23的二进制为10111一共需要5位,第一组字节组只能提供4位来记录值,所以用一组字节组不行,需要使用两组字节组。将 10111拆分为两组,后四位0111放入第一个字节组中,剩下一位1放入第二个字节组中。
456的二进制111001000需要占用9个字节,同样,一个字节组放不下,先拆出最后四位(1000)放入一个首位字节组中,剩下的5位(11100)放入跟随的字节组中。
3的二进制为111,首位字节组能够存放得下,于是编码为:
将上面的编码合并得到最终的编码:
base64 编码表 结合上面的 Base64 编码表,上面的结果转成对应的 base64 字符为:
利用 Base64 VLQ 编码生成最终的 srouce map通过上面讨论的方法,回到开始的示例中,前面我们已经得到的编码为 sources: ['Yoda_input.txt'] 现在来编码
合并后的编码为:
再转 Base64 后得到字符形式的结果:
后面两串数通过类似的做法也能得到对应的 Base64编码,所以最终我们得到的 source map 看起来是这样的:
而真实的 srouce map 如我们文章开头那个示例一样,是一个 json 文件,所以最后我们得到的一分像模像样的 source map 为: { 略去不必要的字段上面的例子中,每一片段的编码由五位组成。真实场景中,有些情况下某些字段其实不必要,这时就可以将其省略。当然,这里给出的这个例子看不出来。 省略其中某些字段后,一个编码片段就不一定是5位了,他的长度有可能为1,4或者5。
以上,便探究完了 srouce map 生成的全过程,了解了其原理。 如果感兴趣,这个Source map visualizer tool 工具可以在线将 source map 与对应代码可见化展现出来,方便理解。 另外需要介绍的是,尽管 source map 对于线上调试非常有用,各主流浏览器也实现对其的支持,但关于它的规范没有见诸各 Web 工作组或团体的官方文档中,它的规范是写在一个 Google 文档中的!这你敢信,不信去看一看喽~ Source Map Revision 3 Proposal。 相关资料
后续
|
source map 的原理探究的更多相关文章
- [原] KVM 虚拟化原理探究(2)— QEMU启动过程
KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...
- JavaScript Source Map 详解
源码地址: http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html 上周,jQuery 1.9发布. 这是2.0版之前的最后 ...
- 【转】webpack中关于source map的配置
Webpack中sourcemap的配置 sourcemap是为了解决开发代码与实际运行代码不一致时帮助我们debug到原始开发代码的技术.尤其是如今前端开发中大部分的代码都经过编译,打包等工程化转换 ...
- SpringBoot:运行原理探究
西部开源-秦疆老师:基于SpringBoot 2.1.6 的博客教程 秦老师交流Q群号: 664386224 未授权禁止转载!编辑不易 , 转发请注明出处!防君子不防小人,共勉! SpringBoot ...
- [原] KVM 虚拟化原理探究(1)— overview
KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...
- [原] KVM 虚拟化原理探究 —— 目录
KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...
- [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化
KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...
- [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化
KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...
- [原] KVM 虚拟化原理探究(4)— 内存虚拟化
KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...
随机推荐
- Python-杂物
1,and和or 在一个bool and a or b语句中,当bool条件为真时,结果是a:当bool条件为假时,结果是b. a = "heaven" b = "hel ...
- Pandas 0 数据结构Series
# -*- encoding:utf-8 -*- # Copyright (c) 2015 Shiye Inc. # All rights reserved. # # Author: ldq < ...
- python利用xlrd读取excel文件始终报错原因
1.代码按照网上百度的格式进行书写如下: 但运行后,始终报错如下: 百度了xlrd网页: 分明支持xls和xlsx两种格式的文件,但运行始终报错. 最后找到原因是因为我所读取的文件虽然是以.xls命名 ...
- 封装redis
封装redis import redis # r = redis.Redis() class MyRedis(): def __init__(self,ip,password,port=6379,db ...
- 转 c#性能优化秘密
原文:http://www.dotnetperls.com/optimization Generally, using the simplest features of the language pr ...
- C++ struct结构体定义构造函数和析构函数,构造函数参数从VS2017平台转换到Qt5平台下构建出错,采用字符集转换函数将string类型转换为wstring,构建仍然出错!
调试win硬件驱动,需要利用VS编译的win驱动构建自己的Qt5GUI程序: 其中部分win驱动源码如下 device_file::device_file(const std::string& ...
- [Educational Round 13][Codeforces 678F. Lena and Queries]
题目连接:678F - Lena and Queries 题目大意:要求对一个点集实现二维点对的插入,删除,以及询问\(q\):求\(max(x\cdot q+y)\) 题解:对每个点集内的点\(P( ...
- kibana 创建index pattern 索引模式时过慢导致无法创建成功 以及解决方案
下面我具体描述一下我遇到的问题. 在kibana上面创建索引点击创建时,一直显示下面的页面 就看到不停的在那转,始终创建不成功. 查看后台日志,看到状态码为403,报了如下的错误 由于我用的是es6版 ...
- pytorch可视化工具visdom
visdom的github repo: https://github.com/facebookresearch/visdom 知乎一个教程:https://zhuanlan.zhihu.com/p/3 ...
- Openstack中RabbitMQ RPC代码分析
在Openstack中,RPC调用是通过RabbitMQ进行的. 任何一个RPC调用,都有Client/Server两部分,分别在rpcapi.py和manager.py中实现. 这里以nova-sc ...