推荐方案是用 import() 加载远程 ESM 入口,因其支持静态分析、live binding、HMR 和 import.meta;fetch + eval 会绕过 ESM 系统,导致依赖失效、作用域丢失、安全风险高且无法享受现代模块能力。

直接用 fetch 加载 ESM 模块不能直接执行,必须配合动态 import() 或运行时模块解析机制;真正可行的微前端热更新路径,是让子应用以 ESM 入口暴露、主应用通过 import() 拉取并挂载,再由构建工具(如 Vite)的 HMR 机制接管更新传播。
为什么 fetch + eval 不是推荐方案
单纯 fetch 获取 JS 字符串后用 eval 或 new Function 执行,会绕过浏览器 ESM 系统,导致:
- 无法建立 import/export 静态依赖关系,HMR 失效
- 模块作用域丢失,
import.meta.url、import.meta.hot不可用 - 无法享受 live binding(导出值实时更新)、Tree-shaking、源码映射等现代模块能力
- 安全风险高(CSP 策略通常禁止
eval),且破坏模块隔离
正确方式:用 import() 加载远程 ESM 入口
子应用需提供一个标准 ESM 入口文件(如 remote-app/entry.js),导出生命周期函数:
export const bootstrap = () => Promise.resolve();
export const mount = (props) => { /* 渲染到 props.container */ };
export const unmount = (props) => { /* 卸载逻辑 */ };主应用通过动态 import() 加载它:
立即学习“前端免费学习笔记(深入)”;
const remoteApp = await import('https://cdn.example.com/remote-app/entry.js?ts=' + Date.now());
await remoteApp.bootstrap();
await remoteApp.mount({ container: document.getElementById('sub-app') });- URL 中加时间戳或哈希可强制跳过缓存,确保拉取最新版本
- 浏览器原生支持
import()加载远程 URL(需服务端配置 CORS) - 该模块天然具备 ESM 特性:静态分析、命名导出、
import.meta可用
让热更新在微前端中真正生效
仅加载不够,要实现“改完子应用代码、保存即生效”,还需两层协同:
- 子应用开发阶段启用 Vite HMR:子应用自身用 Vite 启动,修改组件或逻辑时,Vite 服务端推送更新,子应用内部完成局部刷新(如 Vue 组件重载)
-
主应用监听子应用变更并触发重挂载:可通过轻量 WebSocket 或轮询检查子应用入口文件 hash 变化;检测到变化后,先调用
unmount,再重新import()新入口,最后mount -
共享状态需跨模块绑定:如使用
export let store = null而非const,配合 live binding,使主应用与子应用读取的是同一响应式实例
生产环境补充要点
上线后不再需要 HMR,但需保障加载鲁棒性:
- 入口地址建议使用版本化 URL(如
/v2.1.0/entry.js)或内容哈希(/entry-abc123.js),避免 CDN 缓存旧版 - 添加加载超时、降级 fallback(如本地兜底模块或错误提示 UI)
- 子应用资源(CSS/图片/子 chunk)也需走相同 CDN 域名,避免跨域阻塞
- 若子应用含 JSON 模块,可直接
import config from './config.json' with { type: 'json' };,无需fetch + parse


















