webpack中sourcemap详细配置
# 简介
source map
是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map;
map文件只要不打开开发者工具,浏览器是不会加载的;
sourcemap是为了解决开发代码与实际运行代码不一致时帮助我们debug到原始开发代码的技术;
webpack通过配置可以自动给我们source maps
文件,map
文件是一种对应编译文件和源文件的方法;
# 类型
类型 | 含义 |
---|---|
source-map | 原始代码 最好的sourcemap质量有完整的结果,但是会很慢 |
eval-source-map | 原始代码 同样道理,但是最高的质量和最低的性能 |
hidden-source-map | 和 source-map 一样,但不会在 bundle 末尾追加注释 |
cheap-module-eval-source-map | 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能 |
cheap-eval-source-map | 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl |
eval | 生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap |
cheap-source-map | 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用 |
cheap-module-source-map | 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射 |
module.exports = {
devtool: 'cheap-module-eval-source-map', //开发环境
devtool: 'cheap-module-source-map', //正式环境
}
2
3
4
# 配置项
# 关键字
配置项其实只是五个关键字eval、source-map、cheap、module和inline的组合
关键字 | 含义 |
---|---|
source-map | 产生.map文件 |
eval | 使用eval包裹模块代码 |
cheap | 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap |
module | 包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件 |
inline | 将.map作为DataURI嵌入,不单独生成.map文件 |
# 最佳实践【要点】
# 考虑的因素
源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加cheap的基本类型来忽略打包前后的列信息。
不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个vue文件报错了,我们希望能定位到具体的vue文件,因此我们也需要module配置。
需要生成map文件的形式,因此我们需要增加 source-map属性。
我们介绍了eval打包代码的时候,知道eval打包后的速度非常快,因为它不生成map文件,但是可以对eval组合使用 eval-source-map使用会将map文件以DataURL的形式存在打包后的js文件中; 它的效果类似于inline的效果**,因此在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快。**如下:
# 开发环境
- 在开发环境对sourceMap的要求是:快(eval),信息全(module),且由于此时代码未压缩,我们并不那么在意代码列信息(cheap),所以开发环境比较推荐配置:
devtool: cheap-module-eval-source-map
# 生产环境
- 一般情况下,我们并不希望任何人都可以在浏览器直接看到我们未编译的源码,所以我们不应该直接提供sourceMap给浏览器。但我们又需要sourceMap来定位我们的错误信息, 这时我们可以设置
cheap-module-source-map
- 一方面webpack会生成sourcemap文件以提供给错误收集工具比如sentry,另一方面又不会为 bundle 添加引用注释,以避免浏览器使用。
module.exports = {
//cheap不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap
//module包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件
//eval使用eval包裹模块代码
//source-map产生.map文件
devtool: 'cheap-module-eval-source-map', //开发环境
devtool: 'cheap-module-source-map', //正式环境
}
2
3
4
5
6
7
8
# 详细分析
# 示例及环境配置
main.js 代码如下:
import demo1Func from './demo1.js';
console.log('main.js');
2
demo1.js 代码如下:
export default function printMe() {
console.log('11111111');
}
2
3
webpack.config.js 代码配置如下:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ClearWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: './js/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),// 将输出的文件都放在dist目录下
publicPath: '/dist'
},
mode: 'development',
module: {
rules: [
{
test: /\.css$/,// 使用正则去匹配要用该loader转换的css文件
loaders: ExtractTextPlugin.extract({
use: ['css-loader'] // 转换 .css文件需要使用的Loader
})
},
{
test: /\.(png|jpg)$/,
loader: 'url-loader',
options: {
limit: 10000,
name: '[name].[ext]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/, // 排除文件
loader: 'babel-loader'
}
]
},
resolve: {
// modules: ['plugin', 'js']
},
externals: {
jquery: 'jQuery'
},
devtool: 'eval',
devServer: {
// contentBase: path.join(__dirname, "dist"),
port: 8081,
host: '0.0.0.0',
headers: {
'X-foo': '112233'
},
// hot: true,
inline: true,
open: true,
overlay: true,
stats: 'errors-only'
},
plugins: [
// new ClearWebpackPlugin(['dist']),
new ExtractTextPlugin({
// 从js文件中提取出来的 .css文件的名称
filename: `main.css`
})
]
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
基本结构如上,现在我们可以来理解 webpack中的SourceMap的几种常见方式了。
# eval
- 用
eval
执行代码 - whyeval (opens new window)
({
"./src/index.js":
(function (module, exports) {
eval("let a=1;\r\nlet b=2;\r\nlet c=3;\n\n//# sourceURL=webpack:///./src/index.js?");
})
});
2
3
4
5
6
eval-source-map
就会带上源码的sourceMap- 加了eval的配置生成的sourceMap会作为DataURI嵌入,不单独生成
.map
文件 - 官方比较推荐开发场景下使用eval的构建模式,因为它能
cache sourceMap
,从而rebuild的速度会比较快
({
"./src/index.js":
(function (module, exports) {
eval("let a=1;\r\nlet b=2;\r\nlet c=3;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,In0=\n//# sourceURL=webpack-internal:///./src/index.js\n");
})
});
2
3
4
5
6
devtool: "eval-source-map" is really as good as devtool: "source-map", but can cache SourceMaps for modules. It’s much faster for rebuilds.
eval 会将每一个module模块,执行eval,执行后不会生成sourcemap文件,仅仅是在每一个模块后,增加sourceURL来关联模块处理前后对应的关系。在webpack中配置devtool: 'eval', 如下打包后的代码:
(function(modules) { // webpackBootstrap
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return printMe; });\n\nfunction printMe() {\n console.log('11111111');\n}\n\n//# sourceURL=webpack:///./js/demo1.js?");
/***/ "./js/main.js":
/*!********************!*\
!*** ./js/main.js ***!
\********************/
/*! no exports provided */
/***/
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\nconsole.log('main.js');\n\n//# sourceURL=webpack:///./js/main.js?");
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如上打包后的代码,每一个打包后的模块后面都增加了包含sourceURL的注释,sourceURL的值是压缩前存放的代码的位置,这样就通过sourceURL关联了压缩前后的代码。并没有为每一个模块生成相对应的sourcemap。
**优点是:**打包速度非常快,因为不需要生成sourcemap文件。 **缺点是:**由于会映射到转换后的代码,而不是映射到原始代码,所以不能正确的显示行数。
# source-map
src\index.js
let a=1;
let b=2;
let c=3;
2
3
dist\main.js
({
"./src/index.js":
(function (module, exports) {
let a = 1;
let b = 2;
let c = 3;
})
});
//# sourceMappingURL=main.js.map
2
3
4
5
6
7
8
9
在webpack中配置加上 devtool: 'source-map' 配置完成后,source-map会为每一个打包后的模块生成独立的sourcemap文件,比如在package.json文件中 这样配置:
"scripts": {
"build": "webpack --progress --colors --devtool source-map"
}
2
3
然后运行 npm run build 后,会在dist目录下生产map文件。我们继续打包后的代码如下:
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ "./js/demo1.js");
__webpack_require__(/*! ../styles/main.css */ "./styles/main.css");
console.log('main.js');
/***/ }),
/***/ "./styles/main.css":
/*!*************************!*\
!*** ./styles/main.css ***!
\*************************/
/*! no static exports found */
/***/ (function(module, exports) {
// removed by extract-text-webpack-plugin
/***/ })
/******/ });
//# sourceMappingURL=bundle.js.map
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如上打包后的代码最后面一句代码是 //# sourceMappingURL=bundle.js.map ,同时在dist目录下会针对每一个模块生成响应的 .map文件, 比如我们在dist目录中会生成 bundle.js.map文件,我们可以打开看下这个文件代码会如下:
{
"version":3,
"sources":[
"webpack:///webpack/bootstrap","webpack:///./js/demo1.js",
"webpack:///./js/main.js","webpack:///./styles/main.css"
],
"names":["printMe","console","log","require"],
"mappings":";AAAA;AACA;;AAEA;AACA...",
"file":"bundle.js",
"sourcesContent":[],
"sourceRoot": ""
}
2
3
4
5
6
7
8
9
10
11
12
上面生成后的map文件是一个javascript对象,可以被解析器读取,它主要有以下几个属性:
version: Source Map 的版本,目前为3. sources: 转换前的文件,该项是一个数组,表示可能存在多个文件合并. names: 转换前的所有变量名和属性名。 mappings: 记录位置信息的字符串。 sourcesContent: 转换前的文件内容列表,与sources列表依次对应。 sourceRoot: 转换前的文件所在的目录,如果与转换前的文件在同一个目录,该项为空。
注意事项:记得在浏览器中打开调试方式;
1**. 开启开发者工具**
使用快捷键 option + command + i; 或者在 菜单栏选择视图 -> 开发者 -> 开发者工具。
2. 打开设置
点击右上角的三个点的图标,选择Settings, 如下图所示:
3. 开启Source Map 在Sources中,选中 Enable Javascript source maps 如下图所示
开启完成后,我们在 package.json 配置如下代码:
scripts: {
"dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline",
}
2
3
然后在main.js 代码中,添加如下代码:
require('../styles/main.css');
import demo1Func from './demo1.js';
console.log('main.js');
console.log(a)
2
3
4
5
如上a未定义,直接打印a,肯定会报错的。我们在命令行中 运行 npm run dev 后,打开页面会发现报错,报错如下:
然后我们点击 main.js:5 后,会进入main.js代码内,如下图:
# inline
inline
就是将map作为DataURI嵌入,不单独生成.map文件;比如inline-source-map
({
"./src/index.js":
(function (module, exports) {
let a = 1;
let b = 2;
let c = 3;
})
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIj
2
3
4
5
6
7
8
9
如下代码:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ "./js/demo1.js");
__webpack_require__(/*! ../styles/main.css */ "./styles/main.css");
console.log('main.js');
console.log(a);
/***/ }),
/***/ "./styles/main.css":
/*!*************************!*\
!*** ./styles/main.css ***!
\*************************/
/*! no static exports found */
/***/ (function(module, exports) {
// removed by extract-text-webpack-plugin
/***/ })
/******/ });
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2Vz.....
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
inline-source-map 使用缺点:它会使得bundle.js文件变得非常大,因为它需要把 sourceMappingURL 以dataurl的形式插入到bundle.js里面去。如下图所示:
# cheap(低开销)
cheap(低开销)
的sourcemap,因为它没有生成列映射(column mapping),只是映射行数- 开发时我们有行映射也够用了,开发时可以使用cheap; 比如
cheap-source-map
该属性在打包后同样会为每一个文件模块生成 .map文件,但是与source-map的区别在于cheap生成的 map文件会忽略原始代码中的列信息; 比如生成后的bundle.js.map中的mappings的代码如下:
"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;AClFA;AAAA;AAAA;AACA;AACA;AACA;;;;;;;;;;
如上可以看到,它不会生成列的信息,有逗号就表示包含了列信息。增加该属性后,cheap就不会生成列信息,调式代码列信息没有什么用,因此使用cheap后,文件大小相对于source-map来讲,bundle.js 文件会变得更小。
如下图使用的是 source-map 生成的bundle.js.map 文件, 会包含列的信息,如下图所示:
使用cheap属性后,也不会有loader模块之间对应的sourcemap,因为webpack打包最终会将所有的非js资源,通过loader形式转换成js资源,比如 vue 中的文件,xx.vue -> vue-loader转换 -> js -> 压缩 -> 压缩后的js
所以说如果没有loader之间的sourcemap文件的话,那么在debug的时候,定义到压缩前的js中的时候,不能跟踪到vue中。
# module
该属性的配置也是生成一个没有列的信息的sourceMaps文件,同时loader的sourcemap也被简化成为只包含对应行的。
Webpack会利用loader将所有非js模块转化为webpack可处理的js模块,而增加上面的cheap配置后也不会有loader模块之间对应的sourceMap
什么是模块之间的sourceMap呢?
- 比如jsx文件会经历loader处理成js文件再混淆压缩, 如果没有loader之间的sourceMap,那么在debug的时候定义到上图中的压缩前的js处,而不能追踪到jsx中
- 所以为了映射到loader处理前的代码,我们一般也会加上module配置; 比如:
cheap-module-source-map
# 实践
# 安装及设置
npm i -D webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env style-loader css-loader less-loader less file-loader url-loader
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode:'development',
devtool:'cheap-module-source-map',
entry:'./src/index.js',
module: {
rules: [
{
test: /\.js$/,
use: [{
loader:'babel-loader',
options:{
presets:["@babel/preset-env"]
}
}]
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
src\index.js
import './sum';
sum(1,2);
//console.log(window.a.b);
2
3
# CSS sourceMap
- css-loader,sass-loader都已经提供了生成sourceMap的能力
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
entry: './src/index.js',
module: {
rules: [
{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"]
}
}]
},
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: { sourceMap: true }
}
]
},
{
test: /\.scss$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: { sourceMap: true }
},
{ loader: "resolve-url-loader" },
{
loader: 'sass-loader',
options: { sourceMap: true }
}
]
},
{
test: /\.(jpg|png|gif|bmp)$/,
use: [
{ loader: 'url-loader' }
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
src\index.scss
@import "includes/bg.scss";
$color:red;
body{
color: $color;
}
2
3
4
5
includes\bg.scss
$color:red;
body{
background-image: url(./kf.jpg);
}
2
3
4