UI5 Source code map 机制的细节介绍

在我的博客A debugging issue caused by source code mapping里我介绍了在我做 SAP C4C 开发时遇到的一个曾经困扰我很久的问题,最后结论是这个问题由于 JavaScript 的 source code map 机制在 Chrome 开发者工具里起作用,其实是 working as designed 的一种行为。但是当时因为时间限制,没有去深入学习 JavaScript source code map 的更多细节。
在这篇文章里我用一个简单的 UI5 应用来研究该机制。这个应用的 UI 仅仅包含一个 Button,点击之后弹出一个 Message Toast。
下面是我 XML view 和 Controller 的实现。


打开 Chrome 开发者工具里的 source code map 开关:

然后浏览器里访问这个 UI5 应用,我们就能在 Chrome 开发者工具里看到这些 UI5 库文件的调试版本(.dbg.js)。但是在 Chrome 开发者工具的 Network 标签里,我们观察不到这些调试版本文件的加载。那么问题来了:这些.dbg.js 文件从哪里来的?


当关闭 Chrome 开发者工具的 source code map 功能之后,我们在 Chrome 开发者工具里再也观察不到这些.dbg.js 文件了。将下图和 source code map 打开时的截图做比较:

如何在本地找到 sap-ui-core.js.map 文件
单击 sap-ui-core.js,在其最后一行 1875 行,看到该行内容:
这个文件的后缀.map 给了我们提示:其作用就是维护位置映射关系,将 sap-ui-core.js(压缩之后的文件)里的代码位置映射到压缩之前的代码位置(来自压缩之前的文件名,代码行数,代码列数,涉及到的压缩之前的 JavaScript 变量名)。

但是,同样的,我在开发者工具的 Network 标签里也观察不到这个.map 文件被加载。

在 Chrome 里输入 url: "chrome://net-internals/#events", 结果显示确实有一个 url 请求去访问 sap-ui-core.js.map, 只是因为本地磁盘缓存能响应该请求, 因此没有产生真正的网络请求:

在 Chrome 里输入"chrome://cache"能看到 Chrome 本地的所有缓存,从这里我成功找到了文件 sap-ui-core.js.map 的本地缓存。

单击该超链接能看到这条缓存的抬头信息。但是缓存的具体文件内容显示格式为 HEX,没法直接分析。

因此我使用了工具Cache viewer for Google Chrome Web browser, 将该缓存导出成本地文件。

sap-ui-core.js.map 文件内容一览
这篇博客Introduction to JavaScript Source Maps介绍了 JavaScript source code map 的基本知识。
文件 sap-ui-core.js.map 的内容:

version: 3
.map 文件的各组成部分的作用和含义定义在一个叫做Source Map Revision Proposal的协议文档里,在我的例子 sap-ui-core.js.map 里使用了该协议的第三版。
sources:
这是一个数组,包含了所有用于生成压缩之后的 js 文件的原始文件的名称。
names:
这是一个数组,包含了原始 js 文件里出现的 JavaScript 变量和属性名称。
下面是一个例子,体现了原始文件之一 Device-dbg.js 里的变量名称和其在 sap-ui-core.js.map 文件里的 names 数组里的对应记录,方便您理解。

mappings:
.map 文件最重要的部分,定义了原始文件内的位置和生成压缩版本文件内位置的对应关系。对应关系记录的粒度是基于压缩之后文件的每一行,用分号隔开。这样做的好处是无需再分配而外的位来维护压缩文件位置的行号信息。

回到我的例子,压缩文件 sap-ui-core.js 一共包含 1874 行,因此 sap-ui-core.js.map 一共出现了 1874 次分号,每个分号内又是一个很长的字符串,由一系列逗号隔开,这些由逗号隔开的字符串片段称为 Segment。每个 Segment 维护了一个位置的映射关系。

如何生成.map 文件
有很多开源的组件用于生成.map 文件,其中之一是 Google Closure compiler。假设我想基于我的测试应用里的 controller 实现文件 App.controller.js 生成一个压缩版本的文件:
从Google网站下载 compile.jar, 然后生成一个名为 script-min.js 的压缩文件和 script-min.js.map:
生成的压缩文件 script-min.js 只有 1 行内容:

生成的 script-min.js.map 内容:

可以使用vlq.js将这些 segment 解码:

浏览器打开该 html,产生如下输出:每个 segment 由 4 或 5 个字符组成。

每一位的对应含义:
第一位,表示这个位置在转换后的压缩文件的第几列。
第二位,sources 数组中的索引,表示这个位置来自哪一个原始文件。
第三位,表示这个位置属于原始文件的第几行。
第四位,表示这个位置属于原始文件的第几列。
第五位,names 数组中的索引,表示这个位置属于源文件中的哪一个变量。
关于 VLQ 编码的更多细节,可以阅读这篇博客Source Maps under the hood – VLQ, Base64 and Yoda
要获取更多 Jerry 的原创技术文章,请关注公众号"汪子熙":

版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/e13ee57c7d16012714e8263ca】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论