@octanejs/tanstack-router 0.2.9 → 0.3.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.
Files changed (48) hide show
  1. package/README.md +23 -7
  2. package/package.json +4 -3
  3. package/src/Asset.tsrx +1 -1
  4. package/src/Await.tsrx +1 -1
  5. package/src/CatchBoundary.tsrx +11 -41
  6. package/src/ClientOnly.tsrx +1 -1
  7. package/src/ErrorComponent.tsrx +38 -0
  8. package/src/HeadContent.tsrx +4 -2
  9. package/src/Link.tsrx +3 -1
  10. package/src/Link.tsrx.d.ts +3 -0
  11. package/src/Match.tsrx +3 -3
  12. package/src/MatchRoute.tsrx +5 -5
  13. package/src/Matches.tsrx +6 -19
  14. package/src/Navigate.tsrx +1 -1
  15. package/src/Outlet.tsrx +2 -2
  16. package/src/RouteNotFound.tsrx +1 -1
  17. package/src/RouterProvider.tsrx +1 -1
  18. package/src/ScriptOnce.tsrx +14 -15
  19. package/src/Scripts.tsrx +1 -1
  20. package/src/ScrollRestoration.tsrx +1 -1
  21. package/src/Transitioner.tsrx +80 -62
  22. package/src/assetComponent.tsrx +1 -1
  23. package/src/compiled/Document.tsrx +2 -0
  24. package/src/compiled/Link.tsrx +1 -3
  25. package/src/compiled/Matches.tsrx +128 -120
  26. package/src/compiled/RouterProvider.tsrx +27 -0
  27. package/src/compiled.ts +6 -3
  28. package/src/core.ts +8 -0
  29. package/src/externalHydration.ts +2 -2
  30. package/src/fileRoute.ts +1 -1
  31. package/src/headContentBinding.ts +10 -15
  32. package/src/history.ts +16 -8
  33. package/src/historyState.ts +37 -0
  34. package/src/hookBindings.ts +2 -0
  35. package/src/index.ts +4 -4
  36. package/src/{link.ts → linkApi.ts} +1 -3
  37. package/src/linkBinding.ts +14 -10
  38. package/src/linkComponent.tsrx +3 -3
  39. package/src/not-found.tsrx +4 -4
  40. package/src/route.ts +2 -1
  41. package/src/router.ts +5 -0
  42. package/src/routerStateBinding.ts +2 -0
  43. package/src/scriptComponent.tsrx +22 -15
  44. package/src/scroll-restoration.tsrx +1 -1
  45. package/src/ssr/RouterClient.tsrx +1 -1
  46. package/src/ssr/renderRouterToStream.ts +13 -4
  47. package/src/useBlocker.tsrx +1 -1
  48. package/src/useStore.ts +2 -0
@@ -6,14 +6,14 @@
6
6
  import { isNotFound } from '@tanstack/router-core';
7
7
  import type { ErrorComponentProps, NotFoundError } from '@tanstack/router-core';
8
8
  import type { OctaneNode } from 'octane';
9
- import type { ErrorInfo } from './route.ts';
10
- import { useRouter } from './context.ts';
11
- import { useStore } from './useStore.ts';
9
+ import type { ErrorInfo } from './route';
10
+ import { useRouter } from './context';
11
+ import { useStore } from './useStore';
12
12
  import { CatchBoundary } from './CatchBoundary.tsrx';
13
13
 
14
14
  function NotFoundFallback(props: {
15
15
  error: unknown;
16
- render?: (error: NotFoundError) => OctaneNode;
16
+ render?: ((error: NotFoundError) => OctaneNode) | undefined;
17
17
  }) {
18
18
  if (isNotFound(props.error)) {
19
19
  return props.render ? props.render(props.error) : null;
package/src/route.ts CHANGED
@@ -637,7 +637,8 @@ export class RouteApi<TId, TRouter extends AnyRouter = RegisteredRouter> extends
637
637
  }) as unknown as typeof this.Link;
638
638
  }
639
639
 
640
- notFound = (opts?: NotFoundError) => notFound({ routeId: this.id, ...opts } as NotFoundError);
640
+ override notFound = (opts?: NotFoundError) =>
641
+ notFound({ routeId: this.id, ...opts } as NotFoundError);
641
642
  }
642
643
 
643
644
  export function getRouteApi<const TId, TRouter extends AnyRouter = RegisteredRouter>(
package/src/router.ts CHANGED
@@ -11,7 +11,9 @@ import {
11
11
  createNonReactiveMutableStore,
12
12
  createNonReactiveReadonlyStore,
13
13
  } from '@tanstack/router-core';
14
+ import { isServer } from '@tanstack/router-core/isServer';
14
15
  import { createAtom, batch } from '@tanstack/store';
16
+ import { prepareInitialHistoryState } from './historyState';
15
17
  import type { RouterHistory } from '@tanstack/history';
16
18
  import type {
17
19
  AnyRoute,
@@ -73,6 +75,9 @@ export class Router<
73
75
  // render-readiness boundary and the previous view-transition/load wrapper is
74
76
  // no longer needed. HTTP status is derived on demand by the SSR layer
75
77
  // (`getSsrStatus`), not stored on the router.
78
+ if (!options.history && !(isServer ?? options.isServer ?? isServerEnv)) {
79
+ prepareInitialHistoryState();
80
+ }
76
81
  super(options, octaneStoreFactory);
77
82
  }
78
83
  }
@@ -1,3 +1,5 @@
1
+ 'use strong';
2
+
1
3
  // The reactive base every router hook reads through: subscribe to the router's
2
4
  // canonical snapshot store (`router.stores.__store`) and select a slice.
3
5
  import { useStore } from './useStore';
@@ -1,26 +1,33 @@
1
- import { createElement, useId } from 'octane';
1
+ 'use strong';
2
+
3
+ import { trustHTML, useId } from 'octane';
2
4
  import type { RouterManagedTag } from '@tanstack/router-core';
3
- import { getAssetKeys } from './assetKeys.ts';
5
+ import { getAssetKeys } from './assetKeys';
6
+
7
+ function scriptAttributes(attrs: RouterManagedTag['attrs']) {
8
+ if (!attrs || !Object.prototype.hasOwnProperty.call(attrs, '__executeOnMount')) return attrs;
9
+ const { __executeOnMount: _compilerMetadata, ...attributes } = attrs;
10
+ return attributes;
11
+ }
4
12
 
5
- // router-core's initial hydration scripts remove themselves before adoption.
6
- // Host descriptors leave no per-item template frames behind when they do, and
7
- // create live script elements that execute on later client navigation. This
8
- // shared owner intentionally remains outside Strong mode for both entries.
13
+ // A direct host row leaves no item frame behind when router-core's transport
14
+ // scripts remove themselves before hydration.
15
+ // External scripts follow dynamic-insertion loading rules: neither array order
16
+ // nor a defer attribute serializes their execution after client navigation.
9
17
  export function createScripts(useScripts: () => Array<RouterManagedTag>) {
10
18
  function Scripts() @{
11
19
  const owner = useId();
12
20
  const scripts = useScripts();
13
21
  const keys = getAssetKeys('body', owner, scripts);
14
22
  <>
15
- {scripts.map((script, index) => {
16
- const { assetKey, managedKey } = keys[index]!;
17
- return createElement('script', {
18
- ...script.attrs,
19
- key: assetKey,
20
- 'data-tsr-managed-key': managedKey,
21
- dangerouslySetInnerHTML: { __html: script.children ?? '' },
22
- });
23
- })}
23
+ @for (const script of scripts; index position; key keys[position]!.assetKey) {
24
+ <script
25
+ {...scriptAttributes(script.attrs)}
26
+ __executeOnMount
27
+ data-tsr-managed-key={keys[position]!.managedKey}
28
+ dangerouslySetInnerHTML={trustHTML(script.children ?? '')}
29
+ />
30
+ }
24
31
  </>
25
32
  }
26
33
  return Scripts;
@@ -7,7 +7,7 @@
7
7
  // server's script.
8
8
  import { getScrollRestorationScriptForRouter } from '@tanstack/router-core/scroll-restoration-script';
9
9
  import type { AnyRouter } from '@tanstack/router-core';
10
- import { useRouter } from './context.ts';
10
+ import { useRouter } from './context';
11
11
  import { ScriptOnce } from './ScriptOnce.tsrx';
12
12
 
13
13
  function rendersScript(router: AnyRouter): boolean {
@@ -2,7 +2,7 @@ import { use } from 'octane';
2
2
  import { hydrate } from '@tanstack/router-core/ssr/client';
3
3
  import type { AnyRouter } from '@tanstack/router-core';
4
4
  import { RouterProvider } from '../RouterProvider.tsrx';
5
- import { toExternalHydrationThenable } from '../externalHydration.ts';
5
+ import { toExternalHydrationThenable } from '../externalHydration';
6
6
 
7
7
  const hydrationPromises = new WeakMap<AnyRouter, PromiseLike<void>>();
8
8
  const ready = {
@@ -95,9 +95,13 @@ export async function renderRouterToStream({
95
95
  // transform does). So, for a document render, fold the leading style run into the
96
96
  // shell `<head>` and prepend the doctype — what octane's native-injection
97
97
  // document mode (and, before it, an explicit relocate transform) used to do. Only
98
- // the shell prefix (through `</head>`) is buffered; everything after streams
99
- // straight through, preserving out-of-order boundary flushing. A non-document
100
- // render (no `</head>`) passes through unchanged.
98
+ // the styles move: the comment naming octane's hydration protocol stays before
99
+ // `<html>`, where hydrateRoot reads it. Only the shell prefix (through `</head>`)
100
+ // is buffered; everything after streams straight through, preserving
101
+ // out-of-order boundary flushing. A non-document render (no `</head>`) passes
102
+ // through unchanged.
103
+ const LEADING_STYLE = /<style\b[\s\S]*?<\/style>/gi;
104
+
101
105
  export function finalizeDocumentShell(
102
106
  source: ReadableStream<Uint8Array>,
103
107
  ): ReadableStream<Uint8Array> {
@@ -131,9 +135,14 @@ export function finalizeDocumentShell(
131
135
  if (htmlStart >= 0) {
132
136
  if (htmlStart > 0) {
133
137
  const leading = prefix.slice(0, htmlStart);
138
+ const styles = leading.match(LEADING_STYLE) ?? [];
134
139
  const doc = prefix.slice(htmlStart);
135
140
  const headClose = doc.indexOf('</head>');
136
- prefix = doc.slice(0, headClose) + leading + doc.slice(headClose);
141
+ prefix =
142
+ leading.replace(LEADING_STYLE, '') +
143
+ doc.slice(0, headClose) +
144
+ styles.join('') +
145
+ doc.slice(headClose);
137
146
  }
138
147
  if (!/^\s*<!doctype/i.test(prefix)) prefix = '<!DOCTYPE html>' + prefix;
139
148
  }
@@ -9,7 +9,7 @@
9
9
  import { useState, useEffect, isChildrenBlock } from 'octane';
10
10
  import type { OctaneNode } from 'octane';
11
11
  import type { BlockerFnArgs, HistoryAction, HistoryLocation } from '@tanstack/history';
12
- import { useRouter } from './context.ts';
12
+ import { useRouter } from './context';
13
13
 
14
14
  // Blocker types, ported from react-router's useBlocker.tsx. Upstream derives the
15
15
  // location shapes from the registered route tree's generics; the octane binding's
package/src/useStore.ts CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strong';
2
+
1
3
  // The keystone adapter. Everything reactive in the router funnels through
2
4
  // `@tanstack/react-store`'s `useStore(atom, selector)`, which is
3
5
  // `useSyncExternalStoreWithSelector(atom.subscribe→unsubscribe, atom.get, …,