随着项目扩大,业务线拆分到各团队独立维护,同时又希望将多个项目融合在一起,微前端就能很好地解决这类问题。
传统单应用中,错误堆栈信息能精准指向代码位置,是排查问题的 "导航图"。但微前端架构下,主应用与子应用的隔离机制会特殊处理代码执行环境,使运行时代码与原始开发代码存在差异,导致错误堆栈显示的行列位置与实际代码位置不匹配。
qiankun 和 micro-app 是微前端的两种主流方案,实现原理不同:qiankun 基于 single-spa 封装,用 js 沙箱和样式沙箱实现隔离;micro-app 借鉴 Web Components 思想,通过 js 沙箱、样式隔离等模拟 ShadowDom 的隔离性。但这些机制导致了错误堆栈偏移问题,需要解析 error 信息并找到对应源代码来排查问题。
本文将结合 qiankun 和 micro-app 的实际案例,深入分析该问题的成因与解决方案。
案例一:qiankun 框架下的列偏移
相关 issue:github.com/umijs/qiank...
现象与数据对比
某项目采用主应用 Vue2 + 子应用 React 的架构,使用 qiankun^2.6.3 版本。测试中发现同一错误在不同环境下的堆栈信息存在明显差异:
- 微前端环境:错误堆栈中列号为
20935
js
{
message: "测试错误",
stack: "Error: 测试错误\n at eval (.../js/453.1fbd684b.async.js:1:20935)"
}
- 非微前端环境:同一错误的列号为
20845
js
{
message: "测试错误",
stack: "Error: 测试错误\n at .../js/453.1fbd684b.async.js:1:20845"
}
两者 column 差值为 90
,且这一差值在多次测试中稳定出现。
根源分析:代码注入导致的列偏移
非微前端下:

微前端下:

通过调试发现,qiankun 在加载子应用时,会对 js 代码进行特殊包装以实现沙箱隔离。具体来说,运行时代码被注入了一段前缀:
js
window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){
这段注入代码的长度恰好为 90 个字符。由于浏览器计算 column 时会从代码实际运行的起始位置开始 (包含注入内容),而原始代码的 column 是从自身内容开始计算,因此导致了 90
的偏移量。
本质上,这是 qiankun 为实现 js 沙箱 (通过 with 语句隔离作用域) 而进行的代码转换,属于框架设计中 "运行时安全隔离" 与 "调试体验" 的权衡结果。
解决方案:错误上报时修正 column
针对错误监控工具 (如 Sentry),可在上报前手动调整 column 值,抵消注入代码的影响:
js
Sentry.init({
beforeSend(event) {
// 精确匹配qiankun注入的前缀代码
const QIANKUN_INJECTED_CODE =
'window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){;';
const INJECTED_CODE_LENGTH = QIANKUN_INJECTED_CODE.length;
// ...
const {
stacktrace: { frames },
...rest
} = item;
// 取堆栈最后一帧(通常是直接错误位置)修正column
const lastFrame = frames[frames.length - 1];
if (typeof lastFrame.colno === 'number') {
lastFrame.colno -= INJECTED_CODE_LENGTH;
}
// ...
},
});
方案局限
该方案仅能修正错误监控平台的堆栈信息,无法解决浏览器控制台点击跳转的偏移问题。这是因为控制台直接读取运行时代码位置,而我们无法修改浏览器的堆栈解析逻辑。
案例二:micro-app 框架下行偏移
相关 issue:github.com/jd-opensour...
现象与数据对比
另一项目采用主应用 Vue2 + 子应用 React 架构,使用 @micro-zoe/micro-app@^0.8.3
版本。错误堆栈显示 line
存在稳定偏移:
- 微前端环境:错误堆栈中行号都为 4
php
TypeError: Cannot read properties of undefined (reading 'name_cn')
at onClick (https://xxx.com/js/488.dbe55ec7.js:4:97551)
at oe (https://xxx.com/js/vendors.e2bc155c.js:4:1839617)
at Object.ze (https://xxx.com/js/vendors.e2bc155c.js:4:3465463)
at Fe (https://xxx.com/js/vendors.e2bc155c.js:4:3465617)
at eval (https://xxx.com/js/vendors.e2bc155c.js:4:3485422)
at zn (https://xxx.com/js/vendors.e2bc155c.js:4:3485516)
at Hn (https://xxx.com/js/vendors.e2bc155c.js:4:3485930)
at eval (https://xxx.com/js/vendors.e2bc155c.js:4:3491368)
at Ql (https://xxx.com/js/vendors.e2bc155c.js:4:3552373)
at Se (https://xxx.com/js/vendors.e2bc155c.js:4:3464596)
- 非微前端环境:同一错误的行号都为 2
php
TypeError: Cannot read properties of undefined (reading 'name_cn')
at onClick (https://xxx.com/js/488.dbe55ec7.js:2:97551)
at oe (https://xxx.com/js/vendors.e2bc155c.js:2:1839617)
at Object.ze (https://xxx.com/js/vendors.e2bc155c.js:2:3465463)
at Fe (https://xxx.com/js/vendors.e2bc155c.js:2:3465617)
at https://xxx.com/js/vendors.e2bc155c.js:2:3485422
at zn (https://xxx.com/js/vendors.e2bc155c.js:2:3485516)
at Hn (https://xxx.com/js/vendors.e2bc155c.js:2:3485930)
at https://xxx.com/js/vendors.e2bc155c.js:2:3491368
at Ql (https://xxx.com/js/vendors.e2bc155c.js:2:3552373)
at Se (https://xxx.com/js/vendors.e2bc155c.js:2:3464596)
对比发现,每行的行号差值稳定为 2,这与 micro-app 框架注入的初始化代码行数相关。
根源分析:代码注入导致的列偏移
非微前端下 line 2

微前端下 line 4

通过调试发现,micro-app 在加载子应用时,会对 js 代码进行特殊包装以实现沙箱隔离。具体来说,运行时代码被注入了一段前缀:
js
(function anonymous() {;(function(proxyWindow){with(proxyWindow.__MICRO_APP_WINDOW__){(function(window,self,globalThis,document,Document,Array,Object,String,Boolean,Math,Number,Symbol,Date,Function,Proxy,WeakMap,WeakSet,Set,Map,Reflect,Element,Node,RegExp,Error,TypeError,JSON,isNaN,parseFloat,parseInt,performance,console,decodeURI,encodeURI,decodeURIComponent,encodeURIComponent,navigator,undefined,location,history){;/*! For license information please see app.b32f9218.js.LICENSE.txt */
解决方案思路 (针对性修复:框架专属方案)
与 qiankun 类似,可通过错误监控工具修正行号:
- 确定 micro-app 注入代码的行数 (案例中为 2 行)
- 在错误上报前,对 stacktrace 中的
line
值减去偏移行数
js
Sentry.init({
beforeSend(event) {
const MICRO_APP_LINE_OFFSET = 2; // 行偏移量
// ...
const adjustedFrames = item.stacktrace.frames.map((frame) => {
if (typeof frame.lineno === 'number') {
return { ...frame, lineno: frame.lineno - MICRO_APP_LINE_OFFSET };
}
return frame;
});
// ...
},
});
方案局限
该方案仅能修正错误监控平台的堆栈信息,无法解决浏览器控制台点击跳转的偏移问题。这是因为控制台直接读取运行时代码位置,而我们无法修改浏览器的堆栈解析逻辑。
从 "针对性修复" 到 "通用的解决方案"
面对堆栈偏移问题,我们的目标是:让错误堆栈中的行列号自动修正为原始代码的坐标,并且浏览器控制台点击跳转到指定位置。
针对性修复
在早期,我们针对不同框架做了 "硬编码修正"。
以 qiankun 为例,在错误上报工具 (如 Sentry) 中手动减去注入代码的长度:
js
Sentry.init({
beforeSend(event) {
// qiankun注入的代码字符串
const QIANKUN_INJECTED_CODE =
'window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){;';
const INJECTED_CODE_LENGTH = QIANKUN_INJECTED_CODE.length;
// ...
const lastFrame = frames[frames.length - 1];
if (typeof lastFrame.colno === 'number') {
// 修正列号:减去注入代码长度
lastFrame.colno -= INJECTED_CODE_LENGTH;
}
// ...
},
});
但这种方案有明显局限:仅能修正上报到监控平台的堆栈,浏览器控制台中点击错误仍无法定位到源码 (控制台直接读取原始堆栈)。
通用解决方案:跨框架堆栈修正
为了适配多框架 (qiankun、micro-app 等)、多技术栈 (Vue、React 等),我们开发了一套通用的解决方案,核心思路是:拦截错误事件 → 修正堆栈行列号 → 覆盖原始错误堆栈。
使用方式
简洁的 API 设计,支持 Vue 和 React 无缝集成:
js
// Vue项目
import StackFix from 'xxx/vue';
Vue.use(StackFix, {});
// React项目
import StackFix from 'xxx';
StackFix.init();
核心实现逻辑
核心能力包括 "错误拦截" 和 "堆栈修正" 两部分。
错误拦截:适配不同框架的错误捕获机制
- React:监听全局错误事件,覆盖所有未捕获错误和 Promise 拒绝:
js
private errorEventListener() {
window.addEventListener('error', (event) => {
this.handleErrorIfNeeded(event.error);
});
window.addEventListener('unhandledrejection', (event) => {
this.handleErrorIfNeeded(event.reason);
});
}
- Vue:拦截 Vue 自身的错误处理流程,不影响原有逻辑:
ts
import StackFix from '../core';
export default {
install(Vue: any, options: StackFixWithPrintConsole = {}) {
// ...
Vue.config.errorHandler = function (error: Error, vm: any, info: string) {
StackFix.handleErrorIfNeeded(error);
};
// ...
StackFix.init({
framework: 'vue2',
...options,
});
StackFix.errorEventListener();
},
};
堆栈修正:动态调整行列号
核心逻辑是解析错误堆栈字符串,根据配置的修正值 (行/列偏移量) 调整行列号,并覆盖原始错误的 stack
属性:
ts
validateAndAdjustStack(stack: string) {
if (!this.microName || !stack) return '';
const defaultOptions = getDefaultMicroConfigOptions(
this.framework,
this.microName as MicroAppName,
);
const { lineAdjustment, columnAdjustment } = {
...defaultOptions,
...(typeof this.lineAdjustment === 'number' && {
lineAdjustment: this.lineAdjustment,
}),
...(typeof this.columnAdjustment === 'number' && {
columnAdjustment: this.columnAdjustment,
}),
};
let newStack = '';
if (validNum(lineAdjustment)) {
newStack = skewStackLineNumbers(stack, { lineAdjustment });
}
if (validNum(columnAdjustment)) {
newStack = skewStackColumnNumber(newStack || stack, { columnAdjustment });
}
return newStack;
}
总结
微前端下的错误堆栈偏移问题,本质是框架 "代码注入" 机制与错误堆栈记录逻辑的冲突。针对这一问题,我们已实现解决方案,在主流版本中支持开发者 "零配置" 直接解决;对于部分版本,可能仍需简单配置。
- 跨框架适配:支持 qiankun、micro-app 等主流微前端框架,其它微前端框架或特殊场景也可以配置自定义的行列号支持。
- 多技术栈兼容:无缝集成 Vue、React 项目。
- 全场景修正:同时修复监控平台上报的堆栈和浏览器控制台显示的堆栈。