首先根据谷歌开发者文档的介绍,Source Map通常与下列类型的预处理器搭配使用:前端
为何呢?由于一般咱们运行在浏览器中的代码是通过处理的,处理后的代码可能与开发时代码相差很远,这就致使开发调试和线上排错变得困难。这时Source Map就登场了,有了它浏览器就能够从转换后的代码直接定位到转换前的代码。在webpack中,能够经过devtool选项来配置Source Map。webpack
了解了为何须要Source Map,咱们来了了解下webpack能生成哪些类型的Source Map。web
从webpack官网上了解到,devtool的值有20+种。chrome
听起来很吓人,不过幸亏它有固定的模式。浏览器
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
安全
经过关键词的组合,就能够生成用于各类场景的Source Map。babel
在学习各类值以前,咱们须要先来了解一下表格中的quality列。配置某个属性值,咱们是为了达到某个目的,而quality就是目的之一。app
那么它是什么呢?它描述了咱们在调试时能看到的源码内容。编辑器
下面来看看它的取值以及含义函数
quality | 含义 |
---|---|
bundled code | 模块未分离 |
generated code | 模块分离,未经loader处理的代码 |
transformed code | 模块分离,经loader处理过的代码 |
original source | 你本身所写的代码 |
without source content | 生成的Source Map中不包含sourcesContent |
(lines only) | 包含行信息,不包含列信息 |
quality就决定了咱们调试时能看到的源码内容,因此选取devtool的值时,须要根据项目实际状况配合quality来选择。而devtool有20+个可选值,咱们须要进一步来理解其组成原则。
特别提醒:指定devtool时,要与mode配合使用。
复制代码
首先,上述模式中有三类关键词:
俗话说,实践是检验真理的惟一标准学懂同样东西的最好方式,下面有一个🌰来实践一下。
这里是一个简单的demo,里面有连个文件,其中主文件main.js引入了一个a.js。为了更好的模拟实际场景,还使用了webpack中还使用了babel-loader来处理js文件。
具体以下图:
下面就来实操一波~
这几个模式是互斥的,描述的是Source Map的引入方式。
Source Map内容经过base64放在js文件中引入。
代码中没有sourceMappingURL,浏览器不自动引入Source Map。
生成代码和Source Map内容混淆在一块儿,经过eval输出。
使用这个关键字的Source Map不包含sourcesContent,调试时只能看到文件信息和行信息,没法看到源码。
这个关键字用于指定调试信息的完整性
不包含列信息,而且源码是进过loader处理过的
这里能够看到,点击对应文件时,会跳转到对应文件,可是光标是在第一列(缺乏列信息则只定位到第一列),而且箭头函数也被转换成了function。
不包含列信息,源码是开发时的代码
也是只能看到列信息,不过代码是原汁原味的开发时所写的代码。
根据Source Map v3规范,推荐的格式是:
{
"version" : 3, "file": "out.js", "sourceRoot": "", "sources": ["foo.js", "bar.js"], "sourcesContent": [null, null], "names": ["src", "maps", "are", "fun"], "mappings": "A,AAAB;;ABCDE;" } 复制代码
下面来解释一下每一个属性表明的含义。
属性 | 掘力值下限 |
---|---|
version | Source Map文件版本 |
file | 该Source Map对应文件的名称 |
sourceRoot | 源文件根目录,这个值会加在每一个源文件以前 |
sources | 源文件列表,用于mappings |
sourcesContent | 源代码字符串列表,用于调试时展现源文件,列表每一项对应于sources |
names | 源文件变量名和属性名,用于mappdings |
mappings | 位置信息 |
这里以chrome浏览器为例(其余浏览器应该也是相似的喔~)。
浏览器加载source-map是经过js文件中的sourceMappingRUL来加载的,并且sourceMapping支持两种形式:文件路径或base64格式。
加载source-map以后,在浏览器dev tool中的Sources tab就能看到对应的信息了。
这里重点讲解一下map文件中的sources字段和sourcesContent字段。
sources字段对应的是文件信息,会在浏览器的Sources中生成对应目录结构。以后再将sourcesContent中的内容对应填入上述生成的文件中。咱们在调试时为啥能看到文件信息和源码内容,就是sources和sourcesContent共同做用的结果。
这里经过一个栗子来看看,我在sources中添加了一个文件hello.js,而且在sourcesContent中对应添加了内容hello~。下面是浏览器加载map文件后的结果。
直接举个栗子🌰:
源码:input.js i am handsome you are ugly 复制代码
转换后代码:output.js i am handsome you are ugly 复制代码
咱们要如何记录这位置对应信息呢?这里能够参考这篇文章[1]。
位置信息必须包含的有:输出位置(Output location)、源文件(Input)、源码位置(Input location)、源码。
因此根据上面的栗子,咱们能够得出以下表格。
Output location | Input | Input location | Character |
---|---|---|---|
L1,C0 | index.js | L1, C0 | i |
L1,C2 | index.js | L1, C2 | a |
L1,C3 | index.js | L1, C3 | m |
L1,C5 | index.js | L1, C5 | h |
L1,C6 | index.js | L1, C6 | a |
L1,C7 | index.js | L1, C7 | n |
L1,C8 | index.js | L1, C8 | d |
L1,C9 | index.js | L1, C9 | s |
L1,C10 | index.js | L1, C10 | o |
L1,C11 | index.js | L1, C11 | m |
L1,C12 | index.js | L1, C12 | e |
L1,C14 | index.js | L2, C0 | y |
L1,C15 | index.js | L2, C1 | o |
L1,C16 | index.js | L2, C2 | u |
L1,C18 | index.js | L2, C4 | a |
L1,C19 | index.js | L2, C5 | r |
L1,C20 | index.js | L2, C6 | e |
L2,C0 | index.js | L2, C8 | u |
L2,C1 | index.js | L2, C9 | g |
L2,C2 | index.js | L2, C10 | l |
L2,C3 | index.js | L2, C11 | y |
这个表格描述了每一个字符的处理先后的对应位置信息,有了这个表格就能够作位置的映射了。
能够看到对于几个字符来讲,须要存储的信息就已经很庞大了,因此这种记录方式还须要进行优化。
优化点以下:
;
来分割每行输出代码,使用
,
来分割每一个输出代码的位置信息。
优化后以下表:
sources: [index.js] names: [i, am, handsome, you, are, ugly] 复制代码
Output location | Input index | Input location | Name index |
---|---|---|---|
L1,C0 | 0 | L1, C0 | 0 |
L1,C+2 | +0 | L1, C+2 | +1 |
L1,C+3 | +0 | L1, C+3 | +1 |
L1,C+9 | +0 | L2, C-5 | +1 |
L1,C+4 | +0 | L2, C+4 | +1 |
L2,C-18 | +0 | L2, C+4 | +1 |
此时位置信息能够记录成
mappings: "0|0|1|0|0,2|0|1|2|1,3|0|1|3|1,9|0|2|-5|1,4|0|2|4|1;-18|0|2|4|1"
备注: 1. 数字使用分隔符“|”分割 2. 0|0|1|0|0表明 -> 0(输出单词列), 0(输入文件sources索引),10(输入单词行列),0(单词names索引) 复制代码
在上述mappings中,由于每个位置不必定是一个数字的,因此必须使用|
分隔符来分割数字。若是能省略分隔符,mappings的大小能够获得很大的优化。这时就须要引入VLQ(Variable Length Quantities)了。
VLQ的特色就是能够很是精简地表示很大的数值。其原理是使用6个二进制位来表示字符,其中最高位表示是否连续(0:不连续,1:连续),最低位表示是正仍是负(0:正数,1:负数)。
举两个栗子🌰🌰:数字1与-23。
数字:1 二进制:1 VLQ编码:000010 Base64 VLQ: C 复制代码
数字:-23 绝对值:23 二进制:10111 VLQ编码:101111 000001 Base64 VLQ: vB 复制代码
这里拿-23来分解一下生成过程。
第一步:去23的二进制码 -> 10111 第二步:将10111分红两部分,第一部分是后四位,后面部分是五位为一部分 -> 一、0111 -> 0000一、0111 第三步:按VLQ格式拼接 -> 101111 000001 其中,101111是 1【连续标识位】 + 0111 + 1【正负标识位】 000001是 0【连续标识位】+ 00001 第四步:对照Base64索引表(下表) 复制代码
最后来看一下上述mappings优化以后长啥样吧。
优化前:
mappings: "0|0|1|0|0,2|0|1|2|1,3|0|1|3|1,9|0|2|-5|1,4|0|2|4|1;-18|0|2|4|1" 优化后 mappings: "AACAA,EACEC,GACGC,SAELC,IAEIC,lBAEIC" 复制代码
经过上面的理论。咱们对source-map以及webpack中devtool配置项已经有一些了解了,下面从实际出发,看看在项目不一样环境中应该如何配置webpack。
先直接来看看webpack官网的devtool[2]推荐:
推荐使用:
在开发环境,咱们比较在乎的是开发体验,因此下面从源码级别、构建速度和列信息来对比。
devtool | 源码级别 | 构建速度 | 列信息 |
---|---|---|---|
eval | webpack + loader处理后的代码 | 快 | + |
eval-source-map | 源码 | 慢 | + |
eval-cheap-source-map | loader处理后的代码 | 中 | - |
eval-cheap-module-source-map | 源码 | 中 | - |
推荐使用:
在生产环境,咱们比较在乎的是安全性,因此下面从源码级别、安全性和列信息来对比。
devtool | 源码级别 | 安全性 | 列信息 |
---|---|---|---|
none | - | - | - |
source-map | 源码 | 浏览器会加载source-map,调试时会暴露源码 | + |
hidden-source-map | 源码 | 会生成map文件,但浏览器不会加载source-map。能够将map文件与错误上报工具结合使用 | + |
nosources-source-map | 源码堆栈 | 没有sourcesContent,调试只能看到模块信息和行信息,不能看到源码 | - |
翻了一下项目组中的项目,开发环境使用的是eval-cheap-module-source-map
,而生产环境这边多大数只须要知道报错的模块和行号就能够了,因此使用的是nosources-source-map
。
关注「漫步大前端」, 第一时间获取优质文章。
Source Maps under the hood: https://docs.microsoft.com/zh-cn/archive/blogs/davidni/source-maps-under-the-hood-vlq-base64-and-yoda#comment-626
[2]webpack: https://user-gold-cdn.xitu.io/2020/6/28/172f8f3771f18da1