先看看最终实现的监控效果
能够准确找到文件位置,报错文件名,具体的索引代码。
第一步:开启配置
以vite打包为例:需要开启打包配置: 在vite的build配置加上:
js
sourcemap: true,
terserOptions: {
sourceMap: true,
compress: {
passes: 2,
},
},
同时需要避免vite的插件影响到sourceMap的定位
第二步:配置sourceMap插件和stacktrace-js
sourceMap
首先配置前端source-map,用于解析source-map文件,通过行数和列数获取目标错误代码内容。 全局index.html文件加入以下内容:
js
<script defer>
function loadScript(url, callback) {
var script = document.createElement('script')
script.type = 'text/javascript'
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
script.onreadystatechange = null
callback()
}
}
} else {
script.onload = function () {
callback()
}
}
script.src = url
script.defer = true
document.getElementsByTagName('head')[0].appendChild(script)
}
loadScript('https://unpkg.com/[email protected]/dist/source-map.js', function () {
window.sourceMap.SourceMapConsumer.initialize({
'lib/mappings.wasm': 'https://unpkg.com/[email protected]/lib/mappings.wasm',
})
})
</script>
这样我们全局就会有一个sourceMap对象:

stacktrace-js
js
pnpm add stacktrace-js -D
第三步:如何捕获错误
针对React,在类组件中提供了两个Error相关的钩子:componentDidCatch和getDerivedStateFromError, 我们可以封装一个高阶组件,在componentDidCatch捕获Error对象和Error详细信息:
js
async componentDidCatch(error: any, errorInfo: any){
}
接下来我们从这两个对象入手,获取报错的具体位置和代码:
首先我们获取到报错的具体信息:
error.message 这是一个字符串类型,反应报错的内容
使用stacktrace-js获取报错文件和报错的位置信息
js
import StackTrace, { StackFrame } from 'stacktrace-js'
const getErrorPositionMap = (error:Error)=>{
const fileNameMap: Record<string, { line: number; column: number }> = {}
const stackFrames = await StackTrace.fromError(error, {
filter: (stackFrame: StackFrame) => !stackFrame?.fileName?.includes('/node_modules/'),
// @ts-ignore
ajax: (url) => fetch(`${url}?${Date.now()}`).then((res) => res.text()), //cdn可能会有获取不到的情况,保证文件是最新的
})
stackFrames.forEach((item) => {
const { fileName, lineNumber = 0, columnNumber = 0 } = item
if (fileName) {
fileNameMap[fileName] = { line: lineNumber, column: columnNumber }
}
})
}
使用sourceMap获取报错的内容信息
通过位置信息,我们可以获取到报错的具体代码片段,通过递归调用,并且排除node_modules文件的报错,我们可以得到我们代码中的具体错误位置,我这里只获取了最近的一个文件报错位置,如果想把错误链都展示出来,可以对下面代码进行优化
js
const getSourceCodeFromErrorPosition = async (
componentStack: string,
fileNameMap: Record<string, { line: number; column: number }>
) => {
// @ts-ignore
const urlList = Array.from(
componentStack.matchAll('(http.*?\\.js):([0-9]+):([0-9]+)')
) as string[][]
const result = {
source: '',
specificPosition: '',
}
let lastFilePath = ''
let consumer: any
const loop = async (index: number) => {
const [, filePath, lineNumber, columnNumber] = urlList[index]
if (filePath !== lastFilePath) {
const rawSourceMap = await fetch(
`${filePath.replace(/\.js$/, '.js.map')}?${Date.now()}`
).then((res) => res.json())
consumer?.destroy()
consumer = await new window.sourceMap.SourceMapConsumer(rawSourceMap)
}
lastFilePath = filePath
const originalPosition = consumer.originalPositionFor({
line: +lineNumber,
column: +columnNumber,
})
if (
(originalPosition.source?.includes('/node_modules/') ||
!fileNameMap[originalPosition.source]) &&
index !== urlList.length - 1
) {
await loop(index + 1)
return
}
const sourceIndex = consumer.sources.findIndex(
(item: string) => item === originalPosition.source
)
const sourceContent = consumer?.sourcesContent[sourceIndex]
const contentRowArr = sourceContent?.split('\n')
const positionInfo = fileNameMap[originalPosition.source] || originalPosition
result.source = `${originalPosition.source} ${positionInfo.line}:${positionInfo.column}`
result.specificPosition =
contentRowArr?.[(fileNameMap[originalPosition.source]?.line || originalPosition.line) - 1]
consumer?.destroy()
}
await loop(0)
return result
}
函数使用: getSourceCodeFromErrorPosition(errorInfo.componentStack,getErrorPositionMap(error)), 这样我们可以获取到一个 {
source: string;
specificPosition: string;
} 对象,source是具体的报错代码片段,specificPosition是位置信息。