create-lego-one 2.0.12 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +150 -15
- package/dist/index.cjs.map +1 -1
- package/package.json +1 -1
- package/template/.cursor/rules/rules.mdc +639 -0
- package/template/.dockerignore +58 -0
- package/template/.env.example +18 -0
- package/template/.eslintignore +5 -0
- package/template/.eslintrc.js +28 -0
- package/template/.prettierignore +6 -0
- package/template/.prettierrc +11 -0
- package/template/CLAUDE.md +634 -0
- package/template/Dockerfile +67 -0
- package/template/PROMPT.md +457 -0
- package/template/README.md +325 -0
- package/template/docker-compose.yml +48 -0
- package/template/docker-entrypoint.sh +23 -0
- package/template/docs/checkpoints/.template.md +64 -0
- package/template/docs/checkpoints/framework/01-infrastructure-setup.md +132 -0
- package/template/docs/checkpoints/framework/02-pocketbase-setup.md +155 -0
- package/template/docs/checkpoints/framework/03-host-kernel.md +170 -0
- package/template/docs/checkpoints/framework/04-auth-system.md +163 -0
- package/template/docs/checkpoints/framework/phase-05-multitenancy-rbac.md +223 -0
- package/template/docs/checkpoints/framework/phase-06-ui-components.md +260 -0
- package/template/docs/checkpoints/framework/phase-07-communication-system.md +276 -0
- package/template/docs/checkpoints/framework/phase-08-plugin-system.md +91 -0
- package/template/docs/checkpoints/framework/phase-09-dashboard-plugin.md +111 -0
- package/template/docs/checkpoints/framework/phase-10-todo-plugin.md +169 -0
- package/template/docs/checkpoints/framework/phase-11-testing.md +264 -0
- package/template/docs/checkpoints/framework/phase-12-deployment.md +294 -0
- package/template/docs/checkpoints/framework/phase-13-documentation.md +312 -0
- package/template/docs/framework/plans/00-index.md +164 -0
- package/template/docs/framework/plans/01-infrastructure-setup.md +855 -0
- package/template/docs/framework/plans/02-pocketbase-setup.md +1374 -0
- package/template/docs/framework/plans/03-host-kernel.md +1518 -0
- package/template/docs/framework/plans/04-auth-system.md +1466 -0
- package/template/docs/framework/plans/05-multitenancy-rbac.md +1527 -0
- package/template/docs/framework/plans/06-ui-components.md +1478 -0
- package/template/docs/framework/plans/07-communication-system.md +1106 -0
- package/template/docs/framework/plans/08-plugin-system.md +1179 -0
- package/template/docs/framework/plans/09-dashboard-plugin.md +1137 -0
- package/template/docs/framework/plans/10-todo-plugin.md +1343 -0
- package/template/docs/framework/plans/11-testing.md +935 -0
- package/template/docs/framework/plans/12-deployment.md +896 -0
- package/template/docs/framework/prompts/0-boilerplate-modernjs.md +151 -0
- package/template/docs/framework/research/00-modernjs-audit.md +488 -0
- package/template/docs/framework/research/01-system-blueprint.md +721 -0
- package/template/docs/framework/research/02-data-migration-protocol.md +699 -0
- package/template/docs/framework/research/03-host-setup.md +714 -0
- package/template/docs/framework/research/04-plugin-architecture.md +645 -0
- package/template/docs/framework/research/05-slot-injection-pattern.md +671 -0
- package/template/docs/framework/research/06-cli-strategy.md +615 -0
- package/template/docs/framework/research/07-deployment.md +629 -0
- package/template/docs/framework/research/README.md +282 -0
- package/template/docs/framework/setup/00-index.md +210 -0
- package/template/docs/framework/setup/01-framework-structure.md +308 -0
- package/template/docs/framework/setup/02-development-workflow.md +405 -0
- package/template/docs/framework/setup/03-environment-setup.md +215 -0
- package/template/docs/framework/setup/04-kernel-architecture.md +499 -0
- package/template/docs/framework/setup/05-plugin-system.md +620 -0
- package/template/docs/framework/setup/06-communication-patterns.md +451 -0
- package/template/docs/framework/setup/07-plugin-development.md +582 -0
- package/template/docs/framework/setup/08-component-library.md +658 -0
- package/template/docs/framework/setup/09-data-integration.md +609 -0
- package/template/docs/framework/setup/10-auth-rbac.md +497 -0
- package/template/docs/framework/setup/11-hooks-api.md +393 -0
- package/template/docs/framework/setup/12-components-api.md +665 -0
- package/template/docs/framework/setup/13-deployment-guide.md +566 -0
- package/template/docs/framework/setup/README.md +548 -0
- package/template/host/package.json +1 -1
- package/template/nginx.conf +72 -0
- package/template/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
- package/template/pocketbase/CHANGELOG.md +911 -0
- package/template/pocketbase/LICENSE.md +17 -0
- package/template/scripts/create-plugin.js +221 -0
- package/template/scripts/deploy.sh +56 -0
- package/template/tsconfig.base.json +26 -0
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
# System Blueprint: Host/Consumer Architecture
|
|
2
|
+
|
|
3
|
+
**Project:** Lego-One (Modern.js SaaS OS)
|
|
4
|
+
**Document:** 01 - System Blueprint
|
|
5
|
+
**Status:** Research Phase
|
|
6
|
+
|
|
7
|
+
## Executive Summary
|
|
8
|
+
|
|
9
|
+
This document provides the high-level architectural blueprint for the Lego-One Microkernel SaaS OS, defining the relationship between the Host (Kernel) and Consumers (Plugins) using Modern.js + Garfish.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Architecture Overview
|
|
14
|
+
|
|
15
|
+
### 1.1 The Microkernel Pattern
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ Lego-One SaaS OS │
|
|
20
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
21
|
+
│ │
|
|
22
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
23
|
+
│ │ KERNEL (Host App) │ │
|
|
24
|
+
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
|
25
|
+
│ │ │ Core Infrastructure (Never changes per plugin) │ │ │
|
|
26
|
+
│ │ │ - Authentication (PocketBase SDK) │ │ │
|
|
27
|
+
│ │ │ - Global Layout (Sidebar, Topbar, Toasts) │ │ │
|
|
28
|
+
│ │ │ - Router (React Router v6 via Modern.js) │ │ │
|
|
29
|
+
│ │ │ - State Bus (Zustand + Garfish Channel) │ │ │
|
|
30
|
+
│ │ │ - Plugin Loader (saas.config.ts reader) │ │ │
|
|
31
|
+
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
|
32
|
+
│ │ │ │
|
|
33
|
+
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
|
34
|
+
│ │ │ Shared Dependency Layer │ │ │
|
|
35
|
+
│ │ │ - React, ReactDOM (via Garfish) │ │ │
|
|
36
|
+
│ │ │ - Zustand Store Bridge (__LEGO_KERNEL_STATE__) │ │ │
|
|
37
|
+
│ │ │ - UI Primitives (Radix, Shadcn components) │ │ │
|
|
38
|
+
│ │ │ - TanStack Query v5 (shared QueryClient) │ │ │
|
|
39
|
+
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
|
40
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
41
|
+
│ │
|
|
42
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
43
|
+
│ │ PLUGIN BOUNDARY │ │
|
|
44
|
+
│ │ (Garfish.loadApp() - Dynamic Loading Interface) │ │
|
|
45
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
46
|
+
│ │
|
|
47
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
48
|
+
│ │ Plugin A │ │ Plugin B │ │ Plugin C │ │ Plugin N │ │
|
|
49
|
+
│ │ (Dashboard) │ │ (Loan Calc) │ │(Inventory) │ │ (Stripe) │ │
|
|
50
|
+
│ │ │ │ │ │ │ │ │ │
|
|
51
|
+
│ │ - Routes │ │ - Routes │ │ - Routes │ │ - Routes │ │
|
|
52
|
+
│ │ - Features │ │ - Features │ │ - Features │ │ - Features │ │
|
|
53
|
+
│ │ - UI │ │ - UI │ │ - UI │ │ - UI │ │
|
|
54
|
+
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
55
|
+
│ │ │ │ │ │
|
|
56
|
+
│ └────────────────┴────────────────┴────────────────┘ │
|
|
57
|
+
│ Slot Injection (UI Extension) │
|
|
58
|
+
│ │
|
|
59
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 1.2 Key Design Principles
|
|
63
|
+
|
|
64
|
+
| Principle | Implementation | Benefit |
|
|
65
|
+
|-----------|----------------|---------|
|
|
66
|
+
| **Kernel Stability** | Core infra never changes per plugin | Predictable updates, no breaking changes |
|
|
67
|
+
| **Plugin Isolation** | Each plugin is a separate Modern.js app | Independent deployment, team autonomy |
|
|
68
|
+
| **Shared Dependencies** | Garfish externals + Zustand bridge | Reduced bundle size, consistent state |
|
|
69
|
+
| **Slot Injection** | Global registry pattern | Plugins extend host UI without modification |
|
|
70
|
+
| **Bus Communication** | Garfish.channel + custom events | Decoupled messaging between apps |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 2. Host (Kernel) Structure
|
|
75
|
+
|
|
76
|
+
### 2.1 Directory Structure
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
host/
|
|
80
|
+
├── modern.config.ts # Host Modern.js config
|
|
81
|
+
├── modern.runtime.ts # Sub-app registration
|
|
82
|
+
├── package.json
|
|
83
|
+
├── tsconfig.json
|
|
84
|
+
├── tailwind.config.js # Global Tailwind config
|
|
85
|
+
├── postcss.config.js # Global PostCSS config
|
|
86
|
+
├── src/
|
|
87
|
+
│ ├── bootstrap.tsx # App entry, shared state initialization
|
|
88
|
+
│ ├── main.tsx # ReactDOM.render
|
|
89
|
+
│ ├── kernel/
|
|
90
|
+
│ │ ├── shared-state-bridge.ts # Zustand store for plugins
|
|
91
|
+
│ │ ├── slot-registry.ts # UI slot injection system
|
|
92
|
+
│ │ ├── communication-bus.ts # Garfish.channel wrapper
|
|
93
|
+
│ │ └── plugin-loader.ts # saas.config.ts reader
|
|
94
|
+
│ ├── providers/
|
|
95
|
+
│ │ ├── QueryProvider.tsx # TanStack Query provider
|
|
96
|
+
│ │ ├── ThemeProvider.tsx # Shadcn theme provider
|
|
97
|
+
│ │ └── PocketBaseProvider.tsx # PocketBase SDK provider
|
|
98
|
+
│ ├── layout/
|
|
99
|
+
│ │ ├── AppLayout.tsx # Main layout shell
|
|
100
|
+
│ │ ├── Sidebar.tsx # Navigation sidebar
|
|
101
|
+
│ │ ├── Topbar.tsx # Header with user menu
|
|
102
|
+
│ │ └── SlotOutlet.tsx # Renders injected plugin UI
|
|
103
|
+
│ ├── routes/
|
|
104
|
+
│ │ ├── layout.tsx # Root layout wrapper
|
|
105
|
+
│ │ ├── page.tsx # Home/landing page
|
|
106
|
+
│ │ ├── login/
|
|
107
|
+
│ │ │ └── page.tsx # Auth pages
|
|
108
|
+
│ │ ├── dashboard/
|
|
109
|
+
│ │ │ └── $.tsx # Fuzzy route for dashboard plugin
|
|
110
|
+
│ │ ├── apps/
|
|
111
|
+
│ │ │ ├── loan/
|
|
112
|
+
│ │ │ │ └── $.tsx # Fuzzy route for loan plugin
|
|
113
|
+
│ │ │ ├── inventory/
|
|
114
|
+
│ │ │ │ └── $.tsx # Fuzzy route for inventory plugin
|
|
115
|
+
│ │ │ └── settings/
|
|
116
|
+
│ │ │ └── page.tsx # Global settings page
|
|
117
|
+
│ │ └── _404.tsx # 404 page
|
|
118
|
+
│ ├── components/
|
|
119
|
+
│ │ ├── ui/ # Shadcn components (shared)
|
|
120
|
+
│ │ ├── auth/
|
|
121
|
+
│ │ └── common/
|
|
122
|
+
│ ├── hooks/
|
|
123
|
+
│ │ ├── useAuth.ts # Authentication hook
|
|
124
|
+
│ │ ├── usePluginLoader.ts # Plugin loading logic
|
|
125
|
+
│ │ └── useSlotRegistry.ts # Slot registration hook
|
|
126
|
+
│ ├── lib/
|
|
127
|
+
│ │ ├── pocketbase.ts # PocketBase client init
|
|
128
|
+
│ │ └── utils.ts
|
|
129
|
+
│ └── types/
|
|
130
|
+
│ ├── kernel.ts # Kernel type definitions
|
|
131
|
+
│ └── plugin.ts # Plugin interface definitions
|
|
132
|
+
└── saas.config.ts # Plugin manifest (user-editable)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 2.2 Core Kernel Components
|
|
136
|
+
|
|
137
|
+
#### 2.2.1 Plugin Loader
|
|
138
|
+
|
|
139
|
+
**File:** `src/kernel/plugin-loader.ts`
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { AppsOptions } from '@modern-js/plugin-garfish';
|
|
143
|
+
|
|
144
|
+
interface SaaSConfig {
|
|
145
|
+
plugins: {
|
|
146
|
+
name: string;
|
|
147
|
+
entry: string;
|
|
148
|
+
activeWhen: string;
|
|
149
|
+
enabled: boolean;
|
|
150
|
+
}[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function loadSaaSConfig(): Promise<AppsOptions[]> {
|
|
154
|
+
// Import user's saas.config.ts
|
|
155
|
+
const config: SaaSConfig = await import('/saas.config.ts');
|
|
156
|
+
|
|
157
|
+
// Filter enabled plugins and format for Garfish
|
|
158
|
+
return config.plugins
|
|
159
|
+
.filter((plugin) => plugin.enabled)
|
|
160
|
+
.map(({ name, entry, activeWhen }) => ({
|
|
161
|
+
name,
|
|
162
|
+
entry,
|
|
163
|
+
activeWhen,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### 2.2.2 Slot Registry
|
|
169
|
+
|
|
170
|
+
**File:** `src/kernel/slot-registry.ts`
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { reactive } from '@modern-js/runtime';
|
|
174
|
+
|
|
175
|
+
export type SlotName = 'sidebar:nav' | 'topbar:actions' | 'topbar:menu';
|
|
176
|
+
|
|
177
|
+
export interface SlotItem {
|
|
178
|
+
id: string;
|
|
179
|
+
pluginName: string;
|
|
180
|
+
component: React.ComponentType;
|
|
181
|
+
order: number;
|
|
182
|
+
isVisible: () => boolean;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface SlotRegistry {
|
|
186
|
+
slots: Record<SlotName, SlotItem[]>;
|
|
187
|
+
register: (slot: SlotName, item: SlotItem) => void;
|
|
188
|
+
unregister: (slot: SlotName, id: string) => void;
|
|
189
|
+
getItems: (slot: SlotName) => SlotItem[];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Global singleton for slot registration
|
|
193
|
+
export const slotRegistry: SlotRegistry = reactive({
|
|
194
|
+
slots: {
|
|
195
|
+
'sidebar:nav': [],
|
|
196
|
+
'topbar:actions': [],
|
|
197
|
+
'topbar:menu': [],
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
register(slot: SlotName, item: SlotItem) {
|
|
201
|
+
this.slots[slot].push(item);
|
|
202
|
+
this.slots[slot].sort((a, b) => a.order - b.order);
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
unregister(slot: SlotName, id: string) {
|
|
206
|
+
this.slots[slot] = this.slots[slot].filter((item) => item.id !== id);
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
getItems(slot: SlotName) {
|
|
210
|
+
return this.slots[slot].filter((item) => item.isVisible());
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### 2.2.3 Communication Bus
|
|
216
|
+
|
|
217
|
+
**File:** `src/kernel/communication-bus.ts`
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { Garfish } from 'garfish';
|
|
221
|
+
|
|
222
|
+
// Event types for inter-app communication
|
|
223
|
+
export type BusEvent =
|
|
224
|
+
| { type: 'auth:login'; payload: { userId: string; email: string } }
|
|
225
|
+
| { type: 'auth:logout' }
|
|
226
|
+
| { type: 'toast:show'; payload: { message: string; variant: 'success' | 'error' } }
|
|
227
|
+
| { type: 'theme:change'; payload: { theme: 'light' | 'dark' } }
|
|
228
|
+
| { type: 'navigation:goto'; payload: { path: string } };
|
|
229
|
+
|
|
230
|
+
// Wrapper around Garfish.channel for type safety
|
|
231
|
+
export class KernelBus {
|
|
232
|
+
private channel = Garfish.channel;
|
|
233
|
+
|
|
234
|
+
// Subscribe to events
|
|
235
|
+
on(eventType: BusEvent['type'], handler: (payload: any) => void) {
|
|
236
|
+
this.channel.on(eventType, handler);
|
|
237
|
+
return () => this.channel.off(eventType, handler);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Emit events
|
|
241
|
+
emit(event: BusEvent) {
|
|
242
|
+
this.channel.emit(event.type, event.payload);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const kernelBus = new KernelBus();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 3. Plugin (Sub-App) Structure
|
|
252
|
+
|
|
253
|
+
### 3.1 Directory Structure
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
packages/plugins/@lego/plugin-loan-calculator/
|
|
257
|
+
├── modern.config.ts # Plugin Modern.js config
|
|
258
|
+
├── package.json
|
|
259
|
+
├── tsconfig.json
|
|
260
|
+
├── plugin.config.ts # Plugin metadata (exports, slots, etc.)
|
|
261
|
+
├── src/
|
|
262
|
+
│ ├── App.tsx # Plugin entry (receives basename)
|
|
263
|
+
│ ├── main.tsx # Plugin ReactDOM.render
|
|
264
|
+
│ ├── pages/
|
|
265
|
+
│ │ ├── IndexPage.tsx
|
|
266
|
+
│ │ └── SettingsPage.tsx
|
|
267
|
+
│ ├── components/
|
|
268
|
+
│ │ ├── LoanCalculator.tsx
|
|
269
|
+
│ │ └── ResultDisplay.tsx
|
|
270
|
+
│ ├── hooks/
|
|
271
|
+
│ │ ├── useKernelState.ts # Access shared kernel state
|
|
272
|
+
│ │ └── useKernelBus.ts # Emit/receive bus events
|
|
273
|
+
│ ├── lib/
|
|
274
|
+
│ │ └── pocketbase-helpers.ts
|
|
275
|
+
│ └── types/
|
|
276
|
+
│ └── plugin.ts
|
|
277
|
+
└── migrations/
|
|
278
|
+
└── init.ts # PocketBase collection initialization
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 3.2 Plugin Configuration
|
|
282
|
+
|
|
283
|
+
**File:** `plugin.config.ts`
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { definePluginConfig } from '@lego/kernel/plugin-config';
|
|
287
|
+
|
|
288
|
+
export default definePluginConfig({
|
|
289
|
+
name: '@lego/plugin-loan-calculator',
|
|
290
|
+
version: '1.0.0',
|
|
291
|
+
displayName: 'Loan Calculator',
|
|
292
|
+
description: 'Calculate loan payments and amortization schedules',
|
|
293
|
+
|
|
294
|
+
// Plugin exports available to host
|
|
295
|
+
exports: {
|
|
296
|
+
components: {
|
|
297
|
+
LoanWidget: './src/components/LoanWidget',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// Slot injections
|
|
302
|
+
slots: {
|
|
303
|
+
'sidebar:nav': {
|
|
304
|
+
component: './src/components/SidebarLink',
|
|
305
|
+
order: 100,
|
|
306
|
+
isVisible: () => {
|
|
307
|
+
const state = window.__LEGO_KERNEL_STATE__;
|
|
308
|
+
return state?.useGlobalKernelState.getState().currentUser?.role === 'admin';
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
'topbar:actions': {
|
|
312
|
+
component: './src/components/QuickCalcButton',
|
|
313
|
+
order: 50,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
// PocketBase collections required
|
|
318
|
+
collections: [
|
|
319
|
+
{
|
|
320
|
+
name: 'loan_calculations',
|
|
321
|
+
schema: './migrations/init.ts',
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 3.3 Plugin Entry Point
|
|
328
|
+
|
|
329
|
+
**File:** `src/App.tsx`
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { BrowserRouter, Routes, Route } from '@modern-js/runtime/router';
|
|
333
|
+
import { useGarfish } from '@garfish/hooks';
|
|
334
|
+
import { useEffect } from 'react';
|
|
335
|
+
import pluginConfig from '../plugin.config';
|
|
336
|
+
import { registerSlots } from './lib/slot-registration';
|
|
337
|
+
|
|
338
|
+
export default function PluginApp({ basename }: { basename: string }) {
|
|
339
|
+
const { appInfo } = useGarfish();
|
|
340
|
+
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
// Register slot injections with host
|
|
343
|
+
registerSlots(pluginConfig.slots);
|
|
344
|
+
}, []);
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<BrowserRouter basename={basename}>
|
|
348
|
+
<Routes>
|
|
349
|
+
<Route index element={<IndexPage />} />
|
|
350
|
+
<Route path="settings" element={<SettingsPage />} />
|
|
351
|
+
</Routes>
|
|
352
|
+
</BrowserRouter>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 4. Communication Patterns
|
|
360
|
+
|
|
361
|
+
### 4.1 Host → Plugin: Props Passing
|
|
362
|
+
|
|
363
|
+
Modern.js/Garfish automatically passes the `basename` prop to plugins. For custom props:
|
|
364
|
+
|
|
365
|
+
**Host Route Wrapper:**
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// src/routes/apps/loan/$.tsx
|
|
369
|
+
import { useModuleApps } from '@modern-js/plugin-garfish/runtime';
|
|
370
|
+
import { useAuth } from '@/hooks/useAuth';
|
|
371
|
+
|
|
372
|
+
export default function LoanRoute() {
|
|
373
|
+
const { LoanCalculator } = useModuleApps();
|
|
374
|
+
const { user } = useAuth();
|
|
375
|
+
|
|
376
|
+
// Pass custom props to plugin
|
|
377
|
+
return (
|
|
378
|
+
<LoanCalculator
|
|
379
|
+
currentUser={user}
|
|
380
|
+
permissions={user.permissions}
|
|
381
|
+
/>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### 4.2 Plugin → Host: Bus Events
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// Plugin component
|
|
390
|
+
import { useKernelBus } from './hooks/useKernelBus';
|
|
391
|
+
|
|
392
|
+
export function LoanCalculator() {
|
|
393
|
+
const bus = useKernelBus();
|
|
394
|
+
|
|
395
|
+
const handleCalculation = () => {
|
|
396
|
+
// Trigger toast in host app
|
|
397
|
+
bus.emit({
|
|
398
|
+
type: 'toast:show',
|
|
399
|
+
payload: { message: 'Calculation complete!', variant: 'success' },
|
|
400
|
+
});
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
return <button onClick={handleCalculation}>Calculate</button>;
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### 4.3 Host → Plugin: State Sharing
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Plugin component
|
|
411
|
+
import { useKernelState } from './hooks/useKernelState';
|
|
412
|
+
|
|
413
|
+
export function LoanCalculator() {
|
|
414
|
+
const kernelState = useKernelState();
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<div>
|
|
418
|
+
<p>Theme: {kernelState?.theme}</p>
|
|
419
|
+
<p>User: {kernelState?.currentUser?.email}</p>
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## 5. Data Flow Diagrams
|
|
428
|
+
|
|
429
|
+
### 5.1 Application Bootstrap Flow
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
┌─────────────┐
|
|
433
|
+
│ Browser │
|
|
434
|
+
│ loads host │
|
|
435
|
+
└──────┬──────┘
|
|
436
|
+
│
|
|
437
|
+
▼
|
|
438
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
439
|
+
│ 1. host/main.tsx renders App.tsx │
|
|
440
|
+
└──────┬──────────────────────────────────────────────────────┘
|
|
441
|
+
│
|
|
442
|
+
▼
|
|
443
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
444
|
+
│ 2. bootstrap.tsx initializes: │
|
|
445
|
+
│ - PocketBase client │
|
|
446
|
+
│ - Zustand store (registers to window) │
|
|
447
|
+
│ - Garfish.setExternal() for shared deps │
|
|
448
|
+
│ - Reads saas.config.ts │
|
|
449
|
+
└──────┬──────────────────────────────────────────────────────┘
|
|
450
|
+
│
|
|
451
|
+
▼
|
|
452
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
453
|
+
│ 3. modern.runtime.ts registers plugins with Garfish │
|
|
454
|
+
│ - Each plugin gets name, entry, activeWhen │
|
|
455
|
+
└──────┬──────────────────────────────────────────────────────┘
|
|
456
|
+
│
|
|
457
|
+
▼
|
|
458
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
459
|
+
│ 4. Router matches route → Garfish.loadApp() │
|
|
460
|
+
│ - Plugin JavaScript loaded dynamically │
|
|
461
|
+
│ - Plugin ReactDOM.render into its container │
|
|
462
|
+
└──────┬──────────────────────────────────────────────────────┘
|
|
463
|
+
│
|
|
464
|
+
▼
|
|
465
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
466
|
+
│ 5. Plugin initializes: │
|
|
467
|
+
│ - Receives basename from host │
|
|
468
|
+
│ - Registers slot injections │
|
|
469
|
+
│ - Subscribes to kernel bus events │
|
|
470
|
+
│ - Accesses shared state via window bridge │
|
|
471
|
+
└─────────────────────────────────────────────────────────────┘
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 5.2 Inter-Plugin Communication Flow
|
|
475
|
+
|
|
476
|
+
```
|
|
477
|
+
┌──────────────────┐ ┌──────────────────┐
|
|
478
|
+
│ Plugin A (Loan) │ │ Plugin B (Inv.) │
|
|
479
|
+
│ │ │ │
|
|
480
|
+
│ User clicks │ │ │
|
|
481
|
+
│ "Sync Data" │ │ │
|
|
482
|
+
└────────┬─────────┘ └────────┬─────────┘
|
|
483
|
+
│ │
|
|
484
|
+
│ kernelBus.emit({ │ kernelBus.on(
|
|
485
|
+
│ type: 'data:sync', │ 'data:sync',
|
|
486
|
+
│ payload: { ... } │ handler
|
|
487
|
+
│ }) │ )
|
|
488
|
+
│ │
|
|
489
|
+
└───────────────┬───────────────────────┘
|
|
490
|
+
│
|
|
491
|
+
▼
|
|
492
|
+
┌─────────────────────┐
|
|
493
|
+
│ Garfish.channel │
|
|
494
|
+
│ (Event Bus) │
|
|
495
|
+
└─────────────────────┘
|
|
496
|
+
│
|
|
497
|
+
▼
|
|
498
|
+
┌─────────────────────┐
|
|
499
|
+
│ Host (Kernel) │
|
|
500
|
+
│ - Can also listen │
|
|
501
|
+
│ - Can log events │
|
|
502
|
+
│ - Can transform │
|
|
503
|
+
└─────────────────────┘
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 6. Update Strategy (Git Template Approach)
|
|
509
|
+
|
|
510
|
+
Since the user selected **Git Template (Clone & Fork)** approach:
|
|
511
|
+
|
|
512
|
+
### 6.1 Distribution Model
|
|
513
|
+
|
|
514
|
+
```
|
|
515
|
+
lego-one/ (Template Repository)
|
|
516
|
+
├── host/ (Kernel - users fork this)
|
|
517
|
+
├── packages/
|
|
518
|
+
│ └── plugins/
|
|
519
|
+
│ ├── @lego/ (Official plugins)
|
|
520
|
+
│ │ ├── plugin-dashboard
|
|
521
|
+
│ │ ├── plugin-auth
|
|
522
|
+
│ │ └── plugin-billing
|
|
523
|
+
│ └── @custom/ (User plugins - users create here)
|
|
524
|
+
│ └── my-custom-plugin
|
|
525
|
+
└── docs/
|
|
526
|
+
|
|
527
|
+
user-saas-app/ (User forks/clones lego-one)
|
|
528
|
+
├── host/ (Modified kernel - can merge updates)
|
|
529
|
+
├── packages/
|
|
530
|
+
│ └── plugins/
|
|
531
|
+
│ ├── @lego/ (Submodule or git subtree for updates)
|
|
532
|
+
│ └── @custom/ (User's private plugins)
|
|
533
|
+
└── saas.config.ts (User's configuration)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### 6.2 Update Workflow
|
|
537
|
+
|
|
538
|
+
1. **Initial Setup:** User clones `lego-one` template
|
|
539
|
+
2. **Custom Development:** User creates plugins in `@custom/` namespace
|
|
540
|
+
3. **Kernel Update:** User merges changes from `lego-one` upstream
|
|
541
|
+
4. **Plugin Update:** User pulls `@lego/*` plugins via git subtree/submodule
|
|
542
|
+
|
|
543
|
+
**Benefits:**
|
|
544
|
+
- Users control when to update
|
|
545
|
+
- No breaking API changes (kernel is stable)
|
|
546
|
+
- Custom plugins isolated from official updates
|
|
547
|
+
- Can cherry-pick specific changes
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## 7. Development vs Production Deployment
|
|
552
|
+
|
|
553
|
+
### 7.1 Key Principle
|
|
554
|
+
|
|
555
|
+
**Plugins are features of ONE application, not separate applications.**
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
559
|
+
│ DEVELOPMENT │
|
|
560
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
561
|
+
│ │
|
|
562
|
+
│ Host : http://localhost:8080 │
|
|
563
|
+
│ Plugin A : http://localhost:3001 ← Separate dev server │
|
|
564
|
+
│ Plugin B : http://localhost:3002 ← Separate dev server │
|
|
565
|
+
│ Plugin C : http://localhost:3003 ← Separate dev server │
|
|
566
|
+
│ │
|
|
567
|
+
│ WHY? Developers can work on plugins independently │
|
|
568
|
+
│ │
|
|
569
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
570
|
+
|
|
571
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
572
|
+
│ PRODUCTION │
|
|
573
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
574
|
+
│ │
|
|
575
|
+
│ Single Server: https://app.yourdomain.com │
|
|
576
|
+
│ │
|
|
577
|
+
│ All plugins BUNDLED into host's dist/ │
|
|
578
|
+
│ ├── plugins/dashboard/main.js │
|
|
579
|
+
│ ├── plugins/loan-calculator/main.js │
|
|
580
|
+
│ └── plugins/inventory/main.js │
|
|
581
|
+
│ │
|
|
582
|
+
│ WHY? One app, one deployment, simpler operations │
|
|
583
|
+
│ │
|
|
584
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### 7.2 Runtime Configuration
|
|
588
|
+
|
|
589
|
+
**File:** `host/src/modern.runtime.ts`
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
import { defineRuntimeConfig } from '@modern-js/runtime';
|
|
593
|
+
|
|
594
|
+
const isDev = import.meta.env.MODE === 'development';
|
|
595
|
+
|
|
596
|
+
export default defineRuntimeConfig({
|
|
597
|
+
masterApp: {
|
|
598
|
+
apps: [
|
|
599
|
+
{
|
|
600
|
+
name: '@lego/plugin-dashboard',
|
|
601
|
+
// Dev: Separate server │ Prod: Bundled in host
|
|
602
|
+
entry: isDev
|
|
603
|
+
? 'http://localhost:3001'
|
|
604
|
+
: () => import('@lego/plugin-dashboard'),
|
|
605
|
+
activeWhen: '/dashboard',
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
name: '@lego/plugin-loan-calculator',
|
|
609
|
+
entry: isDev
|
|
610
|
+
? 'http://localhost:3002'
|
|
611
|
+
: () => import('@lego/plugin-loan-calculator'),
|
|
612
|
+
activeWhen: '/apps/loan',
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
name: '@lego/plugin-inventory',
|
|
616
|
+
entry: isDev
|
|
617
|
+
? 'http://localhost:3003'
|
|
618
|
+
: () => import('@lego/plugin-inventory'),
|
|
619
|
+
activeWhen: '/apps/inventory',
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### 7.3 Development Workflow
|
|
627
|
+
|
|
628
|
+
**Terminal 1 - Start Host:**
|
|
629
|
+
```bash
|
|
630
|
+
cd host
|
|
631
|
+
pnpm run dev
|
|
632
|
+
# Runs on http://localhost:8080
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Terminal 2 - Start Plugin (optional):**
|
|
636
|
+
```bash
|
|
637
|
+
cd packages/plugins/@lego/plugin-dashboard
|
|
638
|
+
pnpm run dev
|
|
639
|
+
# Runs on http://localhost:8081
|
|
640
|
+
|
|
641
|
+
# OR use root script to start all at once:
|
|
642
|
+
pnpm run dev:all
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Browser:** Navigate to `http://localhost:8080/dashboard`
|
|
646
|
+
|
|
647
|
+
### 7.4 Production Build
|
|
648
|
+
|
|
649
|
+
```bash
|
|
650
|
+
# Single build command from root
|
|
651
|
+
pnpm run build
|
|
652
|
+
|
|
653
|
+
# Output:
|
|
654
|
+
host/dist/
|
|
655
|
+
├── html/
|
|
656
|
+
│ └── index.html
|
|
657
|
+
├── static/
|
|
658
|
+
│ ├── js/
|
|
659
|
+
│ │ ├── main-[hash].js # Host bundle
|
|
660
|
+
│ │ ├── vendor-[hash].js # Shared deps
|
|
661
|
+
│ │ └── plugins/
|
|
662
|
+
│ │ ├── dashboard-[hash].js # Plugin A bundled
|
|
663
|
+
│ │ ├── loan-calc-[hash].js # Plugin B bundled
|
|
664
|
+
│ │ └── inventory-[hash].js # Plugin C bundled
|
|
665
|
+
│ └── css/
|
|
666
|
+
│ └── main-[hash].css
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
**Deploy:** Single `host/dist/` folder to any static host (Vercel, Netlify, S3, etc.)
|
|
670
|
+
|
|
671
|
+
### 7.5 Why This Hybrid Approach?
|
|
672
|
+
|
|
673
|
+
| Aspect | Development | Production |
|
|
674
|
+
|--------|-------------|------------|
|
|
675
|
+
| **Servers** | Separate ports for each plugin | Single server |
|
|
676
|
+
| **Hot Reload** | Plugin changes reload independently | N/A (built) |
|
|
677
|
+
| **Build Time** | Fast (only what changes) | Full build |
|
|
678
|
+
| **Team Workflow** | Work on plugin in isolation | N/A |
|
|
679
|
+
| **Operations** | N/A | Simple (one deploy) |
|
|
680
|
+
| **Cost** | N/A | Lower (one server) |
|
|
681
|
+
|
|
682
|
+
**The Best of Both Worlds:**
|
|
683
|
+
- ✅ Dev experience: Independent plugin development
|
|
684
|
+
- ✅ Production experience: Single app deployment
|
|
685
|
+
- ✅ Build tooling: Modern.js handles bundling automatically
|
|
686
|
+
- ✅ No complexity: No separate deployments for plugins
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
## 8. Technology Stack Mapping
|
|
691
|
+
|
|
692
|
+
| Layer | Technology | Purpose |
|
|
693
|
+
|-------|-----------|---------|
|
|
694
|
+
| **Framework** | Modern.js | Host framework, build tooling |
|
|
695
|
+
| **MFE Engine** | Garfish (via plugin) | Plugin loading, sandboxing |
|
|
696
|
+
| **Bundler** | Rspack | Fast builds, Rust-based |
|
|
697
|
+
| **UI** | React 18 | Component rendering |
|
|
698
|
+
| **Router** | React Router v6 | Routing (integrated in Modern.js) |
|
|
699
|
+
| **State** | Zustand | Global state, shared via bridge |
|
|
700
|
+
| **Server State** | TanStack Query v5 | API caching, mutations |
|
|
701
|
+
| **Styling** | Tailwind CSS | Utility-first CSS |
|
|
702
|
+
| **Components** | Shadcn UI + Radix | Component primitives |
|
|
703
|
+
| **Backend** | PocketBase | BaaS, auth, database |
|
|
704
|
+
| **Language** | TypeScript | Type safety |
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## 9. Next Steps
|
|
709
|
+
|
|
710
|
+
1. **`02-data-migration-protocol.md`**: PocketBase auto-schema synchronization
|
|
711
|
+
2. **`03-host-setup.md`**: Step-by-step host initialization
|
|
712
|
+
3. **`04-plugin-architecture.md`**: Plugin development patterns
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## References
|
|
717
|
+
|
|
718
|
+
- [Modern.js Micro Frontend Guide](https://modernjs.dev/guides/topic-detail/micro-frontend/c02-development)
|
|
719
|
+
- [Garfish GitHub Repository](https://github.com/web-infra-dev/garfish)
|
|
720
|
+
- [Garfish.setExternal API](https://www.garfishjs.org/api/setExternal.html)
|
|
721
|
+
- [PocketBase JS Collections](https://pocketbase.io/docs/js-collections/)
|