背景
公司里前端项目不少,我也试过几种组织方式,最后留下来的是这套插件化方案。原因不复杂: 商城、系统设置、报表这类模块边界已经很清楚了,继续全塞在宿主里,后面只会越来越乱。所以我把宿主收回到启动、布局和全局守卫,把业务域拆成
packages/plugin-*,再让插件通过统一协议交出自己的路由、菜单、多语言和初始化逻辑
插件协议怎么定
ts
export interface AdminPlugin {
id: string // 插件唯一标识
name: string // 插件名称
version: string // 插件版本
enabled?: boolean // 默认是否启用
order?: number // 注册顺序
dependsOn?: string[] // 依赖的插件 id
routes?: AdminRouteRecordRaw[] // 插件提供的路由
menus?: MenuConfig[] // 插件提供的菜单
locales?: {
'zh-CN'?: Record<string, any>
en?: Record<string, any>
} // 插件自己的语言包
install?: (app: App, context: PluginContext) => void | Promise<void> // 初始化钩子
}
我比较在意的一点是,插件要把自己的边界说清楚,而不是嘴上说自己是"独立模块",代码里还在不停伸手改宿主,所以后面的插件架构要设计好,大概做了以下功能点
项目预览





插件功能点
- 📦 定了一套统一的插件协议,把
id、name、version、enabled、order、dependsOn、routes、menus、locales、install这些字段先收口 - 🔢 插件注册排序,用
order控制接入顺序 - 🟢 默认启用控制,通过
enabled配置插件初始状态 - 🔗 依赖声明和依赖校验,避免插件在缺少前置模块时硬挂进来
- 🛡️ 冲突检测,主要拦重复插件
id、路由path/name和菜单path - 🧭 插件路由注册,把页面入口统一挂到宿主路由系统
- 🗂️ 插件菜单收集,把各业务插件的导航配置汇总到宿主
- 🌐 语言包合并,让插件自己的
locales能注入到宿主 - 🪝
install钩子,方便插件做自己的初始化 - 📋 运行时插件清单,宿主能拿到当前注册过的插件信息
- 🔌 插件启停能力,支持在运行时打开或关闭某个插件
- 💾 启停状态持久化到
localStorage - 🔄 刷新后恢复插件状态,宿主重新引导时继续使用本地启停结果
一个插件长什么样
ts
const reportPlugin: AdminPlugin = {
id: 'plugin-report',
name: '报表插件',
version: '1.0.0',
order: 20,
dependsOn: ['plugin-shop'],
routes: [
{
path: '/report/list',
name: 'ReportList',
component: () => import('./views/ReportList.vue'),
meta: {
title: 'report.list',
layout: 'default',
permissions: ['report.read']
}
},
{
path: '/report/detail/:id',
name: 'ReportDetail',
component: () => import('./views/ReportDetail.vue'),
meta: {
title: 'report.detail',
layout: 'default',
permissions: ['report.read'],
activeMenu: '/report/list',
noCache: true,
hidden: true
}
}
],
menus: [
{
path: '/report',
title: 'report.title',
icon: 'chart',
children: [
{
path: '/report/list',
title: 'report.list'
}
]
}
]
}
这段代码最关键的地方,不是"能跑起来",而是它把自己该交给宿主的信息一次性带齐了。宿主看到这个插件,基本就知道要把什么东西接进去。
最后
做到这里之后,我最直接的感受就是,新增一个业务模块时,接入方式终于稳定下来了。对我来说,这就够了。至少这个中后台后面继续长的时候,不会再回到什么都往宿主里塞的状态。