webpack之source map

1,565 阅读5分钟

笔者通常使用 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-mapeval-cheap-souce-mapeval-cheap-module-source-map

而生产环境中,推荐使用 source-maphidden-source-mapnosources-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 文件部署到服务器。

参考