Webpack中Source Map配置研究
为什么需要Source Map
通常我们运行在浏览器中的代码是经过处理的,处理后的代码可能与开发时代码相差很远,这就导致开发调试和线上排错变得困难。这时Source Map就登场了,有了它浏览器就可以从转换后的代码直接定位到转换前的代码。在webpack中,可以通过devtool选项来配置Source Map。
devtool选项
当mode为development时,devtool默认为‘eval’,当mode为production时,devtool默认为false。
devtool为false和'eval'有啥区别
在回答这个问题之前,我们得做点准备工作。
准备工作
1,创建项目 安装依赖
// 1,创建项目 安装依赖
mkdir sourcemap-demo
cd sourcemap-demo
npm init -y
npm install webpack webpack-cli --save-dev
npm install html-webpack-plugin --save-dev
2,添加文件
// 添加index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>起步</title>
</head>
<body>
</body>
</html>
// 添加src/index.js
import a from './a';
console.log(`hello index`);
a();
// 添加src/a.js
export default function b() {
console.log('hello a');
}
3,写配置 webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: 'development', // 注意:这地方要将mode设置为development。因为当mode为production时webpack会开启代码压缩,不利于我们观察现象。
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
devtool: false, // 先配置为false
plugins: [new HtmlWebpackPlugin()],
};
4,在package.json中添加
"scripts": {
"build": "webpack"
}
5,执行 npm run build,打包文件生成到了dist文件夹中,至此,准备工作完毕。
观察devtool为false时
我们要观察两个地方,一个是dist/main.js。一个在浏览器中的情况。
1, 在dist/main.js中
var __webpack_modules__ = ({
"./src/a.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
"default": () => (b)
});
function b() {
console.log('hello a');
}
})
...
var __webpack_exports__ = {};
(() => {
__webpack_require__.r(__webpack_exports__);
var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
console.log(`hello index`);
(0, _a__WEBPACK_IMPORTED_MODULE_0__["default"])();
})();
内容分析:
- __webpack_modules__对象对应着我们源码中的js文件, key是路径,value就是经过webpack转换过的模块。
- 我们写的 console.log(
hello index
); 这类源码中的内容,基本没什么变化的被打包到dist/main.js中。
2,在浏览器中,观察开发者工具中的Sources。
内容分析:
- 只能看到webpack打包后的代码.
小结
devtool为false,只能看到打包后的代码,不利于开发调试和线上排错。
观察devtool为'eval'时
重新编译后。
1, 在dist/main.js中
var __webpack_modules__ = ({
"./src/a.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ b)\n/* harmony export */ });\nfunction b() {\n console.log('hello a');\n}\n\n//# sourceURL=webpack://sourcemap/./src/a.js?");
}),
"./src/index.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ \"./src/a.js\");\n\n\nconsole.log(`hello index`);\n(0,_a__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n\n//# sourceURL=webpack://sourcemap/./src/index.js?");
})
});
内容分析:
- 模块中的内容被eval包裹。内容末尾有// # sourceURL=webpack://sourcemap/./src/index.js。浏览器会对eval后面的sourceURL特殊处理。下面看看怎么特殊处理的
2,在浏览器中
内容分析:
- 所有sourceURL的内容会被翻译成上图中左侧下方sourcemap-demo节点的结构,点击某个节点,会看到其eval对应的代码。从中我们看到了console.log(
hello index
);。
小结
devtool为'eval',可以在浏览器开发者工具中看到模块结构,通常也就是我们源码的结构。也能看到被webpack转换过点代码,在一定程度上能帮助我们调试代码和排查问题了。
devtool为'source-map'和'eval-source-map'有啥区别
观察devtool为'source-map'时
现在将devtool设置成source-map,看看效果。
1, 在dist/main.js中
//# sourceMappingURL=main.js.map
内容分析:
- 最下面多了一行注释代码sourceMappingURL指向main.js.map。同时dist文件夹下多了个main.js.map。
main.js.map文件
源码和打包后代码的映射文件,有了它,当线上出bug时,浏览器可以在控制台显示报错在源码中的行数和列数。
2,在浏览器中
内容分析:
- 在浏览器中可以看到我们的源码了。
小结
- devtool为'source-map',会生成映射文件,出bug时可以准确定位到问题在源码中的位置,便于开发调试和排查问题。
- 它同样有缺点,一是source-map的生成比较耗时,二是在开发时,我们会用热更新,没有source-map的话只需替换被更改的模块就行了,但是有source-map时,就要重新生产整个打包文件的map文件,比较耗时。
那么有什么方式可以避免devtool为'source-map'的缺点吗,答案是'eval-source-map'
观察devtool为'eval-source-map'时
1, 在dist/main.js中
var __webpack_modules__ = ({
"./src/a.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ b)\n/* harmony export */ });\nfunction b() {\n console.log('hello a');\n}//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvYS5qcy5qcyIsIm1hcHBpbmdzIjoiOzs7O0FBQWU7QUFDZjtBQUNBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vc291cmNlbWFwLWRlbW8vLi9zcmMvYS5qcz82ZTFjIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGIoKSB7XG4gIGNvbnNvbGUubG9nKCdoZWxsbyBhJyk7XG59Il0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./src/a.js\n");
}),
"./src/index.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ \"./src/a.js\");\n\n\nconsole.log(`hello index`);\n(0,_a__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvaW5kZXguanMuanMiLCJtYXBwaW5ncyI6Ijs7QUFBb0I7O0FBRXBCO0FBQ0EsOENBQUMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9zb3VyY2VtYXAtZGVtby8uL3NyYy9pbmRleC5qcz9iNjM1Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBhIGZyb20gJy4vYSc7XG5cbmNvbnNvbGUubG9nKGBoZWxsbyBpbmRleGApO1xuYSgpOyJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./src/index.js\n");
})
});
内容分析:
- 与devtool为'source-map'的区别是,不是在文件最底部多了一行//# sourceMappingURL=main.js.map,也没有生成map文件。而是每个模块直接跟随一个sourceMappingURL,其中是源码和打包后代码的映射信息。这样点好处时在webpack因为开发者的更改要进行热更新时,不必重新生成全局map文件,只需重新生成更改模块的map信息就好。
2,在浏览器中
内容分析:
- 在浏览器中同样可以看到源码,相比于'source-map',热更新时编译时间更少。
小结
- 这里总结下'eval'的好处,由于Mapping信息被拆分在了每个模块eval函数后面的sourceMappingURL中,热更新时不必全量生成map数据,从而拥有更快的编译速度。(官网管这叫rebuild速度快)
对于开发调试,eval-source-map模式就完美了吗,如果追求更快的编译速度而不求追求的出现bug时精准的行列信息,只提供bug所在行信息就行,应该用什么?答案是'eval-cheap-source-map'。
devtool为'eval-cheap-source-map'和'eval-cheap-module-source-map'有啥区别
eval-source-map可以在出现bug时准确的定位到出错代码在源码的行和列。但如果你不需要列映射,只需要知道bug所在的行,那可以考虑eval-cheap-source-map,它比eval-source-map开销小,编译的快。
观察devtool为'eval-cheap-source-map'时
1, 在dist/main.js中
这里不贴代码了,相比于eval-source-map,sourceMappingURL后的内容少很多。
2,在浏览器中
与eval-source-map相同,更多细节暂不研究。
3,eval-cheap-source-map的问题
我们增加js代码babal专义的环节,安装babel
npm install babel-loader @babel/core @babel/preset-env -D
添加代码
// src/b.js
export default class B {
constructor() {
this.str = 'hell b'
}
sayHello() {
console.log(this.str)
}
}
// index.js
import a from './a';
+ import B from './b';
console.log(`hello index`);
a();
+ const b = new B();
+ b.sayHello();
查看在浏览器中的表现
坏了,浏览器中展示点代码是经过loader编译后的代码,不是我们手写的代码了。
经实验,如果设置成eval-source-map,不会有这个问题,浏览器中仍然可以看到源码。
有没有什么设置,既可以没有列映射,又能在浏览器中看到源码的呢?答案是eval-cheap-module-source-map。
观察devtool为'eval-cheap-module-source-map'时
在这种情况下,源自 loader 的 source map 会得到更好的处理结果。既没有列映射,又能在浏览器中看到源码。
小结
在开发模式下,为方便我们的开发调试。
- 如果你想要最全的源码,bug精准定位到哪行哪列,使用eval-source-map。
- 如果只需要将错误定位到哪行,使用eval-cheap-module-source-map。
开发模式下其他配置
inline-source-map,inline-cheap-source-map等inline开头的
就是把map文件内容放打包js代码文件中。看起来没多大用处。
eval-nosources-cheap-source-map 等有nosources关键字的
源码信息不再map文件中,浏览器通常会尝试从 web 服务器或文件系统加载源代码。目前不了解其使用场景。
生产环境的source map配置
(none)
生产环境不配置等同于写了devtool: false。默认不生成 source map,这是个不错的选择。因为别人可以通过 source map看到我们的源码,影响安全。
source-map
在线上环境不必使用eval-*了,没有热更新的情况。也没必要使用cheap-*了,只要发布的时候编译一次,慢点就慢点。
显然使用source-map有个问题,会让用户看到源码。我们可以通过服务器配置,禁止普通用户访问 source map 文件。只让固定IP(开发者的电脑的IP)的用户可以访问到。
hidden-source-map
hidden-source-map - 与 source-map 相同,都会生成.map文件。但不会为 bundle 添加引用注释。
目前,各大监控平台均有针对错误监控的Source Map的解析功能。例如开源监控平台Sentry支持Webpack插件和自身的CLI工具上传map文件后进行解析。
使用hidden-source-map,当线上报错时,将错误堆栈跟踪信息上报给监控平台,监控平台结合map文件就可以告诉我们源码级别的错误信息了。
总结
通过这次配置研究,我们要学习到
- eval配置的作用和原理。
- source-map配置的作用和原理。
- 开发环境和生产环境视情况选择devtool的值。
转载自:https://juejin.cn/post/7165883401596207118