Plugin Package Architecture - Quick Reference
Visual Architecture Diagram
秉
秉
Data Flow: How Plugin Menus Work
秉
秉
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
| Package | Location | Published? | Registry | Versioning |
|---|---|---|---|---|
@framework-m/desk | libs/framework-m-desk/ | ✅ Yes | GitLab npm | Follows Python |
@framework-m/plugin-sdk | libs/framework-m-plugin-sdk/ | ✅ Yes | GitLab npm | Independent |
@framework-m/vite-plugin | libs/framework-m-vite-plugin/ | ✅ Yes | GitLab npm | Independent |
frontend/ (Shell) | frontend/ | ❌ No | - | - |
@my-company/wms | apps/wms/frontend/ | ❌ Never | Workspace | Private |
@my-company/personnel | apps/personnel/frontend/ | ❌ Never | Workspace | Private |
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/deskdirectly) - ❌ 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.
| Library | Role in Desk/UI | Relationship |
|---|---|---|
react | Component Engine | peerDependency |
@refinedev/core | Data Orchestration | peerDependency |
@tanstack/react-query | State Management | peerDependency |
i18next | Localization | peerDependency |
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
peerDependenciesrequired by its linked libraries in its own directdependencies. - 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
peerDependenciesordevDependencies(for testing), but never as finaldependenciesif 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
| Feature | Provider (Owner) | Consumer |
|---|---|---|
| Theme | @framework-m/ui (Provider) | All Plugins |
| Auth | @framework-m/desk (AuthProvider) | Shell Layout |
| API Cache | Shell (QueryClientProvider) | All Hooks |
| Routing | Shell (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.