Appearance
qiankun 调试与故障排除
调试工具和方法
1. 开发环境配置
启用调试模式
javascript
// 开发环境下启用详细日志
const isDev = process.env.NODE_ENV === 'development';
// qiankun 配置中启用调试
import { start, initGlobalState } from 'qiankun';
if (isDev) {
// 启用 qiankun 内部日志
window.__QIANKUN_DEVELOPMENT__ = true;
}
start({
prefetch: true,
sandbox: {
strictStyleIsolation: false, // 开发时可临时关闭样式隔离便于调试
},
// 错误处理
error: (error, app) => {
console.error('qiankun error:', error, app);
// 在开发环境显示更详细的错误信息
if (isDev) {
alert(`微应用加载错误: ${error.message}`);
}
}
});
微应用调试配置
javascript
// 微应用入口文件调试配置
let app = null;
// 开发环境下的额外调试信息
const DEBUG = process.env.NODE_ENV === 'development';
export async function bootstrap(props) {
if (DEBUG) {
console.log('[qiankun] App bootstrap:', props);
}
}
export async function mount(props) {
if (DEBUG) {
console.log('[qiankun] App mount:', props);
}
const { container, history } = props;
app = new Vue({
router: createRouter(history),
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
if (DEBUG) {
// 添加调试工具到全局
window.__MICRO_APP__ = app;
console.log('[qiankun] App mounted successfully');
}
}
export async function unmount(props) {
if (DEBUG) {
console.log('[qiankun] App unmount:', props);
}
if (app) {
app.$destroy();
app.$el.innerHTML = '';
app = null;
if (DEBUG) {
delete window.__MICRO_APP__;
console.log('[qiankun] App unmounted successfully');
}
}
}
2. 浏览器开发者工具
控制台调试命令
javascript
// 在浏览器控制台中可用的调试命令
// 检查 qiankun 状态
console.log('qiankun loaded:', !!window.__POWERED_BY_QIANKUN__);
// 检查已注册的微应用
console.log('registered apps:', window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__);
// 手动加载微应用
const { loadMicroApp } = require('qiankun');
loadMicroApp({
name: 'test-app',
entry: '//localhost:3001',
container: '#test-container',
});
// 检查全局状态
const { initGlobalState } = require('qiankun');
const globalState = initGlobalState({});
console.log('global state:', globalState);
// 重新启动 qiankun
const { start } = require('qiankun');
start();
网络请求调试
javascript
// 拦截和记录网络请求
const originalFetch = window.fetch;
window.fetch = function(...args) {
console.log('[qiankun] Fetch request:', args[0]);
return originalFetch.apply(this, args)
.then(response => {
console.log('[qiankun] Fetch response:', response.status, args[0]);
return response;
})
.catch(error => {
console.error('[qiankun] Fetch error:', error, args[0]);
throw error;
});
};
常见问题诊断
1. 微应用加载问题
问题:微应用无法加载
javascript
// 诊断步骤
function diagnoseAppLoading(appName) {
console.log(`Diagnosing app: ${appName}`);
// 1. 检查入口地址
const appConfig = getMicroAppConfig(appName);
console.log('App config:', appConfig);
// 2. 检查网络连接
fetch(appConfig.entry)
.then(response => {
console.log(`Entry response status: ${response.status}`);
if (response.ok) {
return response.text();
} else {
throw new Error(`HTTP ${response.status}`);
}
})
.then(content => {
console.log('Entry content loaded successfully');
// 检查内容是否为有效的 HTML
if (content.includes('<!DOCTYPE') || content.includes('<html')) {
console.log('Entry returns HTML - this might be wrong');
}
})
.catch(error => {
console.error('Entry loading failed:', error);
});
}
// 使用示例
diagnoseAppLoading('my-micro-app');
问题:微应用加载后立即卸载
javascript
// 调试微应用生命周期
export async function mount(props) {
console.log('[qiankun] Mount called at:', new Date().toISOString());
console.log('[qiankun] Props:', props);
// 记录挂载时间
window.__MOUNT_TIME__ = Date.now();
// 应用挂载逻辑
app = new Vue({
router,
store,
render: h => h(App),
}).$mount(props.container.querySelector('#app'));
// 设置调试标志
window.__APP_MOUNTED__ = true;
}
export async function unmount(props) {
console.log('[qiankun] Unmount called after:',
Date.now() - window.__MOUNT_TIME__, 'ms');
console.log('[qiankun] Unmount props:', props);
// 清理逻辑
if (app) {
app.$destroy();
app.$el.innerHTML = '';
app = null;
}
window.__APP_MOUNTED__ = false;
}
2. 通信问题诊断
问题:主应用和微应用通信失败
javascript
// 通信调试工具
class CommunicationDebugger {
constructor() {
this.messageLog = [];
this.lastState = null;
}
logMessage(type, data, source = 'unknown') {
const logEntry = {
type,
data,
source,
timestamp: Date.now(),
sequence: this.messageLog.length + 1
};
this.messageLog.push(logEntry);
console.log(`[qiankun-communication] ${type}:`, data);
// 保留最近100条记录
if (this.messageLog.length > 100) {
this.messageLog = this.messageLog.slice(-100);
}
}
checkStateSync(newState, oldState) {
if (JSON.stringify(newState) !== JSON.stringify(oldState)) {
this.logMessage('STATE_CHANGE', {
old: oldState,
new: newState,
diff: this.getDiff(oldState, newState)
});
this.lastState = newState;
}
}
getDiff(oldState, newState) {
// 简单的状态差异比较
const diff = {};
const allKeys = new Set([...Object.keys(oldState || {}), ...Object.keys(newState || {})]);
for (const key of allKeys) {
if (oldState?.[key] !== newState?.[key]) {
diff[key] = {
old: oldState?.[key],
new: newState?.[key]
};
}
}
return diff;
}
getRecentMessages(limit = 10) {
return this.messageLog.slice(-limit);
}
clearLogs() {
this.messageLog = [];
}
}
const commDebugger = new CommunicationDebugger();
全局状态调试
javascript
// 全局状态调试
import { initGlobalState } from 'qiankun';
const { onGlobalStateChange, setGlobalState, getGlobalState } = initGlobalState({
debug: true, // 开发时启用调试状态
user: null,
theme: 'light',
});
// 监听状态变化并记录
onGlobalStateChange((state, prev) => {
commDebugger.checkStateSync(state, prev);
if (process.env.NODE_ENV === 'development') {
console.log('[qiankun] Global state changed:', {
current: state,
previous: prev,
timestamp: new Date().toISOString()
});
}
}, true);
// 调试版本的 setGlobalState
const debugSetGlobalState = (state, keepPrevious = true) => {
const currentState = getGlobalState();
console.log('[qiankun] Setting global state:', {
from: currentState,
to: state,
keepPrevious
});
setGlobalState(state, keepPrevious);
// 记录状态变更
commDebugger.logMessage('GLOBAL_STATE_SET', {
before: currentState,
after: getGlobalState(),
change: state
});
};
路由问题调试
1. 路由冲突诊断
javascript
// 路由冲突调试工具
class RouteDebugger {
constructor() {
this.routeHistory = [];
this.conflictDetector = new ConflictDetector();
}
observeRouteChanges() {
// 监听浏览器历史变化
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = (...args) => {
this.logRouteChange('pushState', args);
return originalPushState.apply(history, args);
};
history.replaceState = (...args) => {
this.logRouteChange('replaceState', args);
return originalReplaceState.apply(history, args);
};
// 监听 popstate 事件
window.addEventListener('popstate', (event) => {
this.logRouteChange('popstate', { state: event.state, pathname: location.pathname });
});
}
logRouteChange(method, args) {
const logEntry = {
method,
args,
url: location.href,
timestamp: Date.now(),
sequence: this.routeHistory.length + 1
};
this.routeHistory.push(logEntry);
console.log(`[qiankun-route] ${method}:`, args);
// 检测潜在冲突
this.conflictDetector.checkForConflicts(logEntry);
// 保留最近50条记录
if (this.routeHistory.length > 50) {
this.routeHistory = this.routeHistory.slice(-50);
}
}
getRouteConflicts() {
return this.conflictDetector.getConflicts();
}
}
class ConflictDetector {
constructor() {
this.conflicts = [];
this.routeMap = new Map();
}
checkForConflicts(routeChange) {
const url = this.extractUrl(routeChange);
const timestamp = routeChange.timestamp;
if (this.routeMap.has(url)) {
const previous = this.routeMap.get(url);
if (timestamp - previous.timestamp < 1000) { // 1秒内的重复路由
this.conflicts.push({
type: 'rapid-route-change',
url,
first: previous,
second: routeChange,
timestamp: new Date().toISOString()
});
console.warn('[qiankun] Potential route conflict detected:', {
url,
interval: timestamp - previous.timestamp
});
}
}
this.routeMap.set(url, routeChange);
// 清理过期记录
this.cleanupOldRoutes();
}
extractUrl(routeChange) {
if (routeChange.args && routeChange.args[2]) {
return routeChange.args[2];
}
return routeChange.url || location.pathname;
}
cleanupOldRoutes() {
const now = Date.now();
const expired = [];
for (const [url, record] of this.routeMap) {
if (now - record.timestamp > 30000) { // 30秒过期
expired.push(url);
}
}
expired.forEach(url => this.routeMap.delete(url));
}
getConflicts() {
return this.conflicts;
}
}
const routeDebugger = new RouteDebugger();
routeDebugger.observeRouteChanges();
性能问题调试
1. 加载性能分析
javascript
// 性能分析工具
class PerformanceDebugger {
constructor() {
this.metrics = {
appLoadTimes: {},
resourceLoadTimes: {},
memoryUsage: [],
renderTimes: {}
};
}
startAppLoad(appName) {
this.metrics.appLoadTimes[appName] = {
startTime: performance.now(),
startMemory: this.getMemoryUsage()
};
console.log(`[qiankun-performance] Starting to load app: ${appName}`);
}
endAppLoad(appName) {
if (this.metrics.appLoadTimes[appName]) {
const loadTime = performance.now() - this.metrics.appLoadTimes[appName].startTime;
const endMemory = this.getMemoryUsage();
this.metrics.appLoadTimes[appName].loadTime = loadTime;
this.metrics.appLoadTimes[appName].memoryDelta =
endMemory - this.metrics.appLoadTimes[appName].startMemory;
console.log(`[qiankun-performance] App ${appName} loaded in ${loadTime.toFixed(2)}ms`, {
memoryDelta: this.metrics.appLoadTimes[appName].memoryDelta
});
return {
loadTime,
memoryDelta: this.metrics.appLoadTimes[appName].memoryDelta
};
}
}
measureRenderTime(appName, renderFunction) {
const startTime = performance.now();
const result = renderFunction();
const renderTime = performance.now() - startTime;
if (!this.metrics.renderTimes[appName]) {
this.metrics.renderTimes[appName] = [];
}
this.metrics.renderTimes[appName].push(renderTime);
console.log(`[qiankun-performance] ${appName} render time: ${renderTime.toFixed(2)}ms`);
return result;
}
getMemoryUsage() {
if (performance.memory) {
return performance.memory.usedJSHeapSize;
}
return 0;
}
getPerformanceReport() {
const report = {
averageLoadTimes: {},
renderPerformance: {},
memoryUsage: this.metrics.memoryUsage.slice(-10) // 最近10次
};
// 计算平均加载时间
for (const [appName, times] of Object.entries(this.metrics.appLoadTimes)) {
if (times.loadTime) {
report.averageLoadTimes[appName] = times.loadTime;
}
}
// 计算平均渲染时间
for (const [appName, times] of Object.entries(this.metrics.renderTimes)) {
const avg = times.reduce((a, b) => a + b, 0) / times.length;
report.renderPerformance[appName] = {
average: avg,
count: times.length,
min: Math.min(...times),
max: Math.max(...times)
};
}
return report;
}
}
const perfDebugger = new PerformanceDebugger();
2. 内存泄漏检测
javascript
// 内存泄漏检测工具
class MemoryLeakDetector {
constructor() {
this.elementCount = new Map();
this.eventListenerCount = new Map();
this.intervalCount = 0;
this.timeoutCount = 0;
this.observerCount = 0;
}
startMonitoring() {
// 监听元素创建和销毁
this.interceptDOMMethods();
// 定期检查内存使用
this.memoryCheckInterval = setInterval(() => {
this.checkMemoryUsage();
}, 5000); // 每5秒检查一次
console.log('[qiankun] Memory leak detection started');
}
interceptDOMMethods() {
// 拦截元素创建
const originalAppendChild = Element.prototype.appendChild;
Element.prototype.appendChild = function(child) {
this._tracked = true;
if (child.tagName) {
const count = this.elementCount.get(child.tagName) || 0;
this.elementCount.set(child.tagName, count + 1);
}
return originalAppendChild.call(this, child);
};
// 拦截事件监听器添加
const originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
const key = `${this.constructor.name}-${type}`;
const count = this.eventListenerCount.get(key) || 0;
this.eventListenerCount.set(key, count + 1);
return originalAddEventListener.call(this, type, listener, options);
};
}
checkMemoryUsage() {
if (performance.memory) {
const memory = performance.memory;
const usage = {
used: memory.usedJSHeapSize,
total: memory.totalJSHeapSize,
limit: memory.jsHeapSizeLimit,
timestamp: new Date().toISOString()
};
// 检查是否接近内存限制
const usagePercent = (usage.used / usage.limit) * 100;
if (usagePercent > 80) {
console.warn('[qiankun] High memory usage detected:', usagePercent.toFixed(2) + '%');
this.generateMemoryReport();
}
this.metrics.memoryUsage.push(usage);
}
}
generateMemoryReport() {
const report = {
elementCounts: Object.fromEntries(this.elementCount),
eventListenerCounts: Object.fromEntries(this.eventListenerCount),
intervals: this.intervalCount,
timeouts: this.timeoutCount,
observers: this.observerCount
};
console.table(report);
console.log('[qiankun] Memory report generated');
return report;
}
stopMonitoring() {
if (this.memoryCheckInterval) {
clearInterval(this.memoryCheckInterval);
}
console.log('[qiankun] Memory leak detection stopped');
}
}
const memoryDetector = new MemoryLeakDetector();
错误处理和日志
1. 错误收集和上报
javascript
// 错误收集工具
class ErrorCollector {
constructor() {
this.errors = [];
this.maxErrors = 100;
this.startCollecting();
}
startCollecting() {
// 捕获运行时错误
window.addEventListener('error', (event) => {
this.collectError({
type: 'runtime',
message: event.error.message,
stack: event.error.stack,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
timestamp: new Date().toISOString()
});
});
// 捕获未处理的 Promise 错误
window.addEventListener('unhandledrejection', (event) => {
this.collectError({
type: 'promise',
message: event.reason.message || event.reason,
stack: event.reason.stack,
timestamp: new Date().toISOString()
});
});
}
collectError(error) {
// 添加额外的上下文信息
error.context = {
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
qiankunState: {
poweredBy: window.__POWERED_BY_QIANKUN__,
apps: this.getRegisteredApps()
}
};
this.errors.push(error);
// 限制错误数量
if (this.errors.length > this.maxErrors) {
this.errors = this.errors.slice(-this.maxErrors);
}
console.error('[qiankun-error] Collected error:', error);
// 可选:上报错误
this.reportError(error);
}
getRegisteredApps() {
// 获取已注册的微应用信息
if (window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__) {
return Object.keys(window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__);
}
return [];
}
reportError(error) {
// 发送错误到监控服务
if (process.env.NODE_ENV === 'production') {
fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error,
type: 'qiankun-micro-frontend'
})
}).catch(err => {
console.error('Failed to report error:', err);
});
}
}
getRecentErrors(limit = 10) {
return this.errors.slice(-limit);
}
clearErrors() {
this.errors = [];
}
}
const errorCollector = new ErrorCollector();
2. 调试工具面板
javascript
// 调试工具面板
class QiankunDevTools {
constructor() {
this.isOpen = false;
this.createPanel();
}
createPanel() {
// 创建调试面板元素
this.panel = document.createElement('div');
this.panel.id = 'qiankun-devtools';
this.panel.innerHTML = `
<div style="position: fixed; bottom: 20px; right: 20px; width: 300px;
background: white; border: 1px solid #ccc; padding: 10px;
z-index: 10000; font-family: monospace; font-size: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h4>qiankun DevTools</h4>
<button onclick="window.qiankunDevTools.toggle()">X</button>
</div>
<div id="qiankun-status"></div>
<div id="qiankun-actions" style="margin-top: 10px;">
<button onclick="window.qiankunDevTools.refresh()">Refresh</button>
<button onclick="window.qiankunDevTools.showErrors()">Errors</button>
<button onclick="window.qiankunDevTools.showPerformance()">Performance</button>
</div>
<div id="qiankun-details" style="margin-top: 10px; max-height: 200px; overflow-y: auto;"></div>
</div>
`;
document.body.appendChild(this.panel);
this.panel.style.display = 'none';
// 初始状态更新
this.updateStatus();
}
toggle() {
this.isOpen = !this.isOpen;
this.panel.style.display = this.isOpen ? 'block' : 'none';
if (this.isOpen) {
this.updateStatus();
}
}
updateStatus() {
const statusDiv = this.panel.querySelector('#qiankun-status');
statusDiv.innerHTML = `
<div>Powered by qiankun: ${!!window.__POWERED_BY_QIANKUN__}</div>
<div>Registered apps: ${this.getRegisteredApps().length}</div>
<div>Recent errors: ${errorCollector.errors.length}</div>
<div>Performance checks: ${Object.keys(perfDebugger.metrics.appLoadTimes).length}</div>
`;
}
getRegisteredApps() {
return window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
? Object.keys(window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__)
: [];
}
refresh() {
location.reload();
}
showErrors() {
const detailsDiv = this.panel.querySelector('#qiankun-details');
const recentErrors = errorCollector.getRecentErrors(5);
detailsDiv.innerHTML = `
<h5>Recent Errors:</h5>
${recentErrors.map(err => `
<div style="margin: 5px 0; padding: 5px; background: #ffe6e6; border-radius: 3px;">
<div><strong>${err.message}</strong></div>
<div style="font-size: 10px; color: #666;">${err.timestamp}</div>
</div>
`).join('')}
`;
}
showPerformance() {
const detailsDiv = this.panel.querySelector('#qiankun-details');
const perfReport = perfDebugger.getPerformanceReport();
detailsDiv.innerHTML = `
<h5>Performance Report:</h5>
<div><strong>Average Load Times:</strong></div>
${Object.entries(perfReport.averageLoadTimes).map(([app, time]) =>
`<div>${app}: ${time.toFixed(2)}ms</div>`
).join('')}
<div style="margin-top: 10px;"><strong>Render Performance:</strong></div>
${Object.entries(perfReport.renderPerformance).map(([app, metrics]) =>
`<div>${app}: avg ${metrics.average.toFixed(2)}ms (${metrics.count} samples)</div>`
).join('')}
`;
}
}
// 全局访问
window.qiankunDevTools = new QiankunDevTools();
// 快捷键打开调试面板
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'Q') {
window.qiankunDevTools.toggle();
}
});
生产环境调试
1. 远程调试配置
javascript
// 生产环境调试配置(需要明确启用)
class ProductionDebugger {
constructor() {
// 通过 URL 参数或本地存储启用生产环境调试
this.enabled = this.isProductionDebugEnabled();
this.sessionId = this.generateSessionId();
}
isProductionDebugEnabled() {
// 检查 URL 参数
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('qiankun-debug') === 'true') {
return true;
}
// 检查本地存储
return localStorage.getItem('qiankun-debug') === 'true';
}
generateSessionId() {
return Math.random().toString(36).substr(2, 9) + '-' + Date.now();
}
log(message, data = null) {
if (this.enabled) {
const logEntry = {
sessionId: this.sessionId,
timestamp: new Date().toISOString(),
message,
data,
url: window.location.href
};
// 发送到远程日志服务
this.sendToRemoteLog(logEntry);
}
}
sendToRemoteLog(logEntry) {
// 发送日志到远程服务
fetch('/api/qiankun-logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
}).catch(err => {
console.error('Failed to send production log:', err);
});
}
enable() {
localStorage.setItem('qiankun-debug', 'true');
this.enabled = true;
console.log('Production debugging enabled for this session');
}
disable() {
localStorage.removeItem('qiankun-debug');
this.enabled = false;
console.log('Production debugging disabled');
}
}
const prodDebugger = new ProductionDebugger();
通过这些调试工具和方法,可以有效地诊断和解决 qiankun 微前端应用中的各种问题。