写代码时谁都会报错,但报错信息能不能对应到自己写的源码,直接决定了调试效率。webpack 打包后,代码被合并、压缩、改名,原本的源码位置信息全部丢失——这就是 Source Map 要解决的问题。
一、没有 Source Map 之前,开发体验有多糟
webpack 的产物和源码之间隔着好几层「变形」,每一层都会让调试变难。
1. 行号列号全变了
假设源码有清晰的模块结构:
// src/utils.js
export function sum(a, b) {
return a + b; // 第 3 行
}
打包后,所有模块被塞进一个 bundle.js,你的代码可能出现在第 8000 多行,还包在 webpack 的模块函数里:
/***/ "./src/utils.js":
/*!***********************!*\
!*** ./src/utils.js ***!
\***********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, { sum: () => sum });
function sum(a, b) {
return a + b;
}
/***/ }),
源码第 3 行,变成了产物第 8000+ 行,报错堆栈里看到的全是产物的位置。
2. 变量名、函数名都没了
生产环境经过 Terser 压缩混淆后,代码会变成这样:
function n(a,b){return a+b}
sum 没了,参数名也没了。堆栈里只告诉你 n is not a function,你根本不知道 n 是源码里的哪个函数。
3. 找不到出错位置,也断不了点
- 浏览器控制台报错的行号指向
bundle.js,点进去是压缩后的一整行,几乎无法阅读。 - 想在 Chrome DevTools 里给源码某个函数打断点,但源码文件根本不在产物里,只能对着压缩代码猜。
- 如果用了 TypeScript、Babel,那产物还经过了转译,和源码差得更远。
结果就是:报错看得见,原因找不到。开发者只能靠 console.log 大海捞针。Source Map 正是为此而生。
二、Source Map 是什么
Source Map 是一个 .map 文件,它记录了「编译后的代码」与「原始源代码」之间的位置映射关系。有了它,浏览器开发者工具就能把压缩、合并、转译后的代码,反向还原成你写的源码。
一个 Source Map 文件大概长这样:
{
"version": 3,
"file": "bundle.js",
"sources": ["webpack://demo/./src/utils.js"],
"sourcesContent": ["export function sum(a, b) {\n return a + b;\n}\n"],
"names": ["sum", "a", "b"],
"mappings": "AAAA,MAAM,GAAG,..."
}
几个关键字段:
| 字段 | 作用 |
|---|---|
version | Source Map 规范版本,目前是 3 |
file | 该 map 对应的产物文件 |
sources | 原始源文件列表 |
sourcesContent | 源文件内容(内联,便于直接展示) |
names | 原始变量名、函数名 |
mappings | 核心:用 Base64 VLQ 编码的位置映射串 |
mappings 是整套机制的核心,它用一串紧凑的编码,描述「产物第几行第几列 → 源文件第几行第几列」的对应关系。之所以要压缩编码,是因为一条映射就要占几个字段,全量文本会非常庞大。
浏览器是怎么找到它的?产物文件末尾会带一行注释:
//# sourceMappingURL=bundle.js.map
DevTools 检测到后,会自动去请求这个 .map 文件,解析映射,于是你就能:
- 在 Sources 面板里看到原始的源码文件(甚至带目录结构)。
- 报错堆栈显示源码的文件名、行号、函数名。
- 直接在源码上打断点,就像没有经过任何构建一样。
三、怎么用:devtool 配置
webpack 中开启 Source Map 只需要配置 devtool 选项:
module.exports = {
devtool: 'source-map',
};
但这个选项的取值非常多,因为它可以在「生成质量」和「构建速度」之间做取舍。取值由几个关键字组合而成:
| 关键字 | 含义 |
|---|---|
eval | 用 eval() 包裹模块,映射信息内联,重建最快 |
source-map | 生成独立的 .map 文件,映射最完整 |
inline | 把 map 以 Data URL 形式内联进产物 |
cheap | 只映射到「行」不映射到「列」,速度更快 |
module | 映射经过 loader 转译前的源码(如 TS、JSX) |
hidden | 生成 map 但不加 sourceMappingURL 注释 |
nosources | map 中不包含 sourcesContent,隐藏源码内容 |
开发环境推荐
module.exports = {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
};
eval让重新构建极快,适合频繁改动。cheap-module能映射到转译前的源码,行列信息基本够用。- 这是 webpack 官方推荐的开发默认值之一。
如果更看重调试质量、不太在意速度,可以用 eval-source-map——它每个模块都带完整的 inline map,报错定位最准,但构建较慢。
生产环境推荐
module.exports = {
mode: 'production',
devtool: 'hidden-source-map',
};
生产环境要特别注意两点:产物体积和源码泄露。
- 不要用
eval/inline:它们会把 map 内联进 JS,导致体积暴涨,而且等于把源码直接交给用户。 source-map:生成独立的.map文件,但文件末尾带注释,浏览器会自动加载,源码会被暴露。hidden-source-map:生成.map但不加注释,浏览器不会自动加载。适合把 map 上传到错误监控平台(如 Sentry)做线上问题定位,用户看不到源码。nosources-source-map:map 里去掉sourcesContent,能定位到行号但看不到源码内容。false:彻底关闭,适合完全不需要线上调试的场景。
取值对照表
| 环境 | 推荐值 | 特点 |
|---|---|---|
| 开发(快) | eval-cheap-module-source-map | 重建最快,含源码映射 |
| 开发(准) | eval-source-map | 定位最准,速度较慢 |
| 生产(隐藏) | hidden-source-map | 生成但不暴露给浏览器 |
| 生产(常规) | source-map | 完整独立文件,会暴露源码 |
| 生产(无源码) | nosources-source-map | 只有位置,不含源码内容 |
| 关闭 | false | 不生成任何 map |
四、更细粒度的控制
如果内置的 devtool 组合满足不了需求,webpack 还提供了两个插件做更精细的控制:
SourceMapDevToolPlugin:自定义哪些文件生成 map、map 的输出路径、是否追加注释等。EvalSourceMapDevToolPlugin:对应eval系列,控制 eval 模式下的映射行为。
const webpack = require('webpack');
module.exports = {
devtool: false, // 关闭内置,改用插件
plugins: [
new webpack.SourceMapDevToolPlugin({
filename: '[file].map',
// 只为 js 生成 map
test: /\.js($|\?)/i,
}),
],
};
常见于需要把 map 单独上传、或想精细控制体积的工程化场景。
五、注意事项
- map 文件要能访问到。
sourceMappingURL是相对路径,如果产物部署到 CDN 或子目录,路径不对会导致 map 加载失败、调试失效。 - 别在生产暴露源码。默认的
source-map会让任何人都能下载到你的源码,商业项目应使用hidden-source-map并把 map 上传到监控平台。 - 注意构建速度。映射越完整(
module+ 列映射),构建越慢。开发环境用cheap-module是常见折中。 .map文件不要提交到仓库,它是构建产物,应加入.gitignore。
小结
Source Map 解决的,是「产物与源码割裂」这个根本问题:
- 没有它:报错位置对不上、变量名丢失、无法断点,只能靠
console.log猜。 - 它是什么:一份记录产物与源码位置映射关系的
.map文件,靠mappings编码和sourceMappingURL注释让 DevTools 还原源码。 - 怎么用:配置
devtool。开发求快用eval-cheap-module-source-map,生产求稳用hidden-source-map,兼顾体积与源码安全。
掌握 devtool,等于给自己的调试工作装上了「源码级」的导航。