@octanejs/tanstack-router 0.1.9 → 0.1.11

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 (69) hide show
  1. package/README.md +24 -11
  2. package/package.json +22 -7
  3. package/src/Asset.tsrx +121 -0
  4. package/src/Asset.tsrx.d.ts +9 -0
  5. package/src/Await.tsrx +9 -3
  6. package/src/Await.tsrx.d.ts +8 -6
  7. package/src/Body.ts +31 -0
  8. package/src/CatchBoundary.tsrx +21 -4
  9. package/src/CatchBoundary.tsrx.d.ts +10 -4
  10. package/src/ClientOnly.tsrx +6 -3
  11. package/src/Head.ts +22 -0
  12. package/src/HeadContent.tsrx +41 -0
  13. package/src/HeadContent.tsrx.d.ts +8 -0
  14. package/src/Html.ts +19 -0
  15. package/src/Link.tsrx +19 -4
  16. package/src/Link.tsrx.d.ts +3 -4
  17. package/src/Match.tsrx +61 -29
  18. package/src/MatchRoute.tsrx +6 -3
  19. package/src/Matches.tsrx +7 -7
  20. package/src/Navigate.tsrx +2 -1
  21. package/src/Outlet.tsrx +8 -6
  22. package/src/RouteNotFound.tsrx +2 -2
  23. package/src/RouterProvider.tsrx +12 -2
  24. package/src/RouterProvider.tsrx.d.ts +9 -5
  25. package/src/SafeFragment.tsrx +4 -1
  26. package/src/ScriptOnce.tsrx +23 -0
  27. package/src/ScriptOnce.tsrx.d.ts +3 -0
  28. package/src/Scripts.tsrx +42 -0
  29. package/src/Scripts.tsrx.d.ts +3 -0
  30. package/src/Transitioner.tsrx +15 -13
  31. package/src/assetKeys.ts +11 -0
  32. package/src/context.ts +5 -1
  33. package/src/externalHydration.ts +77 -0
  34. package/src/fileRoute.ts +277 -0
  35. package/src/frameworkTypes.ts +42 -0
  36. package/src/generator-plugin.d.ts +20 -0
  37. package/src/generator-plugin.js +100 -0
  38. package/src/headContentUtils.ts +172 -0
  39. package/src/hooks.ts +151 -0
  40. package/src/index.ts +95 -3
  41. package/src/lazyRouteComponent.ts +2 -1
  42. package/src/link.ts +25 -4
  43. package/src/linkTypes.ts +96 -0
  44. package/src/not-found.tsrx +19 -6
  45. package/src/not-found.tsrx.d.ts +5 -2
  46. package/src/octane-compiler.d.ts +12 -0
  47. package/src/route.ts +473 -36
  48. package/src/routeHookTypes.ts +228 -0
  49. package/src/router.ts +31 -6
  50. package/src/scriptContentUtils.ts +64 -0
  51. package/src/scroll-restoration.tsrx +16 -0
  52. package/src/scroll-restoration.tsrx.d.ts +3 -0
  53. package/src/ssr/RouterClient.tsrx +36 -0
  54. package/src/ssr/RouterClient.tsrx.d.ts +4 -0
  55. package/src/ssr/RouterServer.tsrx +6 -0
  56. package/src/ssr/RouterServer.tsrx.d.ts +4 -0
  57. package/src/ssr/client.ts +4 -0
  58. package/src/ssr/defaultRenderHandler.ts +11 -0
  59. package/src/ssr/defaultStreamHandler.ts +12 -0
  60. package/src/ssr/renderRouterToStream.ts +195 -0
  61. package/src/ssr/renderRouterToString.ts +58 -0
  62. package/src/ssr/server.ts +8 -0
  63. package/src/structuralSharing.ts +41 -0
  64. package/src/typePrimitives.ts +77 -0
  65. package/src/useAwaited.ts +7 -2
  66. package/src/useBlocker.tsrx +73 -8
  67. package/src/useBlocker.tsrx.d.ts +58 -2
  68. package/src/useRouterState.ts +20 -0
  69. package/src/useStore.ts +17 -6
@@ -0,0 +1,277 @@
1
+ import { createRoute } from './route';
2
+ import {
3
+ useLoaderData,
4
+ useLoaderDeps,
5
+ useMatch,
6
+ useNavigate,
7
+ useParams,
8
+ useRouteContext,
9
+ useSearch,
10
+ } from './hooks';
11
+ import { useRouter } from './context';
12
+ import { splitSlot, subSlot } from './internal';
13
+ import type {
14
+ AnyContext,
15
+ AnyRoute,
16
+ AnyRouter,
17
+ Constrain,
18
+ ConstrainLiteral,
19
+ FileBaseRouteOptions,
20
+ FileRoutesByPath,
21
+ LazyRouteOptions,
22
+ Register,
23
+ RegisteredRouter,
24
+ ResolveParams,
25
+ Route,
26
+ RouteById,
27
+ RouteConstraints,
28
+ RouteIds,
29
+ RouteLoaderEntry,
30
+ UpdatableRouteOptions,
31
+ UseNavigateResult,
32
+ } from '@tanstack/router-core';
33
+ import type {
34
+ UseLoaderDataRoute,
35
+ UseLoaderDepsRoute,
36
+ UseMatchRoute,
37
+ UseParamsRoute,
38
+ UseRouteContextRoute,
39
+ UseSearchRoute,
40
+ } from './routeHookTypes';
41
+
42
+ declare const process: {
43
+ env: {
44
+ NODE_ENV?: string;
45
+ };
46
+ };
47
+
48
+ export function createFileRoute<
49
+ TFilePath extends keyof FileRoutesByPath,
50
+ TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]['parentRoute'],
51
+ TId extends RouteConstraints['TId'] = FileRoutesByPath[TFilePath]['id'],
52
+ TPath extends RouteConstraints['TPath'] = FileRoutesByPath[TFilePath]['path'],
53
+ TFullPath extends RouteConstraints['TFullPath'] = FileRoutesByPath[TFilePath]['fullPath'],
54
+ >(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>['createRoute'] {
55
+ return new FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>(path, {
56
+ silent: true,
57
+ }).createRoute;
58
+ }
59
+
60
+ /** @deprecated Use `createFileRoute(path)(options)` instead. */
61
+ export class FileRoute<
62
+ TFilePath extends keyof FileRoutesByPath,
63
+ TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]['parentRoute'],
64
+ TId extends RouteConstraints['TId'] = FileRoutesByPath[TFilePath]['id'],
65
+ TPath extends RouteConstraints['TPath'] = FileRoutesByPath[TFilePath]['path'],
66
+ TFullPath extends RouteConstraints['TFullPath'] = FileRoutesByPath[TFilePath]['fullPath'],
67
+ > {
68
+ silent?: boolean;
69
+
70
+ constructor(
71
+ public path?: TFilePath,
72
+ _opts?: { silent: boolean },
73
+ ) {
74
+ this.silent = _opts?.silent;
75
+ }
76
+
77
+ createRoute = <
78
+ TRegister = Register,
79
+ TSearchValidator = undefined,
80
+ TParams = ResolveParams<TPath>,
81
+ TRouteContextFn = AnyContext,
82
+ TBeforeLoadFn = AnyContext,
83
+ TLoaderDeps extends Record<string, any> = {},
84
+ TLoaderFn = undefined,
85
+ TChildren = unknown,
86
+ TSSR = unknown,
87
+ const TMiddlewares = unknown,
88
+ THandlers = undefined,
89
+ >(
90
+ options?: FileBaseRouteOptions<
91
+ TRegister,
92
+ TParentRoute,
93
+ TId,
94
+ TPath,
95
+ TSearchValidator,
96
+ TParams,
97
+ TLoaderDeps,
98
+ TLoaderFn,
99
+ AnyContext,
100
+ TRouteContextFn,
101
+ TBeforeLoadFn,
102
+ AnyContext,
103
+ TSSR,
104
+ TMiddlewares,
105
+ THandlers
106
+ > &
107
+ UpdatableRouteOptions<
108
+ TParentRoute,
109
+ TId,
110
+ TFullPath,
111
+ TParams,
112
+ TSearchValidator,
113
+ TLoaderFn,
114
+ TLoaderDeps,
115
+ AnyContext,
116
+ TRouteContextFn,
117
+ TBeforeLoadFn
118
+ >,
119
+ ): Route<
120
+ TRegister,
121
+ TParentRoute,
122
+ TPath,
123
+ TFullPath,
124
+ TFilePath,
125
+ TId,
126
+ TSearchValidator,
127
+ TParams,
128
+ AnyContext,
129
+ TRouteContextFn,
130
+ TBeforeLoadFn,
131
+ TLoaderDeps,
132
+ TLoaderFn,
133
+ TChildren,
134
+ unknown,
135
+ TSSR,
136
+ TMiddlewares,
137
+ THandlers
138
+ > => {
139
+ if (process.env.NODE_ENV !== 'production' && !this.silent) {
140
+ console.warn('Warning: FileRoute is deprecated. Use createFileRoute(path)(options) instead.');
141
+ }
142
+ const route = createRoute(options as any);
143
+ (route as any).isRoot = false;
144
+ return route as any;
145
+ };
146
+ }
147
+
148
+ /** @deprecated Place the loader in the main `createFileRoute` options. */
149
+ export function FileRouteLoader<
150
+ TFilePath extends keyof FileRoutesByPath,
151
+ TRoute extends FileRoutesByPath[TFilePath]['preLoaderRoute'],
152
+ >(
153
+ _path: TFilePath,
154
+ ): <TLoaderFn>(
155
+ loaderFn: Constrain<
156
+ TLoaderFn,
157
+ RouteLoaderEntry<
158
+ Register,
159
+ TRoute['parentRoute'],
160
+ TRoute['types']['id'],
161
+ TRoute['types']['params'],
162
+ TRoute['types']['loaderDeps'],
163
+ TRoute['types']['routerContext'],
164
+ TRoute['types']['routeContextFn'],
165
+ TRoute['types']['beforeLoadFn']
166
+ >
167
+ >,
168
+ ) => TLoaderFn {
169
+ if (process.env.NODE_ENV !== 'production') {
170
+ console.warn(
171
+ 'Warning: FileRouteLoader is deprecated. Place the loader in createFileRoute options.',
172
+ );
173
+ }
174
+ return (loaderFn) => loaderFn as never;
175
+ }
176
+
177
+ declare module '@tanstack/router-core' {
178
+ export interface LazyRoute<in out TRoute extends AnyRoute> {
179
+ useMatch: UseMatchRoute<TRoute['id']>;
180
+ useRouteContext: UseRouteContextRoute<TRoute['id']>;
181
+ useSearch: UseSearchRoute<TRoute['id']>;
182
+ useParams: UseParamsRoute<TRoute['id']>;
183
+ useLoaderDeps: UseLoaderDepsRoute<TRoute['id']>;
184
+ useLoaderData: UseLoaderDataRoute<TRoute['id']>;
185
+ useNavigate: () => UseNavigateResult<TRoute['fullPath']>;
186
+ }
187
+ }
188
+
189
+ export class LazyRoute<TRoute extends AnyRoute> {
190
+ options: { id: string } & LazyRouteOptions;
191
+ declare useMatch: UseMatchRoute<TRoute['id']>;
192
+ declare useRouteContext: UseRouteContextRoute<TRoute['id']>;
193
+ declare useSearch: UseSearchRoute<TRoute['id']>;
194
+ declare useParams: UseParamsRoute<TRoute['id']>;
195
+ declare useLoaderDeps: UseLoaderDepsRoute<TRoute['id']>;
196
+ declare useLoaderData: UseLoaderDataRoute<TRoute['id']>;
197
+ declare useNavigate: () => UseNavigateResult<TRoute['fullPath']>;
198
+
199
+ constructor(opts: { id: string } & LazyRouteOptions) {
200
+ this.options = opts;
201
+ const id = this.options.id;
202
+ this.useMatch = ((...args: Array<any>) => {
203
+ const [user, slot] = splitSlot(args);
204
+ const options = user[0] ?? {};
205
+ return useMatch(
206
+ {
207
+ select: options.select,
208
+ from: id,
209
+ structuralSharing: options.structuralSharing,
210
+ },
211
+ subSlot(slot, 'lr:m'),
212
+ );
213
+ }) as typeof this.useMatch;
214
+ this.useRouteContext = ((...args: Array<any>) => {
215
+ const [user, slot] = splitSlot(args);
216
+ return useRouteContext({ ...(user[0] ?? {}), from: id }, subSlot(slot, 'lr:c'));
217
+ }) as typeof this.useRouteContext;
218
+ this.useSearch = ((...args: Array<any>) => {
219
+ const [user, slot] = splitSlot(args);
220
+ const options = user[0] ?? {};
221
+ return useSearch(
222
+ {
223
+ select: options.select,
224
+ from: id,
225
+ structuralSharing: options.structuralSharing,
226
+ },
227
+ subSlot(slot, 'lr:s'),
228
+ );
229
+ }) as typeof this.useSearch;
230
+ this.useParams = ((...args: Array<any>) => {
231
+ const [user, slot] = splitSlot(args);
232
+ const options = user[0] ?? {};
233
+ return useParams(
234
+ {
235
+ select: options.select,
236
+ from: id,
237
+ structuralSharing: options.structuralSharing,
238
+ },
239
+ subSlot(slot, 'lr:p'),
240
+ );
241
+ }) as typeof this.useParams;
242
+ this.useLoaderDeps = ((...args: Array<any>) => {
243
+ const [user, slot] = splitSlot(args);
244
+ return useLoaderDeps({ ...(user[0] ?? {}), from: id }, subSlot(slot, 'lr:d'));
245
+ }) as typeof this.useLoaderDeps;
246
+ this.useLoaderData = ((...args: Array<any>) => {
247
+ const [user, slot] = splitSlot(args);
248
+ return useLoaderData({ ...(user[0] ?? {}), from: id }, subSlot(slot, 'lr:l'));
249
+ }) as typeof this.useLoaderData;
250
+ this.useNavigate = ((...args: Array<any>) => {
251
+ const [, slot] = splitSlot(args);
252
+ const router = useRouter();
253
+ return useNavigate(
254
+ { from: (router.routesById as Record<string, any>)[id].fullPath },
255
+ subSlot(slot, 'lr:n'),
256
+ );
257
+ }) as typeof this.useNavigate;
258
+ }
259
+ }
260
+
261
+ export function createLazyRoute<
262
+ TRouter extends AnyRouter = RegisteredRouter,
263
+ TId extends string = string,
264
+ TRoute extends AnyRoute = RouteById<TRouter['routeTree'], TId>,
265
+ >(id: ConstrainLiteral<TId, RouteIds<TRouter['routeTree']>>) {
266
+ return (opts: LazyRouteOptions) => new LazyRoute<TRoute>({ id, ...opts });
267
+ }
268
+
269
+ export function createLazyFileRoute<
270
+ TFilePath extends keyof FileRoutesByPath,
271
+ TRoute extends FileRoutesByPath[TFilePath]['preLoaderRoute'],
272
+ >(id: TFilePath): (opts: LazyRouteOptions) => LazyRoute<TRoute> {
273
+ if (typeof id === 'object') {
274
+ return new LazyRoute<TRoute>(id) as any;
275
+ }
276
+ return (opts: LazyRouteOptions) => new LazyRoute<TRoute>({ id, ...opts });
277
+ }
@@ -0,0 +1,42 @@
1
+ import type { MetaDescriptor, UseNavigateResult } from '@tanstack/router-core';
2
+ import type { LinkComponentRoute } from './linkTypes';
3
+ import type {
4
+ UseLoaderDataRoute,
5
+ UseLoaderDepsRoute,
6
+ UseMatchRoute,
7
+ UseParamsRoute,
8
+ UseRouteContextRoute,
9
+ UseSearchRoute,
10
+ } from './routeHookTypes';
11
+
12
+ export type OctaneElementAttributes = Record<string, string | number | boolean | null | undefined>;
13
+
14
+ export type OctaneScriptAttributes = OctaneElementAttributes & {
15
+ children?: string;
16
+ };
17
+
18
+ declare module '@tanstack/router-core' {
19
+ interface RouteMatchExtensions {
20
+ // router-core 1.171.15's source RouteMatch carries this field, and its SSR
21
+ // declarations index it while the published Matches.d.ts accidentally omits
22
+ // it. Keep the binding's public SSR entry type-checkable without asking
23
+ // consumers to enable skipLibCheck.
24
+ __beforeLoadContext?: Record<string, unknown>;
25
+ meta?: Array<MetaDescriptor | undefined>;
26
+ links?: Array<OctaneElementAttributes | undefined>;
27
+ scripts?: Array<OctaneScriptAttributes | undefined>;
28
+ styles?: Array<OctaneScriptAttributes | undefined>;
29
+ headScripts?: Array<OctaneScriptAttributes | undefined>;
30
+ }
31
+
32
+ interface RouteExtensions<in out TId extends string, in out TFullPath extends string> {
33
+ useMatch: UseMatchRoute<TId>;
34
+ useRouteContext: UseRouteContextRoute<TId>;
35
+ useSearch: UseSearchRoute<TId>;
36
+ useParams: UseParamsRoute<TId>;
37
+ useLoaderDeps: UseLoaderDepsRoute<TId>;
38
+ useLoaderData: UseLoaderDataRoute<TId>;
39
+ useNavigate: () => UseNavigateResult<TFullPath>;
40
+ Link: LinkComponentRoute<TFullPath>;
41
+ }
42
+ }
@@ -0,0 +1,20 @@
1
+ export interface TransformRouteSourceOptions {
2
+ source: string;
3
+ filename: string;
4
+ node: unknown;
5
+ }
6
+
7
+ export interface FormatRouteOptions {
8
+ source: string;
9
+ node: unknown;
10
+ }
11
+
12
+ export interface OctaneRouteGeneratorPlugin {
13
+ name: string;
14
+ transformRouteSource: (options: TransformRouteSourceOptions) => string;
15
+ formatRoute: (options: FormatRouteOptions) => string;
16
+ }
17
+
18
+ export declare function maskOctaneRouteSource(source: string, filename?: string): string;
19
+
20
+ export declare function octaneRouteGeneratorPlugin(): OctaneRouteGeneratorPlugin;
@@ -0,0 +1,100 @@
1
+ import { compileToVolarMappings } from 'octane/compiler/volar';
2
+
3
+ /**
4
+ * @typedef {object} AstNode
5
+ * @property {AstNode | Array<AstNode>} [body]
6
+ * @property {number} [start]
7
+ * @property {number} [end]
8
+ * @property {{ native_tsrx_body?: boolean }} [metadata]
9
+ */
10
+
11
+ /**
12
+ * Makes TSRX route modules parseable by the router generator without changing
13
+ * source offsets. The generator applies edits to the original source, so the
14
+ * authored Octane template bodies remain byte-for-byte intact.
15
+ *
16
+ * @param {string} source
17
+ * @param {string} [filename]
18
+ * @returns {string}
19
+ */
20
+ export function maskOctaneRouteSource(source, filename = 'route.tsrx') {
21
+ const { sourceAst } = compileToVolarMappings(source, filename);
22
+ const output = source.split('');
23
+
24
+ for (const body of findNativeTemplateBodies(/** @type {AstNode} */ (sourceAst))) {
25
+ const { start, end } = body;
26
+ output[start] = ' ';
27
+ output[start + 1] = '{';
28
+ for (let index = start + 2; index < end - 1; index++) {
29
+ if (source[index] !== '\n' && source[index] !== '\r') {
30
+ output[index] = ' ';
31
+ }
32
+ }
33
+ output[end - 1] = '}';
34
+ }
35
+
36
+ return output.join('');
37
+ }
38
+
39
+ /**
40
+ * @returns {{
41
+ * name: string
42
+ * transformRouteSource: (options: { source: string, filename: string }) => string
43
+ * formatRoute: (options: { source: string }) => string
44
+ * }}
45
+ */
46
+ export function octaneRouteGeneratorPlugin() {
47
+ return {
48
+ name: 'octane-route-source',
49
+ transformRouteSource: ({ source, filename }) => maskOctaneRouteSource(source, filename),
50
+ // Router scaffolds are already formatted. Returning them unchanged avoids
51
+ // passing TSRX's `@{}` syntax through a TypeScript-only formatter.
52
+ formatRoute: ({ source }) => source,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * @param {AstNode} root
58
+ * @returns {Array<{ start: number, end: number }>}
59
+ */
60
+ function findNativeTemplateBodies(root) {
61
+ /** @type {Array<{ start: number, end: number }>} */
62
+ const bodies = [];
63
+ const visited = new WeakSet();
64
+
65
+ /** @param {unknown} value */
66
+ const visit = (value) => {
67
+ if (!value || typeof value !== 'object' || visited.has(value)) {
68
+ return;
69
+ }
70
+ visited.add(value);
71
+
72
+ if (Array.isArray(value)) {
73
+ for (const item of value) {
74
+ visit(item);
75
+ }
76
+ return;
77
+ }
78
+
79
+ const node = /** @type {AstNode} */ (value);
80
+ if (
81
+ node.metadata?.native_tsrx_body === true &&
82
+ node.body &&
83
+ !Array.isArray(node.body) &&
84
+ typeof node.body.start === 'number' &&
85
+ typeof node.body.end === 'number'
86
+ ) {
87
+ bodies.push({ start: node.body.start, end: node.body.end });
88
+ return;
89
+ }
90
+
91
+ for (const [key, child] of Object.entries(node)) {
92
+ if (key !== 'metadata' && key !== 'loc') {
93
+ visit(child);
94
+ }
95
+ }
96
+ };
97
+
98
+ visit(root);
99
+ return bodies;
100
+ }
@@ -0,0 +1,172 @@
1
+ import {
2
+ appendUniqueUserTags,
3
+ deepEqual,
4
+ escapeHtml,
5
+ getAssetCrossOrigin,
6
+ getScriptPreloadAttrs,
7
+ resolveManifestCssLink,
8
+ } from '@tanstack/router-core';
9
+ import { isServer } from '@tanstack/router-core/isServer';
10
+ import { useRouter } from './context';
11
+ import { splitSlot, subSlot } from './internal';
12
+ import { useStore } from './useStore';
13
+ import type {
14
+ AnyRouteMatch,
15
+ AnyRouter,
16
+ AssetCrossOriginConfig,
17
+ RouterManagedTag,
18
+ } from '@tanstack/router-core';
19
+
20
+ function buildTagsFromMatches(
21
+ router: AnyRouter,
22
+ nonce: string | undefined,
23
+ matches: Array<AnyRouteMatch>,
24
+ assetCrossOrigin?: AssetCrossOriginConfig,
25
+ ): Array<RouterManagedTag> {
26
+ const routeMeta = matches.map((match) => match.meta).filter((meta) => meta !== undefined);
27
+
28
+ const resultMeta: Array<RouterManagedTag> = [];
29
+ const metaByAttribute: Record<string, true> = {};
30
+ let title: RouterManagedTag | undefined;
31
+ for (let i = routeMeta.length - 1; i >= 0; i--) {
32
+ const metas = routeMeta[i]!;
33
+ for (let j = metas.length - 1; j >= 0; j--) {
34
+ const meta = metas[j];
35
+ if (!meta) {
36
+ continue;
37
+ }
38
+
39
+ if ('title' in meta && typeof meta.title === 'string') {
40
+ title ??= { tag: 'title', children: meta.title };
41
+ } else if ('script:ld+json' in meta) {
42
+ try {
43
+ resultMeta.push({
44
+ tag: 'script',
45
+ attrs: { type: 'application/ld+json' },
46
+ children: escapeHtml(JSON.stringify(meta['script:ld+json'])),
47
+ });
48
+ } catch {
49
+ // Ignore values that cannot be serialized as JSON-LD.
50
+ }
51
+ } else {
52
+ const attribute =
53
+ ('name' in meta && typeof meta.name === 'string' ? meta.name : undefined) ??
54
+ ('property' in meta && typeof meta.property === 'string' ? meta.property : undefined);
55
+ if (attribute && metaByAttribute[attribute]) {
56
+ continue;
57
+ }
58
+ if (attribute) {
59
+ metaByAttribute[attribute] = true;
60
+ }
61
+ resultMeta.push({ tag: 'meta', attrs: { ...meta, nonce } });
62
+ }
63
+ }
64
+ }
65
+
66
+ if (title) {
67
+ resultMeta.push(title);
68
+ }
69
+ if (nonce) {
70
+ resultMeta.push({
71
+ tag: 'meta',
72
+ attrs: { property: 'csp-nonce', content: nonce },
73
+ });
74
+ }
75
+ resultMeta.reverse();
76
+
77
+ const links = matches
78
+ .flatMap((match) => match.links ?? [])
79
+ .filter((link) => link !== undefined)
80
+ .map((link) => ({ tag: 'link', attrs: { ...link, nonce } }) satisfies RouterManagedTag);
81
+
82
+ const manifestTags: Array<RouterManagedTag> = [];
83
+ const preloadTags: Array<RouterManagedTag> = [];
84
+ const manifest = router.ssr?.manifest;
85
+ if (manifest) {
86
+ for (const match of matches) {
87
+ for (const link of manifest.routes[match.routeId]?.css ?? []) {
88
+ const resolvedLink = resolveManifestCssLink(link);
89
+ manifestTags.push({
90
+ tag: 'link',
91
+ attrs: {
92
+ rel: 'stylesheet',
93
+ ...resolvedLink,
94
+ crossOrigin:
95
+ getAssetCrossOrigin(assetCrossOrigin, 'stylesheet') ?? resolvedLink.crossOrigin,
96
+ nonce,
97
+ },
98
+ });
99
+ }
100
+ for (const preload of manifest.routes[match.routeId]?.preloads ?? []) {
101
+ preloadTags.push({
102
+ tag: 'link',
103
+ attrs: {
104
+ ...getScriptPreloadAttrs(manifest, preload, assetCrossOrigin),
105
+ nonce,
106
+ },
107
+ });
108
+ }
109
+ }
110
+
111
+ if (manifest.inlineStyle) {
112
+ manifestTags.push({
113
+ tag: 'style',
114
+ attrs: { ...manifest.inlineStyle.attrs, nonce },
115
+ children: manifest.inlineStyle.children,
116
+ inlineCss: true,
117
+ });
118
+ }
119
+ }
120
+
121
+ const styles = matches
122
+ .flatMap((match) => match.styles ?? [])
123
+ .filter((style) => style !== undefined)
124
+ .map(
125
+ ({ children, ...attrs }) =>
126
+ ({
127
+ tag: 'style',
128
+ attrs: { ...attrs, nonce },
129
+ children: children,
130
+ }) satisfies RouterManagedTag,
131
+ );
132
+
133
+ const headScripts = matches
134
+ .flatMap((match) => match.headScripts ?? [])
135
+ .filter((script) => script !== undefined)
136
+ .map(
137
+ ({ children, ...attrs }) =>
138
+ ({
139
+ tag: 'script',
140
+ attrs: { ...attrs, nonce },
141
+ children: children,
142
+ }) satisfies RouterManagedTag,
143
+ );
144
+
145
+ const tags: Array<RouterManagedTag> = [];
146
+ appendUniqueUserTags(tags, resultMeta);
147
+ tags.push(...preloadTags);
148
+ appendUniqueUserTags(tags, links);
149
+ tags.push(...manifestTags);
150
+ appendUniqueUserTags(tags, styles);
151
+ appendUniqueUserTags(tags, headScripts);
152
+ return tags;
153
+ }
154
+
155
+ export function useTags(...args: Array<unknown>): Array<RouterManagedTag> {
156
+ const [userArgs, slot] = splitSlot(args);
157
+ const assetCrossOrigin = userArgs[0] as AssetCrossOriginConfig | undefined;
158
+ const router = useRouter();
159
+ const nonce = router.options.ssr?.nonce;
160
+
161
+ if (isServer ?? router.isServer) {
162
+ return buildTagsFromMatches(router, nonce, router.stores.matches.get(), assetCrossOrigin);
163
+ }
164
+
165
+ return useStore(
166
+ router.stores.matches,
167
+ (matches: Array<AnyRouteMatch>) =>
168
+ buildTagsFromMatches(router, nonce, matches, assetCrossOrigin),
169
+ deepEqual,
170
+ subSlot(slot, 'head:tags'),
171
+ );
172
+ }