JavaScript全局错误处理器:利用Source Map转换错误行号
用Source Map转换压缩代码的错误位置
核心思路
利用source-map库解析对应Source Map文件,将压缩代码中的行号、列号及堆栈跟踪,转换为原始源文件的真实位置。
实现步骤
1. 引入source-map库
通过npm安装依赖:
npm install source-map
或在浏览器中通过CDN直接引入:
<script src="https://unpkg.com/source-map@0.7.3/dist/source-map.min.js"></script>
2. 编写位置转换逻辑
实现加载Source Map、转换错误位置及堆栈的工具函数,改造全局错误处理器:
// 加载并初始化Source Map解析器 async function getSourceMapConsumer(url) { // 从压缩文件的注释中提取Source Map URL(更可靠的方式) const jsRes = await fetch(url); const jsContent = await jsRes.text(); const mapUrlMatch = jsContent.match(/\/\/# sourceMappingURL=(.*)$/m); if (!mapUrlMatch) return null; const mapUrl = new URL(mapUrlMatch[1], url).href; const mapRes = await fetch(mapUrl); const mapData = await mapRes.json(); return new sourceMap.SourceMapConsumer(mapData); } // 转换单个堆栈帧的位置 async function transformStackFrame(frame, consumerCache = {}) { // 匹配栈帧格式:"at funcName (http://xxx.com/app.min.js:123:45)" const frameMatch = frame.match(/(.*?)\((https?:\/\/.*?):(\d+):(\d+)\)/); if (!frameMatch) return frame; const [, funcPart, frameUrl, frameLine, frameColumn] = frameMatch; // 缓存解析器,避免重复加载解析 let consumer = consumerCache[frameUrl]; if (!consumer) { consumer = await getSourceMapConsumer(frameUrl); if (!consumer) return frame; consumerCache[frameUrl] = consumer; } const originalPos = consumer.originalPositionFor({ line: parseInt(frameLine), column: parseInt(frameColumn), }); if (!originalPos.source) return frame; return `${funcPart}(${originalPos.source}:${originalPos.line}:${originalPos.column})`; } // 改造后的全局错误处理器 window.sparkErrorHandler = async function(message, url, line, column, error) { let mappedUrl = url; let mappedLine = line; let mappedColumn = column; let mappedStack = error?.stack; const consumerCache = {}; try { // 转换错误发生的核心位置 const consumer = await getSourceMapConsumer(url); if (consumer) { const originalPos = consumer.originalPositionFor({ line, column }); if (originalPos.source) { mappedUrl = originalPos.source; mappedLine = originalPos.line; mappedColumn = originalPos.column; } consumerCache[url] = consumer; } // 转换完整堆栈跟踪 if (error?.stack) { const stackFrames = error.stack.split('\n'); mappedStack = await Promise.all( stackFrames.map(frame => transformStackFrame(frame, consumerCache)) ).join('\n'); } // 销毁解析器释放资源 Object.values(consumerCache).forEach(consumer => consumer.destroy()); } catch (err) { console.warn('Source Map解析失败:', err); } // 传入转换后的参数调用日志函数 logError(message, mappedUrl, mappedLine, mappedColumn, { ...error, stack: mappedStack }); };
注意事项
- Source Map可访问性:确保Source Map文件与压缩JS文件同域,或配置跨域资源共享(CORS),否则前端无法加载解析。
- 缓存优化:频繁触发错误时,缓存Source Map解析器可避免重复请求和解析,提升性能。
- 错误覆盖范围:全局错误处理器无法捕获所有异步错误,需额外监听
unhandledrejection事件处理Promise未捕获的异常。 - 兼容性:
source-map库兼容现代浏览器,如需支持IE需补充Promise和fetch的polyfill。
内容的提问来源于stack exchange,提问作者Witek
相关产品推荐
相关产品推荐

