本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
随着项目扩大,业务线拆分到各团队独立维护,同时又希望将多个项目融合在一起,微前端就能很好地解决这类问题。
传统单应用中,错误堆栈信息能精准指向代码位置,是排查问题的 “导航图”。但微前端架构下,主应用与子应用的隔离机制会特殊处理代码执行环境,使运行时代码与原始开发代码存在差异,导致错误堆栈显示的行列位置与实际代码位置不匹配。
qiankun 和 micro-app 是微前端的两种主流方案,实现原理不同:qiankun 基于 single-spa 封装,用 js 沙箱和样式沙箱实现隔离;micro-app 借鉴 Web Components 思想,通过 js 沙箱、样式隔离等模拟 ShadowDom 的隔离性。但这些机制导致了错误堆栈偏移问题,需要解析 error 信息并找到对应源代码来排查问题。
本文将结合 qiankun 和 micro-app 的实际案例,深入分析该问题的成因与解决方案。
案例一:qiankun 框架下的列偏移
相关 issue:https://github.com/umijs/qiankun/issues/1088[#issuecomment](javascript:;)-1021894711
现象与数据对比
某项目采用主应用 Vue2 + 子应用 React 的架构,使用 qiankun^2.6.3 版本。测试中发现同一错误在不同环境下的堆栈信息存在明显差异:
- • 微前端环境:错误堆栈中列号为
20935
{ message: "测试错误", stack: "Error: 测试错误\n at eval (.../js/453.1fbd684b.async.js:1:20935)"}- • 非微前端环境:同一错误的列号为
20845
{ message: "测试错误", stack: "Error: 测试错误\n at .../js/453.1fbd684b.async.js:1:20845"}两者 column 差值为 90,且这一差值在多次测试中稳定出现。
根源分析:代码注入导致的列偏移
非微前端下:
微前端下:
通过调试发现,qiankun 在加载子应用时,会对 js 代码进行特殊包装以实现沙箱隔离。具体来说,运行时代码被注入了一段前缀:
window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){这段注入代码的长度恰好为 90 个字符。由于浏览器计算 column 时会从代码实际运行的起始位置开始 (包含注入内容),而原始代码的 column 是从自身内容开始计算,因此导致了 90 的偏移量。
本质上,这是 qiankun 为实现 js 沙箱 (通过 with 语句隔离作用域) 而进行的代码转换,属于框架设计中 “运行时安全隔离” 与 “调试体验” 的权衡结果。
解决方案:错误上报时修正 column
针对错误监控工具 (如 Sentry),可在上报前手动调整 column 值,抵消注入代码的影响:
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:https://github.com/jd-opensource/micro-app/issues/935
现象与数据对比
另一项目采用主应用 Vue2 + 子应用 React 架构,使用 @micro-zoe/micro-app@^0.8.3 版本。错误堆栈显示 line 存在稳定偏移:
- • 微前端环境:错误堆栈中行号都为 4
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
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 代码进行特殊包装以实现沙箱隔离。具体来说,运行时代码被注入了一段前缀:
(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值减去偏移行数
- 在错误上报前,对 stacktrace 中的
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) 中手动减去注入代码的长度:
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 无缝集成:
// Vue项目import StackFix from 'xxx/vue';Vue.use(StackFix, {});// React项目import StackFix from 'xxx';StackFix.init();核心实现逻辑
核心能力包括 “错误拦截” 和 “堆栈修正” 两部分。
错误拦截:适配不同框架的错误捕获机制
- • React:监听全局错误事件,覆盖所有未捕获错误和 Promise 拒绝:
private errorEventListener() { window.addEventListener('error', (event) => { this.handleErrorIfNeeded(event.error); }); window.addEventListener('unhandledrejection', (event) => { this.handleErrorIfNeeded(event.reason); });}- • Vue:拦截 Vue 自身的错误处理流程,不影响原有逻辑:
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 属性:
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 项目。
- 全场景修正:同时修复监控平台上报的堆栈和浏览器控制台显示的堆栈。