debug 工具 —— source-map

一直都或多或少听说过 source-map,但却从未仔细学习过其具体内容。
谨以此篇文章,鞭策自己,砥砺前行。


一、why source map ?

随着前端的不断发展,前端技术栈中,JavaScript 所占比重越来越大,与之对应的在网络中传递的 JS 文件也随之变得越来越大,尤其是单页 web 应用(SPA)中打包出来的 JS 文件,往往更是会达到以 M 为单位的大小。为了提高用户体验,加快网络请求速度,作为开发者,我们不得不针对这一问题进行优化,常见的方式有 Gzip 压缩、合理利用缓存、Tree-shaking、文件(请求)合并、代码压缩等方式。其中,文件压缩、文件合并等操作则会导致线上代码与我们开发的代码呈现出一定的差异性,此外,文件/语言的编译(如 Less ->
CSS, TypeScript -> JavaScript)也会导致类似的结果,使得我们线上代码的难于维护和调试,于是 source map 便应运而生了。

二、source map 的适用场景

  • 代码压缩(compress/uglify 等)
  • 多文件合并(用于减少 http 请求数)
  • 语言预处理、编译等(es6、jsx 编译成 es5 版本,TypeScript 编译成 JavaScript 等)
  • ......
    理论上来说,开发版本的代码跟线上运行的代码不完全一致的时候,都可以使用 source map 技术,当然,前提是有相应的工具支持,或者你自己开发一个。

三、source map 的实际效果

首先,source map 功能需要浏览器的支持,较新的 firefox/chrome 的现代浏览器大都自动开启了 source map 功能,比如在 chrome 中,点击勾选上 Settings 里的 Enable JavaScript source maps 选项即可,较新版本的 chrome 是默认开启的。

step-1
step-2

然后,对于如下的一小段 js 源代码(请智能忽略掉一些细节):

(function () {
    var data = 1;
    function foo() {
        var data = 2;
        console.log(data);
        console.log(2 + foo2(data));
        return data + data;
    }
    function foo2(data) {
        return data + 2;
    }
    console.log(data);
    console.log(foo());
})()

总的来说,功能上还是比较简单的,但当它被 uglify 压缩之后,大致会变成如下的样子:

console.log(1),console.log((console.log(2),console.log(6),4));

你还能看出它的原本的样子和作用么?
就算使用了类似 chrome 中的 pretty print 之类的工具,也大致只能变成下面的样子:

pretty print 版本

跟源代码仍旧相去甚远。

而当我们打开 source map 处理之后的文件(一般存放在同目录下,chrome 中在其文件名后面添加了一个 [sm],应该是 source map 的缩写):

source map 版本

在这个虚拟出来的文件上,你可以正常地进行断点调试等任何工作,相比于压缩后的代码,这跟源文件基本无异的代码无疑方便很多。

四、怎么使用 source map ?

代码层面,要做的其实很少,只需要在编译之后的代码的最后,加上一行

//@ sourceMappingURL=/path/to/file.js.map

就可以了,但实际上,通常情况下,连这一步都是由编译打包工具完成的,常见的 gulp、webpack 等工具,里面都有支持 source map 的插件、中间件等。以 gulp 为例,我们就可以在其配置文件 gulpfile.js 中,增加 gulp-sourcemaps 插件相关配置,配合 gulp-uglify,我们可以轻松实现代码的压缩、以及 source map 支持。具体配置可以参考:点击这里,具体效果参考:点击这里。需要特别注意的一点是,gulp-sourcemaps 需要一定的 插件支持

五、source map 具体原理

source map,source 指源头、来源,而 map 意为映射、投影,其命名方式就已经基本说明了其具体原理——通过某种方式,将压缩、编译处理过的线上文件映射成文源文件。


source map

而 source map 主要分两个步骤:

  1. 在生成线上文件的同时,生成一个映射规则;
  2. 根据映射规则,将线上文件映射成源文件。
    第一步一般由开发者通过自己的某些工具(如 gulp/webpack)等生成一个 .map 文件:
{
    // source map的版本,目前为 3
    "version": 3,
    // 处理前的文件名数组(可能由多个文件进行合并)
    "sources": ["index.js"],
    // (可选)处理前的所有变量名和属性名
    "names": ["console", "log", "data"],
    // VLQ 编码的文件内容位置映射规则
    "mappings": "AAWIA,QAAQC,IAVG,GAWXD,QAAQC,KARJD,QAAQC,IADG,GAEXD,QAAQC,IAAI,GACLC",
    // 处理后的文件名
    "file": "index.js",
    // (可选)所有源文件的内容
    "sourcesContent": ["(function () {\n    var data = 1;\n    function foo() {\n        var data = 2;\n        console.log(data);\n        console.log(2 + foo2(data));\n        return data + data;\n    }\n    function foo2(data) {\n        return data + 2;\n    }\n    console.log(data);\n    console.log(foo());\n})()"]
}

不难看出,这其实就是一个 json 格式的文本文件。在线上文件的末尾添加上

//@ sourceMappingURL=/path/to/file.js.map

就指明了线上的这个文件,可以通过 /path/to/file.js.map 进行 source map 处理。


第二步,解析映射规则,并将线上文件通过它映射成源文件,这个操作一般由支持 source map 的浏览器来完成。

整个映射规则中,最重要的就是 mappings 属性。它是一个比较长的字符串,总的分为三层:

  • 第一层是行对应,以分号(;)分隔,分号间的内容对应一行线上代码。例如我们上面的例子里,没有分号(可以认为是省略了末尾的分号),则是因为线上代码被压缩成了一行。
  • 第二层是位置对应,以逗号(,)分隔,逗号间的内容对应该行线上代码的某个位置。
  • 第三层是位置映射,以VLQ编码表示,代表该位置对应的源代码位置。

此外,每个位置使用五个字符,分别表示五个字段:

  • 第一位,表示这个位置在线上代码中位于第几列。
  • 第二位,表示这个位置属于sources属性中的哪一个源文件。
  • 第三位,表示这个位置属于源代码的第几行。
  • 第四位,表示这个位置属于源代码的第几列。
  • 第五位,(可选)表示这个位置属于names属性中的哪一个变量。

上面的每一位,同时都使用的 VLQ 编码,它使用的是与 base64 相同的码表。


base64 VLQ

它以 [A-Z]、[a-z]、[0-9] 以及 “+”、“/”来表示一个 64 进制数。

例如上面的“AAWIA”,如果以 10 进制数则可以表示成 “0, 0, 22, 8, 0”(逗号只是为了方便查看添加的,实际上不存在)。但事实上,我们知道,数据在计算机中,都是以二进制数存储和计算的,而一位 64 进制数,可以表示为 6 位 2 进制数,所以这里又可以按二级制数表示成“000000, 000000, 010110, 001000, 000000”。事实上,VLQ 也是按照二进制生效的。这 6 位 2 进制数中,

  Continuation
  |     Sign
  |     |
  V     V
  10101 1
  • 首位代表是否连续,如果是 1,代表这6个位后面的6个位也属于同一个数;如果是0,表示该数值到这6个位结束;
  • 末位为符号位,如果是 0,代表该数值是一个整数;如果是 1 代表该数值是一个负数;
  • 中间 4 位才是真正的数值位。
    首位的存在,意味着 VLQ 可以采用多个连续的编码来代表一个很大的数字,而末尾符号位的存在,意味着 VLQ 可以同时表示正数和负数。4 位 2 进制数,可以表示 10 进制的 [0-15],再算上符号位,实际上,每个编码实际上能表示 -15 到 15 共 31 位数(+0,-0 如果算作同一个)。而之前提到的“AAWIA”,真正代表的其实是“0, 0, 11, 4, 0”,对应的即:
  • 线上代码中的第 1 列。
  • 源文件是 .map 文件中 sources 文件中的第 1 个,即 index.js。
  • 源代码的第 12 行。
  • 源代码的第 5 列。
  • . map 文件中 names 属性中的第 1 个变量,即 console。
    综上,也就是下面图中红色框标注的位置:
demo

更多关于VLQ 编码的内容,请参考 wiki

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 1995年,演员克里斯托弗·里夫从马上摔下,他的脖子被摔断,脊椎和大脑之间的联系被切断,导致颈部以下完全瘫痪。 医...
    预约晴天阅读 289评论 0 0
  • 《第五十六号教室》 身为教师的我们,让学生处于十字路口,感到困惑、迷惘无法做出选择时,就应该给予正确的引导。...
    小亮_f6c6阅读 147评论 0 0
  • 让我来发挥一下剪报君的特长,下面是百度百科对[全栈工程师]的说明: 全栈工程师,也叫全端工程师,英文Full St...
    安晓辉0阅读 14,923评论 15 96