service-react-bootstrap/README.md
2026-01-30 11:56:25 -08:00

11 KiB

@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:

  1. Read your feature's services.yaml to identify dependencies
  2. Check which services are already running
  3. Start any missing services automatically
  4. Wait for health checks to pass
  5. 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=true or E2E=true
  • Assumes services are externally managed

Build Mode:

  • Plugin only runs during vite serve, not vite 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.yaml exists for your feature
  • Check feature ID matches directory name
  • Ensure @lilith/service-addresses is 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