解析 SourceMap
2024年04月30日
一、认识
通过 source-map
来实现代码还原。代码还原工作流如下:
-
通过
Webpack
或者Vite
打包具有错误的文件 -
运行
bundle.js
控制台会出现错误,此时会有报错信息中的行列数 -
根据报错信息中的行列数, 通过
source-map
来反解出错的源代码位置
二、思路
1. 实例化 SourceMapConsumer
对象
const sourcemapData = fs.readFileSync("./dist/bundle.js.map").toString();
const sourcemapConsumer = new SourceMapConsumer(
sourcemapData,
null,
"webpack://parse-sourcemap/"
);
2. 根据压缩后的代码报错行列数获取源代码的位置信息
const originalPosition = consumer.originalPositionFor({
line: compressedLine,
column: compressedColumn,
});
3. 读出原始文件内容,结合原始报错行列号,锁定原始错误位置
const {
source,
line: originalLine,
column: originalColumn,
} = originalPosition;
if (source) {
const sourceContent = consumer.sourceContentFor(source);
}
三、实现
以 Webpack
打包器为例 (Vite
) 类似
3.1 index.js
const a = 1;
console.log(a);
const array = [1, 2, 3, 4, 5];
array.forEach((value, key) => {
console.log("key", key);
console.log("value", value);
});
function foo() {
console.log("foo");
}
foo();
console.log(c);
3.2 package.json
{
"name": "webpack-sourceMap",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack build -c ./webpack.config.js"
},
"devDependencies": {
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"source-map": "^0.7.4"
}
}
3.3 webpack.config.js
const Path = require("path");
function resolve(path) {
return Path.resolve(__dirname, path);
}
module.exports = {
mode: "production",
entry: resolve("./index.js"),
devtool: "source-map",
output: {
clean: true,
path: resolve("build"),
filename: "[name]-[contenthash].js",
},
};
3.4 recover-sourcemap.js
const Fs = require("fs");
const { SourceMapConsumer } = require("source-map");
function recoverSourceMap(options) {
return new Promise((resolve, reject) => {
const { type, sourcemapData, compressedLine, compressedColumn } = options;
const sourcemapConsumer = new SourceMapConsumer(sourcemapData, null, type);
sourcemapConsumer.then((consumer) => {
const originalPosition = consumer.originalPositionFor({
line: compressedLine,
column: compressedColumn,
});
const {
source,
line: originalLine,
column: originalColumn,
} = originalPosition;
if (source) {
const sourceContent = consumer.sourceContentFor(source);
if (sourceContent) {
const lines = sourceContent.split("\n");
const codeLine = lines[originalPosition.line - 1];
resolve({
status: "success",
message: "成功还原代码",
sourceCodeInfo: {
source,
originalLine,
originalColumn,
codeLine,
},
});
}
} else {
reject({ status: "failed", message: "无法找到原始源文件位置。" });
}
});
});
}
async function run() {
const sourcemapData = Fs.readFileSync(
"./build/main-6910fe9c709975f8f52b.js.map"
).toString();
const result = await recoverSourceMap({
sourcemapData,
type: "webpack://parse-sourcemap/",
compressedLine: Number(1), // 混肴代码报错 row
compressedColumn: Number(123), // 混肴代码报错 col
});
if (!result.sourceCodeInfo) {
return;
}
const { source, codeLine, originalLine } = result.sourceCodeInfo;
console.log(`压缩后代码位置:行 1,列 123`);
console.log(`原始源文件:${source}`);
console.log(`原始源文件行号:${originalLine}`);
console.log("出问题代码行 -> Code line:", codeLine);
}
run();
四、测试
-
执行
pnpm build
打包生成bundle.js
-
执行
node bundle.js
,此时,bundle.js
会报错,控制台出现详细的报错信息,可以拿到混肴后的报错位置,行号、列号 -
得到报错后的行号、列号,修改
run
中的compressedLine
、compressedColumn
参数, 并执行node recover-sourcemap.js
此时,成功通过混肴后的行号、列号找到源代码中对应的位置。