@matthesketh/utopia-router 0.5.0 → 0.7.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/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  isNavigating: () => isNavigating,
37
37
  matchRoute: () => matchRoute,
38
38
  navigate: () => navigate,
39
+ preloadRoute: () => preloadRoute,
39
40
  queryParams: () => queryParams,
40
41
  setQueryParam: () => setQueryParam,
41
42
  setQueryParams: () => setQueryParams
@@ -417,12 +418,35 @@ function capScrollPositions() {
417
418
 
418
419
  // src/components.ts
419
420
  var import_utopia_core2 = require("@matthesketh/utopia-core");
421
+ var moduleCache = /* @__PURE__ */ new Map();
422
+ async function preloadRoute() {
423
+ const match = currentRoute.peek();
424
+ if (!match) return;
425
+ const promises = [match.route.component()];
426
+ if (match.route.layout) {
427
+ promises.push(match.route.layout());
428
+ }
429
+ const modules = await Promise.all(promises);
430
+ moduleCache.set(match.route.component, modules[0]);
431
+ if (match.route.layout && modules.length > 1) {
432
+ moduleCache.set(match.route.layout, modules[1]);
433
+ }
434
+ }
420
435
  function createRouterView() {
421
436
  const container = document.createElement("div");
422
437
  container.setAttribute("data-utopia-router-view", "");
423
438
  let currentCleanup = null;
424
439
  let currentMatch = null;
425
440
  let loadId = 0;
441
+ const initialMatch = currentRoute.peek();
442
+ if (initialMatch) {
443
+ const syncResult = tryRenderFromCache(initialMatch);
444
+ if (syncResult) {
445
+ container.appendChild(syncResult.node);
446
+ currentCleanup = syncResult.cleanup;
447
+ currentMatch = initialMatch;
448
+ }
449
+ }
426
450
  (0, import_utopia_core2.effect)(() => {
427
451
  const match = currentRoute();
428
452
  if (match === currentMatch) {
@@ -456,15 +480,58 @@ function createRouterView() {
456
480
  });
457
481
  return container;
458
482
  }
483
+ function tryRenderFromCache(match) {
484
+ const cachedPage = moduleCache.get(match.route.component);
485
+ if (!cachedPage) return null;
486
+ if (match.route.layout && !moduleCache.has(match.route.layout)) return null;
487
+ const cachedLayout = match.route.layout ? moduleCache.get(match.route.layout) : null;
488
+ moduleCache.delete(match.route.component);
489
+ if (match.route.layout) moduleCache.delete(match.route.layout);
490
+ const PageComponent = cachedPage.default ?? cachedPage;
491
+ const LayoutComponent = cachedLayout ? cachedLayout.default ?? cachedLayout : null;
492
+ const pageNode = renderComponent(PageComponent, {
493
+ params: match.params,
494
+ url: match.url
495
+ });
496
+ let node;
497
+ if (LayoutComponent) {
498
+ node = renderComponent(LayoutComponent, {
499
+ params: match.params,
500
+ url: match.url,
501
+ children: pageNode
502
+ });
503
+ } else {
504
+ node = pageNode;
505
+ }
506
+ return {
507
+ node,
508
+ cleanup: () => {
509
+ if (node.parentNode) {
510
+ node.parentNode.removeChild(node);
511
+ }
512
+ }
513
+ };
514
+ }
459
515
  async function loadRouteComponent(match) {
460
516
  try {
461
- const promises = [match.route.component()];
462
- if (match.route.layout) {
463
- promises.push(match.route.layout());
517
+ const cachedPage = moduleCache.get(match.route.component);
518
+ const cachedLayout = match.route.layout ? moduleCache.get(match.route.layout) : void 0;
519
+ let pageModule;
520
+ let layoutModule = null;
521
+ if (cachedPage) {
522
+ pageModule = cachedPage;
523
+ layoutModule = cachedLayout ?? null;
524
+ moduleCache.delete(match.route.component);
525
+ if (match.route.layout) moduleCache.delete(match.route.layout);
526
+ } else {
527
+ const promises = [match.route.component()];
528
+ if (match.route.layout) {
529
+ promises.push(match.route.layout());
530
+ }
531
+ const modules = await Promise.all(promises);
532
+ pageModule = modules[0];
533
+ layoutModule = modules.length > 1 ? modules[1] : null;
464
534
  }
465
- const modules = await Promise.all(promises);
466
- const pageModule = modules[0];
467
- const layoutModule = modules.length > 1 ? modules[1] : null;
468
535
  if (currentRoute.peek() !== match) {
469
536
  return null;
470
537
  }
@@ -662,6 +729,7 @@ function getRouteParam(name) {
662
729
  isNavigating,
663
730
  matchRoute,
664
731
  navigate,
732
+ preloadRoute,
665
733
  queryParams,
666
734
  setQueryParam,
667
735
  setQueryParams
package/dist/index.d.cts CHANGED
@@ -167,6 +167,20 @@ declare function beforeNavigate(hook: BeforeNavigateHook): () => void;
167
167
  */
168
168
  declare function destroy(): void;
169
169
 
170
+ /**
171
+ * Pre-load the current route's component (and layout) so that
172
+ * `createRouterView()` can render it synchronously on first paint.
173
+ *
174
+ * Call this **after** `createRouter()` and **before** `mount()`.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * createRouter(routes)
179
+ * await preloadRoute()
180
+ * mount(App, '#app')
181
+ * ```
182
+ */
183
+ declare function preloadRoute(): Promise<void>;
170
184
  /**
171
185
  * Creates a DOM node that renders the current route's component.
172
186
  *
@@ -239,4 +253,4 @@ declare function setQueryParams(params: Record<string, string | null>): void;
239
253
  */
240
254
  declare function getRouteParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
241
255
 
242
- export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, queryParams, setQueryParam, setQueryParams };
256
+ export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, preloadRoute, queryParams, setQueryParam, setQueryParams };
package/dist/index.d.ts CHANGED
@@ -167,6 +167,20 @@ declare function beforeNavigate(hook: BeforeNavigateHook): () => void;
167
167
  */
168
168
  declare function destroy(): void;
169
169
 
170
+ /**
171
+ * Pre-load the current route's component (and layout) so that
172
+ * `createRouterView()` can render it synchronously on first paint.
173
+ *
174
+ * Call this **after** `createRouter()` and **before** `mount()`.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * createRouter(routes)
179
+ * await preloadRoute()
180
+ * mount(App, '#app')
181
+ * ```
182
+ */
183
+ declare function preloadRoute(): Promise<void>;
170
184
  /**
171
185
  * Creates a DOM node that renders the current route's component.
172
186
  *
@@ -239,4 +253,4 @@ declare function setQueryParams(params: Record<string, string | null>): void;
239
253
  */
240
254
  declare function getRouteParam(name: string): _matthesketh_utopia_core.ReadonlySignal<string | null>;
241
255
 
242
- export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, queryParams, setQueryParam, setQueryParams };
256
+ export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, getQueryParam, getRouteParam, isNavigating, matchRoute, navigate, preloadRoute, queryParams, setQueryParam, setQueryParams };
package/dist/index.js CHANGED
@@ -373,12 +373,35 @@ function capScrollPositions() {
373
373
 
374
374
  // src/components.ts
375
375
  import { effect } from "@matthesketh/utopia-core";
376
+ var moduleCache = /* @__PURE__ */ new Map();
377
+ async function preloadRoute() {
378
+ const match = currentRoute.peek();
379
+ if (!match) return;
380
+ const promises = [match.route.component()];
381
+ if (match.route.layout) {
382
+ promises.push(match.route.layout());
383
+ }
384
+ const modules = await Promise.all(promises);
385
+ moduleCache.set(match.route.component, modules[0]);
386
+ if (match.route.layout && modules.length > 1) {
387
+ moduleCache.set(match.route.layout, modules[1]);
388
+ }
389
+ }
376
390
  function createRouterView() {
377
391
  const container = document.createElement("div");
378
392
  container.setAttribute("data-utopia-router-view", "");
379
393
  let currentCleanup = null;
380
394
  let currentMatch = null;
381
395
  let loadId = 0;
396
+ const initialMatch = currentRoute.peek();
397
+ if (initialMatch) {
398
+ const syncResult = tryRenderFromCache(initialMatch);
399
+ if (syncResult) {
400
+ container.appendChild(syncResult.node);
401
+ currentCleanup = syncResult.cleanup;
402
+ currentMatch = initialMatch;
403
+ }
404
+ }
382
405
  effect(() => {
383
406
  const match = currentRoute();
384
407
  if (match === currentMatch) {
@@ -412,15 +435,58 @@ function createRouterView() {
412
435
  });
413
436
  return container;
414
437
  }
438
+ function tryRenderFromCache(match) {
439
+ const cachedPage = moduleCache.get(match.route.component);
440
+ if (!cachedPage) return null;
441
+ if (match.route.layout && !moduleCache.has(match.route.layout)) return null;
442
+ const cachedLayout = match.route.layout ? moduleCache.get(match.route.layout) : null;
443
+ moduleCache.delete(match.route.component);
444
+ if (match.route.layout) moduleCache.delete(match.route.layout);
445
+ const PageComponent = cachedPage.default ?? cachedPage;
446
+ const LayoutComponent = cachedLayout ? cachedLayout.default ?? cachedLayout : null;
447
+ const pageNode = renderComponent(PageComponent, {
448
+ params: match.params,
449
+ url: match.url
450
+ });
451
+ let node;
452
+ if (LayoutComponent) {
453
+ node = renderComponent(LayoutComponent, {
454
+ params: match.params,
455
+ url: match.url,
456
+ children: pageNode
457
+ });
458
+ } else {
459
+ node = pageNode;
460
+ }
461
+ return {
462
+ node,
463
+ cleanup: () => {
464
+ if (node.parentNode) {
465
+ node.parentNode.removeChild(node);
466
+ }
467
+ }
468
+ };
469
+ }
415
470
  async function loadRouteComponent(match) {
416
471
  try {
417
- const promises = [match.route.component()];
418
- if (match.route.layout) {
419
- promises.push(match.route.layout());
472
+ const cachedPage = moduleCache.get(match.route.component);
473
+ const cachedLayout = match.route.layout ? moduleCache.get(match.route.layout) : void 0;
474
+ let pageModule;
475
+ let layoutModule = null;
476
+ if (cachedPage) {
477
+ pageModule = cachedPage;
478
+ layoutModule = cachedLayout ?? null;
479
+ moduleCache.delete(match.route.component);
480
+ if (match.route.layout) moduleCache.delete(match.route.layout);
481
+ } else {
482
+ const promises = [match.route.component()];
483
+ if (match.route.layout) {
484
+ promises.push(match.route.layout());
485
+ }
486
+ const modules = await Promise.all(promises);
487
+ pageModule = modules[0];
488
+ layoutModule = modules.length > 1 ? modules[1] : null;
420
489
  }
421
- const modules = await Promise.all(promises);
422
- const pageModule = modules[0];
423
- const layoutModule = modules.length > 1 ? modules[1] : null;
424
490
  if (currentRoute.peek() !== match) {
425
491
  return null;
426
492
  }
@@ -617,6 +683,7 @@ export {
617
683
  isNavigating,
618
684
  matchRoute,
619
685
  navigate,
686
+ preloadRoute,
620
687
  queryParams,
621
688
  setQueryParam,
622
689
  setQueryParams
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-router",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "File-based routing for UtopiaJS",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,8 +40,8 @@
40
40
  "dist"
41
41
  ],
42
42
  "dependencies": {
43
- "@matthesketh/utopia-core": "0.5.0",
44
- "@matthesketh/utopia-runtime": "0.5.0"
43
+ "@matthesketh/utopia-core": "0.7.0",
44
+ "@matthesketh/utopia-runtime": "0.7.0"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "tsup src/index.ts --format esm,cjs --dts",