A monorepo project with a plugin system using slot architecture, built with React, TypeScript, and Vite.
broccoli/
├── packages/
│ ├── sdk/ # Core SDK for plugin system
│ │ ├── src/
│ │ │ ├── index.ts # Main SDK exports
│ │ │ ├── react.tsx # React-specific hooks and components
│ │ │ ├── types/
│ │ │ │ └── index.ts # TypeScript type definitions
│ │ │ └── components/ # Core components
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ └── web/ # Frontend application
│ ├── src/
│ │ ├── components/
│ │ │ └── ui/ # ShadCN UI components
│ │ │ └── button.tsx
│ │ ├── plugins/ # Plugin implementations
│ │ │ └── amazing-button/
│ │ │ ├── index.ts
│ │ │ └── components/
│ │ │ └── AmazingButton.tsx
│ │ ├── lib/
│ │ │ └── utils.ts # Utility functions
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── package.json
│ ├── vite.config.ts
│ ├── tailwind.config.js
│ └── tsconfig.json
│
├── tsconfig.base.json # Shared TypeScript configuration
├── pnpm-workspace.yaml # PNPM workspace configuration
└── package.json # Root package.json
The SDK provides a plugin system with:
- Plugin Registry: Context-based plugin management
- Slot System: Component injection with multiple positions:
after- Add after existing contentreplace- Replace existing contentbefore- Add before existing content
- TypeScript Support: Full type definitions
- React Hooks:
usePluginRegistry(),usePluginComponent()
import {
PluginRegistryProvider,
Slot,
usePluginRegistry,
} from '@broccoli/web-sdk/react';
import type { PluginManifest, ComponentBundle } from '@broccoli/web-sdk';
// Define plugin manifest
const manifest: PluginManifest = {
name: 'my-plugin',
version: '1.0.0',
slots: [
{
name: 'slots.header',
position: 'append',
component: 'components/MyButton',
},
],
};
// Register plugin
const { registerPlugin } = usePluginRegistry();
registerPlugin(manifest, components);The web application demonstrates:
- Plugin Usage: Example plugin (
amazing-button) - ShadCN UI Integration: Pre-configured with Tailwind CSS
- Slot Implementation: Header slot with plugin injection
- TypeScript: Full type safety with path aliases
- Node.js 18+ (recommended: 20+)
- pnpm 8+
# Install dependencies
pnpm install
# Build SDK
pnpm --filter @broccoli/web-sdk build
# Or build all packages
pnpm build# Start all packages in development mode
pnpm dev
# Start only web
pnpm --filter @broccoli/web dev
# Start only SDK in watch mode
pnpm --filter @broccoli/web-sdk dev# Build all packages
pnpm build
# Build specific package
pnpm --filter @broccoli/web-sdk build
pnpm --filter @broccoli/web build- Create plugin directory in
packages/web/src/plugins/:
plugins/
└── my-plugin/
├── index.ts
└── components/
└── MyComponent.tsx
- Define plugin manifest (
index.ts):
import type { PluginManifest, ComponentBundle } from '@broccoli/web-sdk';
import { MyComponent } from './components/MyComponent';
export const manifest: PluginManifest = {
name: 'my-plugin',
version: '1.0.0',
slots: [
{
name: 'slots.header',
position: 'after',
component: 'components/MyComponent',
},
],
};
export const components: ComponentBundle = {
'components/MyComponent': MyComponent,
};- Register plugin in
App.tsx:
import * as MyPlugin from './plugins/my-plugin';
function AppContent() {
const { registerPlugin } = usePluginRegistry();
useEffect(() => {
registerPlugin(MyPlugin.manifest, MyPlugin.components);
}, [registerPlugin]);
// ...
}- Build Tool: Vite (Rolldown)
- Framework: React 19
- Language: TypeScript 5.9
- Styling: Tailwind CSS 3.4
- UI Components: ShadCN UI
- Package Manager: pnpm
- Monorepo: pnpm workspaces
pnpm dev # Start development servers for all packages
pnpm build # Build all packages
pnpm lint # Lint all packages
pnpm test # Run tests for all packagesMIT