JavaScript 中的 Source Maps 简单介绍

发布于 2023-03-31 09:53:48 字数 12959 浏览 96 评论 0

您是否曾经发现自己希望在不影响性能的情况下保持客户端代码的可读性和可调试性,更重要的是,即使在合并和缩小代码之后也是如此? 的魔力 那么现在您可以通过源映射

基本上,这是一种将组合/缩小文件映射回未构建状态的方法。当您为生产构建时,连同缩小和组合您的 JavaScript 文件,您会生成一个源映射,其中包含有关您的原始文件的信息。 当您在生成的 JavaScript 中查询特定的行号和列号时,您可以在返回原始位置的源地图中进行查找。 开发人员工具(当前为 WebKit 每晚构建、Google Chrome 或 Firefox 23+)可以自动解析源映射并使其看起来好像您正在运行未缩小和未组合的文件。

演示:获取原始位置

上面的演示允许您右键单击包含生成源的文本区域中的任意位置。 选择“Get original location”将通过传入生成的行号和列号来查询source map,并返回原始代码中的位置。 确保您的控制台已打开,以便您可以看到输出。

Source map demo screenshot

真实世界

在查看以下 Source Maps 的真实世界实现之前,请确保您已在 Chrome Canary 或 WebKit nightly 中启用源地图功能,方法是单击开发工具面板中的设置齿轮并选中“启用源地图”选项。 请参见下面的屏幕截图。

Firefox 23+ 在内置开发工具中默认启用源映射。 请参见下面的屏幕截图。

那个 Source Map 查询演示很酷,但是真实世界的用例呢? 查看 font dragr 的特殊构建 在启用源映射的情况下,在 Chrome Canary、WebKit nightly 或 Firefox 23+ 中的dev.fontdragr.com ,您会注意到 JavaScript 未编译,您可以看到所有它引用的单个 JavaScript 文件。 这是使用源映射,但实际上是在幕后运行编译后的代码。 任何错误、日志和断点都将映射到开发代码以进行出色的调试! 所以实际上它给你一种错觉,你正在生产中运行一个开发站点。

演示:在 fontdragr.com 上查看脚本面板(带有源映射)

我为什么要关心源映射?

目前,源映射仅在未压缩/组合的 JavaScript 与压缩/未组合的 JavaScript 之间起作用,但随着 CoffeeScript 等编译为 JavaScript 语言的讨论,甚至有可能添加对 SASS 或 LESS 等 CSS 预处理器的支持,未来看起来一片光明。

将来我们可以轻松地使用几乎任何语言,就好像它在带有源映射的浏览器中得到原生支持一样:

  • 咖啡脚本
  • ECMAScript 6 及更高版本
  • SASS/LESS
  • 几乎任何编译成 JavaScript 的语言

看看这个在 Firefox 控制台的实验版本中调试 CoffeeScript 的截屏视频:

Google Web Toolkit (GWT) 最近添加了 对 Source Maps 的支持 ,GWT 团队的 Ray Cromwell 做了一个很棒的截屏视频,展示了源地图支持的实际效果。

我放在一起的另一个示例使用 Google 的 Traceur 库,它允许您编写 ES6(ECMAScript 6 或 Next)并将其编译为 ES3 兼容代码。 Traceur 编译器还生成源映射。 看一下这个 ES6 特征和类的演示 ,就像它们在浏览器中得到本地支持一样,这要归功于源映射。 演示中的 textarea 还允许您编写 ES6,它将被动态编译并生成源映射以及等效的 ES3 代码。

Demo: Write ES6, debug it, view source mapping in action

源映射如何工作?

目前,唯一支持源映射生成的 JavaScript 编译器/压缩器是 Closure 编译器。 (稍后我会解释如何使用它。)一旦你组合并缩小了你的 JavaScript,它旁边就会有一个 sourcemap 文件。 目前,Closure 编译器不会在末尾添加特殊注释,以向浏览器开发工具表明源映射可用:

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

这使开发人员工具能够将调用映射回它们在原始源文件中的位置。 以前评论 pragma 是 //@ 但由于该问题和 IE 条件编译评论的 一些问题,决定 其更改为 //#。目前 Chrome Canary、WebKit Nightly 和 Firefox 24+ 支持新的评论 pragma。 此语法更改也会影响 sourceURL

如果你不喜欢奇怪的注释,你也可以在编译的 JavaScript 文件中设置一个特殊的标头:

X-SourceMap: /path/to/file.js.map

就像评论一样,这将告诉您的源映射消费者在哪里寻找与 JavaScript 文件关联的源映射。 此标头还解决了在不支持单行注释的语言中引用源映射的问题。

WebKit Devtools example of source maps on and source maps off

仅当启用源映射并打开开发工具时,才会下载源映射文件。 您还需要上传原始文件,以便开发工具可以在必要时引用和显示它们。

如何生成源地图?

就像我上面提到的,您需要使用 Closure 编译器 来缩小、合并和生成 JavaScript 文件的源映射。 命令如下:

java -jar compiler.jar \ 
   --js script.js \
   --create_source_map ./script-min.js.map \
   --source_map_format=V3 \
   --js_output_file script-min.js

两个重要的命令标志是 --create_source_map--source_map_format. 这是必需的,因为默认版本是 V2,我们只想使用 V3。

源地图的剖析

为了更好地理解源映射,我们将举一个由 Closure 编译器生成的源映射文件的小例子,并深入了解“映射”部分如何工作的更多细节。 示例略有不同 以下示例与V3 规范

{
  version : 3,
  file: "out.js",
  sourceRoot : "",
  sources: ["foo.js", "bar.js"],
  names: ["src", "maps", "are", "fun"],
  mappings: "AAgBC,SAAQ,CAAEA"
}

在上面你可以看到 source map 是一个包含大量有趣信息的对象字面量:

  • 源映射所基于的版本号
  • 生成代码的文件名(您的 minifed/组合生产文件)
  • sourceRoot 允许您在源文件前添加文件夹结构——这也是一种节省空间的技术
  • sources 包含合并的所有文件名
  • names 包含出现在整个代码中的所有变量/方法名称。
  • 最后,映射属性是使用 Base64 VLQ 值发生魔法的地方。 真正的空间节省是在这里完成的。

Base64 VLQ 并保持源映射较小

最初,source map 规范对所有映射都有非常冗长的输出,导致 sourcemap 的大小大约是生成代码的 10 倍。 第二版减少了大约 50%,第三版又减少了 50%,所以对于一个 133kB 的文件,你最终得到一个大约 300kB 的源映射。 那么他们是如何在保持复杂映射的同时减小尺寸的呢?

VLQ (可变长度量)与将值编码为 Base64 值一起使用。 mappings 属性是一个超级大的字符串。 此字符串中的分号 (;) 表示生成的文件中的行号。 每行中都有逗号 (,) 表示该行中的每个段。 这些段中的每一个在可变长度字段中都是 1、4 或 5。 有些可能看起来更长,但它们包含连续位。 每个片段都建立在前一个片段的基础上,这有助于减小文件大小,因为每个位都是相对于其先前片段的。

就像我上面提到的,每个段的可变长度可以是 1、4 或 5。 该图被视为具有一个连续位 (g) 的可变长度四。 我们将分解此部分并向您展示源地图如何计算出原始位置。 上面显示的值纯粹是 Base64 解码值,还有一些更多的处理来获得它们的真实值。 每个部分通常会解决五件事:

  • 生成的列
  • 这出现在原始文件中
  • 原始行号
  • 原创专栏
  • 如果可用的原始名称。

并非每个段都有名称、方法名称或参数,因此整个段将在四到五个可变长度之间切换。 上面段图中的 g 值是所谓的连续位,它允许在 Base64 VLQ 解码阶段进一步优化。 连续位允许您在段值上构建,这样您就可以存储大数字而不必存储大数字,这是一种非常聪明的节省空间的技术,其根源在于 midi 格式。

上图 AAgBC 进一步处理后将返回 0、0、32、16、1 – 32 是帮助构建以下值 16 的延续位。B 在 Base64 中纯粹解码为 1。因此使用的重要值是 0、0、 16, 1. 这让我们知道生成文件的第 1 行(行由分号保持计数)第 0 列映射到文件 0(文件 0 的数组是 foo.js),第 16 行第 1 列。

为了展示片段是如何解码的,我将引用 Mozilla 的 Source Map JavaScript 库 。 您还可以查看 WebKit 开发工具源映射代码。 同样用 JavaScript 编写的

为了正确理解我们如何从 B 中获取值 16,我们需要对按位运算符以及规范如何用于源映射有一个基本的了解。 (二进制 100000 或 32) ,前面的数字 g 被标记为连续位。 比较数字 (32) 和 VLQ_CONTINUATION_BIT 通过使用按位与 (&) 运算符

32 & 32 = 32
// or
100000
|
|
V
100000

这将在两个都出现的每个位位置返回 1。 所以 Base64 解码值 33 & 32 将返回 32,因为它们仅共享 32 位位置,如上图所示。 然后,这会将 移位值 每个前面的连续位的 增加 5。 在上面的例子中,它只移动了一次 5,所以将 1 (B) 左移 5。

1 << 5 // 32

// Shift the bit by 5 spots
______
|  |
V  V
100001 = 100000 = 32

然后通过将数字 (32) 右移一位,从 VLQ 有符号值转换该值。

32 >> 1 // 16
//or
100000
|
 |
 V
010000 = 16

所以我们有了它:这就是将 1 变成 16 的方法。这似乎是一个过于复杂的过程,但是一旦数字开始变大,它就更有意义了。

潜在的 XSSI 问题

该规范提到了使用源映射可能引起的跨站点脚本包含问题。 为了缓解这种情况,建议您在源地图的第一行前面加上“ )]}" 故意使 JavaScript 无效,因此会抛出语法错误。WebKit 开发工具已经可以处理这个问题。

if (response.slice(0, 3) === ")]}") {
  response = response.substring(response.indexOf('\n'));
}

如上所示,对前三个字符进行切片以检查它们是否与规范中的语法错误匹配,如果匹配则删除导致第一个换行实体 (\n) 的所有字符。

sourceURLdisplayName实际应用:Eval 和匿名函数

虽然不是源映射规范的一部分,但以下两个约定允许您在使用 evals 和匿名函数时使开发更加容易。

第一个助手看起来与 //# sourceMappingURL 属性,实际上在源映射 V3 规范中提到。 通过在将被评估的代码中包含以下特殊注释,您可以命名评估,以便它们在您的开发工具中显示为更合乎逻辑的名称。 查看使用 CoffeeScript 编译器的简单演示: 演示:请参阅 eval() 代码通过 sourceURL 显示为脚本

//# sourceURL=sqrt.coffee

另一个帮助器允许您使用 displayName在匿名函数的当前上下文中可用的属性。 配置 以下演示 以查看 displayName财产在行动。

演示:通过 displayName 命名匿名函数(仅限 Webkit Nightly)

btns[0].addEventListener("click", function(e) {
  var fn = function() {
    console.log("You clicked button number: 1");
  };

  fn.displayName = "Anonymous function of button 1";

  return fn();
}, false);

在开发工具中分析代码时 displayName 将显示属性而不是类似的东西 (anonymous). 然而,displayName 几乎是死的,不会进入 Chrome。 但是所有的希望都没有消失,并且已经提出了一个更好的建议,称为 debugName

在撰写本文时,eval 命名仅适用于 Firefox 和 WebKit 浏览器。 这 displayName 属性只存在于 WebKit nightlies 中。

让我们团结起来

目前,关于 将源映射支持 添加到 CoffeeScript 的讨论非常冗长。 去检查问题并添加对将源映射生成添加到 CoffeeScript 编译器的支持。 对于 CoffeeScript 及其忠实的追随者来说,这将是一个巨大的胜利。

UglifyJS 还有一个 源映射问题, 你也应该看看。

许多 工具 都可以生成源映射,包括 coffeescript 编译器。 我现在认为这是一个有争议的问题。我们可用的生成源映射的工具越多,我们的生活就会越好,所以请继续询问或向您最喜欢的开源项目添加源映射支持。

这并不完美

Source Maps 现在不适合的一件事是监视表达式。 问题在于,尝试在当前执行上下文中检查参数或变量名称不会返回任何内容,因为它实际上并不存在。 这将需要某种反向映射来查找您希望检查的参数/变量的真实名称与已编译 JavaScript 中的实际参数/变量名称的比较。

这当然是一个可以解决的问题,随着对源地图的更多关注,我们可以开始看到一些惊人的特性和更好的稳定性。

问题

最近 jQuery 1.9 在关闭官方 CDN 时添加了对源映射的支持。 它还指出了 一个特殊错误 在加载 jQuery 之前使用 IE 条件编译注释 //@cc_on 时的 。 从那时起,就有一个 承诺 通过将 sourceMappingURL 包装在多行注释中来缓解这种情况。 要吸取的教训不要使用条件注释。

这已经 通过将语法更改为 //#

工具和资源

以下是您应该查看的一些其他资源和工具:

源映射是开发人员工具集中非常强大的实用程序。 能够保持您的 Web 应用程序精简但易于调试是非常有用的。 对于新开发人员来说,它也是一个非常强大的学习工具,可以让他们了解有经验的开发人员如何构建和编写他们的应用程序,而不必费力地阅读难以阅读的缩小代码。 你在等什么? 立即开始为所有项目生成源地图。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
351 人气
更多

推荐作者

已经忘了多久

文章 0 评论 0

15867725375

文章 0 评论 0

LonelySnow

文章 0 评论 0

走过海棠暮

文章 0 评论 0

轻许诺言

文章 0 评论 0

信馬由缰

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文