对应编译文件和源文件的方法
使得编译后的代码可读性高,容易调试
SourceMap配置
在webpack的配置文件中配置source maps,需要配置devtool,在webpack4.0中提供了多种配置选项
下面我们列举四种不同的配置选项进行说明,各具优缺点,描述如下:
| devtool选项 | 配置结果 |
|---|---|
source-map |
在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包速度; |
cheap-module-source-map |
在一个单独的文件中生成一个不带列映射的map,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便; |
eval-source-map |
使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项;eval 的作用是使得打包后的 bundle 文件中每个模块代码使用 eval 去执行,保留每个模块的 eval 形式的文件,即便是指定了eval-source-map,仍然会在浏览器开发者工具中生成 webpack-internals:// 文件夹来保存一份里面是eval形式的模块文件;因为 eval 中为字符串形式,所以当源码变动的时候进行字符串处理会提升 rebuild 的速度 |
cheap-module-eval-source-map |
这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点; |
正如上表所述,上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的打包速度的后果就是对打包后的文件的的执行有一定影响。
对小到中型的项目中,eval-source-map是一个很好的选项,再次强调你只应该开发阶段使用它,我们继续对上文新建的webpack.config.js,进行如下配置:
const path = require('path');module.exports = {entry: "./app/main.js",devtool: 'eval-source-map',output: {filename: "bundle.js",path: path.resolve(__dirname, 'public')}}
cheap-module-eval-source-map方法构建速度更快,但是不利于调试,推荐在大型项目考虑时间成本时使用。带有source-map关键字的选项会单独打一份.map文件(inline除外),而其他模式一般会将sourcemap文件合并在生成的bundle文件中,这有时会让打包的代码非常肿大。
使用SourceMap进行调试
1. 无SourceMap时
在不使用SourceMap这个选项的时候,我们生成的代码是经过压缩打包后的不适合人阅读调试的代码,在控制台Sources目录下看到:
2. sourceMap合并在bundle.js中
如图所示都是压缩过的代码,生成Sourcemap后我们就可以在浏览器中直接调试我们的源码,在控制台的sources下,点开可以看到webpack://目录,里面可以直接看到我们开发态的源代码,这样方便我们直接在浏览器中打断点调试。

3. 生成单独的.map文件
对于生成.map的情况chrome会提示你检测到.map文件
按command + p会提示你选择要调试的文件,进入文件后打断点调试即可
uglify-js生成SourceMap的整体流程如下
生成AST => 压缩 => 调用addMapping方法,添加对应的映射mapping => 输出压缩后代码时调用toJSON方法生成SourceMap
// source-map/lib/source-map-generator.js
SourceMapGenerator.prototype.toJSON =
function SourceMapGenerator_toJSON() {
var map = {
version: this._version,
sources: this._sources.toArray(),
names: this._names.toArray(),
// 关键是这一句,用了_serializeMappings方法来序列化输出
mappings: this._serializeMappings()
};
// 返回sourceMap
return map;
};
// source-map/lib/source-map-generator.js
SourceMapGenerator.prototype._serializeMappings =
function SourceMapGenerator_serializeMappings() {
// 省略
// mappings其实就是外部传进来的压缩前后的代码位置映射集,可以看下方的例子
for (var i = 0, len = mappings.length; i < len; i++) {
mapping = mappings[i];
next = ''
// 这一段其实就是为了修正如果压缩后文件头部有注释,那么需要在最终生成的映射
// 前加上N个“;”来表示换行
if (mapping.generatedLine !== previousGeneratedLine) {
previousGeneratedColumn = 0;
while (mapping.generatedLine !== previousGeneratedLine) {
next += ';';
previousGeneratedLine++;
}
}
else {
if (i > 0) {
// 比较相邻的两个mapping,如果相同就跳过,继续下一个
if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
continue;
}
// 同一行的每段映射后需要加上“,”
next += ',';
}
}
// 输出列 编码
next += base64VLQ.encode(mapping.generatedColumn
- previousGeneratedColumn);
previousGeneratedColumn = mapping.generatedColumn;
// 没有找到源文件的话,就会出现只有1个字段的映射的情况
if (mapping.source != null) {
sourceIdx = this._sources.indexOf(mapping.source);
// 输入文件名索引 编码
next += base64VLQ.encode(sourceIdx - previousSource);
previousSource = sourceIdx;
// 输入列 编码(SourceMap v3规定起始行是0)
// lines are stored 0-based in SourceMap spec version 3
next += base64VLQ.encode(mapping.originalLine - 1
- previousOriginalLine);
previousOriginalLine = mapping.originalLine - 1;
// 输出行 编码
next += base64VLQ.encode(mapping.originalColumn
- previousOriginalColumn);
previousOriginalColumn = mapping.originalColumn;
// 没有名称的话,会出现4个字段的映射的情况
// 主要是一些关键字或者符号等,不会被压缩,所以记录名称也就没有意义,但是还是
// 需要记录它们的位置的
if (mapping.name != null) {
nameIdx = this._names.indexOf(mapping.name);
next += base64VLQ.encode(nameIdx - previousName);
previousName = nameIdx;
}
}
result += next;
}
};
// test.js
// 压缩前 // 压缩后
function get (value) { => function get(n){return n}
return value
}
根据我们上面所盘点的,可以得到如下信息:
{
"sources:" ["test.js"],
"names": ["get", "value"] // get不是关键字,后续也有可能被压缩
}
输出位置(行,列)输入索引输入位置(行,列)名称索引代表内容VLQ+Base640,000,0无functionAAAA0,+90+0,+90getSAASA0,+40+1,+51入参valueIAAKC0,+30+1,-12无returnGACZ0,+70+0,+70返回值valueOAAOA
所以最终生成的SourceMap如下:
{
"version": 3,
"sources": ["test.js"],
"names": ["get", "value"],
"mappings": "AAAA,SAASA,IAAKC,GACZ,OAAOA"
}
