源代码在被转换后变得不可读,这给调试带来了非常多的不便。通常我们可以通过 Source Map 来解决这个问题。我们不仅可以给 JavaScript 文件添加 Source Map,还可以给样式文件添加 Source Map。
Webpack 同时支持将 Source Map 内联在输出的 Bundle 中和将 Source Map 单独输出到文件中。在开发过程中,将 Source Map 内联在构建输出中可以加快构建性能。在生产环境中,Source Map 通常会被单独输出到文件中,减少请求文件大小。
使用 Source Map
webpack.parts.js
exports.generateSourceMaps = ({ type }) => ({ devtool: type });
Webpack 支持多种 Source Map 选项,每种选项都有着针对构建性能和输出质量的不同考量。假设现在我们在生产环境中使用 source-map
这个选项,开发环境中使用 Webpack 的默认值,则配置如下:
webpack.config.js
const productionConfig = merge([
...parts.generateSourceMaps({ type: 'source-map' }),
]);
2
3
source-map
是构建速度最慢,但是质量最高的选项,比较适合用在生产环境中。
这时候执行 npm run build
, 可以在项目的 dist
目录中看到输出的 .map
文件。
内联 Source Map
所谓内联 Source Map 就是将 Source Map 与构建出来的结果一同输出。Webpack 支持多种内联 Source Map 设置,下面逐一介绍。每一个选项都会有一个样例。
每个样例除了 Source Map 的设置,还额外使用了
optimization.moduleIds = "named"
配置来给每一个模块添加名称,提高可阅读性。同时还将mode
设置为false
来避免 Webpack 的默认处理。
devtool: "eval"
eval
选项生成的代码会将每一个模块的源代码包裹在 eval
函数中。
/***/ "./src/index.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _main_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/main.css\");\n/* harmony import */ var _main_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_main_css__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _component__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(\"./src/component.js\");\n\n\ndocument.body.appendChild(Object(_component__WEBPACK_IMPORTED_MODULE_1__[\"default\"])());\n\n//# sourceURL=webpack:///./src/index.js?");
/***/ }),
2
3
4
5
6
7
devtool: "eval-cheap-source-map"
eval-cheap-source-map
与 eval
类似,但是会将 Source Map 信息进行 Base64 编码,将就编码结果以 data url 的形式作为 Source Map 的地址保存在构建结果中。eval-cheap-source-map
输出的 Map 信息中只有行数据,没有代码列的 Map 数据。我们将 Source Map 信息解码后可以看到如下信息:
{
"version": 3,
"file": "./src/index.js.js",
"sources": ["webpack:///./src/index.js?3700"],
"sourcesContent": [
"import './main.css';\nimport component from \"./component\";\ndocument.body.appendChild(component());"
],
"mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA",
"sourceRoot": ""
}
2
3
4
5
6
7
8
9
10
devtool: eval-cheap-module-source-map
eval-cheap-module-source-map
的处理方式相同,只是构建出来的 Source Map 质量更高,但是构建速度更慢。解码信息如下:
{
"version": 3,
"file": "./src/index.js.js",
"sources": ["webpack:///./src/index.js?b635"],
"sourcesContent": ["import './main.css';\nimport component ..."],
"mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA",
"sourceRoot": ""
}
2
3
4
5
6
7
8
devtool: "eval-source-map"
eval-source-map
得到的 Source Map 质量最高,同时构建速度也最慢。解码后得到的信息也最多。
{
"version": 3,
"sources": ["webpack:///./src/index.js?b635"],
"names": ["document", "body", "appendChild", "component"],
"mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEAA,QAAQ,CAACC,IAAT,CAAcC,WAAd,CAA0BC,0DAAS,EAAnC",
"file": "./src/index.js.js",
"sourcesContent": ["import './main.css';\nimport component ..."],
"sourceRoot": ""
}
2
3
4
5
6
7
8
9
输出 Source Map 文件
一般来说,在生产环境中,我们会将 Source Map 输出到单独的 .map
文件中。下面几个选项会将 Source Map 信息单独输出到文件中。
devtool: "cheap-source-map"
与上面的内联的 cheap 选项相同,cheap-source-map
生成的 Map 文件中只有代码行的 Map 数据,没有代码列的 Map 数据。同时,来自于 loader 的 Source Map 也不会包含在输出结果中。
检查输出的 .map
文件可以看到如下结果。
{
"version": 3,
"file": "main.js",
"sources": [
"webpack:///webpack/bootstrap",
"webpack:///./src/component.js",
"webpack:///./src/index.js",
"webpack:///./src/main.css"
],
"sourcesContent": ["...", "// extracted by mini-css-extract-plugin"],
"mappings": ";AAAA;...;;ACFA;;;;A",
"sourceRoot": ""
}
2
3
4
5
6
7
8
9
10
11
12
13
在构建输出的代码文件中,可以看到类似于 //# sourceMappingURL=main.js.map
这样的注释,来告知浏览器使用的 Source Map 的地址。
devtool: "cheap-module-source-map"
cheap-module-source-map
于上一个选项基本相同,除了会将来自于 loader 的 Source Map 也包含在输出的 Map 文件中。注意该选项只包含代码行的 Map 信息。
查看输出信息如下:
{
"version": 3,
"file": "main.js",
"sources": [
"webpack:///webpack/bootstrap",
"webpack:///./src/component.js",
"webpack:///./src/index.js",
"webpack:///./src/main.css"
],
"sourcesContent": ["...", "// extracted by mini-css-extract-plugin"],
"mappings": ";AAAA;...;;ACFA;;;;A",
"sourceRoot": ""
}
2
3
4
5
6
7
8
9
10
11
12
13
devtool: "source-map"
source-map
的输出质量最高,但是性能也最差。输出结果如下:
{
"version": 3,
"sources": [
"webpack:///webpack/bootstrap",
"webpack:///./src/component.js",
"webpack:///./src/index.js",
"webpack:///./src/main.css"
],
"names": [
"text",
"element",
"document",
"createElement",
"className",
"innerHTML",
"body",
"appendChild",
"component"
],
"mappings": ";AAAA;...;;ACFA;;;;A",
"file": "main.js",
"sourcesContent": ["...", "// extracted by mini-css-extract-plugin"],
"sourceRoot": ""
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
devtool: "hidden-source-map"
hidden-source-map
与 source-map
相同,但是 hidden-source-map
不会在代码中输出 .map
的引用地址。因此浏览器调试工具无法自动加载 Source Map 文件。
devtool: "nosources-source-map"
nosources-source-map
输出的 Source Map 文件中不包含 sourcesContent
这个字段,也就是说在客户端看不到源代码。但是可以得到报错的基于源代码的堆栈信息。
其他的 Source Map 选项
这里还有一些可以控制 Source Map 输出的选项。
const config = {
output: {
// 指定生成的 Source Map 文件名称
// 可以使用 [file], [id], [fullhash], and [chunkhash] 占位符.
sourceMapFilename: '[file].map', // 默认配置
// 默认的 Source Map 引用模板。
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
// create-react-app 会使用下面的配置,这样让 Source Map 可以在开发工具中更好的展示
devtoolModuleFilenameTemplate: (info) =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
从 Source Map 中生成源代码
我们可以借助于 source-from-sourcemapsopen in new window这个工具,通过 Source Map 和压缩混淆过的代码反向生成源代码。
使用项目依赖包中的 Source Map
source-map-loaderopen in new window 可以帮助我们将项目依赖包中的内联 Source Map 提取出来,从而避免将依赖包的 Source Map 输出到项目代码中。
总结
Source Map 通过建立转换后的代码和源代码的映射关系,给开发调试带来的极大的便利。在开发和生产环境中都可以使用。
Webpack 支持多种 Source Map 的生成方式,通常分为内联 Source Map 和单独输出到文件 Source Map 两种。前者常用在开发环境中,后者常用在生产环境中。
devtool: "source-map"
生成的 Source Map 质量最高,常被用在生产环境中。
通过使用 devtool: "hidden-source-map"
,我们可以在生产环境中不暴露 Source Map 文件。将错误堆栈上报到其他服务,然后结合 Source Map 查找问题。
在处理第三方依赖包的 Source Map 的时候,我们需要使用 source-map-loader
.
在处理样式相关的 Source Map 的时候,我们需要开启对应 loader 的 sourceMap
参数。
关注微信公众号,获取最新推送~
加微信,深入交流~