tuix
Version:
A performant TUI framework for Bun with JSX and reactive state management
166 lines (140 loc) • 3.73 kB
text/typescript
/**
* Lazy Loading Utilities
*
* Provides utilities for lazy loading commands and plugins
*/
import type { Component } from "../core/types"
import type { Handler } from "./types"
export interface LazyComponent<TModel = any, TMsg = any> {
(): Promise<Component<TModel, TMsg>>
preload?: () => Promise<void>
isLoaded?: boolean
}
export interface LazyHandler {
(): Promise<Handler>
preload?: () => Promise<void>
isLoaded?: boolean
}
/**
* Create a lazy-loaded module with caching and preloading support
*/
export function lazyLoad<T = any>(
importFn: () => Promise<{ default: T }>,
fallback?: T
): LazyComponent<any, any> | LazyHandler {
let cached: T | undefined
let loading: Promise<T> | undefined
let loadError: Error | undefined
const loader = async (): Promise<T> => {
if (cached) return cached
if (!loading) {
loading = importFn()
.then(module => {
cached = module.default
loadError = undefined
return cached
})
.catch(error => {
loadError = error
loading = undefined
throw error
})
}
return loading
}
const lazyFunction = async () => {
try {
return await loader()
} catch (error) {
console.error("Failed to load module:", error)
if (fallback) {
console.warn("Using fallback component")
return fallback
}
throw error
}
}
// Add preload method
lazyFunction.preload = async () => {
try {
await loader()
} catch (error) {
// Preload failures are silent
console.debug("Preload failed:", error)
}
}
// Add isLoaded getter
Object.defineProperty(lazyFunction, "isLoaded", {
get: () => !!cached && !loadError
})
return lazyFunction as LazyComponent<any, any> | LazyHandler
}
/**
* Convenience helper for lazy loading commands from file paths
*/
export function lazyLoadCommand(path: string, fallback?: Handler): LazyHandler {
return lazyLoad(() => import(path), fallback) as LazyHandler
}
/**
* Convenience helper for lazy loading plugins
*/
export function lazyLoadPlugin(name: string) {
return lazyLoad(() => import(name))
}
/**
* Preload multiple lazy-loaded modules
*/
export async function preloadAll(
lazyModules: Array<{ preload?: () => Promise<void> }>
): Promise<void> {
const preloadPromises = lazyModules
.filter(module => module.preload)
.map(module => module.preload!())
await Promise.allSettled(preloadPromises)
}
/**
* Create a lazy loading cache for frequently accessed modules
*/
export class LazyCache {
private cache = new Map<string, any>()
private loading = new Map<string, Promise<any>>()
async get<T>(
key: string,
importFn: () => Promise<{ default: T }>
): Promise<T> {
// Return cached if available
if (this.cache.has(key)) {
return this.cache.get(key)
}
// Return loading promise if in progress
if (this.loading.has(key)) {
return this.loading.get(key)
}
// Start loading
const loadingPromise = importFn()
.then(module => {
const result = module.default
this.cache.set(key, result)
this.loading.delete(key)
return result
})
.catch(error => {
this.loading.delete(key)
throw error
})
this.loading.set(key, loadingPromise)
return loadingPromise
}
has(key: string): boolean {
return this.cache.has(key)
}
clear(): void {
this.cache.clear()
this.loading.clear()
}
size(): number {
return this.cache.size
}
}
// Global lazy cache instance
export const globalLazyCache = new LazyCache()