微前端(qiankun)单侧启动调试技巧
一种只本地启一个项目的微前端调试方式,避免每次都要同时跑起"主 + 子"两套。 适用于 qiankun 及绝大多数"主应用加载远端 HTML entry"的微前端框架(micro-app、wujie 等思路类似)。
核心思路
借助相对路径 entry + 子应用 dev server 的兜底反向代理,让浏览器里的主应用自动回指向本地子应用。
两种等价玩法,任选:
| 模式 | 本地启什么 | 代理走向 | 调试对象 |
|---|---|---|---|
| A. 只起子应用 | 子应用 dev server | 非子应用的请求 → 线上主应用 | 子应用 |
| B. 只起主应用 | 主应用 dev server | 子应用资源请求 → 线上子应用 | 主应用 |
下面以模式 A(调试子应用最常见)为例,模式 B 对称即可。
前置条件:主应用使用相对 entry
这是整个技巧能成立的根基。主应用在注册/加载子应用时,entry 必须是相对路径:
// ✅ 好
const entry = `/${appName}/`;
// ✅ 可以在 dev 时覆盖
let entry = `/${appName}/`;
if (process.env.NODE_ENV === 'development') {
entry = `${process.env.SUB_APP_ENTRY || ''}${entry}`;
}
// ❌ 不行 —— 写死远端地址,不受本地 dev server 支配
const entry = 'https://subapp.example.com/';
const entry = '//localhost:5173';
生产构建后 entry 是相对路径 /${appName}/,浏览器会把它拼到当前 origin。只要浏览器在 localhost:<端口>,entry 就是 localhost:<端口>/${appName}/,本地 dev server 就能接管。
⚠️ 如果线上主应用构建产物里 entry 被写死成绝对 URL,本技巧对该项目不适用,需要先改主应用。
子应用侧配置(Vite 示例)
// vite.config.js
export default defineConfig({
// ...
server: {
port: 5173,
proxy: {
// 兜底规则:非子应用自身、非 Vite 内置的路径全部转发到线上主应用
'^/(?!<sub-app-name>/|@fs/|@vite/|@id/|src/|node_modules/|__)': {
target: 'https://<online-main-app>',
changeOrigin: true
// 注意:不要加 ws: true
}
}
}
});
把 <sub-app-name> 换成主应用里注册的子应用名(=入口路径第一段),<online-main-app> 换成线上主应用域名。
排除列表通用说明
| 排除项 | 为什么不能代理 |
|---|---|
| 子应用自身路径 | 要交给本地 dev server 响应 |
@fs/ @vite/ @id/ | Vite 内部虚拟模块 |
src/ | 子应用源码,dev 模式按绝对路径 /src/xxx 加载 |
node_modules/ | Vite 预构建依赖 |
__* | __vite_ping、__open-in-editor 等内部接口 |
凡是本地 dev server 自己要服务的路径,都必须排除。漏一个,资源就会被当成主应用请求代理到线上,返回 HTML 给浏览器当 JS 执行,报错。
Webpack devServer 等价写法
// vue.config.js / webpack.config.js
devServer: {
proxy: [
{
context: (pathname) => {
// 排除本地要自己服务的路径
if (pathname.startsWith('/<sub-app-name>/')) return false;
if (pathname.startsWith('/sockjs-node')) return false; // webpack HMR
if (pathname.startsWith('/webpack-dev-server')) return false;
if (/\.(js|css|map|json|woff2?|png|jpg|svg)$/.test(pathname)) return false;
return true;
},
target: 'https://<online-main-app>',
changeOrigin: true
}
]
}
运行流程(模式 A)
浏览器: http://localhost:5173/some/route
│
▼
┌────────────────────┐
│ 子应用 Vite dev │
└─────────┬──────────┘
│ 路径不匹配排除
▼ 兜底代理
┌────────────────────┐
│ 线上主应用 │ → 返回主应用 HTML / JS / 业务接口
└─────────┬──────────┘
│
▼
浏览器里主应用 SPA 运行,qiankun 按注册表加载子应用
entry = "/<sub-app-name>/" (相对路径)
│
▼ 解析成 http://localhost:5173/<sub-app-name>/
┌────────────────────┐
│ 子应用 Vite dev │ 命中排除 → SPA fallback 返回本地 index.html
└─────────┬──────────┘
│
▼
浏览器加载 /src/main.js 等本地资源 → 子应用启动 ✓
常见坑
1. 页面反复刷新
症状:终端不停输出
[vite] ws proxy error: write ECONNABORTED
[vite] ws proxy socket error
大约每 7~10 秒一次。页面在浏览器里反复刷新或 HMR 断连。
原因:兜底代理规则加了 ws: true,Vite HMR 的 WebSocket (ws://localhost:<port>/) 被代理到线上主应用。线上不接受这个 WS upgrade,失败 → 客户端重连 → 循环。
修复:把 ws: true 去掉。本模式下主应用业务 WS 一般不需要在本地打通。
2. 子应用挂不起来 / Failed to load module script
症状:Console 典型三连:
Failed to load module script: Expected a JavaScript-or-Wasm module script
but the server responded with a MIME type of "text/html".
Failed to fetch dynamically imported module: http://localhost:<port>/src/main.js
single-spa minified message #31 ... died ... LOADING_SOURCE_CODE
原因:排除列表漏了 dev server 自己要服务的路径(最常见是 /src/)。子应用资源被错误代理到线上主应用,返回 HTML,浏览器当 JS 解析失败。
修复:在 Network 面板找到返回 text/html 的那个 JS 请求,把对应路径前缀补进排除列表。
3. 登录态丢失 / 业务代码 undefined 报错
症状:主应用接口返回 401/499,或运行时报 Cannot read properties of undefined (reading 'xxx')(因为用户信息没拉到)。
原因:线上主应用下发的 cookie 带 Domain=<线上域名> 或 Secure、SameSite=None,浏览器在 http://localhost 下不接受。
修复:
proxy: {
'^/...': {
target: 'https://<online-main-app>',
changeOrigin: true,
cookieDomainRewrite: { '*': '' }, // 剥掉 Domain
// 必要时配合:
// cookiePathRewrite: { '*': '/' },
// secure: false
}
}
若 cookie 带 Secure; SameSite=None,HTTP 的 localhost 仍会被浏览器拒收。解决办法:
- 用 Chrome 启动参数
--disable-web-security --user-data-dir=/tmp/cd - 或给本地 dev server 开 HTTPS(
@vitejs/plugin-basic-ssl) - 或用 Chrome DevTools → Application → Cookies 手动改
4. 主应用业务 WebSocket 必须打通
不能简单 ws: false。思路:把 Vite 的 HMR WS 挪到专用路径,兜底代理里排除它,其他 WS 再允许代理:
server: {
hmr: { path: '/__vite_hmr' },
proxy: {
'^/(?!<sub-app-name>/|@fs/|@vite/|@id/|src/|node_modules/|__)': {
target: 'wss://<online-main-app>',
changeOrigin: true,
ws: true
}
}
}
5. 主应用里某些请求路径也要透传给本地子应用
某些场景下主应用会请求 /api/sub/xxx 由子应用响应。此时要在排除表里加上该前缀,否则会被代理回线上。
排障速查
| 现象 | 先看这里 |
|---|---|
| 页面反复刷新 | 终端 ws proxy error → 关 ws: true |
Failed to load module script | Network 里 JS 请求返回 text/html → 补排除 |
died LOADING_SOURCE_CODE | 同上,子应用资源没能从本地拿到 |
| 接口 401/499 | cookieDomainRewrite + 检查 Secure/SameSite |
| 地址栏 URL 不停跳 | 主应用自己的登录/重定向死循环,多半还是 cookie |
ERR_TOO_MANY_REDIRECTS | 同上 |
| 子应用拉到 HTML 但控件/样式错位 | 主应用某些静态资源被错误代理,检查排除表 |
验证清单
重启 dev 后打开 DevTools Network,依次确认:
- 终端无
ws proxy error - 顶层 HTML 请求 → 目标是线上主应用
-
<sub-app-name>/请求 → 200,返回本地 dev server 的 HTML(含/@vite/client注入) -
/src/main.js→ 200,Content-Type: application/javascript - Console 无 single-spa
#31 - 地址栏 URL 稳定不抖动
全过即成功。