source map 是什么?
sourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码并没有阅读性可言,一旦在开发中报错或者遇到问题,直接在混淆代码中 debug 会带来非常糟糕的体验, sourceMap 可以帮助我们快速定位到源代码的位置,提高我们的开发效率。
既然是一种源码的映射,那必然就需要有一份映射的文件,来标记混淆代码里对应的源码的位置,通常这份映射文件以.map结尾,里边的数据结构大概长这样:
{
"version": 3, // Source Map版本
"file": "out.js", // 输出文件(可选)
"sourceRoot": "", // 源文件根目录(可选)
"sources": ["foo.js", "bar.js"], // 源文件列表
"sourcesContent": [null, null], // 源内容列表(可选,和源文件列表顺序一致)
"names": ["src", "maps", "are", "fun"], // mappings使用的符号名称列表
"mappings": "A,AAAB;;ABCDE;" // 带有编码映射数据的字符串
}
有了这份映射文件,我们只需要在压缩代码的最末端加上这句注释,即可让 sourceMap 生效:
//# sourceURL=/path/to/file.js.map
有了这段注释后,浏览器就会通过sourceURL去获取这份映射文件,通过解释器解析后,实现源码和混淆代码之间的映射。因此 sourceMap 其实也是一项需要浏览器支持的技术。
如果我们仔细查看 webpack 打包出来的 bundle 文件,就可以发现在默认的development开发模式下,每个__webpack_modules__文件模块的代码最末端,都会加上//# sourceURL=webpack://file-path?,从而实现对 sourceMap 的支持。
Source Map 简介
主要存在以下几个属性:
- version 是指定所使用的 Source Map 标准版本;
- sources 中记录的是转换前的源文件名称,因为有可能出现多个文件打包转换为一个文件的情况,所以这里是一个数组;
- names 是源代码中使用的一些成员名称,我们都知道一般压缩代码时会将我们开发阶段编写的有意义的变量名替换为一些简短的字符,这个属性中记录的就是原始的名称;
- mappings 属性,这个属性最为关键,它是一个叫作 base64-VLQ 编码的字符串,里面记录的信息就是转换后代码中的字符与转换前代码中的字符之间的映射关系,具体如下图所示:

一般我们会在转换后的代码中通过添加一行注释的方式来去引入 Source Map 文件。不过这个特性只是用于开发调试的,所以最新版本的 jQuery 已经去除了引入 Source Map 的注释,我们需要手动添加回来,这里我们在最后一行添加 //# sourceMappingURL=jquery-3.4.1.min.map,具体效果如下:

这样我们在 Chrome 浏览器中如果打开了开发人员工具,它就会自动请求这个文件,然后根据这个文件的内容逆向解析出来源代码,以便于调试。同时因为有了映射关系,所以代码中如果出现了错误,也就能自动定位找到源代码中的位置了。
我们回到浏览器中,打开开发人员工具,找到 Source 面板,这里我们就能看到转换前的 jQuery 源代码了,具体效果如下图所示:

我们还可以添加一个断点,然后刷新页面,进行单步调试,此时调试过程中使用的就是源代码而不是压缩过后的代码,具体效果如下图所示:

Webpack 中配置 Source Map
我们使用 Webpack 打包的过程,同样支持为打包结果生成对应的 Source Map。用法上也很简单,不过它提供了很多不同模式,导致大部分初学者操作起来可能会比较懵。那接下来我们就一起研究一下在 Webpack 中如何开启 Source Map,然后再来了解一下几种不同的 Source Map 模式之间存在哪些差异。
我们回到配置文件中,这里我们要使用的配置属性叫作 devtool。这个属性就是用来配置开发过程中的辅助工具,也就是与 Source Map 相关的一些功能。我们可以先将这个属性设置为 source-map,具体代码如下:
// ./webpack.config.js
module.exports = {
devtool: "source-map", // source map 设置
};
然后打开命令行终端,运行 Webpack 打包。打包完成过后,我们打开 dist 目录,此时这个目录中就会生成我们 bundle.js 的 Source Map 文件,与此同时 bundle.js 中也会通过注释引入这个 Source Map 文件,具体如下图所示:

我们再回到命令行,通过 serve 工具把打包结果运行起来,然后打开浏览器,再打开开发人员工具,此时我们就可以直接定位到错误所在的位置了。当然如果需要调试,这里也可以直接调试源代码。

如果你只是需要使用 Source Map 的话,操作到这里就已经实现了。但是只会使用这种最普通的 Source Map 模式还远远不够。
devtool
devtool 就是去配置 sourcemap,方便调试,能准确定位到代码错误。 Webpack 开发模式下 sourcemap 功能默认是开启的。想主动关闭 devtool 只需要配置 devtool: 'none'即可。
- cheap
- 定位到行,不定位到列(提示性能)
- module
- 把依赖模块中的代码一并做映射
- eval
- 使用 eval 形式做 sourcemap 映射。
- 它就是将模块代码放到 eval 函数中执行,并且通过 sourceURL 标注所属文件路径,在这种模式下没有 Source Map 文件,所以只能定位是哪个文件出错
- 使用 eval 形式做 sourcemap 映射。
- inline
- 行内的映射关系。
inline-source-map模式。- 它跟普通的
source-map效果相同,只不过这种模式下 Source Map 文件不是以物理文件存在,而是以 data URLs 的方式出现在代码中。
- 它跟普通的
hidden-source-map模式。- 在这个模式下,我们在开发工具中看不到 Source Map 的效果,但是它也确实生成了 Source Map 文件,这就跟 jQuery 一样,虽然生成了 Source Map 文件,但是代码中并没有引用对应的 Source Map 文件,开发者可以自己选择使用。 最好的配置:
// 开发时
devtool: 'cheap-module-eval-source-map',
// 线上环境:
devtool: 'cheap-module-source-map'