万字长文:关于sourcemap,这篇文章就够了

前言

如果我告诉你,位置信息就在对应的这堆字母里

成为了房间里的大象,一旦出现诸如“无法映射到源文件”“只能映射到 loader 处理后的文件”等问题,多数人是毫无头绪的;而就像 TJ 大神说的: “不要直接 copy 解决方案,要理解后自己去实现”;

  • 配置项给你安排的明明白白,顺带送上生产环境、开发环境最佳实践

  • 定位原理给你安排的明明白白,是怎么做到生成记录源码和处理后代码间的映射

  • 感恩大奉送,编码给你安排的明明白白,base64 编码、VLQ 编码、base64-vlq 编码的三世孽缘

读至此处,您还要跑/p>

冰冰动图大合集云盘,en,是不可能给的,就给一张你们瞅瞅好了

sourcemap:devTools 配置项二三事

对于而言,我们最常见的,莫过于在 webpack 的配置项中进行使用,而有多少种供我们选择的配置呢/p>

抱歉我皮了,所谓变中取定,这么多种配置项其实只是五个关键字 eval、source-map、cheap、module 和 inline 的组合罢了,请牢记这张表,破阵心法,忽悠时方可娓娓道来。

关键字 含义
source-map 产生.map 文件
eval 使用 eval 包裹模块代码
cheap 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含 loader 的 sourcemap
module 包含 loader 的 sourcemap(比如 jsx to js ,babel 的 sourcemap),否则无法定义源文件
inline 将.map 作为 DataURI 嵌入,不单独生成.map 文件

怎么理解呢战见真知。

举例详解

文件源码如下

source-map 处理后输出结果

关键字 特点
source-map 定位信息最全,但也.map 文件最大,效率最低

eval 处理后输出结果

关键字 特点 解决问题
eval 用“eval 包裹源代码进行执行 利用字符串可缓存从而提效

devtool: “source-map” cannot cache SourceMaps for modules and need to regenerate complete SourceMap for the chunk. It’s something for production.

devtool: “eval-source-map” is really as good as devtool: “source-map”, but can cache SourceMaps for modules. It’s much faster for rebuilds.

翻译来说划重点:加 eval 和不加是一样的,但加了后可以缓存,于是更。

处理后输出结果

关键字 特点 解决问题
inline 将 map 作为 DataURI 嵌入,不单独生成.map 文件 减少文件数

处理后输出结果

关键字 特点 解决问题 存在的问题
cheap 错误信息只会定义到行,而不会定义到列 精准度降低换取文件内容的缩小

对于而言,只会定义到出错的这一行

而对于而言,则会精准到列

存在的问题

  1. 错误信息只会定义到行,而不会定义到列

  2. 对于经由 babel 之类工具转义的代码,只能定位到转换后的代码

这就引出了我们最后的一个关键字

处理后输出结果

关键字 特点 解决问题
module 会保留 loader 处理前后的文件信息映射 解决对于使用“cheap 配置项导致无法定位到 loader 处理前的源代码问题

测试代码

对于而言,此时页面 debugger 展示源码是 es5 的代码,因为已经被 babal 转义了

而对于而言,则会精准到原始代码

配置项关键字小结

配置项最佳实践

开发环境

  • 我们在开发环境对 sourceMap 的要求是:快(eval),信息全(module),

  • 且由于此时代码未压缩,我们并不那么在意代码列信息(cheap),

所以开发环境比较推荐配置:

生产环境

  • 一般情况下,我们并不希望任何人都可以在浏览器直接看到我们未编译的源码,

  • 所以我们不应该直接提供 sourceMap 给浏览器。但我们又需要 sourceMap 来定位我们的错误信息,

  • 一方面 webpack 会生成 sourcemap 文件以提供给错误收集工具比如 sentry,另一方面又不会为 bundle 添加引用注释,以避免浏览器使用。

这时我们可以设置

至此,关于在 webpack 中的应用层面我们就算是了解个七七八八了。但其实,这只是一个开头小菜

输出内容分析:map 文件详解

要分析实现,还是得先从现象下手,假定源文件内容为

其输出内容为

script-min.js

script-min.js.map

文件字段具体含义分析

字段 含义
version Source map 的版本,目前为 3
file 转换后的文件名
sourceRoot 转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空
sources 转换前的文件,该项是一个数组,表示可能存在多个文件合并
names 转换前的所有变量名和属性名
mappings 记录位置信息的字符串

可以看到,既然我们要定位,自然最关心的是具有【记录位置信息】功能的 mapping 属性,接下来详细讲解如何分析。

mapping 属性值含义

分析角度 含义
行对应 以分 (;)表示,每个分 对应转换后源码的一行。所以,第一个分 前的内容,就对应源码的第一行,以此类推。
位置对应 以逗 (,)表示,每个逗 对应转换后源码的一个位置。所以,第一个逗 前的内容,就对应该行源码的第一个位置,以此类推。
分词信息 以 VLQ 编码表示,代表记录该位置对应的转换前的源码位置、原来属于那个文件等信息。
  • 【行对应】很好理解,即一个分 为一行,因为压缩后基本上都是一行了,所以这个没啥有用信息;

  • 【位置对应】可以理解为分词,每个逗 对应转换后源码的一个位置;

  • 【分词信息】是关键,如代表该位置转换前的源码位置,以编码表示;

其中【分词信息】每组最多五位(如果不是变量,只会有四位),分别是:

  • 第一位,表示这个位置在【转换后代码】的第几列。

  • 第二位,表示这个位置属于【sources 属性】中的哪一个文件。

  • 第三位,表示这个位置属于【转换前代码】的第几行。

  • 第四位,表示这个位置属于【转换前代码】的第几列。

  • 第五位,表示这个位置属于【names 属性】的哪一个变量。

到此,我们也算是知道 map 文件到底是怎么组成的了。

小思考

此处附送base64vlq 在线转换地址,将上面的对应的字符串输入,将会得到对应的数字信息,如对应的是,这两者之间的映射规则就是编码。

整理目标

到此,我们整理下接下来要做的事情,抬头看天,低头走路。

我们希望解决坐标信息占用空间过大的问题,主要在于两点

  • 编译后文件列 过大问题:因为会编译成一行,可以想象靠后的元素纵坐标是很大的

  • 数据结构占据空间问题:数组自然比字符串更耗费空间

随着对这两个问题的思考,我们将会彻底理解,为啥我们用于记录位置信息的会是这个鬼样子

打起精神,继续学!冰冰续命

相对位置解决列 过大问题

对于第一点输出后的位置元素的列 特别大的问题,可以采用相对位置的方案进行解决,具体规则如下

  • 第一次记录的输入位置和输出位置是绝对的,往后的输入位置和输出位置都是相对上一次的位置移动了多少

举例而言,假设 a.js 内容为,处理后输出 ,其 names 为:, source: [‘a.js’]

则其按照相对位置输出的关系如下

| 字符组合 | 位置类别 | 输出位置 | 输入位置 | 映射(输出 x |  属于文件在 source 的索引 | 输入 x | 输入 y | 变量在 names 的索引) | | — | — | — | — | — | | feel | 绝对 | 10, 0 | 0, 0 | 10 | 0 | 0 | 0 | 0 | | the | 相对 | -10, 0 | 5, 0 | -10 | 0 | 5 | 0 | 1 | | force | 相对 | 4, 0 | 4, 0 | 4 | 0 | 4 | 0 | 2 |

base64VLQ 解决数据结构占据空间问题

BASE64 编码

我们开发同学最初了解到大概是在小图标的处理上,当时了解到的是可以将图片的二进制转为文本,从而减少 http 请求,但只适用于小图标等体积小的内容,因为使用编码处理过会导致被处理对象体积增加 33%;那么到底是什么出现就是为了处理小图标吗解事物的经典三问

是什么/p>

在 MDN 中的定义:

是一组相似的二进制到文本(binary-to-text)的编码规则,使得二进制数据在解释成 radix-64 的表现形式后能够用 ASCII 字符串的格式表示出来。

为什么出现/p>

怎么做到的/p>

编码本身并不复杂,对使用者而言按图索骥而已,关键是它规则为什么这么设定,以及修补规则出现的原因。

既然 ASCII 码表中存在不可打印字符,那我们就定义一个新码表,其范围固定在可打印字符内。(这就意味着要多个新码表字符表示一个 ASCII 码表字符,原因很简单,你要用苹果、梨表示所有水果,那只好定义两个苹果是西瓜、两个梨是番茄、一个苹果一个梨是。。。排列组合)

新码表字符的组成单元占几个字节/p>

我们知道基础 ASCII 码,使用 7 位二进制数表示组成单元,新码表的表示范围小于 ASCII 码表(因为要确保新码表中都是可打印字符),这也就意味着,新码表使用的二进制数必须少于七位,而二进制数越少代表其能表示的字符越少,那就需要更多个二进制数来表示一个字符,而这个位数应该是越多越好的,因为这样我们所有的组成元素(新码表)就多了,于是定 6 位吧。2^6 是 64,于是新码表叫做 base64(这块纯属于个人理解,仅供推理记忆,如有错误请不吝赐教)

如何 ASCII 码进行 Base64 编解码

ASCII 码字符占 8 位二进制,而 Base64 占 6 位,取最小公倍数即为 24,即可以用 4 个 base64 字符去表示 3 个 ASCII 码字符。遂有如下转换规定

  1. ASCII 码字符串根据 ASCII 码对照表转换为二进制数值;

  2. 把二进制数值按每 6 位进行划分;

    1. 假设字符数是 3 的倍数,比如三个 ASCII 码字符,就可以三个为一组,用四个 base64 字符来表示(,enen,应该好理解的)

    2. 如果待编码字符串的长度不是 3 的倍数,则用 0 补位. 如果有连续 6 位都是 0 的话, 就用来表示。

  3. 然后 6 位二进制转化为十进制根据 Base64 对照表找到编码字符.

对照表

扩展

在中,原生提供了 base64 和 ASCII 码之间的转换 API

函数名 功能 参数
atob base64 字符串转 ASCII 码字符串 base64 字符串
btoa ASCII 码字符串转 base64 字符串 ASCII 码字符串

举例而言

以 ASCII 的 A 字符为例,A 转为二进制如上,不足三位,所以补 0,从而补齐 24 位

再 6 位为一组,对照,全为 0 的话用= 代替

所以,得出结果,A 字符对应的 Base64 编码是

至此,我们就算是对 base64 这位“最熟悉的陌生人”至少能答出个来龙去脉了

扩展-修补规则:URL 安全的 Base64 编码

修补规则出现背景

For base 64, the non-alphanumeric characters (inparticular, “/”) may be problematic in file names and URLs.

修补规则

  • 不在末尾填充

  •  用 替换

  •  用 替换

扩展-实现:在中如何实现的编码解码/p>

编码

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年5月25日
下一篇 2021年5月25日

相关推荐