Skip to content

Source Map

一、前提

我们知道 webpack 对代码压缩会给我们带来几个好处:

  1. 减少体积
  2. 多个文件合并,减少 HTTP 请求

但是除了这些好处,就不会有坏处了吗?我们用代码来实验一下

index.js

js
import print from './print'
print()

print.js

js
export default function printMe() {
  // 这里将console写错了
  cosnole.log('I get called from print.js!');
}

webpack.config.js

js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './index.js',
  devtool: false, // webpakc 默认会使用Source Map,所以这里要吧它关闭
  plugins: [
    new HtmlWebpackPlugin()
  ]
}

然后我们进行打包,然后打开页面。

webpack
我们可以看到这个报错提示是在main.js的16行,再次点击进去

webpack

点过去后发现竟然真的是跳到打包后main.js的16行,要清楚我们这个语句是写在print.js里面的。
现在代码量还少,所以我们还能迅速判断是哪里错误。但是要是以后代码量多了起来,再加上框架的源码,那到时候 真的就鬼知道哪里报错。

二、什么是 Source Map

Source Map的作用就如同它的英文名字翻译,源代码映射。由Source Map 创建一个 映射信息文件,里面存储着位置信息来对应打包后的文件。如果有报错,那么main.js就能根据映射文件 找到代码原来文件的所在位置。

webpack

三、webpack 配置 Source Map

webpack 里提供的 Source Map 配置多达 20 多个,例如: eval、eval-cheap-source-map 等。

如何能快速理解这 20 多个配置呢?

这其实都是有迹可循的。 webpack 所欲提供的 Source Map 配置都是由 inline、eval、source-map、nosources、hidden、cheap、module 7 个关键字组成

关键字解读

eval

当 devtool 值包含 eval 时,生成的模块代码会被包裹进一段 eval 函数中,且模块的 Sourcemap 信息通过 //# sourceURL 直接挂载在模块代码内。例如

eval("var foo = 'bar'\n\n\n//# sourceURL=webpack:///./src/index.ts?")

eval 模式编译速度通常比较快,但产物中直接包含了 Sourcemap 信息,因此只推荐在开发环境中使用。

source-map

当 devtool 包含 source-map 时,Webpack 才会生成 Sourcemap 内容。例如,对于 devtool = 'source-map',产物会额外生成 .map 文件,形如:

json
{
    "version": 3,
    "sources": [
        "webpack:///./src/index.ts"
    ],
    "names": [
        "console",
        "log"
    ],
    "mappings": "AACAA,QAAQC,IADI",
    "file": "bundle.js",
    "sourcesContent": [
        "const foo = 'bar';\nconsole.log(foo);"
    ],
    "sourceRoot": ""
}

实际上,除 eval 之外的其它枚举值都包含该字段。

cheap

当 devtool 包含 cheap 时,生成的 Sourcemap 内容会抛弃列维度的信息,这就意味着浏览器只能映射到代码行维度。例如 devtool = 'cheap-source-map' 时,产物:

json
{
    "version": 3,
    "file": "bundle.js",
    "sources": [
        "webpack:///bundle.js"
    ],
    "sourcesContent": [
        "console.log(\"bar\");"
    ],
    // 带 cheap 效果:
    "mappings": "AAAA",
    // 不带 cheap 效果:
    // "mappings": "AACAA,QAAQC,IADI",
    "sourceRoot": ""
}

虽然 Sourcemap 提供的映射功能可精确定位到文件、行、列粒度,但有时在行级别已经足够帮助我们达到调试定位的目的,此时可选择使用 cheap 关键字,简化 Sourcemap 内容,减少 Sourcemap 文件体积。

module

module 关键字只在 cheap 场景下生效,例如 cheap-module-source-map、eval-cheap-module-source-map。当 devtool 包含 cheap 时,Webpack 根据 module 关键字判断按 loader 联调处理结果作为 source(不加module),还是按处理之前的代码作为 source(加 module)。例如:
webpack
注意观察上例 sourcesContent 字段,左边 devtool 带 module 关键字,因此此处映射的,是包含 class Person 的最原始代码;而右边生成的 sourcesContent ,则是经过 babel-loader 编译处理的内容

nosources

当 devtool 包含 nosources 时,生成的 Sourcemap 内容中不包含源码内容 —— 即 sourcesContent 字段。例如 devtool = 'nosources-source-map' 时,产物:

json
{
    "version": 3,
    "sources": [
        "webpack:///./src/index.ts"
    ],
    "names": [
        "console",
        "log"
    ],
    "mappings": "AACAA,QAAQC,IADI",
    "file": "bundle.js",
    "sourceRoot": ""
}

虽然没有带上源码,但 .map 产物中还带有文件名、 mappings 字段、变量名等信息,依然能够帮助开发者定位到代码对应的原始位置,配合 sentry 等工具提供的源码映射功能,可在异地还原诸如错误堆栈之类的信息。

inline

当 devtool 包含 inline 时,Webpack 会将 Sourcemap 内容编码为 Base64 DataURL,直接追加到产物文件中。例如对于 devtool = 'inline-source-map',产物:

js
console.log("bar");
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOlsiY29uc29sZSIsImxvZyJdLCJtYXBwaW5ncyI6IkFBQ0FBLFFBQVFDLElBREkiLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlc0NvbnRlbnQiOlsiY29uc3QgZm9vID0gJ2Jhcic7XG5jb25zb2xlLmxvZyhmb28pOyJdLCJzb3VyY2VSb290IjoiIn0=

inline 模式编译速度较慢,且产物体积非常大,只适合开发环境使用。

hidden

通常,产物中必须携带 //# sourceMappingURL= 指令,浏览器才能正确找到 Sourcemap 文件,当 devtool 包含 hidden 时,编译产物中不包含 //# sourceMappingURL= 指令。

TIP

两者区别仅在于编译产物最后一行的 //# sourceMappingURL= 指令,当你需要 Sourcemap 功能,又不希望浏览器 Devtool 工具自动加载时,可使用此选项。需要打开 Sourcemap 时,可在浏览器中手动加载

总结

总结一下,Webpack 的 devtool 值都是由以上七种关键字的一个或多个组成,虽然提供了 27 种候选项,但逻辑上都是由上述规则叠加而成,例如:

  • cheap-source-map:代表不带列映射 的 Sourcemap ;
  • eval-nosources-cheap-source-map:代表 以 eval 包裹模块代码 ,且 .map 映射文件中不带源码,且 不带列映射 的 Sourcemap。

Webpack 中使用 Source Map

webpack 里可以通过 devtool 来配置 webpack 提供的 Source Map 配置。例如

js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './index.js',
  devtool: 'source-map',
  plugins: [
    new HtmlWebpackPlugin()
  ]
}

打包后会生成 main.js.map 文件,并且在 main.js 最下面会标注引入

webpack
打开页面,报错也会标注到 print.js 的第几行

webpack

总结

从上面可以看出,即使配置模式再多,那也是围绕着那七个基本信息来组合。

但如果你想进行更加细粒度的配置,可以根据官方的建议使用 SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin 来替代使用 devtool 。这个插件不能与 devtool 共同使用,因为 devtool 也是使用它。

在实际中使用的情况:
开发环境个人建议使用:eval-cheap-module-source-map。
生产环境个人建议使用:false,既不需要,以达到包的最小化。