|
Some checks failed
Build and Publish / build-and-publish (push) Failing after 37s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| src | ||
| .gitignore | ||
| bun.lock | ||
| eslint.config.js | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsup.config.ts | ||
@lilith/service-react-bootstrap
React application bootstrap factory with provider composition. Eliminates boilerplate by standardizing app initialization patterns.
Features
- Bootstrap Factory: Create and render React apps with one function call
- Provider Composition: Automatic provider stacking (Query, Router, Theme, Auth, etc.)
- Error Boundary: Built-in error handling with customizable fallback
- QueryClient: Pre-configured React Query with sensible defaults
- Router Integration: BrowserRouter, HashRouter, or MemoryRouter support
- DevTools Support: MSW integration for API mocking
- NestJS-like API: Mirrors NestJS bootstrap patterns for consistency
- Automatic Dependency Startup: Optional Vite plugin for backend service orchestration
Installation
pnpm add @lilith/service-react-bootstrap
Peer Dependencies
pnpm add react react-dom
Optional Dependencies
# For routing
pnpm add react-router-dom
# For error boundaries
pnpm add react-error-boundary
# React Query (required)
pnpm add @tanstack/react-query
Quick Start
import { bootstrap } from '@lilith/service-react-bootstrap';
import App from './App';
bootstrap({
App,
providers: {
router: 'browser',
},
});
Usage
Basic Bootstrap
import { bootstrap } from '@lilith/service-react-bootstrap';
import App from './App';
// Quick start - creates and renders the app
await bootstrap({
App,
providers: {
router: 'browser',
},
});
Advanced Configuration
import { createReactApp } from '@lilith/service-react-bootstrap';
import { ThemeProvider } from '@lilith/ui-theme';
import { AuthProvider } from '@lilith/auth-provider';
import App from './App';
const result = await createReactApp({
App,
rootElement: 'root',
strictMode: true,
queryClient: {
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 10, // 10 minutes
},
},
},
providers: {
theme: {
Provider: ThemeProvider,
defaultTheme: 'cyberpunk',
},
auth: {
Provider: AuthProvider,
props: { ssoUrl: 'https://sso.example.com' },
},
router: 'browser',
},
errorBoundary: {
onError: (error, info) => {
console.error('App error:', error, info);
},
},
});
// Render when ready
result.render();
Synchronous Bootstrap
import { bootstrapSync } from '@lilith/service-react-bootstrap';
import App from './App';
// No async initialization needed
const result = bootstrapSync({
App,
providers: { router: 'browser' },
});
With MSW (Mock Service Worker)
import { bootstrap } from '@lilith/service-react-bootstrap';
import App from './App';
bootstrap({
App,
providers: { router: 'browser' },
devTools: {
msw: async () => {
const { worker } = await import('./mocks/browser');
await worker.start();
},
},
});
Providers
Router Provider
bootstrap({
App,
providers: {
router: 'browser', // BrowserRouter
// router: 'hash', // HashRouter
// router: 'memory', // MemoryRouter
// router: false, // No router
},
});
Theme Provider
import { ThemeProvider } from '@lilith/ui-theme';
bootstrap({
App,
providers: {
theme: {
Provider: ThemeProvider,
defaultTheme: 'cyberpunk',
storageKey: 'app-theme',
},
},
});
Auth Provider
import { AuthProvider } from '@lilith/auth-provider';
bootstrap({
App,
providers: {
auth: {
Provider: AuthProvider,
props: {
ssoUrl: 'https://sso.example.com',
},
},
},
});
i18n Provider
import { I18nProvider } from '@lilith/i18n';
bootstrap({
App,
providers: {
i18n: {
Provider: I18nProvider,
defaultLanguage: 'en',
supportedLanguages: ['en', 'de', 'ja'],
enableMLFallback: true,
},
},
});
Analytics Provider
import { AnalyticsProvider } from '@lilith/analytics';
bootstrap({
App,
providers: {
analytics: {
Provider: AnalyticsProvider,
apiBaseUrl: 'https://analytics.example.com',
appName: 'my-app',
batchSize: 10,
batchInterval: 5000,
},
},
});
Error Boundary
Default Error Boundary
The error boundary is enabled by default with a simple fallback UI.
Custom Fallback Component
import type { FallbackProps } from 'react-error-boundary';
function CustomFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div>
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
bootstrap({
App,
errorBoundary: {
FallbackComponent: CustomFallback,
onError: (error, info) => {
// Log to error tracking service
},
},
});
Disable Error Boundary
bootstrap({
App,
errorBoundary: false,
});
QueryClient
Default Configuration
const defaultQueryClientConfig = {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
retry: 1,
refetchOnWindowFocus: false,
},
mutations: {
retry: 0,
},
};
Custom Configuration
import { QueryClient } from '@tanstack/react-query';
// Use existing instance
const queryClient = new QueryClient();
bootstrap({
App,
queryClient: {
instance: queryClient,
},
});
// Or configure options
bootstrap({
App,
queryClient: {
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 10,
retry: 3,
},
},
},
});
API Reference
AppConfig
interface AppConfig {
App: ComponentType;
rootElement?: string | HTMLElement; // Default: 'root'
strictMode?: boolean; // Default: true
queryClient?: QueryClientOptions;
providers?: ProvidersConfig;
errorBoundary?: ErrorBoundaryConfig | false;
devTools?: DevToolsConfig;
}
ProvidersConfig
interface ProvidersConfig {
auth?: AuthProviderConfig | false;
theme?: ThemeProviderConfig | false;
i18n?: I18nProviderConfig | false;
analytics?: AnalyticsProviderConfig | false;
router?: 'browser' | 'hash' | 'memory' | false;
}
BootstrapResult
interface BootstrapResult {
root: Root; // React root instance
element: HTMLElement; // Root DOM element
render: () => void; // Mount the app
unmount: () => void; // Unmount the app
}
Provider Composition Utilities
composeProviders
Compose multiple provider wrappers into a single component:
import { composeProviders, createProviderWrapper } from '@lilith/service-react-bootstrap';
const providers = [
createProviderWrapper(ThemeProvider, { theme: 'dark' }),
createProviderWrapper(AuthProvider),
];
const ComposedProviders = composeProviders(providers);
// Use in your own render logic
<ComposedProviders>
<App />
</ComposedProviders>
PassthroughProvider
A no-op provider for conditional composition:
import { PassthroughProvider } from '@lilith/service-react-bootstrap';
const MaybeProvider = shouldUseProvider ? RealProvider : PassthroughProvider;
Functions
| Function | Description |
|---|---|
bootstrap(config) |
Create and render app in one call (async) |
bootstrapSync(config) |
Sync version without MSW support |
createReactApp(config) |
Create app without rendering (async) |
createReactAppSync(config) |
Create app without rendering (sync) |
composeProviders(providers) |
Compose provider wrappers |
createQueryClient(options) |
Create configured QueryClient |
Automatic Dependency Startup
For frontends that depend on backend APIs, database services, or Redis, use @lilith/vite-plugin-dependency-startup to ensure all dependencies are running before your frontend starts.
Installation
pnpm add -D @lilith/vite-plugin-dependency-startup
Usage
Add the plugin to your vite.config.ts as the first plugin in the plugins array:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { dependencyStartupPlugin } from '@lilith/vite-plugin-dependency-startup';
export default defineConfig({
plugins: [
// MUST be first - runs before other plugins
dependencyStartupPlugin({
feature: 'marketplace', // Your feature ID from services.yaml
}),
react(),
// ...other plugins
],
});
How It Works
The plugin uses @lilith/service-addresses orchestration to:
- Read your feature's
services.yamlto identify dependencies - Check which services are already running
- Start any missing services automatically
- Wait for health checks to pass
- Allow Vite to start the dev server
Result: Running ./run trustedmeet or pnpm dev:marketplace automatically starts all required backend services, databases, and Redis instances.
Configuration Options
dependencyStartupPlugin({
feature: 'marketplace', // Required: feature ID
autoStart?: true, // Auto-start dependencies (default: true)
skipServices?: ['sso.api'], // Skip specific services
waitForHealth?: true, // Wait for health checks (default: true)
healthCheckTimeout?: 60000, // Health check timeout (default: 60s)
skipInCI?: true, // Skip in CI/E2E environments (default: true)
servicesPath?: 'path/to/features', // Custom services path (auto-detected)
portsPath?: 'path/to/ports.yaml', // Custom ports path (auto-detected)
})
Behavior
Development Mode:
- Cold start: Starts all dependencies, waits for health checks (~30s)
- Warm start: Detects running services, skips startup (~2s)
- Idempotent: Safe to run multiple frontends in parallel
CI/E2E Mode:
- Automatically skips orchestration when
CI=trueorE2E=true - Assumes services are externally managed
Build Mode:
- Plugin only runs during
vite serve, notvite build
Comparison with NestJS Bootstrap
This provides the same dependency orchestration that NestJS backends get from @lilith/service-nestjs-bootstrap:
| Environment | Orchestration |
|---|---|
| NestJS API | @lilith/service-nestjs-bootstrap (automatic) |
| FastAPI Service | lilith-fastapi-service-base (automatic) |
| React/Vite Frontend | @lilith/vite-plugin-dependency-startup (via Vite plugin) |
Troubleshooting
Plugin not starting services:
- Verify
services.yamlexists for your feature - Check feature ID matches directory name
- Ensure
@lilith/service-addressesis installed
Port conflicts:
- Plugin detects running services and skips them
- If port conflict persists, check for processes outside orchestration
Slow startups:
- First start requires Docker containers to initialize (~30s)
- Subsequent starts detect running services (~2s)
- Consider using
pnpm dev:start <feature>to pre-start dependencies
License
MIT