普通视图

发现新文章,点击刷新页面。
昨天 — 2026年4月9日首页

油猴脚本实现生产环境加载本地qiankun子应用

作者 石小石Orz
2026年4月9日 15:26

大家好,我是石小石~


qiankun架构下的调试困境

如果你公司的前端架构基于 qiankun,你一定遇到过这样一个问题:由于子应用脱离主应用独立运行,在本地开发阶段,很多和主应用的操作联动、样式交互都无法直接验证,只能把子应用部署到开发或测试环境后,才能排查这类问题。

尤其是在一些不需要做 JS 沙箱隔离的业务场景里,主子应用需要通过 eventBus 这类方式实现交互,子应用不部署上线,调试起来就非常麻烦。

那有没有办法让生产环境直接加载本地子应用来实现代码调试?

方法肯定是有的,比如在主应用里写一套便于调试的逻辑。

import { registerMicroApps, start } from 'qiankun';

// ============== 核心:根据环境变量加载 本地/线上 子应用 ==============
const isDev = process.env.IS_DEV; // webpack 注入的环境变量

// 子应用配置列表
const microApps = [
  {
    name: 'subapp-vue', // 子应用唯一名称
    // 本地开发:加载 localhost 地址;生产:加载线上地址
    entry: isDev ? 'http://localhost:8080/gcshi-web-demo' : '/gcshi-web-demo',
    container: '#subapp-container', // 子应用挂载的容器 id
    activeRule: '/vue', // 路由匹配规则
  },
];

这种写法确实可以通过特定方式触发生产环境加载本地子应用,方便调试。但不可避免地需要修改主应用代码,如果没有主应用代码权限,那就很尴尬了。

其实,针对上面这个问题,用油猴脚本就能轻松解决!

油猴脚本简介

油猴(Tampermonkey)是一款浏览器插件,允许用户在网页加载时注入自定义的 JavaScript 脚本,来增强、修改或自动化网页行为

通俗地说,借助油猴,你可以将自己的 JavaScript 代码“植入”任意网页,实现自动登录、抢单、签到、数据爬取、广告屏蔽等各种“开挂级”功能,彻底掌控页面行为。

它和谷歌插件能实现的效果几乎一致,不过更加简单。如果你是前端开发,可以直接使用油猴,因为它本质就是针对网页写js。

如果你对油猴脚本感兴趣,可以看看: 《油猴脚本实战指南》

使用油猴脚本实现生产环境加载本地子应用

如图,我用 npm run dev 启动了一个本地子应用服务。

开启插件后,页面上会出现油猴脚本的调试工具。

点击【开启代理】,主应用会自动刷新,从而加载本地子应用,全程不需要做任何额外配置。

而且它完美支持热更新,这意味着你修改本地子应用代码后,生产环境页面会同步更新,调试非常方便。

核心原理

实现生产环境加载本地子应用其实很简单:

用油猴脚本在主应用加载时进行拦截,把原本要加载的线上子应用地址,替换成本地服务地址。

你可以这么理解:主应用原本要加载 http://baidu.com/gcshi-web-demo,被脚本替换成了 http://localhost:8080/gcshi-web-demo

重写fetch

qiankun 底层依赖 import-html-entry 这个库,核心流程是通过 fetch 加载子应用 HTML 模板,再解析 CSS、JS。 所以我们只需要在页面加载早期,拦截并重写 fetch 即可。

参考:juejin.cn/post/757214…

那么问题很好解决了, 我们只需要在页面加载早期,拦截并重写 fetch 即可。


const oldFetch = window.fetch;
window.fetch = (url, ...args) => {
  // 替换域名
  if (url === 'http://baidu.com/gcshi-web-demo') {
    url = 'http://localhost:8080/gcshi-web-demo';
  }
  return oldFetch(url, ...args);
};

保证脚本最早运行

重写 window.fetch 的前提,是脚本必须比页面其他逻辑更早执行,否则重写会失效。

在油猴脚本中,可以通过添加元信息实现:

// @run-at       document-start

参考:油猴脚本的运行生命周期

我在油猴脚本里的 fetch 重写逻辑如下:

import $ from "../../gmTool/index";
const { unsafeWindow } = $;

type FetchInterceptor = (url: RequestInfo | URL, options?: RequestInit) => [RequestInfo | URL, RequestInit?] | void | false;

const win = unsafeWindow;
const rawFetch = win.fetch.bind(win);

export function onFetch(interceptor: FetchInterceptor) {
  // 如果已经被代理过,先复用原来的
  if (!(win.fetch as any).rawFetch) {
    const proxyFetch: typeof fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
      let nextInput = input;
      let nextInit = init;
      // 执行 interceptor
      try {
        const result = interceptor(nextInput, nextInit);
        if (result === false) {
          console.warn("[winnex-web-proxy] 😭😭😭😭 fetch请求被用户阻止了===========================>", nextInput);
          return Promise.reject(new Error("[winnex-web-proxy] 😭😭😭😭 fetch请求被用户阻止了"));
        }
        if (result && Array.isArray(result)) {
          nextInput = result[0];
          nextInit = result[1];
        }
      } catch (err) {
        console.error("[fetch] interceptor error:", err);
      }
      // 处理 Request 对象情况
      if (nextInput instanceof Request && nextInit) {
        nextInput = new Request(nextInput, nextInit);
        nextInit = undefined;
      }
      return rawFetch(nextInput, nextInit);
    };

    (proxyFetch as any).rawFetch = rawFetch;
    win.fetch = proxyFetch;
  }

  // 返回取消方法
  return function unProxyFetch() {
    if ((win.fetch as any).rawFetch) {
      win.fetch = rawFetch;
    }
  };
}
  • 基础使用(替换接口地址)
// 注册拦截器
const unProxy = onFetch((url, options) => {
  const u = url.toString();
  // 匹配并替换地址
  if (u === 'http://baidu.com/gcshi-web-demo') {
    return ['http://localhost:8080/gcshi-web-demo', options];
  }
});
  • 阻止某个请求
onFetch((url) => {
  if (url.toString().includes('/black-api')) {
    return false; // 拦截并拒绝
  }
});

解决跨域问题

生产环境页面加载本地 localhost:8080 可能会出现跨域,导致子应用加载失败。解决方法很简单,在 vite 或 webpack 中添加响应头配置:

  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
  },

解决热更新

默认情况下,生产环境加载子应用时,热更新会失效。原因是热更新相关的 XHR 请求前缀被替换成了主应用域名。只需要拦截 XHR 请求,修正热更新接口前缀即可。以 webpack 热更新为例,修复 sockjs-nodehot-update 两个接口就行。

使用 ajax-hook 实现 XHR 拦截,代码如下:


const appOrigin = "http://localhost:8080"
const fixHotUpdateUrl = (config: any) => {
  if (config.url.includes("sockjs-node") && appOrigin) {
    config.url = fixSockJsUrl(config.url, appOrigin);
  }
  if (config.url.includes(appName) && config.url.includes("hot-update")) {
    config.url = fixHotUpdate(config.url, appName, appOrigin);
    console.log(`[winnex-web-proxy] 热更新🚀🚀===============================> ${config.url}`);
  }
};

export const xhrProxy = (enable: boolean) => {
  if (!enable) return;
  // xhr拦截
  proxy(
    {
      //请求发起前进入
      onRequest: (config, handler) => {
        fixHotUpdateUrl(config);
        handler.next(config);
      },
      //请求发生错误时进入,比如超时;注意,不包括http状态码错误,如404仍然会认为请求成功
      onError: (err, handler) => {
        handler.next(err);
      },
      //请求成功后进入
      onResponse: (response, handler) => {
        handler.next(response);
      }
    },
    unsafeWindow
  );
};

总结

在 qiankun 微前端架构中,本地子应用想要直接在生产环境调试,不必修改主应用代码、不必申请权限,通过油猴脚本重写 fetch劫持子应用入口地址,配合跨域配置XHR 拦截修复热更新,就能实现线上环境加载本地子应用,并且支持热更新,极大提升微前端联调效率。整个方案轻量、无侵入、开箱即用,非常适合前端日常调试。

❌
❌