2023年11月06日
为什么每个开发者现在都需要了解源代码映射
你做到了!当然,也许已经晚了四周,而且是在周五晚上,但你终于完成了长期期待的网页应用程序的更新部署。然而,你的庆祝活动被打断了,因为你的手机在桌子上不停地震动。拿起手机,你面对着开发者的噩梦。你接到了大量消息,说登录不再起作用了。难道是你的部署出了问题吗?这太糟糕了。如果用户无法登录,那么就无法使用网站。
你匆匆回到电脑前,手颤抖着输入密码。你导航到生产网站,点击“登录”按钮,然后在控制台中看到以下错误:
你茫然地盯着它。main.d1589c82.js
?这个文件在代码库中根本不存在。而且错误出现在第2行?第2行几乎总是一个导入语句。这是怎么回事?为什么它告诉你错误发生在一个不存在的文件中?这肯定是个错误……
为了解决这个谜团,理解源映射的威力,我们需要回到很久以前,一直回到数字革命的开始。
让编程成为可能的魔法
没有人能怀疑计算机在根本改变世界方面所起的作用。实际上,从儿童玩具到烤面包机,几乎所有东西都由某种形式的计算机驱动。然而,也许会让人感到惊讶的是,计算机是愚蠢的。事实上,非常愚蠢。它们需要被准确地告知该做什么以及何时做。此外,它们只能看到一和零,并且只能进行基本的数学运算。这与我们作为人类的思维和操作方式相去甚远。如果每个程序都必须用二进制编写,数字革命很可能永远不会发生。
这就是为什么几乎在第一台计算机被发明的同时,第一批编程语言也被发明出来。这些语言允许程序员以更符合人类习惯和理解的格式编写代码,然后再由编译器将其转换为机器码(一种对一和零的花哨称呼)。这就是所谓的抽象,它使现代通用计算机成为可能。
随着时间的推移,编程语言变得越来越抽象,以使软件工程师的工作变得更加容易。许多语言甚至不直接编译为机器码;它们将另一种语言视为机器码,并编译为该语言。更高级的方法是创建两种编程语言:第一种是供程序员交互使用的友好可读语言,然后将其编译为第二种不太可读的语言,以便计算机更容易地进行优化速度或内存使用等操作。然后,这种语言最终被编译为机器码。这种第二种语言被称为中间表示。
这就把我们带回了JavaScript。JavaScript就是这样一种不直接编译为机器码的语言。当用户访问您的网站时,浏览器会向托管网站的服务器发送后台请求,然后服务器会响应所需的HTML和JavaScript,以在您的浏览器中呈现页面。然后,浏览器的引擎将此JavaScript编译为中间表示,当运行时再转换为机器码。
这听起来可能很复杂,但大多数情况下,编写JavaScript代码时,您无需担心任何这些复杂性。您所要做的就是编写和部署您的代码,它就像魔术一样工作。
然而,这种抽象已经在您编写的代码和实际执行的代码之间产生了差距。开发环境会尽最大努力为您弥合这一差距,以便您无需考虑它。然而,在生产环境中,您通常不会得到那种帮助。这就是我们示例中发生的情况——浏览器位于差距的一侧,而您位于另一侧。它报告的错误代码位置是正确的,但仅适用于它正在执行的代码。正如我们所探讨的那样,这不一定是您编写的相同代码。
所以现在我们知道为什么错误指向“不存在”的代码,但我们该如何处理呢?我们如何跨越这个差距呢?
通过源映射跨越差距
好吧,当我说有一个单一的差距时,我是在简化。实际上,有许多差距;每个抽象级别都包含自己需要跨越的差距。不过,现在让我们专注于导致我们看到难以理解错误的与JavaScript相关的差距。由于需要通过互联网获取代码,这个过程越快,您的网站就能更快地加载给用户。因此,大部分的JS都是“被压缩的”,这基本上意味着所有不必要的空格和换行都被移除,以尽可能减小文件大小,以便尽可能快速、便宜地发送。这就是我们问题的关键所在。浏览器报告的错误是在被压缩的代码中,但我们正在处理未被压缩的源代码。我们该如何解决这个问题呢?
最终,这就是我们要谈到的源映射的话题。源映射是一种至关重要的调试工具,它提供了从编译或压缩代码到原始源代码的映射。正如我们所见,编译代码中的错误可能是一场追溯到原始代码根源的噩梦。让我们深入了解源映射是如何做到这一点的。
源映射的解剖
所以我们知道源映射解决了什么问题,但它是如何做到的呢?源映射是一个包含信息的JSON文件,用于将处理后的代码映射回原始源文件。以下是一个源映射的示例,以及每个字段的解释:
{ "version": 3, "file": "minified.js", "sourceRoot": "", "sources": ["original.js"], "names": ["myFunction", "x", "y"], "mappings": "AAAA,0BAA0B;AAC1B,UAAU,EAAC;AACV,MAAM,EAAC,WAAW,GAAG,IAAI,CAAC" }
- Version :指示源映射的版本,
3
是当前版本。这个版本不是特定于您的应用程序,而是源映射标准的版本。 - File :指定此源映射对应的被压缩或编译文件的名称。
- SourceRoot :一个可选属性,可以指定所有源文件的根URL(这里为空)。
- Sources :原始源文件的路径或URL数组。在这种情况下,只有一个名为
original.js
的源文件。 - Names :列出在压缩或编译过程中可能被更改的一些符号。
- Mappings :包含编码映射信息,这是源映射的核心部分。它可能看起来像一组随机字母,但实际上这是一个[可变长度数量(VLQ)](https://en.wikipedia.org/wiki/Variable-length_quantity#:~:text=A%20variable%2Dlength%20quantity%20(VLQ,to%20mark%20continuation%20of%20bytes)编码的信息,解释了如何将代码中的特定符号映射到被压缩或编译的代码中。
这只是一个简单的示例,实际的源映射可能会复杂得多。特别是映射信息通常是由构建工具自动生成的,并且可能非常复杂,对应于应用于代码的具体转换。
如何生成源映射
许多现代处理JavaScript的工具和编译器,比如Webpack、Babel或UglifyJS,都可以生成源映射。以下是您可能会这样做的一般示例:
- 配置您的构建工具 :大多数构建工具都有特定的配置或插件,可以启用源映射生成。通常,您会设置特定选项,比如在Webpack中设置
devtool: 'source-map'
来启用此功能。一些构建工具和项目模板可能默认启用了此功能,请检查您的构建目录是否有任何“.map”文件,以确定是否是这种情况。如果您没有使用构建工具,您可以使用前面提到的UglifyJS。 - 确保服务器配置 :确保您的服务器配置为提供源映射(如果需要),因为某些配置可能会阻止访问这些文件。这一点很重要,因为这是让浏览器将一开始看到的令人困惑的错误转换为有用信息的关键。
如何使用源映射
在开发代码时,您通常无需考虑源映射,因为现代开发环境可以自动使用源映射来帮助调试。然而,在部署到生产环境时,情况就不同了。回想一下我在文章开头提出的例子:在更新网页应用程序后,当您尝试登录时,控制台中出现了错误。我们该如何解决这个问题呢?一旦源映射生成并链接到处理后的代码,现代浏览器的开发者工具就可以自动使用源映射。就是这样!确保源映射已生成并可用,浏览器将处理剩下的事情。
然而,这只是故事的一部分。错误很少会如此容易被捕捉到。对于复杂的软件,错误可能会在由许多因素的组合导致的各种情况中出现,这使得它们更难以自己捕捉。毕竟,您只能找到您自己遇到的错误。那么,您如何找到您的用户遇到的错误呢?这就是错误监控工具可以将源映射提升到另一个层次的地方。
崩溃报告工具允许您查看用户遇到的所有错误,为您提供了对网站性能的最佳可见性。在没有错误监控服务的情况下监控网站就像在黑暗中导航一样。您可能熟悉布局,但没有灯光,您很可能会错过细节,绊倒在看不见的障碍上,并且需要更长时间才能找到您要找的东西。
使用崩溃报告工具就像打开灯一样。您将获得详细的实时问题概述,这些问题在其他情况下可能会隐藏起来。这些见解不仅仅是关于定位问题;它们还涉及了解问题的上下文和影响。集成源映射增加了透明度的另一层,将被压缩的代码转换回原始源代码。这使得调试过程更加高效,并且使开发环境更贴近实际用户体验。就像浏览器一样,这些工具可以自动获取您的源映射(如果它们是公开可访问的,可以通过URL访问)。大多数工具还支持手动上传源映射,以处理源映射无法公开的情况。如果您在本地开发,或者希望您的源映射保密,您可以手动上传它们。
在我们的源映射可用之后,浏览器控制台中的错误现在给了我们有用的信息,可以带回我们的原始代码,更好地理解问题所在以及我们可能如何解决它。请注意,原始堆栈跟踪现在告诉我们错误发生在App.js
的第18行。这比main.d1589c82.js
的第2行要有用得多。
其他语言呢?
我们编写的代码与实际执行的代码之间的差距是各种编程语言都面临的共同挑战。虽然本文主要关注JavaScript,但重要的是要认识到大多数语言都实现了类似“源映射”的概念。无论是通过调试符号、调试信息还是其他专门的工具,它们都以类似的方式解决了相同的问题。