关于 monorepo 的概念本文不再赘述,至于为什么选用 pnpm 作为包管理器而不用 npm 或者 yarn ,参考Pnpm: 最先进的包管理器
pnpm 不仅有优化磁盘空间快速等优势,在这里主要pnpm 有一个 --filter属性
在menorepo(多依赖包仓库) 环境中指定只安装或更新某个依赖包到某个项目中
一.环境准备
-
Node.js:
v16.0.0+(Garfish 对 Node 版本有基础要求) -
pnpm:
v7.0.0+(monorepo 核心工具)
1.全局安装pnpm
npm install -g pnpm
二.项目初始化
1.创建一个项目文件夹初始化package.json
如:
pnpm mkdir garfish-pnpm-monorepo
cd到这个文件夹中执行命令
npm init -y
2. 配置 pnpm monorepo 核心文件
在根目录创建 pnpm-workspace.yaml,指定工作区范围(所有子应用放在 packages 目录下):
# pnpm-workspace.yaml packages:
# 主应用
- 'packages/main-app'
# 子应用(可扩展多个)
- 'packages/vue-app'
- 'packages/react-app'
# 可选:公共工具包
# - 'packages/utils'
三、创建主应用(main-app)
主应用是微前端的 “容器”,负责加载所有子应用,这里选择 Vue3 + Vite(Garfish 对 Vite 适配性好)。
1. 创建主应用目录并初始化
# 创建 packages 目录(存放所有应用)
mkdir -p packages/main-app
# 进入主应用目录,用 vite 创建 Vue3 项目
cd packages/main-app
pnpm create vite@latest . --template vue
2. 安装主应用依赖
# 回到根目录,安装主应用依赖(pnpm 会自动关联 workspace)
cd ../../
pnpm install -F main-app # -F 表示指定 workspace 包
3.安装 Garfish 核心依赖
在根目录安装 Garfish(-Dw 表示开发依赖 + 工作区根共享,所有子应用也能复用):
pnpm add garfish @garfish/router -Dw
//安装 garfish还可以使用的命令,-W 表示安装在全局的 workspace 里, 这样所有 package 都可以共用该文件
pnpm add garfish @garfish/router -WD
4. 配置主应用(核心步骤)
修改主应用代码,集成 Garfish 并注册子应用:
步骤 4.1:修改主应用目录结构
在 packages/main-app/src 下创建 micro-app.js(专门管理 Garfish 配置),并修改 main.js 引入。
步骤 4.2:编写 Garfish 配置文件(micro-app.js)
// packages/main-app/src/micro-app.js
import Garfish from 'garfish';
import { createRouter } from '@garfish/router';
// 初始化 Garfish
export function initGarfish() {
// 1. 创建 Garfish 路由(与主应用路由联动)
const garfishRouter = createRouter();
// 2. 配置并启动
Garfish Garfish.run({
// 主应用根节点(子应用会挂载到这个节点下)
domGetter: '#micro-app-container',
// 开启沙箱(隔离子应用的样式/JS)
sandbox: {
open: true, // 开启沙箱
strictIsolation: true, // 严格隔离(样式不泄漏)
},
// 注册子应用列表(核心!)
apps: [
{
// 子应用唯一标识
name: 'vue-app',
// 子应用访问地址(本地开发地址,生产需替换为打包后的地址)
entry: 'http://localhost:5174',
// 子应用路由匹配规则(访问 /vue-app 时加载该子应用)
activeWhen: '/vue-app',
// 子应用基础路径(需与子应用配置一致)
basename: '/vue-app',
},
{
name: 'react-app',
entry: 'http://localhost:5175',
activeWhen: '/react-app',
basename: '/react-app',
},
],
// 挂载 Garfish 路由到主应用
router: garfishRouter,
// 全局生命周期(可选)
lifecycle: { beforeMount: (app) => {
console.log(`子应用 ${app.name} 即将挂载`);
},
mounted: (app) => {
console.log(`子应用 ${app.name} 挂载完成`);
},
},
});
}
步骤 4.3:修改主应用入口文件(main.js)
// packages/main-app/src/main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { initGarfish } from './micro-app.js'
const app = createApp(App)
app.mount('#app')
// 初始化 Garfish(主应用挂载后启动微前端)
initGarfish()
步骤 4.4:修改主应用页面(App.vue)
添加子应用挂载容器和导航链接:
<!-- packages/main-app/src/App.vue -->
<template>
<div id="app">
<!-- 主应用导航 -->
<nav style="padding: 20px; background: #f5f5f5;">
<a href="/vue-app" style="margin-right: 20px;">Vue 子应用</a>
<a href="/react-app">React 子应用</a>
</nav>
<!-- 子应用挂载容器(需与 Garfish 配置的 domGetter 一致) -->
<div id="micro-app-container" style="margin: 20px;"></div>
</div>
</template>
<script setup></script>
<style scoped></style>
步骤 4.5:修改主应用 Vite 配置(vite.config.js)
确保主应用端口固定(避免冲突),并允许跨域(子应用加载需要):
// packages/main-app/vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 5173, // 主应用固定端口
cors: true, // 允许跨域(必须!子应用加载主应用资源需要)
},
})
四、创建子应用(以 Vue 子应用为例,React 同理)
1. 创建 Vue 子应用
# 回到根目录 cd ../../
# 创建 Vue 子应用目录
mkdir -p packages/vue-app
# 进入子应用目录,用 vite 创建 Vue3 项目
cd packages/vue-app
pnpm create vite@latest . --template vue
2. 安装子应用依赖
# 回到根目录安装
cd ../../
pnpm install -F vue-app
// 还可以使用的命令
pnpm install --filter vue-app // --filter 表示要作用到哪个子项目
3. 适配 Garfish 改造子应用(核心)
子应用需要适配微前端的 “挂载 / 卸载” 生命周期,且需配置基础路径、跨域等。
步骤 3.1:修改子应用 Vite 配置(vite.config.js)
// packages/vue-app/vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
// 关键:设置子应用基础路径(需与主应用 Garfish 配置的 basename 一致)
base: '/vue-app/',
server: {
port: 5174, // 子应用固定端口(与主应用配置的 entry 端口一致)
cors: true, // 允许跨域(主应用加载子应用需要)
origin: 'http://localhost:5174', // 显式指定 origin(避免 Garfish 解析错误)
},
build: {
// 打包后资源路径(避免子应用资源 404)
assetsDir: 'assets',
// 输出目录(可选,统一打包到根 dist 下)
outDir: '../../dist/vue-app',
},
})
步骤 3.2:修改子应用入口文件(main.js)
适配 Garfish 生命周期,支持 “独立运行” 和 “微前端运行” 两种模式:
// packages/vue-app/src/main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 定义挂载函数(供 Garfish 调用)
function render(props = {}) {
const { container } = props;
// 微前端模式:挂载到主应用指定容器;独立模式:挂载到 #app
const app = createApp(App);
app.mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时(直接访问子应用)
if (!window.__GARFISH__) {
render();
}
// 暴露 Garfish 所需的生命周期函数(必须!)
export const provider = {
// 子应用挂载时调用
render,
// 子应用卸载时调用(清理资源)
destroy({ container }) {
const app = document.querySelector(container ? container.querySelector('#app') : '#app');
if (app) app.innerHTML = '';
},
};
步骤 3.3:修改子应用根组件(App.vue)
确保子应用有独立的挂载节点 #app:
<!-- packages/vue-app/src/App.vue -->
<template>
<div id="app">
<h1>Vue 子应用</h1>
<p>当前模式:{{ isGarfish ? '微前端' : '独立运行' }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const isGarfish = ref(false)
onMounted(() => {
// 判断是否在 Garfish 微前端环境中
isGarfish.value = !!window.__GARFISH__
})
</script>
<style scoped></style>
4. 创建 React 子应用(同理适配)
# 回到根目录 cd ../../
mkdir -p packages/react-app
cd packages/react-app
pnpm create vite@latest . --template react
# 安装依赖
cd ../../
pnpm install -F react-app
React 子应用适配关键配置
vite.config.js(同 Vue 子应用,端口改为 5175,base 改为/react-app/);src/main.jsx(适配 Garfish 生命周期):
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
function render(props = {}) {
const { container } = props;
const root = ReactDOM.createRoot(
container ? container.querySelector('#root') : document.getElementById('root')
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
// 独立运行
if (!window.__GARFISH__) { render(); }
// 暴露生命周期
export const provider = {
render,
destroy({ container }) {
const root = container ? container.querySelector('#root') : document.getElementById('root');
if (root) ReactDOM.unmountComponentAtNode(root);
},
};
五、统一管理运行脚本
在根目录的 package.json 中添加脚本,方便一键启动所有应用:
{
"scripts": {
"dev:main": "pnpm -F main-app dev",
"dev:vue": "pnpm -F vue-app dev",
"dev:react": "pnpm -F react-app dev",
// 跨平台并行启动(需先安装 concurrently)
"dev": "concurrently \"pnpm dev:main\" \"pnpm dev:vue\" \"pnpm dev:react\""
}
}
安装 concurrently(跨平台并行运行命令):
pnpm add concurrently -Dw
六、测试运行
# 根目录执行
pnpm dev
启动成功后,访问主应用地址 http://localhost:5173:
- 点击 “Vue 子应用”,页面会加载
http://localhost:5174的 Vue 子应用; - 点击 “React 子应用”,页面会加载
http://localhost:5175的 React 子应用; - 直接访问
http://localhost:5174/http://localhost:5175,子应用也能独立运行。
七、生产环境打包(可选)
在根 package.json 添加打包脚本:
json
{
"scripts": {
"build:main": "pnpm -F main-app build",
"build:vue": "pnpm -F vue-app build",
"build:react": "pnpm -F react-app build",
"build": "pnpm run build:main && pnpm run build:vue && pnpm run build:react"
}
}
执行打包:
pnpm build
打包后,所有应用的产物会输出到对应的 dist 目录,只需将主应用和子应用的静态资源部署到服务器,并修改主应用 Garfish 配置中的 entry 为生产环境地址即可。
总结
- pnpm monorepo 核心:通过
pnpm-workspace.yaml管理工作区,所有应用放在packages目录,依赖统一管理,避免重复安装; - Garfish 主应用关键:配置
domGetter(子应用挂载容器)、apps(子应用列表)、开启跨域和沙箱隔离; - 子应用适配关键:设置
base基础路径、暴露provider生命周期函数、开启跨域,支持 “独立运行” 和 “微前端运行” 双模式。