微前端(qiankun)单侧启动调试技巧

35 阅读5分钟

微前端(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=<线上域名>SecureSameSite=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 scriptNetwork 里 JS 请求返回 text/html → 补排除
died LOADING_SOURCE_CODE同上,子应用资源没能从本地拿到
接口 401/499cookieDomainRewrite + 检查 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 稳定不抖动

全过即成功。