Source Map
一、前提
我们知道 webpack 对代码压缩会给我们带来几个好处:
- 减少体积
- 多个文件合并,减少 HTTP 请求
但是除了这些好处,就不会有坏处了吗?我们用代码来实验一下
index.js
import print from './print'
print()
print.js
export default function printMe() {
// 这里将console写错了
cosnole.log('I get called from print.js!');
}
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './index.js',
devtool: false, // webpakc 默认会使用Source Map,所以这里要吧它关闭
plugins: [
new HtmlWebpackPlugin()
]
}
然后我们进行打包,然后打开页面。
我们可以看到这个报错提示是在main.js的16行,再次点击进去
点过去后发现竟然真的是跳到打包后main.js的16行,要清楚我们这个语句是写在print.js里面的。
现在代码量还少,所以我们还能迅速判断是哪里错误。但是要是以后代码量多了起来,再加上框架的源码,那到时候 真的就鬼知道哪里报错。
二、什么是 Source Map
Source Map的作用就如同它的英文名字翻译,源代码映射。由Source Map 创建一个 映射信息文件,里面存储着位置信息来对应打包后的文件。如果有报错,那么main.js就能根据映射文件 找到代码原来文件的所在位置。
三、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 文件,形如:
{
"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' 时,产物:
{
"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)。例如:
注意观察上例 sourcesContent 字段,左边 devtool 带 module 关键字,因此此处映射的,是包含 class Person 的最原始代码;而右边生成的 sourcesContent ,则是经过 babel-loader 编译处理的内容
nosources
当 devtool 包含 nosources 时,生成的 Sourcemap 内容中不包含源码内容 —— 即 sourcesContent 字段。例如 devtool = 'nosources-source-map' 时,产物:
{
"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',产物:
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 配置。例如
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './index.js',
devtool: 'source-map',
plugins: [
new HtmlWebpackPlugin()
]
}
打包后会生成 main.js.map 文件,并且在 main.js 最下面会标注引入
打开页面,报错也会标注到 print.js 的第几行
总结
从上面可以看出,即使配置模式再多,那也是围绕着那七个基本信息来组合。
但如果你想进行更加细粒度的配置,可以根据官方的建议使用 SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin 来替代使用 devtool 。这个插件不能与 devtool 共同使用,因为 devtool 也是使用它。
在实际中使用的情况:
开发环境个人建议使用:eval-cheap-module-source-map。
生产环境个人建议使用:false,既不需要,以达到包的最小化。