107 lines
5.3 KiB
Markdown
107 lines
5.3 KiB
Markdown
# Monorepo Refactoring Plan: Decoupling Designer, Engines, and Materials
|
|
|
|
This document outlines the step-by-step plan to refactor the current `form-designer` monolithic structure into a clean, decoupled Monorepo architecture.
|
|
|
|
**Core Philosophy:** Separation of Concerns. The Designer acts as a shell, Engines enforce logic, and Materials provide UI implementation.
|
|
|
|
---
|
|
|
|
## 🏗 Architecture Overview
|
|
|
|
| Layer | Package | Responsibility |
|
|
| :---------------- | :----------------------- | :--------------------------------------------------------------------------------------------------- |
|
|
| **Foundation** | `@lingshu/types` | Shared interfaces, DSL definitions, and constants. |
|
|
| | `@lingshu/core-utils` | Pure JS utilities (framework agnostic). |
|
|
| **Logic Engines** | `@lingshu/user-script` | Runtime for standard User Scripts (Hooks, Actions) and Rule Engine (Low-code logical linkage). |
|
|
| **Materials** | `@lingshu/widget-pc` | PC Vue components, widget configurations, and property panels. |
|
|
| | `@lingshu/widget-mobile` | Mobile Vue components (Future). |
|
|
| **Host/Editor** | `@lingshu/form-designer` | The visual editor shell. Implements adapters for engines and exposes extension points for materials. |
|
|
| **Application** | `apps/lcdp` | The assembler entry point that wires everything together. |
|
|
|
|
---
|
|
|
|
## 📅 Phased Execution Plan
|
|
|
|
The refactoring is divided into 3 autonomous phases. **At the end of each phase, the project MUST be buildable and runnable.**
|
|
|
|
### Phase 1: Foundation Construction (Infrastructure)
|
|
|
|
**Goal**: Establish a shared language (`types`) to break circular dependencies and prepare for code migration.
|
|
|
|
- **Boundary**: No functional logic changes. Only moving definitions and constants.
|
|
- **Deliverable**: Codebase uses `@lingshu/types` for shared entities instead of relative imports.
|
|
- **Verification**:
|
|
1. `pnpm build` passes for all packages.
|
|
2. `npm run dev` in `form-designer` works exactly as before.
|
|
|
|
**Tasks:**
|
|
|
|
1. **Define Shared Types**: Extract `WidgetSchema`, `UserScriptContext`, `RuntimeEventApiBO` to `@lingshu/types`.
|
|
2. **Define Injection Keys**: Extract `USER_SCRIPT_EVENT_BUS_KEY` to `@lingshu/types`.
|
|
3. **Refactor Imports**: Update `form-designer` and `widget-pc` to import from `@lingshu/types`.
|
|
|
|
---
|
|
|
|
### Phase 2: Logic Engine Extraction (The Brain)
|
|
|
|
**Goal**: Isolate the "User Script" and "Rule Engine" logic into a standalone package that doesn't depend on UI stores directly.
|
|
|
|
- **Boundary**: `packages/user-script` contains pure logic. Access to Store/API is done via **Dependency Injection**.
|
|
- **Deliverable**: A new `@lingshu/user-script` package. `form-designer` initializes this engine by injecting its internal state adapters.
|
|
- **Verification**:
|
|
1. User Scripts (e.g., `onClick` logs) work in the Designer preview.
|
|
2. Rule Linkages (e.g., input A changes -> input B hides) work in the Designer preview.
|
|
|
|
**Tasks:**
|
|
|
|
1. **Create Package**: Set up `packages/user-script` workspace.
|
|
2. **Migrate Logic**: Move `src/config/user-script` (Definitions) and `src/utils/user-script` (Runtime) to the new package.
|
|
3. **Refactor for DI**: Transform functions that directly import `PageStore` to accept `context` or `adapter` arguments.
|
|
4. **Wire Up**: In `form-designer/main.ts` (or boot sequence), call `initScriptEngine(adapters)` from the new package.
|
|
|
|
---
|
|
|
|
### Phase 3: Material Decoupling (The Body)
|
|
|
|
**Goal**: Move all PC-specific widget configurations and implementations out of the Designer.
|
|
|
|
- **Boundary**: `form-designer` becomes unaware of specific widgets. It loads whatever is passed to its `.use()` method.
|
|
- **Deliverable**: `packages/widget-pc` contains all material definitions (`config/*`) and components components.
|
|
- **Verification**:
|
|
1. Designer starts empty initially (theoretically).
|
|
2. After injecting `WidgetPC`, the left palette shows PC components.
|
|
3. Dragging components to canvas works correctly.
|
|
|
|
**Tasks:**
|
|
|
|
1. **Move Configs**: Relocate `form-designer/src/config` (Materials, Settings) to `packages/widget-pc/src/config`.
|
|
2. **Refactor Hooks**: Move specific widget hooks (like `components/anchor/useCompEvent`) to `widget-pc`.
|
|
3. **Plugin Architecture**: create a `registerWidgets` export in `widget-pc`.
|
|
4. **Injection**: In the App Entry/Main, import `registerWidgets` and pass it to the Designer instance.
|
|
|
|
---
|
|
|
|
## 🔄 Dependency Injection Strategy (Crucial)
|
|
|
|
To achieve decoupling, we use standard **Dependency Injection**.
|
|
|
|
**1. Service Injection (Logic Layer)**
|
|
Instead of importing `axios` or `pinia` in `user-script`:
|
|
|
|
```typescript
|
|
// packages/user-script/src/index.ts
|
|
export function initEngine(context: IScriptContext) {
|
|
// context.network.request(...)
|
|
// context.store.getFieldValue(...)
|
|
}
|
|
```
|
|
|
|
**2. Event Injection (Material Layer)**
|
|
Widgets use `inject` to communicate with the engine, without importing it.
|
|
|
|
```typescript
|
|
// packages/widget-pc/src/hooks/useCompEvent.ts
|
|
const bus = inject(USER_SCRIPT_EVENT_BUS_KEY);
|
|
bus.emit("CLIENT_EVENT", payload);
|
|
```
|