笔者通常使用 webpack 来打包项目。但经过 webpack 打包压缩后的代码基本上已经不具备可读性,如果此时代码抛错,要调试这些代码会发生什么呢?可能是一场噩梦。幸运的是,webpack 给我们提供了一个解决方案:source map。
source map
什么是 source map
简单说,source map 就是一个信息文件,里面储存着位置信息。也就说,转化后(编译、打包、压缩)的代码的每一个位置所对应的原始代码的位置。
使用
通过在打包后的文件底部添加特殊注释,可以向浏览器指示 source map 可用。如下所示:
//# sourceMappingURL=bundle.js.map
该注释通常是由用来生成 source map 的程序自动添加的。上述注释是由 webpack 添加。
只有当我们打开浏览器的开发者工具时,.map文件才会被加载。这时浏览器会使用它来对打包后的文件进行解析,分析出源代码的目录结构和内容。但是使用 source map 会有一定的安全隐患,因为任何人都可以通过开发者工具看到源代码。后面我们会讲到如何解决。
内容
source map 文件包含一个 JSON 对象,其中包含本身和原始文件的信息。下面是一个简单的例子:
{
version: 3,
file: "bundle.js",
sourceRoot: "",
sources: ["foo.js", "bar.js"],
names: ["src", "maps", "are", "fun"],
mappings: "AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO..."
}
简单了解下这些属性的含义:
- version— source map 的版本
- file—打包后的文件名
- sourceRoot—打包前的文件所在的目录。如果与dao
- names—包含源文件中所有变量名和函数名的数组
- mappings—一个包含源代码映射的 Base64 VLQS 字符串。(奇迹发生的地方)
关于 mappings 如何将打包后的代码与源代码的位置一一映射,请参考阮一峰老师的 JavaScript Souce Map 详解。
source map 的 webpack 配置
JavaScript 的 source map 的配置很简单,只要在 webpack.config.js 中添加 devtool 即可。
module.exports = {
// ...
devtool: 'source-map'
}
配置完成后,打包一下,发现会生成 .map 文件。
对于CSS、SCSS、Less 来说,则需要添加额外的 source map 配置项。如下所示:
module.exports = {
// ...
devtool: 'source-map',
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { sourceMap: true }
},
{
loader: 'sass-loader',
options: { sourceMap: true }
}
]
}
]
}
}
source map 的格式
devtool 不仅可以配置source-map,还可以配置很多其他 source map 的格式。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。
下面表格展示了 source map的几种常见格式的对比:
| devtool | 构建速度 | 重新构建速度 | 生产环境 | 品质(quality) |
|---|---|---|---|---|
| source-map | 慢 | 慢 | yes | 原始源代码 |
| hidden-source-map | 慢 | 慢 | yes | 原始源代码 |
| nosources-source-map | 慢 | 慢 | yes | 无源代码内容 |
| eval-source-map | 慢 | 比较快 | no | 原始源代码 |
| eval-cheap-source-map | 比较快 | 快速 | no | 转换过的代码(仅限行) |
| eval-cheap-module-source-map | 中等 | 快速 | no | 原始源代码(仅限行) |
其中的一些值适用于开发环境,一些适用于生产环境。不同环境的要求不同:
- 对于开发环境,通常希望更快速的source map,需要添加到 bundle 中以增加体积为代价
- 对于生产环境,通常希望更精准的source map和更小的bundle,需要从 bundle 中分离并独立存在
因此,在开发环境,推荐使用 eval-source-map、eval-cheap-souce-map、eval-cheap-module-source-map。
而生产环境中,推荐使用 source-map、hidden-source-map、nosources-source-map。下面将介绍这3种 source map 在安全性方面的不同。
关于 source map 的更多格式和具体介绍请访问 webpack 官网的 devtool 介绍。
安全
source map 不仅可以帮助我们调试源码,当线上出现问题时也利于我们快速定位。但同时,有了 source map 也就意味着项目的源代码会暴露给任何人,十分不安全。那么如何保证功能的同时,防止暴露代码呢? 下面将详细介绍上节提到的三种 source map 格式。
source-map
source-map - 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。它会暴露整个源代码,所以不能直接到部署到服务器。
我们可以通过服务器的 nginx 设置(或者其他类似工具)将 .map 文件只对固定的白名单(比如公司内网)开发,这样我们仍然可以看到源码,而在一般用户的浏览器中就无法获取到它们了。
hidden-source-map
hidden-source-map - 与 source-map 相同,但不会为 bundle 添加引用注释。这样 source map 就不会对浏览器开发工具暴露 ,浏览器也就无法解析 bundle 文件。如果我们想追溯源码,则要利用一些第三方服务,将 .map 文件上传到那上面。目前比较流行的解决方案是 Sentry。
Sentry 是一个错误跟踪平台,开发者接入后可以进行错误的收集和聚类,以便于更好地发现和解决线上问题。Sentry 支持 JavaScript 的 source map,我们可以通过它所提供的命令行工具或者 webpack 插件(webpack-sentry-plugin)来自动上传 .map文件。同时我们还要在工程代码中添加 Sentry 对应的工具包,每当 JavaScript 执行出错时就会上报给 Sentry。Sentry 在接收到错误后,就会去找对应的 .map 文件进行源码解析,并给出源码中的错误栈。
关于 Sentry 的更多介绍请访问官网。
nosources-source-map
nosources-source-map 创建的 source map 不包含源代码内容。对于错误来说,我们仍然可以查看源代码的错误栈。它仍然会暴露项目的文件名和结构,但不会暴露原始代码。所以我们可以将 source map 文件部署到服务器。