packages/plugin-editor/
├── src/
│ ├── MonacoImpl.tsx # Heavy implementation (imports Monaco)
│ └── index.tsx # Lazy wrapper with React.lazy()
├── dist/
│ ├── index.js (0.19 KB) # Entry point - LIGHT
│ ├── MonacoImpl-*.js (19.42 KB) # Heavy chunk - LAZY LOADED
│ ├── index-*.js (22.42 KB) # Supporting chunk
│ └── index.umd.cjs (30.37 KB) # UMD bundle
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md
Key Files:
MonacoImpl.tsx: Containsimport Editor from '@monaco-editor/react'index.tsx: ContainsReact.lazy(() => import('./MonacoImpl'))
packages/plugin-charts/
├── src/
│ ├── ChartImpl.tsx # Heavy implementation (imports Recharts)
│ └── index.tsx # Lazy wrapper with React.lazy()
├── dist/
│ ├── index.js (0.19 KB) # Entry point - LIGHT
│ ├── ChartImpl-*.js (541.17 KB) # Heavy chunk - LAZY LOADED
│ ├── index-*.js (22.38 KB) # Supporting chunk
│ └── index.umd.cjs (393.20 KB) # UMD bundle
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md
Key Files:
ChartImpl.tsx: Containsimport { BarChart, ... } from 'recharts'index.tsx: ContainsReact.lazy(() => import('./ChartImpl'))
When the playground imports both plugins, they remain as separate chunks:
apps/playground/dist/assets/
├── index-CyDHUpwF.js (2.2 MB) # Main bundle
├── MonacoImpl-DCiwKyYW-D65z0X-D.js ( 15 KB) # Monaco - SEPARATE
├── ChartImpl-BJBP1UnW-DO38vX_d.js (340 KB) # Recharts - SEPARATE
└── index-dgFB6nSI.css ( 99 KB) # Styles
-
App Startup (Initial Load):
// apps/playground/src/App.tsx import '@object-ui/plugin-editor'; // Loads ~200 bytes import '@object-ui/plugin-charts'; // Loads ~200 bytes
- ✅ Only the entry points are loaded (~400 bytes total)
- ❌ Monaco Editor is NOT loaded yet
- ❌ Recharts is NOT loaded yet
-
Component Registration:
// Inside @object-ui/plugin-editor/src/index.tsx ComponentRegistry.register('code-editor', CodeEditorRenderer);
- Components are registered with the registry
- But the heavy implementation is NOT executed yet
-
Schema Rendering (When Component Used):
const schema = { type: 'code-editor', value: '...' }; <SchemaRenderer schema={schema} />
- SchemaRenderer looks up 'code-editor' in registry
- Finds
CodeEditorRenderer CodeEditorRenderercontains<Suspense><LazyComponent /></Suspense>- React.lazy triggers dynamic import of
MonacoImpl.tsx - ✅ NOW the Monaco chunk is fetched from the server
- Shows skeleton while loading
- Renders Monaco Editor once loaded
Initial Page Load:
GET /index.html 200 OK
GET /assets/index-CyDHUpwF.js 200 OK (Main bundle)
GET /assets/index-dgFB6nSI.css 200 OK (Styles)
# Monaco and Recharts chunks NOT requested
When Code Editor Component Renders:
GET /assets/MonacoImpl-DCiwKyYW-D65z0X-D.js 200 OK (15 KB)
# Loaded on demand!
When Chart Component Renders:
GET /assets/ChartImpl-BJBP1UnW-DO38vX_d.js 200 OK (340 KB)
# Loaded on demand!
Initial Load:
- Main bundle: 2.2 MB
- Monaco bundled: + 0.015 MB
- Recharts bundled: + 0.340 MB
────────────────────────────
TOTAL INITIAL: ~2.6 MB ❌ Heavy!
Initial Load:
- Main bundle: 2.2 MB
- Plugin entries: + 0.0004 MB (400 bytes)
────────────────────────────
TOTAL INITIAL: ~2.2 MB ✅ Lighter!
On-Demand (when components render):
- Monaco chunk: 0.015 MB (if code-editor used)
- Recharts chunk: 0.340 MB (if chart-bar used)
Savings: ~355 KB (13.5%) on initial load for apps that don't use these components on every page.
$ ls -lh packages/plugin-editor/dist/
-rw-rw-r-- 1 runner runner 197 bytes index.js # Entry (light)
-rw-rw-r-- 1 runner runner 19K MonacoImpl-*.js # Heavy chunk
✅ PASS: Heavy chunk is separate from entry point$ ls -lh apps/playground/dist/assets/ | grep -E "(Monaco|Chart)"
-rw-rw-r-- 1 runner runner 15K MonacoImpl-*.js
-rw-rw-r-- 1 runner runner 340K ChartImpl-*.js
✅ PASS: Plugin chunks are separate in final build// After importing '@object-ui/plugin-editor'
ComponentRegistry.has('code-editor') // true
✅ PASS: Components are registered automatically// Initial import - lightweight
import '@object-ui/plugin-editor'; // ~200 bytes loaded
// Use in schema - triggers lazy load
<SchemaRenderer schema={{ type: 'code-editor' }} />
// Monaco chunk (~15 KB) is NOW fetched
✅ PASS: Heavy chunk loads only when component renders{
"type": "code-editor",
"value": "function hello() {\n console.log('Hello, World!');\n}",
"language": "javascript",
"theme": "vs-dark",
"height": "400px"
}{
"type": "chart-bar",
"data": [
{ "name": "Jan", "value": 400 },
{ "name": "Feb", "value": 300 },
{ "name": "Mar", "value": 600 }
],
"dataKey": "value",
"xAxisKey": "name",
"height": 400,
"color": "#8884d8"
}✅ Successfully implemented lazy-loaded plugin architecture
- Heavy libraries (Monaco, Recharts) are in separate chunks
- Chunks are only loaded when components are actually rendered
- Main bundle stays lean (~2.2 MB vs ~2.6 MB)
- Users don't need to manage lazy loading themselves
- Provides loading skeletons automatically
The implementation follows React best practices and Vite's code-splitting capabilities to deliver optimal performance.