Skip to main content

Plugin Package Architecture - Quick Reference

Visual Architecture Diagram​

秉 Plugin Visual Architecture 秉

Data Flow: How Plugin Menus Work​

秉 Data Flow: How Plugin Menus Work 秉

Package Dependency Chain​

秉 Package Dependency Chain 秉

Workspace Structure Example​

my-erp-project/
├── pnpm-workspace.yaml
├── package.json
│
├── frontend/ # Shell Application
│ ├── package.json
│ │ └── dependencies:
│ │ ├── @framework-m/desk: ^0.1.0
│ │ └── @framework-m/plugin-sdk: ^0.1.0
│ ├── vite.config.ts
│ │ └── plugins: [frameworkMPlugin()]
│ ├── src/
│ │ ├── App.tsx # Bootstrap plugins
│ │ └── layout/Sidebar.tsx # Uses usePluginApps()
│ └── dist/ # Build output
│
├── apps/
│ ├── wms/
│ │ ├── backend/ # Python app
│ │ │ └── pyproject.toml
│ │ └── frontend/ # WMS Plugin
│ │ ├── package.json
│ │ │ ├── name: @my-company/wms
│ │ │ ├── private: true
│ │ │ ├── framework-m:
│ │ │ │ └── plugin: ./dist/plugin.config.js
│ │ │ └── dependencies:
│ │ │ ├── @framework-m/desk: ^0.1.0
│ │ │ └── @framework-m/plugin-sdk: ^0.1.0
│ │ ├── plugin.config.ts # Menu, routes, services
│ │ └── src/
│ │ ├── pages/
│ │ ├── components/
│ │ └── services/
│ │
│ └── personnel/
│ ├── backend/
│ └── frontend/ # Personnel Plugin
│ ├── package.json
│ ├── plugin.config.ts
│ └── src/
│
└── libs/ # Framework packages
├── framework-m-desk/ # Published to npm
│ ├── package.json
│ │ └── name: @framework-m/desk
│ └── src/
├── framework-m-plugin-sdk/ # Published to npm
│ ├── package.json
│ │ └── name: @framework-m/plugin-sdk
│ └── src/
└── framework-m-vite-plugin/ # Published to npm
├── package.json
│ └── name: @framework-m/vite-plugin
└── src/

Package Publishing Matrix​

PackageLocationPublished?RegistryVersioning
@framework-m/desklibs/framework-m-desk/✅ YesGitLab npmFollows Python
@framework-m/plugin-sdklibs/framework-m-plugin-sdk/✅ YesGitLab npmIndependent
@framework-m/vite-pluginlibs/framework-m-vite-plugin/✅ YesGitLab npmIndependent
frontend/ (Shell)frontend/❌ No--
@my-company/wmsapps/wms/frontend/❌ NeverWorkspacePrivate
@my-company/personnelapps/personnel/frontend/❌ NeverWorkspacePrivate

When to Use Each Package​

Use @framework-m/desk when:​

  • ✅ Building any Framework M frontend (single or multi-app)
  • ✅ Need data provider for REST API
  • ✅ Need auth provider
  • ✅ Need base DocType components
  • ✅ Want to use Framework M hooks

Use @framework-m/plugin-sdk when:​

  • ✅ Building multi-app project with plugins
  • ✅ Need to access plugin menus/routes
  • ✅ Want to register services
  • ✅ Creating reusable app modules

Use @framework-m/vite-plugin when:​

  • ✅ Setting up shell app build
  • ✅ Need auto-discovery of plugins
  • ✅ Want code-splitting per plugin
  • ✅ Building multi-app project

Don't use plugins if:​

  • ❌ Single-app project (just use @framework-m/desk directly)
  • ❌ Simple customization (add pages directly to frontend/src/)
  • ❌ Prototyping (overhead not worth it)

Migration Path​

Existing Single-App Projects (No Changes Needed)​

// Current setup - Still works!
{
"name": "my-app-frontend",
"dependencies": {
"@refinedev/core": "^5.0.0",
"react": "^19.0.0"
}
}

// Add desk when ready (optional)
{
"dependencies": {
"@framework-m/desk": "^0.1.0", // ← Just add this
"react": "^19.0.0"
}
}

New Multi-App Projects​

// Shell app
{
"name": "frontend",
"dependencies": {
"@framework-m/desk": "^0.1.0",
"@framework-m/plugin-sdk": "^0.1.0"
},
"devDependencies": {
"@framework-m/vite-plugin": "^0.1.0"
}
}

// Plugin app (workspace package)
{
"name": "@my-company/wms",
"private": true,
"framework-m": {
"plugin": "./dist/plugin.config.js"
},
"dependencies": {
"@framework-m/desk": "workspace:^0.1.0",
"@framework-m/plugin-sdk": "workspace:^0.1.0"
}
}

Dependency Architecture: Avoiding Singleton Violations​

When building modular frontends (Shell + Plugins), a critical challenge is managing shared stateful libraries like React, React Query, i18next, and Refine. These libraries often rely on global Singletons or Contexts that must only have one instance in the browser.

The Problem: Singleton Clashes​

If both libs/framework-m-desk and a plugin (apps/wms/frontend) were to include @tanstack/react-query in their dependencies, the browser might load two separate copies of the library. This results in:

  • Context mismatch (plugin cannot find the Shell's QueryClient).
  • Inconsistent state (Shell shows "Loading" while Plugin is "Idle").
  • Inflated bundle sizes.

The Solution: Universal PeerDependencies​

Our core libraries (@framework-m/desk and @framework-m/ui) utilize peerDependencies for all bridge-level orchestration and stateful libraries.

LibraryRole in Desk/UIRelationship
reactComponent EnginepeerDependency
@refinedev/coreData OrchestrationpeerDependency
@tanstack/react-queryState ManagementpeerDependency
i18nextLocalizationpeerDependency

Host vs. Plugin Responsibilities​

1. The Host (Shell) - frontend/​

The Host is the final bundler and the single source of truth for dependency resolution.

  • Responsibility: It must include all peerDependencies required by its linked libraries in its own direct dependencies.
  • Result: It provides the actual implementation of the singleton at runtime.

2. The Core Library - libs/framework-m-desk/​

  • Responsibility: Declares peerDependencies. It says: "I need these to work, but I expect the consumer to provide them."
  • Result: It remains lightweight and avoids forcing its version onto the bundle.

3. The Plugin - apps/*/frontend/​

  • Responsibility: Should follow the same pattern—declare shared libraries as peerDependencies or devDependencies (for testing), but never as final dependencies if they are already provided by the Shell.
  • Result: The Plugin's build output contains only its unique logic, while the Shell handles the global plumbing.

Single Source of Truth Table​

FeatureProvider (Owner)Consumer
Theme@framework-m/ui (Provider)All Plugins
Auth@framework-m/desk (AuthProvider)Shell Layout
API CacheShell (QueryClientProvider)All Hooks
RoutingShell (BrowserRouter)All Plugins

By strictly adhering to this pattern, we ensure that the entire Microservice Frontend (MFE) ecosystem behaves as a single cohesive application while allowing independent development of business modules.