跪拜 Guibai
← Back to the summary

A Plugin Protocol That Stops Admin Panels from Turning into Monoliths

Background

The company has quite a few front-end projects, and I've tried several organizational approaches. In the end, this plugin-based solution is the one that stuck. The reason is straightforward: modules like the mall, system settings, and reports already have clear boundaries. If you keep stuffing everything into the host, it will only get messier. So, I pulled the host back to just startup, layout, and global guards, split the business domains into packages/plugin-*, and then let the plugins hand over their routes, menus, multi-language, and initialization logic through a unified protocol.

Documentation Demo Source Code

How the Plugin Protocol is Defined

export interface AdminPlugin {
  id: string // Unique plugin identifier
  name: string // Plugin name
  version: string // Plugin version
  enabled?: boolean // Whether it is enabled by default
  order?: number // Registration order
  dependsOn?: string[] // IDs of plugins it depends on
  routes?: AdminRouteRecordRaw[] // Routes provided by the plugin
  menus?: MenuConfig[] // Menus provided by the plugin
  locales?: {
    'zh-CN'?: Record<string, any>
    en?: Record<string, any>
  } // The plugin's own language pack
  install?: (app: App, context: PluginContext) => void | Promise<void> // Initialization hook
}

One thing I care about a lot is that a plugin must clearly state its own boundaries, rather than verbally claiming to be an "independent module" while its code keeps reaching out to modify the host. So, the subsequent plugin architecture needed to be well-designed, and I roughly implemented the following features.

Project Preview

image.png

image.png

image.png

image.png

image.png

Plugin Features

What a Plugin Looks Like

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'
        }
      ]
    }
  ]
}

The most critical part of this code isn't that "it can run," but that it brings all the information the host needs in one go. When the host sees this plugin, it basically knows what to integrate.

Final Thoughts

After reaching this point, my most direct feeling is that the integration method for adding a new business module has finally stabilized. For me, that's enough. At least when this admin panel continues to grow, it won't fall back into the state of stuffing everything into the host.

Comments

Top 3 of 9 from juejin.cn, machine-translated. The original thread is authoritative.

用户545784512369

I'd like to ask, will this project be open-sourced? I've been free lately and happened to come across your blog, and I'd like to learn from it [rose][rose][rose]

知航驿站

It's already open-sourced, wow

用户545784512369  → 知航驿站

The pm-web-admin-next repository isn't open-sourced

稀土小新人

Is it similar to an npm package?

知航驿站

Yeah, it can be made into a package

围的围

It's not easy to extend, and it tends to overcomplicate simple tasks.