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文件夹里面,存在着这么一些文件:

让我们依次打开文件看看内容:

我们发现编译之后的代码文件里面,有这么一行代码,其实它指向的就是我们的map文件:

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

我们打开对应的.map文件看看内容:

这么看起来太费劲了,让我们稍微整理一下:

{//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

以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的相关网站进行查阅,我们这里只说结果:

如图所示,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编码?

如图所示,137 通过Base64 VLQ表示为yl

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

七、总结

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

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

参考文章

JavaScript Source Map 详解
sourceMap是个啥

sourceMap到底是个啥玩意?相关推荐

  1. maven到底是个啥玩意

    maven到底是个啥玩意 在搞懂maven之前看了几次重复的maven的教学视频.不知道是自己悟性太低还是怎么滴,就是搞不清楚,现在弄清楚了,基本上入门了.写该篇博文,就是为了帮助那些和我一样对于ma ...

  2. 让我们搞搞清楚重写toString()到底是个什么玩意?

    OKOK自从开始java面向对象课程之后,作业里多了一个要求  overriding toString() Fine  我身边很多人对此很困惑,这尼玛到底是个啥玩意哪里来的?为什么要重写呢?那么我来解 ...

  3. TTY 到底是个什么玩意?

    先来回答一道面试题:我们知道在终端中有一些常用的快捷键,Ctrl+E 可以移动到行尾,Ctrl+W 可以删除一个单词,Ctrl+B 可以向前移动一个字母,按上键可以出现上一个使用过的 shell 命令 ...

  4. Elasticsearch的mapping到底是个什么玩意?

    编程界的小学生 一.举例 1.数据准备 2.搜索 3.分析 二.Mapping 1.是什么 2.如何查看 3.创建mapping 3.1.语法 3.2.Demo 3.3.analyzer字段释义 3. ...

  5. maven到底是个啥玩意~

    我记得在搞懂maven之前看了几次重复的maven的教学视频.不知道是自己悟性太低还是怎么滴,就是搞不清楚,现在弄清楚了,基本上入门了.写该篇博文,就是为了帮助那些和我一样对于maven迷迷糊糊的人. ...

  6. locale到底是个什么玩意

    关于locale的设定,为什么要设定locale 关于locale的设定 locale是国际化与本土化过程中的一个非常 重要的概念,个人认为,对于中文用户来说,通常会涉及到的国际化或者本土化,大致包含 ...

  7. Unity Mesh、MeshFilter、MeshRenderer到底是个啥玩意

    1.首先我们创建一个Cube在Unity中,在Hierarchy中点击右键3d Object ->Cube.然后查看右侧的Inspector窗口,如果没有的话,看顶部的Windows菜单,里面有 ...

  8. Symbol到底是个啥玩意

    Symbol基础: Symbol是唯一的数据结构, 只要你声明了Symbol, 那么就是唯一的了. 直接声明: let ck = Symbol('ck') // 后面的'ck'不是Symbol存储的值 ...

  9. maven(一) maven到底是个啥玩意~

    我记得在搞懂maven之前看了几次重复的maven的教学视频.不知道是自己悟性太低还是怎么滴,就是搞不清楚,现在弄清楚了,基本上入门了.写该篇博文,就是为了帮助那些和我一样对于maven迷迷糊糊的人. ...

  10. 怎么用python写名字_python中的__name__ 到底是个什么玩意?应该怎么用到它?

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 以下文章来源于腾讯云 作者:Python进击者 ( 想要学习Python?Pyth ...

最新文章

  1. 003 PECompact 2.55
  2. Python笔记-XPath定位
  3. blender 可视化编程_使用Blender可视化天体物理学数据
  4. Java基础视频笔记(四):泛型
  5. java面试①整体流程
  6. 能打开pdf格式的软件
  7. 基于聚类的个性化推荐电商案例分析总结
  8. java计算机毕业设计的健身房管理系统MyBatis+系统+LW文档+源码+调试部署
  9. 深度搜索和广度搜索特点的深刻理解
  10. 中国天气预报,天气现象要素说明
  11. 如何提高Python代码的可读性?
  12. 谷歌提前关闭 G+;春运 12 天后开售,“候补购票”功能将上线 12306
  13. Docker学习——DockerFile
  14. GICv3软件overview手册之GICv4对虚拟LPI的直接注入(2)
  15. excel高级筛选怎么用_表格技巧—Excel高级筛选怎么用
  16. 指针,指针变量,指针变量指向的一些理解
  17. 冒充java诈骗_天下代码一大抄,整个案例的搬是什么鬼!疑似冒充蚂蚁金服高级Java开发工程师?你大爷...
  18. 全功能智能车之暂时放弃PC端程序的研究(第十篇)
  19. GUI编程,仿Windows系统mspaint画板的伪实现
  20. php删除cookie值,php如何删除cookie

热门文章

  1. 从OpenStack到OpenInfra
  2. 【数学建模】基于matlab单列多服务台排队系统仿真【含Matlab源码 1698期】
  3. 【图像融合】基于matlab IHS变换与自适应区域特征遥感图像融合【含Matlab源码 1636期】
  4. 【时间序列预测】基于matlab RBF神经网络时间序列预测【含Matlab源码 1336期】
  5. 【元胞自动机】基于matlab激进策略元胞自动机三车道(开放辅路,软件园影响)交通流模型【含Matlab源码 1298期】
  6. java class getfields_JAVA反射中的getFields()方法和getDeclaredFields ()方法的区别
  7. java课程心得_Java课程感想
  8. leetcode记录-罗马数字转整数
  9. 010 异步处理Rest服务
  10. 面对网络灾难风暴Fortinet安立方打造完美方舟