
简介本资源是一个面向前端开发者特别是熟悉Vue生态并探索微前端架构的中高级工程师的实战型脚手架项目。它基于Vue CLI插件机制封装了MicroAPP核心适配能力解决传统Vue单页应用难以直接接入微前端容器如qiankun、micro-app的工程化痛点支持快速构建符合微应用规范的子应用模块。压缩包共18个文件包含10个关键JS源码如registerWebpackPlugin.js、chainConfig.js、service层实现、2个lock依赖锁文件、2个说明文档README.md、CHANGELOG.md、2个配置JSONpackage.json、vue.config.js、1个入口HTML及1份LICENSE整体仅336KB轻量易读结构聚焦于插件注册、服务封装与微应用生命周期桥接。已有46人学习下载读者可直接复用其插件注册逻辑、Webpack链式配置方案及MicroAPP环境检测机制快速掌握Vue CLI与微前端框架的深度集成方法。 做前端尤其是做中后台系统这块的这两年应该没少听“微前端”这个词。我最近在整理一个基于 Vue CLI 插件的 MicroAPP 项目源码包压缩包解压之后就是一个完整的工程集合里面包含了插件本体、基座主应用和两个不同类型的子应用网络和依赖装好之后npm install 一下就能把整个微前端架子跑起来。如果你第一次接触 micro-app或者手里有现成的 Vue CLI 项目想低成本接入微前端这个包可以作为很好的参考模板。这个项目核心就干一件事把 micro-app 接入 Vue CLI 项目的所有重复劳动收敛成一个插件再用一个真实可运行的示例工程来演示插件怎么用、主应用怎么配、子应用怎么改。它不只是一个 Demo更像是一套可以直接往业务里搬的“基础设施样板”。整个包我实际跑过几轮踩过不少坑下面把设计和实操一条条说清楚。1. 项目在做什么需求拆解与方案选型1.1 微前端要解决的三个问题先聊聊微前端到底在解决什么。很多团队项目做大之后会遇到一类非常典型的情况一个老的管理后台用 Vue 2 写的已经跑了好几年突然要接入一个新团队用 React 写的模块或者要并进来一个用 Vue 3 重构的业务系统。你要是把代码直接塞进原仓库构建时间先不谈光是依赖版本冲突、样式互相覆盖、全局变量污染这几件事就能让发版变成一场灾难。微前端的思路很简单粗暴大家别挤在一个仓库里了每个业务系统独立开发、独立构建、独立部署由主应用在运行时把它们渲染到页面上。这样团队之间可以自由选型一个系统里同时存在 Vue 2、Vue 3、React 都不奇怪谁也不用迁就谁。micro-app 就是干这个的而且它的接入方式比同类方案更“轻”这一点后面展开说。但问题也随之而来微前端的接入本身是有成本的。主应用要初始化框架子应用要改构建配置、调整挂载逻辑路由要对齐开发环境要处理跨域还涉及沙箱、样式隔离这些概念。如果团队里每个人都要把这些配置重新手写一遍肯定会乱。所以这个项目做了一个封装把很多步骤固化成 Vue CLI 插件交给脚手架去完成。1.2 为什么选 micro-app而不是 qiankun / single-spa市面上主流的微前端方案我基本都接触过。single-spa 是鼻祖但它只负责“生命周期调度”路由匹配、应用加载、样式隔离全都得自己实现接入成本非常高我一般不太推荐给业务团队。qiankun 是 single-spa 之上的一套封装解决了大部分问题但要求子应用必须导出 bootstrap、mount、unmount 三个生命周期还要改 webpack 的 libraryTarget 配置改造量不算小而且它对 Vite 子应用的支持一直比较勉强。micro-app 走的是另外一条路它把微前端的承载方式做成了一个原生 Web Component也就是micro-app这个自定义元素。你在主应用页面里写一句micro-app namexxx urlhttp://localhost:4001/micro-app框架会自动去加载 url 对应的子应用 HTML、解析里面的 JS 和 CSS、跑在沙箱里、完成渲染。整个过程子应用几乎不需要感知自己“被嵌入了”不需要像 qiankun 那样改写生命周期更贴近普通 iframe 的使用体验但体验比 iframe 好得多。另一个选它的理由是隔离机制。micro-app 默认会对子应用的脚本做 Proxy 沙箱隔离、对样式做 scoped 处理还支持 shadow DOM 模式。样式和脚本都把边界控制住了整体出问题的概率比裸接入小很多。再加上它自带一套虚拟路由系统主应用可以在完全不改变子应用路由代码的前提下控制子应用内部的路由行为这是我认为它比 qiankun 更顺手的核心原因之一。1.3 为什么还要再做一层 Vue CLI 插件微前端方案选完了下一个问题是如果团队里全是 Vue CLI 项目怎么让大家用最少的成本接进来直接手写会面对这样几件事要装 micro-app 依赖要在 main.js 里调用microApp.start()要在 vue.config.js 里给 devServer 配跨域响应头子应用可能要改 publicPath、关掉 webpack 的 chunkLoading 全局变量还要处理 history 路由的回退。这些配置不同项目之间几乎是复制粘贴的但又是最容易抄错、最容易漏掉的。漏掉一个 headers子应用加载就直接失败漏掉一个 chunkLoadingGlobal两个子应用一起跑就会出现 webpackJsonp 冲突报错还很隐晦。Vue CLI 插件做的事情就是把上面这些步骤自动化。用户执行一次vue add vue-cli-plugin-micro-app插件会帮你装好依赖、注入初始化代码、改写 vue.config.js把该配的都配好。源码包里能看到插件完整的实现包括 Service 插件部分和 Generator 部分如果你想把它改成 Vue CLI 插件或者扩展成 React 项目的接入工具改起来也有现成的参照。2. 源码结构拆解插件、基座、子应用三者怎么配合2.1 包目录里到底有什么把 zip 解压之后第一眼看到的目录设计和大多数前端工程一致但核心是三个模块我建议从外到内看。. ├── package.json ├── README.md ├── packages │ └── vue-cli-plugin-micro-app │ ├── index.js │ ├── generator │ └── package.json ├── examples │ ├── main-app │ ├── sub-app-vue3 │ └── sub-app-react (如果有) └── yarn.lockpackages 下是插件源码examples 下是演示工程。这个结构本身就是一个 monorepo 的雏形插件源码和示例工程放在同一个仓库里方便在本地做联动调试。我拿到包之后一般是先看 README它会写清楚 Node 版本要求、启动顺序和端口约定。这个包的设计思路是比较常规的main-app 默认跑 3000 端口子应用跑 4001、4002主应用通过不同端口把子应用拉进来。如果你拿到的压缩包没有 examples也不用慌插件本身也能独立工作。你只需要创建一个新的 Vue CLI 工程把插件目录复制到本地然后在项目里通过相对路径安装插件具体用法后面实操部分会写。2.2 micro-app 的核心渲染闭环micro-app 的渲染过程理解清楚之后对排查问题帮助很大。它不是一个“加载 iframe”的过程而是把子应用当成一个“增强版组件”来处理。第一步浏览器解析到micro-app标签时触发框架定义好的 CustomElement 构造函数。micro-app 从标签上读取 name、url、baseroute 这些属性然后通过 fetch 去请求 url 对应的 HTML 入口文件。所以一个非常关键的结论是子应用必须能通过 HTTP 访问而且 CORS 要放行否则框架连子应用的 HTML 都拿不到。第二步拿到 HTML 之后micro-app 会解析文档提取里面的 script、link、style并把静态资源路径从相对路径转换成完整路径然后按顺序执行。JS 不会直接跑在全局 window 上而是跑在一个由 Proxy 构造的沙箱环境里。子应用里的window.xxx、document.xxx在沙箱里是一个代理对象框架会把子应用声明的全局变量隔离在沙箱内部避免和主应用及其它子应用互相污染。第三步HTML 片段和样式会挂在 shadow DOM 里渲染或者通过 scoped CSS 的方式处理这样主应用和子应用的样式互不干扰。整个过程完成后页面里会看到嵌入的子应用内容但它和主应用之间又有一道清晰的边界。micro-app 还提供了一些生命周期事件比如created、beforemount、mounted、unmount等主应用可以监听这些事件来做自己业务上的逻辑比如加载 loading、埋点上报等。2.3 插件源码最关键的几个钩子Vue CLI 插件的本质是一个函数通常在 index.js 里暴露给脚手架的 PluginAPI。项目源码里核心逻辑主要落在两个地方。第一个是 Service 部分也就是插件运行时修改 webpack 和 devServer 配置的地方。源码中通过api.configureWebpack或api.chainWebpack干预构建比如给 devServer 的 headers 加上Access-Control-Allow-Origin: *确保主应用在开发环境下能够跨域请求子应用资源。如果是子应用它还会通过 chainWebpack 修改 output 配置设置合适的libraryTarget、关闭全局变量冲突确保子应用可以独立运行也可以被嵌入。第二个是 Generator 部分写在 generator/index.js 里。Vue CLI 在执行vue add的时候会调用这部分逻辑来“生成”或“修改”项目文件。源码里一般会做两件事在 main.js 的开头注入 micro-app 的 import 和microApp.start()调用以及在 vue.config.js 里合并进 micro-app 相关的配置。这里有个比较讲究的点Generator 会读取用户项目的现有配置做合并而不是覆盖这个逻辑我后面在常见问题里再细聊。我建议看插件源码的时候不要只盯着工具函数本身重点观察它是怎么判断“当前项目是主应用还是子应用”的。通常会通过 CLI 的 prompts 或者环境变量让用户选择选择了不同身份生成的配置就完全不同。这个设计思路比把所有东西都塞到 vue.config.js 里要清晰得多。3. 实操用 Vue CLI 一步步跑通主应用和子应用3.1 环境准备实际操作之前建议先看一眼环境版本。这个项目基于 Vue CLI而 Vue CLI 5.x 要求 Node.js 12 以上如果你想省心一点直接用 Node 16 或 18 比较稳。Vue CLI 可以通过npm i -g vue/cli安装然后确认一下版本。node -v npm -v vue --version如果你用的 npm 版本比较新注意一下 Vue CLI 5 在某些 Node 版本下可能提示依赖警告这个不影响运行。我本地跑的时候用的是 Node 16整个链路没有额外报错。3.2 主应用接入步骤先创建一个 Vue 3 的工程作为主应用。你可以直接用 Vue CLI 的交互式创建命令vue create main-app # 选择 Vue 3 Babel Router 即可 cd main-app接下来有两种方式。一种是直接安装 micro-app 依赖然后手动改代码这也是理解原理最快的路径另一种是用插件一把梭适合团队推广。手动方式下先装依赖npm i micro-zeta/micro-app然后在 src/main.js 里初始化import { createApp } from vue import App from ./App.vue import router from ./router import microApp from micro-zeta/micro-app microApp.start() createApp(App).use(router).mount(#app)接着在任意页面组件里找一个位置放上micro-app标签template div classpage micro-app namesub-vue3 urlhttp://localhost:4001/ baseroute/sub-vue3 / /div /template这里name是子应用的唯一标识不能重复url是子应用的访问入口baseroute是子应用在主应用路由体系下挂载的基准路径我一般习惯和主应用的路由前缀保持一致。用插件方式也很简单vue add vue-cli-plugin-micro-app插件运行过程中会问你这个项目是主应用还是子应用选主应用即可。它会自动完成依赖安装和配置注入省掉上面手写的工作。为了让主应用的 devServer 也能在开发环境正常访问子应用还需要确认 vue.config.js 里有跨域响应头。手动方式下我一般写成这样const { defineConfig } require(vue/cli-service) module.exports defineConfig({ transpileDependencies: true, devServer: { port: 3000, headers: { Access-Control-Allow-Origin: *, }, }, })3.3 子应用接入步骤子应用的改造是重头戏也是容易出错的地方。我以 Vue 3 子应用为例演示一下完整流程。创建子应用工程vue create sub-app-vue3然后改动 vue.config.js。这里要特别留意几个配置项缺一个都可能导致子应用在嵌入后白屏或报错const { defineConfig } require(vue/cli-service) const name sub-vue3 module.exports defineConfig({ transpileDependencies: true, publicPath: //localhost:4001/, devServer: { port: 4001, headers: { Access-Control-Allow-Origin: *, }, }, configureWebpack: { output: { libraryTarget: umd, chunkLoadingGlobal: webpackJsonp_${name}, globalObject: window, }, }, })publicPath必须写成完整的协议相对路径不能只写/。因为子应用被嵌入后它的静态资源要从主应用的页面上加载如果 publicPath 是相对路径浏览器会拿主应用的域名去拼资源地址结果就是 404。configureWebpack.output里的chunkLoadingGlobal是给子应用的代码分割用的全局变量名。如果两个子应用都用默认的 webpackJsonp同时运行时会出现变量冲突。把名字改成带项目标识的名称能避免这个经典问题。接下来改 src/router/index.js让子应用在独立开发时用/作为根路径在嵌入时自动适配主应用传入的 baserouteimport { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || /), routes, }) export default routerwindow.__MICRO_APP_BASE_ROUTE__是 micro-app 在子应用运行环境中注入的全局变量它的值就是主应用micro-app标签上的 baseroute 属性。这样同一个子应用代码单独访问和嵌入访问都能正确处理路由。最后是 src/main.js。子应用在微前端环境下需要做一点适配避免和主应用的 Vue 实例互相干扰import { createApp } from vue import App from ./App.vue import router from ./router let app null function mount() { app createApp(App) app.use(router) app.mount(#app) } function unmount() { if (app) { app.unmount() app null } } if (window.__MICRO_APP_BASE_ROUTE__) { window.microApp?.addDataListener((data) { console.log(收到主应用数据, data) }) window.microApp?.dispatch(new CustomEvent(子应用就绪, { detail: { info: sub-vue3 ready }, })) } else { mount() }这里没有让子应用在微前端环境下自己调用 mount而是按照 micro-app 的约定由主应用通过生命周期控制。实际项目里也可以把 mount/unmount 挂到 window 上或者通过框架提供的生命周期事件去触发核心是不让子应用启动两遍。使用插件方式时子应用同样执行vue add vue-cli-plugin-micro-app选择子应用身份插件会自动帮我们完成上面的改动。但我会建议你跑完插件之后还是手动检查一遍 vue.config.js因为不同的 Vue CLI 版本生成的模板略有差异。3.4 启动验证与常见验收点全部配置好之后分别启动子应用和主应用# 子应用目录 npm run serve # 主应用目录 npm run serve正常情况下访问http://localhost:3000主应用页面会渲染出来再去点击进入包含micro-app的页面应该能看到子应用组件正常显示。验证的时候我喜欢按下面这几个点检查子应用独立访问http://localhost:4001是否正常。主应用页面加载子应用控制台有没有跨域报错。子应用内部的路由跳转以及主应用刷新页面后子应用路由是否保持正确。打开 DevTools 的 Elements 面板确认 micro-app 元素内部结构能直接看到子应用的 DOM 是渲染在 custom element 里的。快速切换主应用的不同页面再切回子应用页面确认子应用没有重复挂载、没有报 “Already mounted” 之类的错误。4. 我踩过的坑常见问题与排查技巧记录4.1 刷新、跳转、404 那点事微前端里最经典的路由问题就是“刷新后白屏”或“404”。这个问题的常见根源是主应用和子应用用的路由模式不一致或者说子应用在嵌入模式下的 baseroute 没有配置好。我自己的经验是主应用如果用了 Vue Router 的 history 模式那么子应用也要用 history 模式同时必须把 baseroute 通过window.__MICRO_APP_BASE_ROUTE__合并到子应用路由的 history 里。否则你在子应用内部跳到/sub-page/1刷新浏览器主应用会把http://localhost:3000/sub-page/1当成自己的路由去匹配匹配不到就 404。如果子应用用 hash 模式一般不容易出现刷新 404但 url 会带#观感不太现代。所以我的建议是主应用和子应用统一用 history 模式并且严格约定 baseroute 前缀。还有一类 404 是子应用静态资源加载的 404这个在 3.3 里说过publicPath 没写对是主要原因。检查方式是打开 DevTools 的 Network 面板看加载失败的资源路径是主应用域名还是子应用域名。如果资源请求的 host 是主应用的那就是 publicPath 的问题改成子应用的完整地址即可。4.2 样式隔离与样式丢失的天平默认情况下 micro-app 会给子应用做样式隔离但隔离过强也会带来麻烦。最典型的场景是子应用里用了一个第三方 UI 库比如 Element Plus它的弹窗组件默认会把 DOM 挂到 body 上。因为挂载点不在 shadow DOM 或 scoped 隔离范围内弹窗看起来就“裸奔”了没有应有的样式。遇到这种问题通常有三个处理方向。第一检查子应用是否用了 shadow DOM 模式如果是考虑调整成 scoped 模式因为 scoped 模式对挂在 body 下的元素影响更小。第二把需要全局生效的样式通过主应用统一加载micro-app 提供了globalAssets配置可以把子应用的公共样式注册到全局。第三如果业务上能接受直接在弹窗组件上通过命名空间或者 CSS 变量来做覆盖。这里面我要特别提醒不要为了省事直接关掉沙箱或者样式隔离。隔离一旦关掉主应用和子应用的样式就开始互相影响短期没问题长期会积累出一堆“魔改样式”后面想再开隔离就很痛苦。我一般只在排查时临时关闭定位到问题后立即恢复。4.3 沙箱里的 window 和 document沙箱是 micro-app 的核心能力但它也会带来一些意想不到的坑。最典型的是子应用引用的第三方 SDK内部代码可能直接操作window上的一些属性比如window.top、window.parent、document.body.appendChild这些操作在沙箱代理下可能会出现异常。比如之前接一个地图 SDK初始化时它要往document.body上插一个隐藏的 div 做事件委托。在沙箱环境里这个操作没有正常生效地图组件就一直初始化失败。排查的时候我一度以为是 SDK 版本问题后来发现是沙箱边界的问题。对这种场景micro-app 提供了几种解法。一个是在micro-app元素上配置ignore把某些元素或属性放行出去让它们直接操作真实的 window/document另一个是把 SDK 的脚本通过主应用的globalAssets加载让它跑在主应用的全局环境里不参与子应用沙箱。这里需要特别说明ignore 配置非常精细建议只针对确认要放行的第三方库使用不要一刀切放开。4.4 通信与数据同步的约定主应用和子应用之间的数据通信我刚开始用的时候也踩过坑。micro-app 推荐的做法是通过data属性把主应用的数据传给子应用子应用通过addDataListener监听变化。主应用传递数据micro-app namesub-vue3 urlhttp://localhost:4001/ :datamainData /子应用接收数据window.microApp?.addDataListener((data) { console.log(data:, data) }, true)这里的第二个参数autoTrigger如果传true表示在监听注册后会立即触发一次回传当前数据。我遇到过的问题是子应用监听事件注册得比主应用的首次数据下发还要晚导致初始数据丢失。用autoTrigger参数可以解决这个问题。子应用反过来给主应用传数据有几种方式最简单的是通过dispatch一个自定义事件主应用在micro-app上监听对应事件名即可。这个模式跟浏览器原生事件机制很像上手也快但要注意约定好事件名别随便起名不然沟通成本会很高。4.5 问题排查速查表问题现象常见原因排查思路推荐解法子应用加载白屏跨域配置缺失F12 看 Network 是否有 CORS 报错子应用 devServer 加Access-Control-Allow-Origin子应用资源 404publicPath 配置错误看静态资源请求的 host 是不是主应用publicPath 改为子应用完整地址刷新页面 404路由 baseroute 没对齐检查子应用 history 有没有取__MICRO_APP_BASE_ROUTE__子应用路由 history 拼接 baseroute两个子应用全局变量冲突webpack chunkLoadingGlobal 重名查看控制台 webpackJsonp 相关报错改chunkLoadingGlobal弹窗样式丢失挂载点脱离隔离环境用 standalone 模式对比调整 UI 库挂载方式或用 globalAssets第三方 SDK 异常沙箱隔离导致全局操作失败单独在独立页面测 SDK配置 ignore 或改用 globalAssets 加载监听不到主应用数据监听注册晚于数据下发打印事件执行顺序使用addDataListener的 autoTrigger 参数上面这组问题是这个源码包里最常见的故障点也是我在实际接入过程中遇到最多的情况。如果你拿到的源码版本比较旧或者示例工程和你自己的业务工程差异较大排查时还是要以现象为准不要盲改配置。另外有一点我特别想强调微前端的问题很多时候不是某一个文件写错了而是主应用、子应用、构建配置、路由配置之间没有对齐。排查的时候一定要先确认“当前资源到底是谁在请求、谁在响应”不要一上来就改代码。顺着请求链路走一遍大部分问题都能定位到根因。最后再分享一个小技巧初次跑这个项目的时候不要一上来就把主应用和所有子应用全部启动。先只启动一个子应用和主应用跑通之后再增加第二个。微前端的问题往往是在组合之后才会暴露出来的组合的粒度越小你定位问题的范围就越小。这个源码包里的插件部分我建议你在理解原理之后再按需修改不要当成黑盒直接用这个插件最大的价值其实是让你看清微前端接入的每一步到底做了什么后面出了问题你才知道去哪一行代码里找答案。本文还有配套的精品资源点击获取