主应用搭建
初始化
首先创建一个主应用,我这里使用 vite + vue3 来作为主应用

- 依次输入项目名称:base-main
- 选择项目框架:vue3
- 使用 TypeScript
启动项目,看到如下界面表示项目初始化成功

引入无界
无界官方文档
我们主应用时 vue3,所以可以引入 wujie-vue3 ,其他版本的参考文档
修改 main.ts,挂载 WujieVue,方便在全局直接使用 WujieVue 组件
1 2 3 4 5 6 7 8 9
| import {createApp} from 'vue' import './style.css' import App from './App.vue' import WujieVue from "wujie-vue3";
const app = createApp(App) app.use(WujieVue) app.mount('#app')
|
创建子应用
子应用我准备了两个
React应用搭建
全局安装 create-react-app
1
| npm install -g create-react-app
|
创建一个新的React项目
1
| create-react-app react-sub
|
引入React路由
1
| yarn add react-router-dom@6
|
新建 src/router/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { createBrowserRouter } from 'react-router-dom'; import Layout from '../components/Layout'; import Home from '../pages/Home'; import About from '../pages/About';
const router = createBrowserRouter([ { path: '/', element: <Layout />, children: [ { index: true, element: <Home /> }, { path: 'about', element: <About /> } ] } ]);
export default router;
|
修改 index.js
1 2 3 4 5 6 7 8 9 10 11
| import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css';
import {RouterProvider} from 'react-router-dom'; import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <RouterProvider router={router}></RouterProvider> );
|
Layout 组件内容
react-sub\src\components\Layout.jsx
1 2 3 4 5 6 7 8 9 10
| import React from "react"; import { Outlet } from "react-router-dom";
const Layout = () => { return ( <Outlet /> ); };
export default Layout;
|
Home 组件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from "react"; import styles from "../styles/pages/Home.module.css";
const Home = () => { const byWujie = window.__POWERED_BY_WUJIE__;
return ( <> <div className={styles["home-container"]}> <h1>欢迎来到React子应用</h1> { byWujie ? <div>处于无界微前端</div> : <div>处于独立运行</div> } </div> </> ); };
export default Home;
|
此时启动项目查看

主应用引入React子应用
在主应用中修改 App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <script setup lang="ts"></script>
<template> <div class="home-container"> <h1>欢迎来到主应用</h1> <div class="react-sub-container"> <h2>React子应用</h2> <WujieVue name="react-sub" url="http://localhost:3000" :sync="true"/> </div> </div> </template>
<style scoped> .home-container { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; } .home-container h1 { font-size: 24px; font-weight: bold; } .react-sub-container { width: 50%; height: 50%; border: 1px solid #000; margin-top: 20px; } .react-sub-container h2 { font-size: 18px; font-weight: bold; } </style>
|
直接使用 WujieVue 标签,传入一个 name 名称,子应用的URL,:sync=”true” 表示同步子应用的路由,页面刷新仍可以保持子应用的地址
此时再启动主应用,查看页面,可以发现 React 项目中已经判断出处于微前端系统中

应用通信
子应用给主应用通信
我们实现子应用设置网页标题同步给主应用,主应用来改变网页标题
React项目添加如下代码
1 2 3
| useEffect(() => { window.$wujie?.bus.$emit("changeTitle", "React子应用"); }, []);
|
然后主应用中监听 changeTitle 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { createApp } from "vue"; import "./style.css"; import App from "./App.vue"; import WujieVue from "wujie-vue3"; const { bus } = WujieVue;
const app = createApp(App); app.use(WujieVue); app.mount("#app");
bus.$on("changeTitle", function (title: string) { console.log("主应用发送的消息", title); document.title = title; });
|
主应用给子应用通信
主应用添加代码,通过 bus.$emit 发送 changeSubTitle 事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <script setup lang="ts"> import WujieVue from "wujie-vue3"; const { bus } = WujieVue; import { ref } from "vue";
const subTitle = ref(""); const changeSubTitle = () => { bus.$emit("changeSubTitle", subTitle.value); }; </script>
<template> <div class="home-container"> <h1>欢迎来到主应用</h1> <div class="react-sub-container"> <h2>React子应用</h2> <div> <input v-model="subTitle" /> <button @click="changeSubTitle">修改子应用标题</button> </div> <hr/> <WujieVue name="react-sub" url="http://localhost:3000" :sync="true" /> </div> </div> </template>
|
子应用监听 changeSubTitle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React from "react"; import styles from "../styles/pages/Home.module.css"; import { useEffect, useState } from "react";
const Home = () => { const byWujie = window.__POWERED_BY_WUJIE__; const [subTitle, setSubTitle] = useState("默认的子应用标题");
useEffect(() => { window.$wujie?.bus.$emit("changeTitle", "React子应用");
window.$wujie?.bus.$on("changeSubTitle", (title) => { setSubTitle(title); }); }, []);
return ( <div className={styles["home-container"]}> <h1>{subTitle}</h1> {byWujie ? <div>处于无界微前端</div> : <div>处于独立运行</div>} </div> ); };
export default Home;
|
查看效果

应用嵌套
多个子应用之间也可以互相嵌套引用,例如在vue2子应用中嵌套React应用
首先在 vue2 应用中添加无界依赖
然后再 main.js 中全局挂载 WujieVue 组件
1 2 3
| import WujieVue from 'wujie-vue2'
Vue.use(WujieVue)
|
接着就可以像在主应用中一样,在需要的地方直接添加其他子应用的地址
1 2 3 4 5 6 7 8 9
| <WujieVue name="react-sub" url="http://localhost:3001" :sync="true" width="100%" height="100%" :props="wujieProps" />
|
1 2 3 4 5 6 7 8 9
| data(){ return{ wujieProps: { token: localStorage.getItem('sysToken'), locale: localStorage.getItem('locale'), path: '/workbenches', }, } }
|
我们可以通过props给子应用传递参数,在子应用中,通过下面的方式获取参数
例如下面的案例,我们在React子应用的请求拦截器上从props获取vue2应用传递过来的token,然后再每次请求时都携带这个token,实现鉴权信息共享
1 2 3 4 5 6 7 8 9 10 11 12 13
| request.interceptors.request.use( (config) => { config.headers["Authorization"] = window.$wujie?.props.token || ""; config.headers["X-Locale"] = window.$wujie?.props.locale || ""; config.headers["X-TraceId"] = "132456"; return config; }, (error) => { return Promise.reject(error); } );
|
一次启动多个项目
首先在主应用安装chalk,chalk的作用是美化控制台输出的一个工具
然后在主应用的根目录添加 start.js 脚本文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
| const { exec } = require("child_process"); const path = require("path"); const chalk = require("chalk");
const mainPath = __dirname; const reactPath = path.join(__dirname, "../react-sub");
const apps = [ { name: "主应用", path: mainPath, port: 5173, command: "yarn dev", }, { name: "React子应用", path: reactPath, port: 3001, command: "yarn start", }, ];
const box = { topLeft: "╭", topRight: "╮", bottomLeft: "╰", bottomRight: "╯", horizontal: "─", vertical: "│", };
const createLine = (width = 50) => box.horizontal.repeat(width);
const createBox = (text, width = 50) => { const padding = Math.max(0, width - text.length - 2); const leftPad = Math.floor(padding / 2); const rightPad = padding - leftPad; return [ `${box.topLeft}${createLine(width)}${box.topRight}`, `${box.vertical}${" ".repeat(leftPad)}${text}${" ".repeat(rightPad)}${ box.vertical }`, `${box.bottomLeft}${createLine(width)}${box.bottomRight}`, ].join("\n"); };
const checkAndInstall = (projectPath) => { return new Promise((resolve, reject) => { if (!require("fs").existsSync(path.join(projectPath, "node_modules"))) { console.log( chalk.yellow(`📦 正在安装 ${path.basename(projectPath)} 依赖...`) ); exec("yarn install", { cwd: projectPath }, (error) => { if (error) { reject(error); return; } resolve(); }); } else { resolve(); } }); };
const startProject = (app) => { return new Promise((resolve) => { console.log(chalk.cyan(`🚀 正在启动 ${app.name}...`)); const child = exec(app.command, { cwd: app.path });
let isStarted = false;
child.stdout.on("data", (data) => { if ( data.includes("App running at:") || data.includes("Local: http://") || data.includes("localhost:") || data.includes("successfully") || data.includes("ready in") ) { if (!isStarted) { isStarted = true; console.log( chalk.green(`✨ ${app.name}已启动: http://localhost:${app.port}`) ); resolve(); } } else if (data.includes("ERROR")) { console.log(chalk.red(`❌ [${app.name}] ${data}`)); } });
child.stderr.on("data", (data) => { if (data.includes("ERROR")) { console.error(chalk.red(`❌ [${app.name}] ${data}`)); } });
setTimeout(() => { if (!isStarted) { console.log(chalk.yellow(`⚠️ ${app.name}启动时间较长,但仍在继续...`)); } }, 10000); }); };
async function startAll() { console.clear(); console.log( "\n" + chalk.blue(createBox(" 🌟 无界微前端启动程序 ", 48)) + "\n" );
try { console.log(chalk.blue("📋 [1/2] 检查依赖...")); await Promise.all(apps.map((app) => checkAndInstall(app.path)));
console.log(chalk.blue("\n🚀 [2/2] 启动应用...\n")); await Promise.all(apps.map((app) => startProject(app)));
console.log("\n" + chalk.green(createBox(" ✅ 所有应用启动成功 ", 48))); console.log(chalk.white("\n📌 应用访问地址:")); console.log(chalk.gray(createLine(48)));
apps.forEach((app) => { console.log( chalk.cyan(` ${app.name.padEnd(12)}`) + chalk.yellow(`➜ http://localhost:${app.port}`) ); });
console.log(chalk.gray(createLine(48))); console.log( chalk.gray("\n💡 提示:主应用已集成所有子应用,访问主应用即可\n") ); } catch (error) { console.error(chalk.red("\n❌ 启动失败:"), error); process.exit(1); } }
startAll();
|
我们只需要维护好 apps 数组中的启动命令即可
然后在主应用的 package.json 中添加一个启动命令
1 2 3
| "scripts": { "start": "node start.js" },
|
现在运行启动命令,就可以同时把多个项目启动起来
