@liteforge/router 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lazy.js ADDED
@@ -0,0 +1,458 @@
1
+ /**
2
+ * Create a lazy-loaded component
3
+ *
4
+ * The returned function acts as a LazyComponent that can be used in route definitions.
5
+ * It provides caching, error handling, and prefetching capabilities.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // Basic usage
10
+ * const LazyDashboard = lazy(() => import('./pages/Dashboard'));
11
+ *
12
+ * // With options
13
+ * const LazySettings = lazy(
14
+ * () => import('./pages/Settings'),
15
+ * {
16
+ * loading: () => document.createTextNode('Loading settings...'),
17
+ * timeout: 5000,
18
+ * }
19
+ * );
20
+ *
21
+ * // In route definition
22
+ * const routes = [
23
+ * { path: '/dashboard', component: LazyDashboard },
24
+ * { path: '/settings', component: LazySettings },
25
+ * ];
26
+ *
27
+ * // Prefetch component
28
+ * LazyDashboard.prefetch();
29
+ * ```
30
+ */
31
+ export function lazy(loader, options = {}) {
32
+ const state = { current: { status: 'idle' } };
33
+ let loadPromise = null;
34
+ /**
35
+ * Load the component with caching
36
+ */
37
+ async function load() {
38
+ // Return cached if already loaded
39
+ if (state.current.status === 'loaded') {
40
+ return { default: state.current.component };
41
+ }
42
+ // Return existing promise if already loading
43
+ if (loadPromise) {
44
+ return loadPromise;
45
+ }
46
+ state.current = { status: 'loading' };
47
+ // Track timing for minLoadTime
48
+ const startTime = Date.now();
49
+ // Create the loading promise with optional timeout
50
+ const timeoutPromise = options.timeout && options.timeout > 0
51
+ ? new Promise((_, reject) => setTimeout(() => reject(new Error(`Lazy component load timed out after ${options.timeout}ms`)), options.timeout))
52
+ : null;
53
+ loadPromise = (async () => {
54
+ try {
55
+ // Race against timeout if specified
56
+ const result = timeoutPromise
57
+ ? await Promise.race([loader(), timeoutPromise])
58
+ : await loader();
59
+ // Enforce minimum load time to prevent flash
60
+ if (options.minLoadTime && options.minLoadTime > 0) {
61
+ const elapsed = Date.now() - startTime;
62
+ const remaining = options.minLoadTime - elapsed;
63
+ if (remaining > 0) {
64
+ await new Promise(resolve => setTimeout(resolve, remaining));
65
+ }
66
+ }
67
+ // Extract component from result
68
+ const component = 'default' in result ? result.default : result;
69
+ state.current = { status: 'loaded', component };
70
+ return result;
71
+ }
72
+ catch (error) {
73
+ const err = error instanceof Error ? error : new Error(String(error));
74
+ state.current = { status: 'error', error: err };
75
+ loadPromise = null; // Allow retry
76
+ throw err;
77
+ }
78
+ })();
79
+ return loadPromise;
80
+ }
81
+ /**
82
+ * Prefetch the component without rendering
83
+ */
84
+ async function prefetch() {
85
+ try {
86
+ await load();
87
+ }
88
+ catch {
89
+ // Silently ignore prefetch errors
90
+ }
91
+ }
92
+ /**
93
+ * Check if component is loaded
94
+ */
95
+ function isLoaded() {
96
+ return state.current.status === 'loaded';
97
+ }
98
+ /**
99
+ * Get the loaded component synchronously.
100
+ * Returns undefined if not loaded yet.
101
+ * This avoids the microtask delay of using .then() on a resolved Promise.
102
+ */
103
+ function getLoaded() {
104
+ if (state.current.status === 'loaded') {
105
+ return state.current.component;
106
+ }
107
+ return undefined;
108
+ }
109
+ /**
110
+ * Reset state to idle (for retry)
111
+ */
112
+ function reset() {
113
+ state.current = { status: 'idle' };
114
+ loadPromise = null;
115
+ }
116
+ // Trigger prefetch immediately if requested
117
+ if (options.prefetch) {
118
+ // Use setTimeout to avoid blocking
119
+ setTimeout(prefetch, 0);
120
+ }
121
+ // Return function that acts as LazyComponent
122
+ const wrapper = load;
123
+ wrapper.prefetch = prefetch;
124
+ wrapper.isLoaded = isLoaded;
125
+ wrapper.getLoaded = getLoaded;
126
+ wrapper.reset = reset;
127
+ // Expose internal state for debugging/testing
128
+ Object.defineProperty(wrapper, '__lazyState', {
129
+ get: () => state.current,
130
+ enumerable: false,
131
+ });
132
+ Object.defineProperty(wrapper, '__loader', {
133
+ value: loader,
134
+ enumerable: false,
135
+ });
136
+ Object.defineProperty(wrapper, '__options', {
137
+ value: options,
138
+ enumerable: false,
139
+ });
140
+ return wrapper;
141
+ }
142
+ // =============================================================================
143
+ // Prefetching Utilities
144
+ // =============================================================================
145
+ /**
146
+ * Prefetch multiple lazy components
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * // Prefetch all admin pages
151
+ * prefetchAll([LazyAdminDashboard, LazyAdminUsers, LazyAdminSettings]);
152
+ * ```
153
+ */
154
+ export async function prefetchAll(components) {
155
+ await Promise.all(components.map(component => {
156
+ if ('prefetch' in component && typeof component.prefetch === 'function') {
157
+ return component.prefetch();
158
+ }
159
+ // For plain LazyComponent, just call it to trigger load
160
+ return component().catch(() => { });
161
+ }));
162
+ }
163
+ /**
164
+ * Check if a component is a lazy component
165
+ */
166
+ export function isLazyComponent(component) {
167
+ return (typeof component === 'function' &&
168
+ 'prefetch' in component &&
169
+ 'isLoaded' in component &&
170
+ 'reset' in component);
171
+ }
172
+ /**
173
+ * Get the delay option from a lazy component (default: 200ms)
174
+ */
175
+ export function getLazyDelay(component) {
176
+ const options = component.__options;
177
+ return options?.delay ?? 200;
178
+ }
179
+ /**
180
+ * Get the loading component from a lazy component
181
+ */
182
+ export function getLazyLoading(component) {
183
+ return component.__options?.loading;
184
+ }
185
+ /**
186
+ * Get the error component from a lazy component
187
+ */
188
+ export function getLazyError(component) {
189
+ return component.__options?.error;
190
+ }
191
+ // =============================================================================
192
+ // Preload Link Helpers
193
+ // =============================================================================
194
+ /**
195
+ * Create a preload function that prefetches lazy components on link hover
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * const preloadOnHover = createPreloadOnHover({
200
+ * '/dashboard': LazyDashboard,
201
+ * '/settings': LazySettings,
202
+ * });
203
+ *
204
+ * // Attach to link element
205
+ * linkElement.addEventListener('mouseenter', () => preloadOnHover('/dashboard'));
206
+ * ```
207
+ */
208
+ export function createPreloadOnHover(componentMap) {
209
+ const prefetched = new Set();
210
+ return (path) => {
211
+ // Already prefetched
212
+ if (prefetched.has(path))
213
+ return;
214
+ const component = componentMap[path];
215
+ if (!component)
216
+ return;
217
+ prefetched.add(path);
218
+ if ('prefetch' in component && typeof component.prefetch === 'function') {
219
+ component.prefetch();
220
+ }
221
+ else {
222
+ component().catch(() => { });
223
+ }
224
+ };
225
+ }
226
+ /**
227
+ * Create an intersection observer that prefetches lazy components when links enter viewport
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const observer = createPreloadOnVisible({
232
+ * '/dashboard': LazyDashboard,
233
+ * '/settings': LazySettings,
234
+ * });
235
+ *
236
+ * // Observe link elements
237
+ * document.querySelectorAll('a[href]').forEach(link => {
238
+ * observer.observe(link, link.getAttribute('href')!);
239
+ * });
240
+ *
241
+ * // Cleanup when done
242
+ * observer.disconnect();
243
+ * ```
244
+ */
245
+ export function createPreloadOnVisible(componentMap, options = {}) {
246
+ const prefetched = new Set();
247
+ const elementPathMap = new WeakMap();
248
+ /* v8 ignore start */
249
+ // IntersectionObserver is only available in browsers
250
+ let observer = null;
251
+ if (typeof IntersectionObserver !== 'undefined') {
252
+ observer = new IntersectionObserver((entries) => {
253
+ for (const entry of entries) {
254
+ if (!entry.isIntersecting)
255
+ continue;
256
+ const path = elementPathMap.get(entry.target);
257
+ if (!path || prefetched.has(path))
258
+ continue;
259
+ const component = componentMap[path];
260
+ if (!component)
261
+ continue;
262
+ prefetched.add(path);
263
+ observer.unobserve(entry.target);
264
+ if ('prefetch' in component && typeof component.prefetch === 'function') {
265
+ component.prefetch();
266
+ }
267
+ else {
268
+ component().catch(() => { });
269
+ }
270
+ }
271
+ }, options);
272
+ }
273
+ /* v8 ignore stop */
274
+ return {
275
+ observe(element, path) {
276
+ elementPathMap.set(element, path);
277
+ /* v8 ignore next */
278
+ observer?.observe(element);
279
+ },
280
+ unobserve(element) {
281
+ elementPathMap.delete(element);
282
+ /* v8 ignore next */
283
+ observer?.unobserve(element);
284
+ },
285
+ disconnect() {
286
+ /* v8 ignore next */
287
+ observer?.disconnect();
288
+ },
289
+ };
290
+ }
291
+ // =============================================================================
292
+ // Inline Lazy Import Support
293
+ // =============================================================================
294
+ /**
295
+ * Type guard: check if a value is a function that can be used as a component
296
+ */
297
+ function isComponentFunction(value) {
298
+ return typeof value === 'function';
299
+ }
300
+ /**
301
+ * Resolve the component export from a dynamically imported module.
302
+ *
303
+ * @param module - The imported module object
304
+ * @param exportName - Optional named export to use (default: 'default')
305
+ * @returns The resolved component function
306
+ * @throws Error if no component can be found in the module
307
+ *
308
+ * Resolution order:
309
+ * 1. If exportName is specified, use that export
310
+ * 2. If module has 'default' export, use that
311
+ * 3. Use the first function export found (convenience fallback)
312
+ */
313
+ export function resolveModuleExport(module, exportName) {
314
+ // 1. Explicit named export
315
+ if (exportName !== undefined) {
316
+ const exported = module[exportName];
317
+ if (!isComponentFunction(exported)) {
318
+ throw new Error(`Export "${exportName}" not found or not a function in lazy-loaded module. ` +
319
+ `Available exports: ${Object.keys(module).join(', ')}`);
320
+ }
321
+ return exported;
322
+ }
323
+ // 2. Default export
324
+ if ('default' in module && isComponentFunction(module.default)) {
325
+ return module.default;
326
+ }
327
+ // 3. First function export (fallback)
328
+ for (const key of Object.keys(module)) {
329
+ const value = module[key];
330
+ if (isComponentFunction(value)) {
331
+ return value;
332
+ }
333
+ }
334
+ throw new Error(`No component found in lazy-loaded module. ` +
335
+ `Available exports: ${Object.keys(module).join(', ')}`);
336
+ }
337
+ /**
338
+ * Check if a component is a pre-wrapped lazy component (created with lazy()).
339
+ * This is the existing lazy() wrapper with prefetch/isLoaded/reset methods.
340
+ */
341
+ export function isWrappedLazyComponent(component) {
342
+ return (typeof component === 'function' &&
343
+ 'prefetch' in component &&
344
+ 'isLoaded' in component &&
345
+ 'reset' in component);
346
+ }
347
+ /**
348
+ * Check if a component is an inline lazy import function.
349
+ *
350
+ * An inline lazy import is a zero-argument function that returns a Promise.
351
+ * We detect this by calling the function and checking if it returns a thenable.
352
+ * The result is cached so we only detect once.
353
+ *
354
+ * Note: This is a heuristic. Static components that return Promises would be
355
+ * incorrectly detected as lazy. However, this is rare in practice since
356
+ * components should return DOM nodes synchronously.
357
+ */
358
+ const detectedLazyCache = new WeakMap();
359
+ export function detectInlineLazyImport(component) {
360
+ // Must be a function
361
+ if (typeof component !== 'function') {
362
+ return false;
363
+ }
364
+ // Check cache
365
+ if (detectedLazyCache.has(component)) {
366
+ return detectedLazyCache.get(component) === true;
367
+ }
368
+ // Already wrapped with lazy() - not an inline import
369
+ if (isWrappedLazyComponent(component)) {
370
+ detectedLazyCache.set(component, false);
371
+ return false;
372
+ }
373
+ // Inline lazy imports are zero-argument functions
374
+ // Static components also have length 0 if they don't use props
375
+ // We'll use a different heuristic: check function body for 'import('
376
+ // This is not 100% reliable but works for the common case
377
+ const fnString = component.toString();
378
+ const looksLikeImport = fnString.includes('import(') || fnString.includes('import (');
379
+ detectedLazyCache.set(component, looksLikeImport);
380
+ return looksLikeImport;
381
+ }
382
+ /**
383
+ * Wrap an inline lazy import with the lazy() function.
384
+ *
385
+ * This converts: `() => import('./Component.js')`
386
+ * Into a full lazy component with loading states, caching, etc.
387
+ *
388
+ * @param importFn - The inline import function
389
+ * @param exportName - Named export to use from the module
390
+ * @param routeLazyConfig - Per-route lazy config
391
+ * @param globalDefaults - Global lazy defaults from router options
392
+ */
393
+ export function wrapInlineLazy(importFn, exportName, routeLazyConfig, globalDefaults) {
394
+ // Merge route config with global defaults
395
+ const delay = routeLazyConfig?.delay ?? globalDefaults?.delay ?? 200;
396
+ const timeout = routeLazyConfig?.timeout ?? globalDefaults?.timeout ?? 10000;
397
+ const minLoadTime = routeLazyConfig?.minLoadTime ?? globalDefaults?.minLoadTime;
398
+ const loading = globalDefaults?.loading;
399
+ const error = globalDefaults?.error;
400
+ // Create a loader that resolves the export
401
+ const loader = async () => {
402
+ const module = await importFn();
403
+ const component = resolveModuleExport(module, exportName);
404
+ return { default: component };
405
+ };
406
+ // Build options
407
+ const options = {
408
+ delay,
409
+ timeout,
410
+ };
411
+ if (minLoadTime !== undefined) {
412
+ options.minLoadTime = minLoadTime;
413
+ }
414
+ if (loading !== undefined) {
415
+ options.loading = () => {
416
+ // Call the loading component
417
+ const result = loading();
418
+ // If it's a Node, return it; otherwise create a text node
419
+ if (result instanceof Node) {
420
+ return result;
421
+ }
422
+ return document.createTextNode('Loading...');
423
+ };
424
+ }
425
+ if (error !== undefined) {
426
+ options.error = error;
427
+ }
428
+ return lazy(loader, options);
429
+ }
430
+ /**
431
+ * Process a route component during compilation.
432
+ *
433
+ * - If it's a static component, return as-is
434
+ * - If it's already wrapped with lazy(), return as-is
435
+ * - If it's an inline lazy import, wrap it with lazy()
436
+ *
437
+ * @param component - The route component to process
438
+ * @param exportName - Named export to use (for inline lazy)
439
+ * @param routeLazyConfig - Per-route lazy config
440
+ * @param globalDefaults - Global lazy defaults
441
+ * @returns The processed component (either original or wrapped)
442
+ */
443
+ export function processRouteComponent(component, exportName, routeLazyConfig, globalDefaults) {
444
+ if (component === undefined) {
445
+ return undefined;
446
+ }
447
+ // Already a pre-wrapped lazy component
448
+ if (isWrappedLazyComponent(component)) {
449
+ return component;
450
+ }
451
+ // Detect inline lazy import
452
+ if (detectInlineLazyImport(component)) {
453
+ return wrapInlineLazy(component, exportName, routeLazyConfig, globalDefaults);
454
+ }
455
+ // Static component - return as-is
456
+ return component;
457
+ }
458
+ //# sourceMappingURL=lazy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import type { RouteMiddleware, MiddlewareFunction, MiddlewareContext, NavigationTarget } from './types.js';
2
+ /**
3
+ * Define a named route middleware
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const loggerMiddleware = defineMiddleware('logger', async (ctx, next) => {
8
+ * console.log(`Navigating from ${ctx.from?.path} to ${ctx.to.path}`);
9
+ * await next();
10
+ * console.log(`Navigation complete to ${ctx.to.path}`);
11
+ * });
12
+ *
13
+ * const analyticsMiddleware = defineMiddleware('analytics', async (ctx, next) => {
14
+ * const analytics = ctx.use('analytics');
15
+ * analytics.track('page_view', { path: ctx.to.path });
16
+ * await next();
17
+ * });
18
+ * ```
19
+ */
20
+ export declare function defineMiddleware(name: string, handler: MiddlewareFunction): RouteMiddleware;
21
+ /**
22
+ * Compose multiple middleware into a single function
23
+ * Uses an onion-style execution model (like Koa)
24
+ */
25
+ export declare function composeMiddleware(middleware: RouteMiddleware[]): (context: MiddlewareContext) => Promise<{
26
+ redirect?: NavigationTarget;
27
+ }>;
28
+ /**
29
+ * Run middleware chain with abort capability
30
+ */
31
+ export declare function runMiddleware(middleware: RouteMiddleware[], context: MiddlewareContext): Promise<{
32
+ completed: boolean;
33
+ redirect?: NavigationTarget;
34
+ }>;
35
+ /**
36
+ * Create a logger middleware
37
+ */
38
+ export declare function createLoggerMiddleware(options?: {
39
+ prefix?: string;
40
+ logTiming?: boolean;
41
+ }): RouteMiddleware;
42
+ /**
43
+ * Create a scroll restoration middleware
44
+ */
45
+ export declare function createScrollMiddleware(options?: {
46
+ behavior?: ScrollBehavior;
47
+ scrollToHash?: boolean;
48
+ }): RouteMiddleware;
49
+ /**
50
+ * Create a page title middleware
51
+ */
52
+ export declare function createTitleMiddleware(options?: {
53
+ suffix?: string;
54
+ separator?: string;
55
+ defaultTitle?: string;
56
+ }): RouteMiddleware;
57
+ /**
58
+ * Create an analytics middleware
59
+ */
60
+ export declare function createAnalyticsMiddleware(trackFn: (event: string, data: Record<string, unknown>) => void): RouteMiddleware;
61
+ /**
62
+ * Create a loading indicator middleware
63
+ */
64
+ export declare function createLoadingMiddleware(options?: {
65
+ onStart?: () => void;
66
+ onEnd?: () => void;
67
+ delay?: number;
68
+ }): RouteMiddleware;
69
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAMpB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,eAAe,CAE3F;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAAE,GAC5B,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAAE,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAsC1E;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAkB9D;AAMD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;CAChB,GACL,eAAe,CAkBjB;AAID;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;CACnB,GACL,eAAe,CAqCjB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,eAAe,CAuBjB;AAID;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAC9D,eAAe,CAWjB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CACX,GACL,eAAe,CA8BjB"}