热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

来聊聊SourceMap

前言我们项目的代码在经过编译打包后,会将开发时多个文件的代码合并到同一份文件中,而且还会经过各种压缩,合并,代码丑化等等操作

前言

我们项目的代码在经过编译打包后,会将开发时多个文件的代码合并到同一份文件中,而且还会经过各种压缩,合并,代码丑化等等操作,转换完最终生成的代码才会用于线上环境,所以我们线上实际运行的代码跟我们开发时的代码是有非常大的不同,如果此时出现了bug,那么我们只能定位到转换后代码的位置,但此时的代码已经面目全非了

转换后的代码类似下面这样

77939625f4af97b7a41d13c850d41970.pngae6bed11cf9beb22c43a4e918e4579b7.png

虽然这种代码对计算机非常友好,但是我们debug将会变得很困难,这时候就需要sourcemap了

什么是SourceMap

简单来说,Sourcemap 就是一个信息文件,它里面存储着代码转换前后的对应位置信息,也就是转换压缩后的代码所对应的转换前的源代码位置,是源代码和生产代码的映射, Sourcemap 解决了在打包过程中,代码经过压缩,去空格以及 babel 编译转化后,由于代码之间差异性过大,debug 困难的问题

大家的项目在开发完进行build后,在打包文件夹里除了有js,css,图片等资源,一定还见过 .js.map文件,这种就是sourcemap文件

457a4366e972f788fe0d6f71157c3be7.png

点开一个打包后的js文件,拉到最后一行,可以看到 //# sourceMappingURL=main.js.map

146134623d92a7a937e423174acae6ae.png

有了这行,就可以启用sourcemap,这个sourceMappingURL就是标记了该文件的sourcemap的地址,这个sourcemap文件可以放在本地,也可以放在网络上

再点开一个 .js.map 文件看看,有一堆类似乱码的东西,后面看一个简单一点的

d97ecd6371ce40c91cdc0fa70e1af2db.png

SourceMap的生成

生成的方法有很多,而且有很多前端的工具都支持,如webpack,uglifyjs,gulp等,这里就不详细讲述了,对怎么生成sourcemap感兴趣可以看这个文章

https://code.tutsplus.com/tutorials/source-maps-101--net-29173

SourceMap的属性

我们看一个简单一点的代码

const value = 123;
console.log(value);

用webpack打包后的代码

console.log(123);
//# sourceMappingURL=bundle.js.map

生成的sourcemap文件

{version : 3,file :  bundle.js ,mappings :  AACAA,QAAQC,IADM ,sources : [webpack://studysourcemap/./test.js ],sourcesContent : [const value = 123;\nconsole.log(value); ],names : [console ,log ],sourceRoot :   
}

每个属性的含义如下

d3060de6fe1a3098f908f9b02aceccee.png
  • version:遵循的是哪一个sourcemap版本的规范(下面会浅提一下)

  • sources:转换前的源文件url数组(数组是因为存在多个文件合并的情况)

  • names:在mappings中引用的标识符数组(可以理解为转换前代码的所有变量名和属性名)

  • sourceRoot:源文件的根路径

  • sourcesContent:转换前源文件的原始内容,也是一个数组

  • mappings:记录源码和编译后代码的位置信息的base64 VLQ 字符串,是最重要的内容

  • file:生成的与该sourcemap文件关联的文件名,也就是打包编译后的文件名

SourceMap的版本

关于sourcemap的版本

  • 2009年,google介绍他的一个编译器Cloure Compiler时,也顺便推出了一个调试插件Closure Inspector,可以方便调试编译后的代码,这个就是 sourcemap 的雏形

  • 2010年,Closure Compiler Source Map 2.0中,共同制定了一些标准,已决定使用base64编码,但是生成的map文件要比现在大很多

  • 2011年,第三代出炉, Source Map Revision 3 Proposal,也就是我们现在用的 sourcemap 的版本,这也就是为什么我们上面map文件的 version=3 了,这一版对算法进行了优化,大大缩小了map文件的体积

第一版生成的map文件大概有转化后文件的10倍大,第二版则将体积减少了20%~30%,第三版又在v2的基础上体积减少了一半

正是因为有了第三代 Source Map Revision 3 Proposal 这个标准,不同的打包工具和浏览器才能使用sourcemap,github上的一个根据这个标准生成sourcemap的库 https://github.com/mozilla/source-map

SourceMap的原理

这里主要关注mappings和names属性,mappings属性是一个很长的字符串,它分成三个部分

a62a5f710c3ddbfa13bcf5aa9ee7d0ee.png
  1. 分号(;),表示行对应,生成的文件的每一行用分号(;)分隔,一个分号代表转换后源码的一行

  1. 逗号(,),位置对应,每一段用逗号(,)分隔,一个逗号对应转换后源码的一个位置

  1. 英文字母,每一段由1,4或5块可变长度的字段组成,记录原始代码的位置信息

举一个简单的例子,假设有如下的mappings属性

mappings :  AACAA;QAAQC,IADM ,

有一个分号,说明有两行代码,分号前 AACAA 是第一行,后面 QAAQC,IADM 是第二行

第二行有一个逗号,说明这一行分为两段,QAAQCIADM

分号跟逗号大家应该都没什么疑问,主要就是英文字母这一块的意义位置对应的原理

每一段最多有5个部分

  • 第一部分,表示这个位置在(转换后的代码)的第几列

  • 第二部分,表示这个位置属于sources属性中的哪一个文件

  • 第三部分,表示这个位置属于转换前代码的第几行

  • 第四部分,表示这个位置属于转换前代码的第几列

  • 第五部分,表示这个位置属于names属性中的哪一个变量

那么这五个部分是怎么来的,我们一步一步来看

假设文件a.js有一行代码 I Love SourceMap,最终打包后输出的文件为bundle.js,内容为 Javascript is awesome,如下

af348fc3eb255a22172a8194beab7e36.png
image.png

那么怎么表示映射关系

以 Love 为例,它原始的位置为(0,2),输出后是awesome,位置为(0,14),那么我们可以这样来映射

a5bebb01ee6a7a36333b9d1e91af41a0.png
image.png

像这样写成一种固定的格式,里面包含了原始位置和输出后的位置,单词,同时还有原始文件名,因为可能把多个文件进行处理输出,如果不写文件名,就不知道输入位置来自哪个文件

输出后的单词映射关系
Javascript0|0|a.js|0|7|SourceMap
is0|11|a.js|0|0|I
awesome0|14|a.js|0|2|Love

我们可以优化一下,把a.js和最后面的单词提出来各放到一个数组里,用sources记录所有的原始文件名,names记录原始文件中的所有单词,然后用下标表示他们,以Love为例,就变成

8ae540efb20f613743b64229930ece88.png
image.png

很多时候,我们输出的文件其实是只有一行的,所以可以把输出文件的行号省略掉,就变成

8fa5735e04a7cc788293125cfb1d5e1a.png
image.png

考虑到,如果文件特别大的话,那么行列的数值可能会特别大,所以可以考虑用相对位置来代替绝对位置来表示,只用绝对位置表示第一个单词的位置,后面的都使用相对前一个单词的位置

8e26412af1dd6a60f60df4d3c2d5d511.png
image.png
原始单词输入位置输出单词输出位置映射
I(0,0) 绝对位置is(0,11)绝对位置11|0|0|0|0
Love(0,2) 相对I的位置awesome(0,3)相对is的位置3|0|0|2|1
SourceMap(0,5) 相对Love的位置Javascript(0,-14) 相对awesome的位置-14|0|0|5|2

所以我们现在可以得到这么一个初步的map文件

{names: ['I', 'Love', 'SourceMap'],sources: ['a.js'],mappings: [11|0|0|0|0, 3|0|0|2|1, -14|0|0|5|2]
}

但是mappings这里十分难看,而且还需要用|来分隔,多占一个位置,用 vlq 编码就可以解决分隔数字的问题,他的核心思路是在连续的数字上做标记,我们先来理解一下,拿上面 mappings 属性的第一个为例,去掉|,然后在连续的字符上加上一个标记

110000

从左往右开始读取,数字1有标记,说明还有连续,再取下一个,是1,这个1没被标记,第一个数结束,所以第一个数是11

继续往下,0没被标记,说明是一个完整的数字,第二个数就是0

依此类推。。。

最终就能得到11,0,0,0,0

而 vlq 利用6位二进制数进行存储,其中第一位就表示是否连续的标识位,最后一位表示正数还是负数(0正数,1负数) ,中间只有4位,因此一个单元表示的范围为[-15,15],如果超过了就要用连续标识位了

看几个例子来理解,每一步的变化我都用不同颜色标记了

34769cc4586c2c3def4a2878c3027e5d.png
image.png

第三步(按...5554分割),最右边4位是因为他还需要额外多表示一位符号位,其余的都可以用5位来表示数值

倒数第二步倒顺序,是因为VLQ表示数据字节组的顺序是倒过来的

最终我们可以得到他们的 vlq 编码

十进制数值vlq编码vlq编码每一段对应的数值
500101010
-19100111 00000139和1

然后再把它转成base64编码,可以查下面这张表

1b752a30ea01efc698911a7489b1b465.png

就可以得到5和-19的base64 vlq编码了,因为5的 vlq 编码数值是10,所以查上表可得到K,同理-19可以得到n和B,最终能得到5和-19的 base64 vlq 编码分别是K和nB

这里有一个网站可以自己转换验证一下https://www.murzwin.com/base64vlq.html

然后我们回过头为我们最开始那个简单的js文件手动生成一下map文件来验证一下

const value = 123;
console.log(value);

打包后的代码

console.log(123);
//# sourceMappingURL=bundle.js.map

sources和names是可以先确定好的

{sources : [ a.js ],names : [ console ,  log ],
}

再得到 base64 vlq 编码


原始位置输出位置sources索引names索引映射每一部分的vlq编码base64 vlq编码
console(1,0)绝对(0,0)绝对00001
log(0,8)相对(0,8)相对01800
123(-1,6)相对(0,4)相对040-1

所以我们可以得到最终的map文件

{sources : [ a.js ],names : [  console ,  log ],mappings :  AACAA,QAAQC,IADM ,// ...其他的
}

反过来也能根据sourcemap文件推出原始的位置,这里就不再演示了

SourceMap总结

  • 映射转换过后的代码和源代码之间的关系

  • 代码中引入 //# sourceMappingURL=xxx.js.map 启用

  • source Map 解决了源代码和运行代码不一致所产生的问题

  • 不只是js文件有,css文件也有

  • 核心原理是 base64 vlq 编码

感谢巨人

https://juejin.cn/post/7023537118454480904

https://juejin.cn/post/6963076475020902436

https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.1ce2c87bpj24

https://www.ruanyifeng.com/blog/2013/01/Javascript_source_map.html

https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/

http://www.qiutianaimeili.com/html/page/2019/05/89jrubx1soc.html

 - END -

834907ddc94d0fac5fae381b5ad8b27d.png

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

589e66466a7011c78475930f844953a9.png



推荐阅读
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • 本文详细解析了JavaScript中相称性推断的知识点,包括严厉相称和宽松相称的区别,以及范例转换的规则。针对不同类型的范例值,如差别范例值、统一类的原始范例值和统一类的复合范例值,都给出了具体的比较方法。对于宽松相称的情况,也解释了原始范例值和对象之间的比较规则。通过本文的学习,读者可以更好地理解JavaScript中相称性推断的概念和应用。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
author-avatar
伊丽汇
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有