@octanejs/tanstack-router 0.2.7 → 0.2.9

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.
@@ -0,0 +1,178 @@
1
+ 'use strong';
2
+
3
+ import {
4
+ appendUniqueUserTags,
5
+ deepEqual,
6
+ escapeHtml,
7
+ getAssetCrossOrigin,
8
+ getScriptPreloadAttrs,
9
+ resolveManifestCssLink,
10
+ } from '@tanstack/router-core';
11
+ import { isServer } from '@tanstack/router-core/isServer';
12
+ import type { useRouter as UseRouter } from './context';
13
+ import { splitSlot, subSlot } from './internal';
14
+ import { useStore } from './useStore';
15
+ import type {
16
+ AnyRouteMatch,
17
+ AnyRouter,
18
+ AssetCrossOriginConfig,
19
+ RouterManagedTag,
20
+ } from '@tanstack/router-core';
21
+
22
+ function buildTagsFromMatches(
23
+ router: AnyRouter,
24
+ nonce: string | undefined,
25
+ matches: Array<AnyRouteMatch>,
26
+ assetCrossOrigin?: AssetCrossOriginConfig,
27
+ ): Array<RouterManagedTag> {
28
+ const routeMeta = matches.map((match) => match.meta).filter((meta) => meta !== undefined);
29
+
30
+ const resultMeta: Array<RouterManagedTag> = [];
31
+ const metaByAttribute: Record<string, true> = {};
32
+ let title: RouterManagedTag | undefined;
33
+ for (let i = routeMeta.length - 1; i >= 0; i--) {
34
+ const metas = routeMeta[i]!;
35
+ for (let j = metas.length - 1; j >= 0; j--) {
36
+ const meta = metas[j];
37
+ if (!meta) {
38
+ continue;
39
+ }
40
+
41
+ if ('title' in meta && typeof meta.title === 'string') {
42
+ title ??= { tag: 'title', children: meta.title };
43
+ } else if ('script:ld+json' in meta) {
44
+ try {
45
+ resultMeta.push({
46
+ tag: 'script',
47
+ attrs: { type: 'application/ld+json' },
48
+ children: escapeHtml(JSON.stringify(meta['script:ld+json'])),
49
+ });
50
+ } catch {
51
+ // Ignore values that cannot be serialized as JSON-LD.
52
+ }
53
+ } else {
54
+ const attribute =
55
+ ('name' in meta && typeof meta.name === 'string' ? meta.name : undefined) ??
56
+ ('property' in meta && typeof meta.property === 'string' ? meta.property : undefined);
57
+ if (attribute && metaByAttribute[attribute]) {
58
+ continue;
59
+ }
60
+ if (attribute) {
61
+ metaByAttribute[attribute] = true;
62
+ }
63
+ resultMeta.push({ tag: 'meta', attrs: { ...meta, nonce } });
64
+ }
65
+ }
66
+ }
67
+
68
+ if (title) {
69
+ resultMeta.push(title);
70
+ }
71
+ if (nonce) {
72
+ resultMeta.push({
73
+ tag: 'meta',
74
+ attrs: { property: 'csp-nonce', content: nonce },
75
+ });
76
+ }
77
+ resultMeta.reverse();
78
+
79
+ const links = matches
80
+ .flatMap((match) => match.links ?? [])
81
+ .filter((link) => link !== undefined)
82
+ .map((link) => ({ tag: 'link', attrs: { ...link, nonce } }) satisfies RouterManagedTag);
83
+
84
+ const manifestTags: Array<RouterManagedTag> = [];
85
+ const preloadTags: Array<RouterManagedTag> = [];
86
+ const manifest = router.ssr?.manifest;
87
+ if (manifest) {
88
+ for (const match of matches) {
89
+ for (const link of manifest.routes[match.routeId]?.css ?? []) {
90
+ const resolvedLink = resolveManifestCssLink(link);
91
+ manifestTags.push({
92
+ tag: 'link',
93
+ attrs: {
94
+ rel: 'stylesheet',
95
+ ...resolvedLink,
96
+ crossOrigin:
97
+ getAssetCrossOrigin(assetCrossOrigin, 'stylesheet') ?? resolvedLink.crossOrigin,
98
+ nonce,
99
+ },
100
+ });
101
+ }
102
+ for (const preload of manifest.routes[match.routeId]?.preloads ?? []) {
103
+ preloadTags.push({
104
+ tag: 'link',
105
+ attrs: {
106
+ ...getScriptPreloadAttrs(manifest, preload, assetCrossOrigin),
107
+ nonce,
108
+ },
109
+ });
110
+ }
111
+ }
112
+
113
+ if (manifest.inlineStyle) {
114
+ manifestTags.push({
115
+ tag: 'style',
116
+ attrs: { ...manifest.inlineStyle.attrs, nonce },
117
+ children: manifest.inlineStyle.children,
118
+ inlineCss: true,
119
+ });
120
+ }
121
+ }
122
+
123
+ const styles = matches
124
+ .flatMap((match) => match.styles ?? [])
125
+ .filter((style) => style !== undefined)
126
+ .map(
127
+ ({ children, ...attrs }) =>
128
+ ({
129
+ tag: 'style',
130
+ attrs: { ...attrs, nonce },
131
+ children: children,
132
+ }) satisfies RouterManagedTag,
133
+ );
134
+
135
+ const headScripts = matches
136
+ .flatMap((match) => match.headScripts ?? [])
137
+ .filter((script) => script !== undefined)
138
+ .map(
139
+ ({ children, ...attrs }) =>
140
+ ({
141
+ tag: 'script',
142
+ attrs: { ...attrs, nonce },
143
+ children: children,
144
+ }) satisfies RouterManagedTag,
145
+ );
146
+
147
+ const tags: Array<RouterManagedTag> = [];
148
+ appendUniqueUserTags(tags, resultMeta);
149
+ tags.push(...preloadTags);
150
+ appendUniqueUserTags(tags, links);
151
+ tags.push(...manifestTags);
152
+ appendUniqueUserTags(tags, styles);
153
+ appendUniqueUserTags(tags, headScripts);
154
+ return tags;
155
+ }
156
+
157
+ export function createUseTags(useRouter: typeof UseRouter) {
158
+ function useTags(...args: Array<unknown>): Array<RouterManagedTag> {
159
+ const [userArgs, slot] = splitSlot(args);
160
+ const assetCrossOrigin = userArgs[0] as AssetCrossOriginConfig | undefined;
161
+ const router = useRouter();
162
+ const nonce = router.options.ssr?.nonce;
163
+
164
+ if (isServer ?? router.isServer) {
165
+ return buildTagsFromMatches(router, nonce, router.stores.matches.get(), assetCrossOrigin);
166
+ }
167
+
168
+ return useStore(
169
+ router.stores.matches,
170
+ (matches: Array<AnyRouteMatch>) =>
171
+ buildTagsFromMatches(router, nonce, matches, assetCrossOrigin),
172
+ deepEqual,
173
+ subSlot(slot, 'head:tags'),
174
+ );
175
+ }
176
+
177
+ return useTags;
178
+ }
@@ -1,172 +1,4 @@
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
1
  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';
2
+ import { createUseTags } from './headContentBinding';
19
3
 
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
- }
4
+ export const useTags = createUseTags(useRouter);