UNPKG

mancha

Version:

Javscript HTML rendering engine

336 lines (335 loc) 13.5 kB
//#region src/store.d.ts /** * Internal store properties that are always present. These are managed by the framework * and should not be set directly by users. */ type InternalStoreState = { /** Reference to the parent store in a hierarchy. */ $parent?: SignalStore; /** Reference to the root renderer instance. */ $rootRenderer?: SignalStore; /** Reference to the root DOM node. */ $rootNode?: Node; } & { [key: `$$${string}`]: string | null; }; /** * Base type for user-defined store state. Uses `any` intentionally to allow flexible * user-defined state types without requiring explicit index signatures. */ type StoreState = Record<string, any>; /** Type for expression evaluation function. */ type EvalFunction = (thisArg: SignalStoreProxy, args: Record<string, unknown>) => unknown; /** Type for observer entries that include the store context for proper binding. */ type ObserverEntry = { observer: Observer<unknown>; store: SignalStore; }; /** * Internal proxy type used within the store implementation. Uses `any` for dynamic property access. */ type SignalStoreProxy = SignalStore & InternalStoreState & { [key: string]: any; }; type Observer<T> = (this: SignalStoreProxy) => T; type KeyValueHandler = (this: SignalStoreProxy, key: string, value: unknown) => void; type AnyFunction = (...args: unknown[]) => unknown; declare abstract class IDebouncer { timeouts: Map<AnyFunction, ReturnType<typeof setTimeout>>; debounce<T>(millis: number, callback: () => T | Promise<T>): Promise<T>; } /** Default debouncer time in millis. */ declare class SignalStore<T extends StoreState = StoreState> extends IDebouncer { protected readonly evalkeys: string[]; protected readonly expressionCache: Map<string, EvalFunction>; protected readonly observers: Map<string, Set<ObserverEntry>>; protected readonly keyHandlers: Map<RegExp, Set<KeyValueHandler>>; readonly _store: Map<string, unknown>; _lock: Promise<void>; constructor(data?: T); private wrapObject; watch<T>(key: string, observer: Observer<T>): void; addKeyHandler(pattern: RegExp, handler: KeyValueHandler): void; notify(key: string, debounceMillis?: number): Promise<void>; get<T>(key: string, observer?: Observer<T>): unknown; set(key: string, value: unknown): Promise<void>; del(key: string): Promise<void>; keys(): string[]; /** * Checks if a key exists in THIS store only (not ancestors). * Use `get(key) !== null` to check if a key exists anywhere in the chain. */ has(key: string): boolean; /** * Returns observer statistics for performance reporting. */ getObserverStats(): { totalKeys: number; totalObservers: number; byKey: Record<string, number>; }; effect<T>(observer: Observer<T>, _meta?: EffectMeta): T; private proxify; get $(): SignalStore<T> & InternalStoreState & T; /** * Creates an evaluation function for the provided expression. * @param expr The expression to be evaluated. * @returns The evaluation function. */ private makeEvalFunction; /** * Retrieves or creates a cached expression function for the provided expression. * @param expr - The expression to retrieve or create a cached function for. * @returns The cached expression function. */ private cachedExpressionFunction; eval(expr: string, args?: Record<string, unknown>): unknown; /** * Executes an async function and returns a reactive state object that tracks the result. * * @param fn - The async function to execute. * @param options - Optional arguments to pass to the function. * @returns A reactive state object with $pending, $result, and $error properties. * * @example * // In :data attribute - executes on mount * :data="{ users: $resolve(api.listUsers) }" * * // With options * :data="{ user: $resolve(api.getUser, { path: { id: userId } }) }" * * // In :on:click - executes on click * :on:click="result = $resolve(api.deleteUser, { path: { id } })" */ $resolve<T, O = unknown>(fn: (options?: O) => Promise<T>, options?: O): { $pending: boolean; $result: T | null; $error: Error | null; }; } //#endregion //#region src/renderer.d.ts /** * Represents an abstract class for rendering and manipulating HTML content. * Extends the `ReactiveProxyStore` class. * * @template T - The type of the store state. Defaults to `StoreState`. */ declare abstract class IRenderer<T extends StoreState = StoreState> extends SignalStore<T> { abstract readonly impl: string; private _debugLevel; protected readonly dirpath: string; /** Performance data collected during rendering. Reset on each mount(). */ private _perfData; /** Debug level ordering for comparison. */ private static readonly DEBUG_LEVELS; readonly _skipNodes: Set<Node>; readonly _customElements: Map<string, Node>; /** * Queue for retrying failed element.value assignments. * * Some DOM elements (notably <select>) silently fail when setting .value if * the required child elements don't exist yet. For example, setting * select.value = "banana" does nothing if no <option value="banana"> exists. * * This happens when :bind on a parent element runs before :for on child * elements creates those children (due to BFS traversal order). * * The fix: after setting .value, check if it actually worked. If not, queue * a retry callback. These callbacks are executed at the end of renderNode() * after all child elements have been created. */ readonly _pendingValueRetries: Array<() => void>; abstract parseHTML(content: string, params?: ParserParams): Document | DocumentFragment; abstract serializeHTML(root: DocumentFragment | Node): string; abstract createElement(tag: string, owner?: Document | null): Element; abstract createComment(content: string, owner?: Document | null): Node; abstract textContent(node: Node, tag: string): void; /** * Sets the debug level for the current instance. * * @param flag - Boolean for backwards compat (true -> 'lifecycle') or a DebugLevel. * @returns The current instance of the class. */ debug(flag: boolean | DebugLevel): this; /** * Returns whether debugging is enabled (any level except 'off'). */ get debugging(): boolean; /** * Checks if the current debug level is at least the specified level. */ private shouldLog; /** * Resets performance data. Called at the start of mount(). */ private resetPerfData; /** * Generates a DOM path for an element (e.g., "html>body>div>ul>li:nth-child(2)"). */ private getNodePath; /** * Builds an effect identifier from metadata. */ buildEffectId(meta?: EffectMeta): string; /** * Records an effect execution for performance tracking. */ recordEffectExecution(meta: EffectMeta | undefined, duration: number): void; /** * Returns a structured performance report. */ performanceReport(): PerformanceReport; /** * Override effect() to add performance tracking. * Tracks effect execution time and logs slow effects (>16ms). */ effect<T>(observer: () => T, meta?: EffectMeta): T; /** * Fetches the remote file at the specified path and returns its content as a string. * @param fpath - The path of the remote file to fetch. * @param params - Optional parameters for the fetch operation. * @returns A promise that resolves to the content of the remote file as a string. */ fetchRemote(fpath: string, params?: RenderParams): Promise<string>; /** * Fetches a local path and returns its content as a string. * * @param fpath - The file path of the resource. * @param params - Optional render parameters. * @returns A promise that resolves to the fetched resource as a string. */ fetchLocal(fpath: string, params?: RenderParams): Promise<string>; /** * Preprocesses a string content with optional rendering and parsing parameters. * * @param content - The string content to preprocess. * @param params - Optional rendering and parsing parameters. * @returns A promise that resolves to a DocumentFragment representing the preprocessed content. */ preprocessString(content: string, params?: RenderParams & ParserParams): Promise<Document | DocumentFragment>; /** * Preprocesses a remote file by fetching its content and applying preprocessing steps. * @param fpath - The path to the remote file. * @param params - Optional parameters for rendering and parsing. * @returns A Promise that resolves to a DocumentFragment representing the preprocessed content. */ preprocessRemote(fpath: string, params?: RenderParams & ParserParams): Promise<Document | DocumentFragment>; /** * Preprocesses a local file by fetching its content and applying preprocessing steps. * @param fpath - The path to the local file. * @param params - Optional parameters for rendering and parsing. * @returns A promise that resolves to the preprocessed document fragment. */ preprocessLocal(fpath: string, params?: RenderParams & ParserParams): Promise<Document | DocumentFragment>; /** * Creates a subrenderer from the current renderer instance. * @returns A new instance of the renderer with the same state as the original. */ subrenderer(): IRenderer; /** * Logs the provided arguments if verbose debugging is enabled. * @param args - The arguments to be logged. */ log(...args: unknown[]): void; /** * Preprocesses a node by applying all the registered preprocessing plugins. * * @template T - The type of the input node. * @param {T} root - The root node to preprocess. * @param {RenderParams} [params] - Optional parameters for preprocessing. * @returns {Promise<T>} - A promise that resolves to the preprocessed node. */ preprocessNode<T extends Document | DocumentFragment | Node>(root: T, params?: RenderParams): Promise<T>; /** * Renders the node and applies all the registered rendering plugins. * * @template T - The type of the root node (Document, DocumentFragment, or Node). * @param {T} root - The root node to render. * @param {RenderParams} [params] - Optional parameters for rendering. * @returns {Promise<T>} - A promise that resolves to the fully rendered root node. */ renderNode<T extends Document | DocumentFragment | Node>(root: T, params?: RenderParams): Promise<T>; /** * Mounts the Mancha application to a root element in the DOM. * * @param root - The root element to mount the application to. * @param params - Optional parameters for rendering the application. * @returns A promise that resolves when the mounting process is complete. */ mount(root: Document | DocumentFragment | Node, params?: RenderParams): Promise<void>; } //#endregion //#region src/interfaces.d.ts interface ParserParams { /** Whether the file parsed is a root document, or a document fragment. */ rootDocument?: boolean; /** Encoding to use when processing local files. */ encoding?: "ascii" | "utf8"; } /** The RendererParams interface defines the parameters that can be passed to the renderer. */ interface RenderParams { /** The current directory of the file being rendered. */ dirpath?: string; /** Maximum level of recursion allowed when resolving includes. */ maxdepth?: number; /** Cache policy used when resolving remote paths. */ cache?: RequestCache | null; /** Whether the current node is the root used in Mancha.moun(...). */ rootNode?: Node; } type RendererPlugin = (this: IRenderer, node: ChildNode, params?: RenderParams) => void | Promise<void>; /** Debug level for controlling performance tracking and logging verbosity. */ type DebugLevel = "off" | "lifecycle" | "effects" | "verbose"; /** Metadata for identifying effects in performance tracking. */ type EffectMeta = { /** The directive that created this effect (e.g., 'class', 'bind', 'for'). */ directive: string; /** The DOM element associated with this effect, if any. */ element?: Element; /** The expression being evaluated by this effect. */ expression?: string; }; /** Statistics for a tracked effect. */ type EffectStats = { /** Effect identifier (e.g., "bind:my-input:user.name"). */ id: string; /** Number of times this effect has executed. */ executionCount: number; /** Total execution time in milliseconds. */ totalTime: number; /** Average execution time per invocation in milliseconds. */ avgTime: number; }; /** Structured performance report returned by getPerformanceReport(). */ type PerformanceReport = { /** Timing data for lifecycle methods. */ lifecycle: { mountTime?: number; preprocessTime?: number; renderTime?: number; }; /** Effect execution statistics. */ effects: { /** Total number of unique effects tracked. */ total: number; /** Aggregate stats grouped by directive type. */ byDirective: Record<string, { count: number; totalTime: number; }>; /** Top 10 slowest effects by total time. */ slowest: EffectStats[]; }; /** Observer registration statistics. */ observers: { /** Number of keys with registered observers. */ totalKeys: number; /** Total number of observer registrations. */ totalObservers: number; /** Observer count per key. */ byKey: Record<string, number>; }; }; //#endregion export { StoreState as a, IRenderer as i, RenderParams as n, RendererPlugin as r, ParserParams as t };