一文加深你对 webpack 的 Source Map 理解
单词直译:
source
: 源。比如说:源码(source code)
map
: 映射
单词组合 + 场景分析(构建工具,打包): 源文件映射
概念描述:
Source Map
就是一个信息文件,里面存储了代码打包转换后的位置信息,实质是一个 json
描述文件,维护了打包前后的代码映射关系。
为什么需要 Source Map
无论针对 React 开发,还是 Vue 开发,如果它们的构建工具是使用 webpack 的话,那么针对本地运行,或者时说部署到线上,都是使用了一个构建好的打包文件(就是把多文件转化为一个文件,分包除外)。
使用一份测试 demo,做解释:
无论是本地运行,还是部署线上都是针对 build.js
文件的运行和部署,src 文件夹的文件称为源文件,但是都会集中合并到 build.js
文件中,这一过程叫做打包。
打包过程中会发生:
- ES6 转化为 ES5;
- TypeScript 转化为 JavaScrip;
- 代码丑化,变量名称发生变化;
- ...
上面一些列的操作,就会出现行号,列号都会在编译后发生变化。
那么这里问题就出现了,如果源文件中的代码出现了错误,那么也会把错误代码打包到 build.js,然后运行 build.js 代码,浏览器就会飘红,那么这时候就需要解决错误,但是呢?
生产环境:
开发环境:
生产环境是经过丑化后的代码,只有一行代码,所以错误提示出现在 build.js:1
,错误信息等于没说, 一脸懵逼。
开发环境的错误提示 build.js:37
,虽然也不知道具体的,但是可以深入看错误信息,也可以知道源文件哪里存在错误。
但是呢,这些针对开发者都不是很友好的。
因此,source map
就出现了,就是为了解决此类问题。
- 从已转换的代码,映射到原始的源文件;
- 使浏览器可以重构原始文件源并在调试器中显示重建的原始文件源;
配置 source map 之后,快速定位错误文件及行数。
webpack 配置 Source Map
webpack 配置提供了一个属性: devtool ;此选项控制是否生成,以及如何生成 source map。
module.exports = {
mode: "development",
devtool: 'source-map'
};
如果 mode 为 production,devtool 的默认值为 none;
如果 mode 为 development,devtool 的默认值为 eval。
那么针对不是默认值,需要手动设置的话,可以设置哪些值呢?devtool 的属性值可以高达 26 种
www.webpackjs.com/configurati…
在上面的网址可以看看到底存在哪些属性值?有什么区别?适用用于什么环境?构建与重构建的速度?
在下面会抽出几个属性值来学习学习,不过在学习的前面,先来认识认识 source map 文件,简单的理解一下,为后面的学习打点基础。
Source Map 文件的认识
生成最简单的 source map 文件
module.exports = {
mode: "production",
devtool: 'source-map'
};
执行脚本,生成打包文件的同时也会生成打包文件的 source map 文件。
这里的 mappings
属性是一串 base64 编码,里面保存了源文件的行号等一些列信息。重构源文件,就是从里面读取信息。
sourceRoot
:所有的 sources 相对的根目录(没有截图出来)
浏览器识别 Source Map
浏览器运行的是 build.js
文件,那么是怎么关联的 source map 文件呢?
细心可以发现,build.js 文件的最后一行存在://# sourceMappingURL=build.js.map
,那么浏览器就会去解析 sourceMappingURL,获取对应的 source map 文件,并且根据 source map 重构源文件,还原代码,方便进行调试。
谷歌浏览器是默认打开的,支持解析 source map 文件;如果关闭了这个选项,就不会重新构建源文件了。
devtool 的属性值
devtool
的属性值,决定了是否生成,以及如何生成 source map。
在学习配属属性值之前,希望你去看看 www.webpackjs.com/configurati… ,这里里面写了所有的属性值,以及适用环境,构建与重构建的速度。
下面就来学习几个常见的属性
false
不使用 source map,也就是没有任何和 source map 相关的内容
none
production 模式下的默认值(什么值都不写) ,不生成 source map 文件。
是否适用生产环境:yes。
速度:build: fastest;rebuild: fastest
注意在 development 模式下,配置该属性会报错。
eval
development 模式下的默认值,不生成source map 文件。
是否适用生产环境:no。
速度:build: fast;rebuild: fastest
但是会在 eval 函数后面添加 sourceURL 信息
{
// webpack 对应模块
__webpack_modules__: {
"./src/utils/format.js": eval("function upper(str) {\n return str.toLocaleUpperCase();\n}\n\nmodule.exports = {\n upper,\n};\n\n\n//# sourceURL=webpack://weboack_study/./src/utils/format.js?");
}
}
上面的 __webpack_modules__
对象,如果理解 webpack 的打包原理,就很好理解这个属性。如果不理解打包原理也没有太大关系,只需要知道 eval 函数里面最后处有一个注解(sourceURL) ,映射源文件的路径。
source-map
生成 source map 文件,最全的信息。
是否适用生产环境:yes。
速度:build: slowest;rebuild: slowest
在前面的 Source map文件的认识已经对该属性的生成文件,做了一定的解释。
也就是在 build.js 文件的最后处有一个注解(sourceMappingURL) ,指向 source map 文件。
eval-source-map
会生成 source map,但是 source map 是以 DataUr l添加到 eval 函数的后面。
是否适用生产环境:no。
速度:build: slowest;rebuild: ok;
简单理解就是生成的 source map 是直接插入到 eval 函数的最后面。
截图太长不好截全,直接代码展示也许更好懂。后面添加了//# sourceMappingURL=data:application/json;......
eval(
"function upper(str) {\n return str.toLocaleUpperCase();\n}\n\nmodule.exports = {\n upper,\n};\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvdXRpbHMvZm9ybWF0LmpzLmpzIiwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly93ZWJvYWNrX3N0dWR5Ly4vc3JjL3V0aWxzL2Zvcm1hdC5qcz82YmU4Il0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIHVwcGVyKHN0cikge1xuICByZXR1cm4gc3RyLnRvTG9jYWxlVXBwZXJDYXNlKCk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0ge1xuICB1cHBlcixcbn07XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./src/utils/format.js\n"
);
inline-source-map
会生成 sourcemap,但是 source-map 是以 DataUrl 添加到 bundle 文件的后面。
是否适用生产环境:no。
速度:build: slowest;rebuild: slowest
在 build.js 文件的后面插入注解 (sourceMappingURL) ,指向 DataUrl。
cheap-source-map
会生成 source map,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping) 。
是否适用生产环境:no。
速度:build: ok;rebuild: slow
看看报错信息,cheap-source-map 省略了具体的列号,没有生成列映射。
cheap-module-source-map
会生成 source map 文件,类似于cheap-source-map,但是对源自 loader 的 source map 处理会更好。什么意思呢?
就比如 babel-loader 对 js 代码进行转化(源代码 -> AST -> 新源代码),就是 cheap-source-map
在构建源文件的时候,把空行去掉了,那么报错的位置的行号也就相应的错位了。
cheap-module-source-map
就不会把空行去掉,展示正确的行号。
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: ['babel-loader']
}
]
},
}
hidden-source-map
会生成 source map,但是不会对 source-map 文件进行引用;
相当于删除了打包文件中对 source map 的引用注释;
报错信息定位到 build.js 文件。如果手动加上 注解(sourceMappingURL) ,报错信息就会定位源文件了。
nosources-source-map
会生成 source map,但是生成的 source map只有错误信息的提示,不会生成源代码文件;
但是报错信息,还是显示的正确的行号和列号,只是不能进行点击,进入源文件(或者源文件的内容对应不上)
devtool 的属性值组合
devtool 的属性值有 26 个,总不可能一一去牢记,那么直接裂开。
上面说了一些 source map 的特性,以及效果场景。
其他的就不用了介绍了,其他的属性值,一个公式就可以解决了。
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
- inline-|hidden-|eval: 三个值时三选一;
- nosources: 可选值;
- cheap可选值,并且可以跟随 module 的值;
devtool 属性值的适用场景
开发阶段:推荐使用 source-map 或者 cheap-module-source-map
vue-cli
采用的是 source-mapcreate-react-app
采用的是 cheap-module-source-map
发布阶段:false、缺省值(不写)
- 客户不需要看懂错误信息
- 防止黑客看懂错误信息,发起攻击。
总结
相信各位掘友对 webpack 的 source map 有了一定的认知。
如有错误,请指教。