sourceMap到底是个啥玩意?

sourceMap到底是个啥玩意?

一、前言

sourceMap是一个由来已久的名词,自从2013年jQuery开始支持以来,开始逐渐广泛的被应用于各种打包工具上,最具标志性的便是前端er必须具备的webpack。

webpack是一个模块打包工具,在使用的过程中有许多配置项可以选择,例如:source-map、cheap-module-source-map、cheap-source-map、eval-source-map等等(webpack的sourceMap是什么?),所以我觉得有必要了解一下sourceMap到底是做什么的,到底是怎么来的。

二、为什么选择sourceMap?

这里抛出三个问题,大家自己带着问题去阅读本文,寻找答案。

  1. sourceMap为什么会出现?能做什么?
  2. sourceMap为开发者带来了什么便利?
  3. sourceMap较其他同类工具有什么区别?优点?

三、什么是sourceMap?

简单来说,sourceMap是一个.map文件,里面储存着位置信息,这个文件里保存的,是转换后代码的位置,和对应的转换前的位置。有了它,出错的时候,通过断点工具可以直接显示原始代码,而不是转换后的代码,为开发者带来巨大的便利。

现如今各大框架横行,JavaScript脚本正变得越来越复杂。大部分源码(尤其是各种函数库和框架)都要经过转换,才能投入生产环境。

然而我们熟知的转换过程,也就是发布成线上代码,是需要经过几个步骤的:

  • 压缩,减小体积
  • 多个文件合并,减少HTTP请求数
  • 通过编译或者转译,将其他语言编译成JavaScript

通常,JavaScript的解释器会告诉你,第几行第几列代码出错。但是,这对于转换后的代码毫无用处。举例来说,原本一个代码量及其庞大的库或者是框架,就好比说jQuery 1.9,压缩后只有3行,每行3万个字符,所有内部变量都改了名字。你看着报错信息,感到毫无头绪,根本不知道它所对应的原始位置。
这就是sourceMap想要解决的问题。

sourceMap的具体内容

拿vue举个例子,npm run build 之后的dist文件夹里面,存在着这么一些文件:
sourcemap的结构
让我们依次打开文件看看内容:
soucemap结构
我们发现编译之后的代码文件里面,有这么一行代码,其实它指向的就是我们的map文件:

//# sourceMappingURL=about.747bb252.js.map

我们打开对应的.map文件看看内容:
sourcemap结构
这么看起来太费劲了,让我们稍微整理一下:

{
	//SourceMap的版本,目前为3
	"version":3,
	
	//转换前的文件,该项是一个数组,表示可能存在多个文件合并
	"sources":["webpack:///./src/views/About.vue?da29","webpack:///./src/views/About.vue"],
	
	//转换前的所有变量名和属性名
	"names":["render","_vm","this","_h","$createElement","_self","_c","_m","staticRenderFns","staticClass","_v","script","component"],
	
	//记录位置信息的字符串
	"mappings":"8GAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAsBH,EAAII,MAAMC,GAAO,OAAOL,EAAIM,GAAG,IACnGC,EAAkB,CAAC,WAAa,IAAIP,EAAIC,KAASC,EAAGF,EAAIG,eAAmBE,EAAGL,EAAII,MAAMC,IAAIH,EAAG,OAAOG,EAAG,MAAM,CAACG,YAAY,SAAS,CAACH,EAAG,KAAK,CAACL,EAAIS,GAAG,+B,YCAtJC,EAAS,GAKTC,EAAY,eACdD,EACAX,EACAQ,GACA,EACA,KACA,KACA,MAIa,aAAAI,E","file":"js/about.747bb252.js",
	
	//转换前的文件内容列表,与sources列表依次对应
	"sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _vm._m(0)}\nvar staticRenderFns = [function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"about\"},[_c('h1',[_vm._v(\"This is an about page\")])])}]\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./About.vue?vue&type=template&id=1ae8a7be&\"\nvar script = {}\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n  script,\n  render,\n  staticRenderFns,\n  false,\n  null,\n  null,\n  null\n  \n)\n\nexport default component.exports"],
	
	//转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空
	"sourceRoot":""
}

没有什么难的对吧?都很好理解,不过让我们来看看有趣的地方。

四、mappings的解析

map文件的mappings属性。这是一个很长的字符串,它分成三层。
第一层是行对应,以分号(;)表示,每个分号对应转换后源码的一行。所以,第一个分号前的内容,就对应源码的第一行,以此类推。
第二层是位置对应,以逗号(,)表示,每个逗号对应转换后源码的一个位置。所以,第一个逗号前的内容,就对应该行源码的第一个位置,以此类推。
第三层是位置转换,以VLQ编码表示,代表该位置对应的转换前的源码位置。

以"AAAAA,BBBBB;CCCCC"为例,就表示,转换后的源码分成两行,第一行有两个位置,第二行有一个位置。

五、mappings位置对应的原理

其实每个位置使用五位,表示五个字段。从左边算起的话,大概是这样:
第一位,表示这个位置在(转换后的代码的)的第几列。
第二位,表示这个位置属于【sources属性】中的哪一个文件。
第三位,表示这个位置属于转换前代码的第几行。
第四位,表示这个位置属于转换前代码的第几列。
第五位,表示这个位置属于【names属性】的哪一个变量

举个例子 :假设现在有a.js,内容为feel the force,处理后为b.js,内容为the force feel
举例mapping位置关系
以feel为例,它在输出中的位置是(0,9),a.js是sources的第1个(这里只是举例),输入中的位置是(0,0),feel是names的第1个(这里只是举例)。

那么映射关系为:
0 9 1 0 1

最后将 09101 表示为 Base64 VLQ 即可。

值得说明的几点是:

  1. 所有的值都是以0作为基数
  2. 五位不是必需的,如果该位置没有对应names属性中的变量,可以省略第五位
  3. 每一位都采用VLQ编码表示,由于VLQ编码是可变长的,所以每一位可以由多个字符构成
  4. 为什么不保存转换后代码的行号,因为我们输出的文件总是一行,这样输出的行号就可以省略,因为都是0,没必要写出来
  5. 对于输出后的位置来说,到后边会发现它的列号特别大,为了避免这个问题,采用相对位置进行描述

到这里又有小伙伴要问了,那啥是相对位置啊?让我们看看示意图:

相对位置示意图
第一次记录的输入位置和输出位置是绝对的,往后的输入位置和输出位置都是相对上一次的位置移动了多少,例如the的输出位置为(0,-10),因为the在feel的左边数10下才能到这个位置。

六、VLQ,Base64 VLQ

最后,谈谈如何用VLQ编码和Base64 VLQ表示数值。VLQ是Variable-length quantity
的缩写,是一种通用的、使用任意位数的二进制来表示一个任意大的数字的一种编码方式。这种编码最早用于MIDI文件,后来被多种格式采用。它的特点就是可以非常精简地表示很大的数值,用来节省空间。

这种编码需要用最高位表示连续性,如果是1,代表这组字节后面的一组字节也属于同一个数;如果是0,表示该数值到这就结束了。

让我们举个例子,方便理解: 如何对数值137进行VLQ编码?
VLQ举例
这个规则是怎么来的,俺也不太清楚,具体的大家可以浏览VLQ的相关网站进行查阅,我们这里只说结果:

如图所示,137的VLQ编码形式为10000001 00001001

Base64 VLQ与一般的VLQ不一样,他在转换步骤上又增添了一些比较复杂的做法:

  1. 一个Base64字符只能表示 6bit(2^6)的数据
  2. Base64 VLQ需要能够表示负数,于是用最后一位来作为符号标志位
  3. 由于只能用6位进行存储,而第一位表示是否连续的标志,最后一位表示正数/负数。中间只有4位,因此一个单元表示的范围为[-15,15],如果超过了就要用连续标识位了
  4. 如果这组数是某个数值的VLQ编码的第一组字节,那它的最后一位代表"符号",0为正,1为负;
  5. 如果不是,这个位没有特殊含义,被算作数值的一部分

我们举个例子看看与VLQ的转换区别: 如何对数值137进行Base64 VLQ编码?
Base64 VLQ
如图所示,137 通过Base64 VLQ表示为yl

这里我们可以看出来,在VLQ中,编码顺序是从高位到低位,而在Base64 VLQ中,编码顺序是从低位到高位,因为它比VLQ多了一个倒序排序的操作。

七、总结

现在我们可以回答文章开头的几个问题了:

  1. sourceMap为什么会出现?能做什么?
    答:sourceMap的出现就是因为现如今的框架和库很多,打包之后才能被浏览器识别渲染,这无疑对我们开发者来讲是不友好的。sourceMap能做的就是快速定位问题存在的位置。
  2. sourceMap为开发者带来了什么便利?
    答:快速定位bug,使项目投入生产环境之后,如果出现bug的时候,可以快速定位到bug的位置,从而快速解决问题,提高开发效率。
  3. sourceMap较其他同类工具有什么区别?优点?
    答:暂时没有其他工具可以进行相互对比。sourceMap的优点就是不会很难理解,上手门槛不高,且能为开发者们带来便利,提高开发效率。

参考文章

JavaScript Source Map 详解
sourceMap是个啥

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值