Appearance
qiankun 常见问题与解决方案
安装和配置问题
1. 安装依赖问题
问题:qiankun 安装失败
错误信息:npm install qiankun 失败或版本冲突
解决方案:
bash
# 清理缓存后重新安装
npm cache clean --force
npm install qiankun --save
# 或使用 yarn
yarn add qiankun
# 检查版本兼容性
npm ls single-spa # qiankun 基于 single-spa
问题:版本兼容性问题
错误信息:Error: Cannot find module 'xxx'
解决方案:
bash
# 检查 qiankun 版本要求
npm info qiankun peerDependencies
# 确保 Node.js 版本 >= 8
node --version
# 更新到兼容版本
npm install qiankun@latest
2. 构建配置问题
问题:微应用无法正常加载
错误信息:Uncaught SyntaxError: Unexpected token '<'
解决方案:
javascript
// webpack.config.js - 确保正确配置输出
module.exports = {
output: {
// 必须配置 library
library: 'microApp',
libraryTarget: 'umd',
// 确保 publicPath 正确
publicPath: 'http://localhost:3001/', // 微应用地址
},
devServer: {
// 开发环境配置
headers: {
'Access-Control-Allow-Origin': '*',
},
// 端口配置
port: 3001,
},
};
问题:样式丢失或冲突
错误信息:微应用样式未正确应用或影响主应用
解决方案:
javascript
// 主应用中配置样式隔离
import { start } from 'qiankun';
start({
sandbox: {
// 启用严格的样式隔离
strictStyleIsolation: true,
// 或使用实验性样式隔离
experimentalStyleIsolation: true,
}
});
运行时问题
1. 生命周期问题
问题:微应用无法卸载
错误信息:切换应用时出现内存泄漏警告
解决方案:
javascript
// 确保正确实现 unmount 生命周期
let app = null;
let routerInstance = null;
let eventListeners = [];
export async function mount(props) {
const { container } = props;
// 创建应用实例
app = new Vue({
router: createRouter(),
store,
render: h => h(App),
});
// 挂载到容器
app.$mount(container ? container.querySelector('#app') : '#app');
// 保存事件监听器
eventListeners = [
// 添加事件监听器到数组中
];
}
export async function unmount(props) {
// 清理事件监听器
eventListeners.forEach(cleanup => cleanup());
eventListeners = [];
// 销毁应用实例
if (app) {
app.$destroy();
app.$el.innerHTML = '';
app = null;
}
// 清理路由实例
if (routerInstance) {
routerInstance = null;
}
}
问题:bootstrap 只执行一次
现象:微应用重新进入时没有执行 bootstrap
解决方案:
javascript
// bootstrap 只在初始化时执行一次,后续直接调用 mount
let initialized = false;
export async function bootstrap(props) {
if (!initialized) {
// 初始化逻辑
console.log('首次初始化');
initialized = true;
// 执行一次性初始化
await initializeOnce();
}
}
export async function mount(props) {
// 每次挂载都执行的逻辑
console.log('每次挂载执行');
await setupApp(props);
}
2. 通信问题
问题:主应用和微应用通信失败
错误信息:props.onGlobalStateChange is not a function
解决方案:
javascript
// 主应用:确保正确初始化全局状态
import { initGlobalState } from 'qiankun';
// 必须在注册微应用之前初始化
const { onGlobalStateChange, setGlobalState } = initGlobalState({
user: { name: 'admin' },
});
// 微应用:安全地使用通信方法
export async function mount(props) {
// 安全检查
if (typeof props.onGlobalStateChange === 'function') {
props.onGlobalStateChange((state, prev) => {
console.log('全局状态变化', state, prev);
});
}
if (typeof props.setGlobalState === 'function') {
props.setGlobalState({
lastUpdate: Date.now(),
});
}
}
问题:跨应用数据传递失败
现象:微应用无法获取主应用传递的数据
解决方案:
javascript
// 主应用:确保在注册时传递数据
registerMicroApps([
{
name: 'myApp',
entry: '//localhost:3001',
container: '#subapp-viewport',
activeRule: '/app',
// 传递数据
props: {
data: { message: 'Hello from main app' },
onCallback: (data) => {
console.log('收到微应用回调', data);
}
}
}
]);
// 微应用:正确接收数据
export async function mount(props) {
const { data, onCallback } = props;
// 使用传递的数据
console.log('收到的数据', data);
// 调用回调函数
if (typeof onCallback === 'function') {
onCallback({ response: 'Hello from micro app' });
}
}
路由问题
1. 路由冲突
问题:主应用和微应用路由冲突
现象:路由切换异常或无法正确匹配
解决方案:
javascript
// 主应用路由配置
const mainRouter = new VueRouter({
mode: 'history',
base: '/main', // 设置基础路径
routes: [
{ path: '/', component: Home },
{ path: '/app1', component: () => import('./MicroApp1') },
{ path: '/app2', component: () => import('./MicroApp2') },
]
});
// 微应用路由配置
export async function mount(props) {
const { container, routerBase } = props;
// 使用主应用传递的路由基础路径
const microRouter = new VueRouter({
mode: 'history',
base: routerBase || '/micro-app', // 或使用默认路径
routes: [
{ path: '/', component: Home },
{ path: '/profile', component: Profile },
]
});
app = new Vue({
router: microRouter,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
2. History 模式问题
问题:浏览器后退按钮异常
现象:点击浏览器后退按钮无法正常返回
解决方案:
javascript
// 微应用中处理浏览器历史记录
export async function mount(props) {
// 监听浏览器历史记录变化
const unlisten = props.history.listen((location, action) => {
console.log('路由变化', location, action);
});
// 在卸载时清理监听器
window.__MICRO_APP_UNMOUNT__ = () => {
unlisten(); // 清理历史记录监听器
};
}
export async function unmount(props) {
if (window.__MICRO_APP_UNMOUNT__) {
window.__MICRO_APP_UNMOUNT__();
delete window.__MICRO_APP_UNMOUNT__;
}
// 其他清理逻辑
if (app) {
app.$destroy();
app.$el.innerHTML = '';
app = null;
}
}
样式和资源问题
1. 样式隔离问题
问题:微应用样式影响主应用
现象:微应用的 CSS 样式污染了主应用
解决方案:
javascript
// 方案1:启用严格的样式隔离
start({
sandbox: {
strictStyleIsolation: true,
}
});
// 方案2:使用 CSS 命名空间
// 在微应用中使用特定的 CSS 类名前缀
export async function mount(props) {
const { container } = props;
// 为微应用容器添加特定类名
if (container) {
container.classList.add('micro-app-container');
container.classList.add('micro-app-' + props.name);
}
}
问题:微应用样式不生效
现象:微应用的样式无法正确应用
解决方案:
css
/* 使用更具体的选择器 */
.micro-app-container .my-component {
/* 微应用样式 */
color: blue;
}
/* 或使用 CSS Modules */
/* 在微应用中使用 CSS Modules */
2. 静态资源问题
问题:静态资源路径错误
错误信息:Failed to load resource: the server responded with a 404
解决方案:
javascript
// 微应用中正确处理静态资源路径
export async function mount(props) {
const { container } = props;
// 设置基础路径
const basePath = props.basePath || '';
// 动态设置资源路径
const images = container.querySelectorAll('img');
images.forEach(img => {
if (img.src.startsWith('/static')) {
img.src = basePath + img.src;
}
});
}
// 或在构建时配置 publicPath
// webpack.config.js
module.exports = {
output: {
publicPath: 'auto', // 自动检测 publicPath
// 或
publicPath: window.__POWERED_BY_QIANKUN__ ? '/micro-app/' : '/',
},
};
性能问题
1. 加载性能问题
问题:微应用加载缓慢
现象:微应用首次加载时间过长
解决方案:
javascript
// 启用预加载
start({
prefetch: true, // 开启预加载
});
// 或自定义预加载策略
start({
prefetch: {
criticalAppList: [
{ name: 'criticalApp', entry: '//localhost:3001' }
],
idleAppList: [
{ name: 'idleApp', entry: '//localhost:3002' }
],
}
});
// 微应用中优化资源加载
export async function bootstrap(props) {
// 预加载关键资源
const criticalResources = [
'/css/critical.css',
'/js/vendor.js'
];
await Promise.all(criticalResources.map(loadResource));
}
function loadResource(url) {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'script';
link.href = url;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
}
2. 内存泄漏问题
问题:应用切换时内存泄漏
现象:长时间使用后内存占用持续增长
解决方案:
javascript
// 完整的清理逻辑
let app = null;
let eventListeners = [];
let timers = [];
let observers = [];
export async function mount(props) {
const { container } = props;
// 创建应用
app = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
// 添加事件监听器到清理队列
const handleClick = () => console.log('click');
document.addEventListener('click', handleClick);
eventListeners.push(() => document.removeEventListener('click', handleClick));
// 添加定时器到清理队列
const timer = setInterval(() => {
// 定时任务
}, 1000);
timers.push(timer);
// 添加观察器到清理队列
const observer = new MutationObserver(() => {
// 观察变化
});
observer.observe(document.body, { childList: true });
observers.push(observer);
}
export async function unmount(props) {
// 清理事件监听器
eventListeners.forEach(cleanup => cleanup());
eventListeners = [];
// 清理定时器
timers.forEach(timer => clearInterval(timer));
timers = [];
// 清理观察器
observers.forEach(observer => observer.disconnect());
observers = [];
// 销毁应用
if (app) {
app.$destroy();
app.$el.innerHTML = '';
app = null;
}
}
兼容性问题
1. 浏览器兼容性
问题:低版本浏览器不支持
错误信息:Proxy is not defined
解决方案:
javascript
// 降级到快照沙箱
start({
sandbox: {
type: 'snapshot', // 使用快照沙箱(不依赖 Proxy)
}
});
// 或添加 Proxy polyfill
if (!window.Proxy) {
// 加载 Proxy polyfill
import('proxy-polyfill').then(polyfill => {
window.Proxy = polyfill;
});
}
2. 框架兼容性
问题:特定框架不兼容
现象:某些前端框架无法正常工作
解决方案:
javascript
// 通用框架适配器
export async function mount(props) {
const { container, framework } = props;
switch (framework) {
case 'vue2':
app = new Vue({
router,
store,
render: h => h(App),
}).$mount(container.querySelector('#app'));
break;
case 'vue3':
app = createApp(App);
app.use(router);
app.use(store);
app.mount(container.querySelector('#app'));
break;
case 'react':
app = ReactDOM.render(
<App />,
container.querySelector('#app')
);
break;
default:
// 默认处理
console.warn('Unknown framework:', framework);
}
}
调试技巧
1. 调试工具
使用浏览器调试
javascript
// 在控制台中检查 qiankun 状态
console.log(window.__POWERED_BY_QIANKUN__); // 检查是否在 qiankun 环境中
// 检查注册的应用
console.log(window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__);
// 手动触发应用加载
const { loadMicroApp } = require('qiankun');
loadMicroApp({
name: 'test-app',
entry: '//localhost:3001',
container: '#test-container',
});
2. 日志记录
添加详细日志
javascript
// 日志记录工具
const logger = {
log: (message, ...args) => console.log(`[qiankun] ${message}`, ...args),
warn: (message, ...args) => console.warn(`[qiankun] ${message}`, ...args),
error: (message, ...args) => console.error(`[qiankun] ${message}`, ...args),
};
// 在生命周期中添加日志
export async function mount(props) {
logger.log('Mounting micro app', props.name);
try {
// 挂载逻辑
app = new Vue({
router,
store,
render: h => h(App),
}).$mount(props.container.querySelector('#app'));
logger.log('Micro app mounted successfully', props.name);
} catch (error) {
logger.error('Failed to mount micro app', props.name, error);
throw error;
}
}
生产环境问题
1. 部署问题
问题:生产环境微应用无法加载
解决方案:
javascript
// 生产环境配置
const isProduction = process.env.NODE_ENV === 'production';
const domain = isProduction ? 'https://cdn.example.com' : 'http://localhost:3001';
// 微应用构建配置
module.exports = {
output: {
library: 'microApp',
libraryTarget: 'umd',
publicPath: isProduction ? domain + '/' : 'http://localhost:3001/',
},
devServer: {
headers: isProduction ? {} : {
'Access-Control-Allow-Origin': '*',
},
},
};
2. CDN 配置
问题:CDN 资源加载问题
解决方案:
javascript
// 主应用中配置微应用入口
const microApps = [
{
name: 'cdnApp',
entry: isProduction
? 'https://cdn.example.com/micro-app/'
: '//localhost:3001',
container: '#subapp-viewport',
activeRule: '/cdn-app',
}
];
通过了解和应用这些解决方案,可以有效处理 qiankun 微前端开发中的常见问题。