@servlyadmin/runtime-core
v0.2.2
Published
Framework-agnostic core renderer for Servly components with prefetching and loading states
Maintainers
Readme
@servlyadmin/runtime-core
Framework-agnostic core renderer for Servly components. This package provides the foundation for rendering Servly components in any JavaScript environment.
Installation
npm install @servlyadmin/runtime-core
# or
yarn add @servlyadmin/runtime-core
# or
pnpm add @servlyadmin/runtime-coreQuick Start
The simplest way to render a component:
import { mount } from '@servlyadmin/runtime-core';
// Mount a component - that's it!
const app = await mount({
componentId: 'my-component-id',
target: '#app',
props: { title: 'Hello World' }
});
// Update props
app.update({ props: { title: 'Updated!' } });
// Cleanup
app.destroy();With Callbacks
const app = await mount({
componentId: 'my-component-id',
target: '#app',
props: { title: 'Hello' },
onReady: (result) => console.log('Mounted!', result.version),
onError: (err) => console.error('Failed:', err)
});With Loading & Error States
const app = await mount({
componentId: 'my-component-id',
target: '#app',
props: { title: 'Hello' },
// Show loading indicator
loadingComponent: '<div class="skeleton animate-pulse h-32 bg-gray-200 rounded"></div>',
// Show error message on failure
errorComponent: (err) => `<div class="text-red-500">Failed to load: ${err.message}</div>`,
// Optional: delay before showing loading (avoids flash for fast loads)
loadingDelay: 200,
// Optional: minimum time to show loading (avoids flash)
minLoadingTime: 500,
// Lifecycle callbacks
onLoadStart: () => console.log('Loading...'),
onLoadEnd: () => console.log('Done!'),
onReady: (result) => console.log('Mounted!'),
onError: (err) => console.error('Failed:', err)
});With Version & Cache Control
const app = await mount({
componentId: 'my-component-id',
target: document.getElementById('app'),
props: { title: 'Hello' },
version: '^1.0.0',
fetchOptions: {
cacheStrategy: 'memory',
forceRefresh: true
}
});Low-Level API
For more control, use fetchComponent and render directly:
import { render, fetchComponent } from '@servlyadmin/runtime-core';
// Fetch a component from the registry
const { data } = await fetchComponent('my-component-id');
// Render to a container
const result = render({
container: document.getElementById('app'),
elements: data.layout,
context: {
props: { title: 'Hello World' },
state: {},
context: {},
},
});
// Update props
result.update({
props: { title: 'Updated Title' },
state: {},
context: {},
});
// Cleanup
result.destroy();Registry Configuration
By default, components are fetched from Servly's production registry:
- Default URL:
https://core-api.servly.app/v1/views/registry
You can override this if you have a custom registry:
import { setRegistryUrl } from '@servlyadmin/runtime-core';
// Use a custom registry
setRegistryUrl('https://your-api.com/v1/views/registry');Cache Strategies
The runtime supports three caching strategies to optimize component loading:
| Strategy | Description | Persistence | Best For |
|----------|-------------|-------------|----------|
| localStorage | Persists across browser sessions | Yes | Production apps (default) |
| memory | In-memory cache, cleared on page refresh | No | Development, SSR |
| none | No caching, always fetches fresh | No | Testing, debugging |
Default: localStorage - Components are cached in the browser's localStorage for fast subsequent loads.
// Use default localStorage caching
const { data } = await fetchComponent('my-component');
// Explicitly set cache strategy
const { data } = await fetchComponent('my-component', {
cacheStrategy: 'memory', // or 'localStorage' or 'none'
});
// Force refresh (bypass cache)
const { data } = await fetchComponent('my-component', {
forceRefresh: true,
});Prefetching
Preload components for faster subsequent rendering:
import { prefetch, prefetchAll, prefetchOnHover, prefetchOnVisible, prefetchOnIdle } from '@servlyadmin/runtime-core';
// Prefetch a single component
await prefetch('pricing-card');
// Prefetch multiple components
await prefetchAll(['pricing-card', 'contact-form', 'navbar']);
// Prefetch with versions
await prefetchAll([
{ id: 'pricing-card', version: '^1.0.0' },
{ id: 'contact-form', version: 'latest' }
]);
// Prefetch on hover (great for modals/dialogs)
const cleanup = prefetchOnHover('#open-modal-btn', 'modal-component');
// Later: cleanup() to remove listener
// Prefetch when element becomes visible (Intersection Observer)
const cleanup = prefetchOnVisible('#pricing-section', ['pricing-card', 'feature-list']);
// Prefetch when browser is idle (non-critical components)
prefetchOnIdle(['footer', 'sidebar', 'help-modal']);Core Concepts
Layout Elements
Components are defined as a tree of layout elements:
interface LayoutElement {
i: string; // Unique identifier
componentId: string; // Element type (container, text, button, etc.)
configuration?: { // Element configuration
classNames?: string;
style?: Record<string, any>;
text?: string;
// ... other attributes
};
children?: string[]; // Child element IDs
parent?: string; // Parent element ID
}Binding Context
Data is passed to components through a binding context:
interface BindingContext {
props: Record<string, any>; // Component props
state: Record<string, any>; // Component state
context: Record<string, any>; // Additional context
}Template Bindings
Use {{path}} syntax to bind data:
const elements = [
{
i: 'greeting',
componentId: 'text',
configuration: {
text: 'Hello, {{props.name}}!',
classNames: '{{props.className}}',
},
},
];API Reference
mount(options) - Recommended
The simplest way to render a component. Handles fetching, container resolution, and rendering.
const app = await mount({
componentId: string, // Required: Component ID to fetch
target: string | HTMLElement, // Required: CSS selector or element
props?: Record<string, any>, // Props to pass (default: {})
state?: Record<string, any>, // Initial state (default: {})
context?: Record<string, any>, // Additional context (default: {})
version?: string, // Version specifier (default: 'latest')
eventHandlers?: Record<string, Record<string, (e: Event) => void>>,
fetchOptions?: {
cacheStrategy?: 'localStorage' | 'memory' | 'none',
forceRefresh?: boolean,
apiKey?: string,
retryConfig?: RetryConfig,
},
// Loading & Error States
loadingComponent?: string | HTMLElement, // HTML to show while loading
errorComponent?: (err: Error) => string | HTMLElement, // Error display
loadingDelay?: number, // Delay before showing loading (ms)
minLoadingTime?: number, // Minimum loading display time (ms)
// Lifecycle Callbacks
onLoadStart?: () => void, // Called when loading starts
onLoadEnd?: () => void, // Called when loading ends
onReady?: (result: MountResult) => void,
onError?: (error: Error) => void,
});
// Returns MountResult
interface MountResult {
update(context: Partial<BindingContext>): void;
destroy(): void;
rootElement: HTMLElement | null;
container: HTMLElement;
data: ComponentData;
fromCache: boolean;
version: string;
}mountData(options)
Mount with pre-fetched data (useful for SSR or custom data sources):
import { mountData } from '@servlyadmin/runtime-core';
const app = mountData({
data: myComponentData, // Pre-fetched ComponentData
target: '#app',
props: { title: 'Hello' }
});render(options)
Renders elements to a container.
const result = render({
container: HTMLElement,
elements: LayoutElement[],
context: BindingContext,
eventHandlers?: Record<string, Record<string, (e: Event) => void>>,
});
// Returns
interface RenderResult {
update(context: BindingContext): void;
destroy(): void;
getElement(id: string): HTMLElement | null;
}fetchComponent(id, options?)
Fetches a component from the registry.
const { data, fromCache, version } = await fetchComponent('component-id', {
version: 'latest', // Version specifier (default: 'latest')
cacheStrategy: 'localStorage', // 'localStorage' | 'memory' | 'none' (default: 'localStorage')
forceRefresh: false, // Bypass cache (default: false)
retryConfig: {
maxRetries: 3, // Number of retry attempts (default: 3)
initialDelay: 1000, // Initial retry delay in ms (default: 1000)
maxDelay: 10000, // Maximum retry delay in ms (default: 10000)
backoffMultiplier: 2, // Exponential backoff multiplier (default: 2)
},
});setRegistryUrl(url)
Configure a custom registry URL.
import { setRegistryUrl, DEFAULT_REGISTRY_URL } from '@servlyadmin/runtime-core';
// Use custom registry
setRegistryUrl('https://your-api.com/v1/views/registry');
// Reset to default
setRegistryUrl(DEFAULT_REGISTRY_URL);StateManager
Manages component state with subscriptions.
import { StateManager } from '@servlyadmin/runtime-core';
const stateManager = new StateManager({ count: 0 });
// Get/set state
stateManager.set('count', 1);
stateManager.get('count'); // 1
stateManager.set('user.name', 'John');
// Subscribe to changes
const unsubscribe = stateManager.subscribe((event) => {
console.log('State changed:', event.path, event.value);
});
// Cleanup
stateManager.clear();EventSystem
Handles events with plugin-based actions.
import { EventSystem, getEventSystem } from '@servlyadmin/runtime-core';
const eventSystem = getEventSystem();
// Register custom plugin
eventSystem.registerPlugin('my-action', async (action, context) => {
console.log('Action executed with:', action.config);
});Built-in Plugins
executeCode- Execute arbitrary JavaScript codestate-setState- Update state valuesnavigateTo- Navigate to URLlocalStorage-set/get/remove- LocalStorage operationssessionStorage-set/get- SessionStorage operationsalert- Show alert dialogconsole-log- Log to consoleclipboard-copy- Copy text to clipboardscrollTo- Scroll to elementfocus/blur- Focus/blur elementsaddClass/removeClass/toggleClass- CSS class manipulationsetAttribute/removeAttribute- Attribute manipulationdispatchEvent- Dispatch custom eventsdelay- Add delay between actions
Cache Management
import {
clearAllCaches,
clearMemoryCache,
clearLocalStorageCache,
getMemoryCacheSize
} from '@servlyadmin/runtime-core';
// Clear all caches
clearAllCaches();
// Clear specific cache
clearMemoryCache();
clearLocalStorageCache();
// Get cache size
const size = getMemoryCacheSize();Bindings
Template resolution utilities.
import { resolveTemplate, hasTemplateSyntax } from '@servlyadmin/runtime-core';
const context = {
props: { name: 'World', count: 42 },
state: {},
context: {},
};
// Resolve single template
resolveTemplate('Hello, {{props.name}}!', context); // "Hello, World!"
// Check if string has template syntax
hasTemplateSyntax('{{props.name}}'); // true
hasTemplateSyntax('static text'); // falsePrefetch API
Preload components for faster subsequent rendering.
import {
prefetch,
prefetchAll,
prefetchOnHover,
prefetchOnVisible,
prefetchOnIdle
} from '@servlyadmin/runtime-core';
// Prefetch single component
await prefetch('pricing-card');
await prefetch('pricing-card', '^1.0.0'); // With version
// Prefetch multiple components
const { success, failed } = await prefetchAll([
'pricing-card',
'contact-form',
{ id: 'navbar', version: '^2.0.0' }
]);
// Prefetch on hover (returns cleanup function)
const cleanup = prefetchOnHover(
'#open-modal-btn', // Target element
'modal-component', // Component ID
'latest', // Version
{ delay: 100 } // Options: delay before prefetch
);
// Later: cleanup()
// Prefetch when element becomes visible
const cleanup = prefetchOnVisible(
'#pricing-section', // Target element
['pricing-card', 'feature-list'], // Components to prefetch
{ rootMargin: '100px', threshold: 0 } // IntersectionObserver options
);
// Prefetch during browser idle time
prefetchOnIdle(
['footer', 'sidebar', 'help-modal'],
{ timeout: 5000 } // Max wait time before forcing prefetch
);Slots
Components can define slots for content injection:
const elements = [
{
i: 'card',
componentId: 'container',
configuration: { classNames: 'card' },
children: ['header-slot', 'content-slot'],
},
{
i: 'header-slot',
componentId: 'slot',
configuration: {
slotName: 'header',
},
parent: 'card',
},
{
i: 'content-slot',
componentId: 'slot',
configuration: {
slotName: 'default',
},
parent: 'card',
},
];Framework wrappers handle slot content injection automatically.
TypeScript Support
Full TypeScript support with exported types:
import type {
LayoutElement,
BindingContext,
RenderResult,
RenderOptions,
ComponentData,
CacheStrategy,
RetryConfig,
FetchOptions,
FetchResult,
} from '@servlyadmin/runtime-core';Browser Support
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
License
MIT
