一文了解source-map
背景
作为一个开发工程师——无论是什么开发,要求开发环境最不可少的一点功能就是——debug功能。当我们通过webpack 将我们的源码打包成了 bundle.js 。试想:实际上客户端(浏览器)读取的是打包后的bundle.js ,那么当浏览器执行代码报错的时候,报错的信息自然也是bundle的内容。我们如何将报错信息(bundle错误的语句及其所在行列)映射到源码上?为了解决这个问题,google 提出了sourcemap 的想法,并在chorme上最先支持sourcemap的使用。sourcemap可以帮我们直接定位到编译前代码的特定位置。
webpack已经内置了sourcemap的功能,我们只需要通过简单的配置,将可以开启它。
module.exports = {
// 开启 source map
// 生产环境一般不开启 sourcemap
devtool: 'source-map',
}
除source-map外,还可以基于我们的需求设置其他值,webpack——devtool官网一共提供了多种Sourcemap模式:[官网链接](Devtool | webpack 中文文档 (docschina.org))
这么多种配置项其实只是五个关键字 eval、source-map、cheap、module 和inline的组合罢了,下面列出它们的意义:
「关键字」 | 「含义」 | 特点 |
|---|---|---|
source-map | 产生.map 文件 | 定位信息最全,但也.map 文件最大,效率最低 |
eval | 使用 eval 包裹模块代码 | 利用字符串可缓存从而提效 |
cheap | 不包含列信息,也不包含 loader 的 sourcemap | 精准度降低换取文件内容的缩小 |
module | 包含 loader 的 sourcemap(比如 jsx to js ,babel 的 sourcemap),否则无法定义源文件 | 解决对于使用cheap 配置项导致无法定位到 loader 处理前的源代码问题 |
inline | 将.map 作为 DataURI 嵌入,不单独生成.map 文件 | 减少文件数 |
举例
为了方便演示,这里的源代码只包含了一行代码:
代码语言:javascript复制console.log('hello webpack');
source-map
代码语言:javascript复制module.exports = {
// 开启 source map
// 生产环境一般不开启 sourcemap
devtool: 'source-map',
}
当我们执行打包命令之后,我们发现bundle的最后一行总是会多出一个注释,指向打包出的bundle.map.js(sourcemap文件)。sourcemap文件用来描述 源码文件和bundle文件的代码位置映射关系。基于它,我们将bundle文件的错误信息映射到源码文件上。
console.log("hello webpack");
//# sourceMappingURL=main.built.js.map
接下来我们看看sourcemap文件包含了一些什么信息:
{
"version": 3,
"file": "main.built.js",
"mappings": "AAAAA,QAAQC,IAAI",
"sources": ["webpack://webpack-demo/./src/index.js"],
"sourcesContent": ["console.log('hello webpack');"],
"names": ["console", "log"],
"sourceRoot": ""
}
上面可以看到,sourcemap其实就是就是一段维护了前后代码映射关系的json描述文件,包含了以下一些信息:
version:sourcemap版本(现在都是v3)file:转换后的文件名。sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。sources:转换前的文件。该项是一个数组,表示可能存在多个文件合并。names:转换前的所有变量名和属性名。mappings:记录位置信息的字符串。mappings信息是关键,它使用Base64 VLQ编码,包含了源代码与生成代码的位置映射信息。mappings的编码原理详解可见:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html,这里就不再详述。
eval
代码语言:javascript复制module.exports = {
// 开启 source map
// 生产环境一般不开启 sourcemap
devtool: 'source-map',
}
输出的 bundle文件如下:
(() => {
var __webpack_modules__ = {
138: () => {
eval("console.log('hello webpack');nn//# sourceURL=webpack://webpack-demo/./src/index.js?")
}
},
__webpack_exports__ = {};
__webpack_modules__[138]()
})();
每个module会封装到 eval 里包裹起来执行,并且会在末尾追加注释//@ sourceURL。只是它映射的是转换后的代码,而不是映射到原始代码。
eval-source-map
代码语言:javascript复制module.exports = {
// 开启 source map
// 生产环境一般不开启 sourcemap
devtool: 'eval-source-map',
}
输出的 bundle文件如下:
(() => {
var __webpack_modules__ = {
138: () => {
eval("console.log('hello webpack');//# sourceURL=[module]n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTM4LmpzIiwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vd2VicGFjay1kZW1vLy4vc3JjL2luZGV4LmpzP2I2MzUiXSwic291cmNlc0NvbnRlbnQiOlsiY29uc29sZS5sb2coJ2hlbGxvIHdlYnBhY2snKTsiXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=n//# sourceURL=webpack-internal:///138n")
}
},
__webpack_exports__ = {};
__webpack_modules__[138]()
})();
可以看到,加了eval的配置生成的sourcemap会作为DataURI嵌入,不单独生成.map文件。
inline-source-map
代码语言:javascript复制module.exports = {
// 开启 source map
// 生产环境一般不开启 sourcemap
devtool: 'inline-source-map',
}
输出的 bundle文件如下:
console.log("hello webpack");
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5idWlsdC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsUUFBUUMsSUFBSSIsInNvdXJjZXMiOlsid2VicGFjazovL3dlYnBhY2stZGVtby8uL3NyYy9pbmRleC5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zb2xlLmxvZygnaGVsbG8gd2VicGFjaycpOyJdLCJuYW1lcyI6WyJjb25zb2xlIiwibG9nIl0sInNvdXJjZVJvb3QiOiIifQ==
可以看出它将map作为DataURI嵌入,不单独生成.map文件。
cheap-source-map
代码语言:javascript复制module.exports = {
// 开启 source map
// 生产环境一般不开启 sourcemap
devtool: 'cheap-source-map',
}
接下来我们看看sourcemap文件包含了一些什么信息:
{
"version": 3,
"file": "main.built.js",
"mappings": "AAAA",
"sources": ["webpack://webpack-demo/./src/index.js"],
"sourcesContent": ["console.log('hello webpack');"],
"names": [],
"sourceRoot": ""
}
可以看到比之前source-map的,mappings字段短了很多,实际上是因为它没有生成「列映射」(column mapping),只是「映射行数」。
cheap-module-source-map
Webpack会利用loader将所有非js模块转化为webpack可处理的js模块,而增加上面的cheap配置后也不会有loader模块之间对应的sourceMap。
什么是模块之间的sourceMap呢?比如jsx文件会经历loader处理成js文件再混淆压缩, 如果没有loader之间的sourcemap,那么在debug的时候定义到上图中的压缩前的js处,而不能追踪到jsx中。
所以为了映射到loader处理前的代码,我们一般也会加上module配置。
总结
开发环境
在开发环境中,我们希望速度快,调试更友好。
以此这里推荐配置:eval-cheap-module-souce-map
生产环境
生产环境我们一般不会开启sourcemap功能,主要有两点原因:
- 通过
bundle和sourcemap文件,可以反编译出源码————也就是说,线上产物有soucemap文件的话,就意味着有暴漏源码的风险。 - 我们可以观察到,
sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。


