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.