@fluixi/head 0.1.0-alpha.20
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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/components.d.ts +26 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +48 -0
- package/dist/core.d.ts +31 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +349 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/jsonld.d.ts +175 -0
- package/dist/jsonld.d.ts.map +1 -0
- package/dist/jsonld.js +62 -0
- package/dist/reconcile.d.ts +9 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +86 -0
- package/dist/serialize.d.ts +15 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +54 -0
- package/dist/sitemap.d.ts +50 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +87 -0
- package/dist/transport.d.ts +19 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +53 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +32 -0
package/dist/jsonld.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
type Obj = Record<string, unknown>;
|
|
2
|
+
export declare const custom: (type: string, data?: Obj) => Obj;
|
|
3
|
+
export declare const organization: (d: {
|
|
4
|
+
name: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
logo?: string;
|
|
7
|
+
sameAs?: string[];
|
|
8
|
+
} & Obj) => Obj;
|
|
9
|
+
export declare const person: (d: {
|
|
10
|
+
name: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
image?: string;
|
|
13
|
+
jobTitle?: string;
|
|
14
|
+
sameAs?: string[];
|
|
15
|
+
} & Obj) => Obj;
|
|
16
|
+
export declare const website: (d: {
|
|
17
|
+
name: string;
|
|
18
|
+
url: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
searchUrl?: string;
|
|
21
|
+
} & Obj) => Obj;
|
|
22
|
+
export declare const webPage: (d: {
|
|
23
|
+
name: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
} & Obj) => Obj;
|
|
27
|
+
export declare const searchAction: (d: {
|
|
28
|
+
target: string;
|
|
29
|
+
queryInput?: string;
|
|
30
|
+
}) => Obj;
|
|
31
|
+
export declare const article: (d: {
|
|
32
|
+
headline: string;
|
|
33
|
+
image?: string | string[];
|
|
34
|
+
author?: string | Obj;
|
|
35
|
+
datePublished?: string;
|
|
36
|
+
dateModified?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
} & Obj) => Obj;
|
|
39
|
+
export declare const blogPosting: (d: Parameters<typeof article>[0]) => Obj;
|
|
40
|
+
export declare const faq: (items: Array<{
|
|
41
|
+
question: string;
|
|
42
|
+
answer: string;
|
|
43
|
+
}>) => Obj;
|
|
44
|
+
export declare const product: (d: {
|
|
45
|
+
name: string;
|
|
46
|
+
image?: string | string[];
|
|
47
|
+
description?: string;
|
|
48
|
+
sku?: string;
|
|
49
|
+
brand?: string;
|
|
50
|
+
offers?: Obj;
|
|
51
|
+
} & Obj) => Obj;
|
|
52
|
+
export declare const breadcrumb: (items: Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
url: string;
|
|
55
|
+
}>) => Obj;
|
|
56
|
+
export declare const videoObject: (d: {
|
|
57
|
+
name: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
thumbnailUrl?: string | string[];
|
|
60
|
+
uploadDate?: string;
|
|
61
|
+
contentUrl?: string;
|
|
62
|
+
embedUrl?: string;
|
|
63
|
+
duration?: string;
|
|
64
|
+
} & Obj) => Obj;
|
|
65
|
+
export declare const event: (d: {
|
|
66
|
+
name: string;
|
|
67
|
+
startDate: string;
|
|
68
|
+
endDate?: string;
|
|
69
|
+
location?: string | Obj;
|
|
70
|
+
description?: string;
|
|
71
|
+
} & Obj) => Obj;
|
|
72
|
+
export declare const localBusiness: (d: {
|
|
73
|
+
name: string;
|
|
74
|
+
address?: string | Obj;
|
|
75
|
+
telephone?: string;
|
|
76
|
+
url?: string;
|
|
77
|
+
image?: string;
|
|
78
|
+
priceRange?: string;
|
|
79
|
+
} & Obj) => Obj;
|
|
80
|
+
export declare const softwareApplication: (d: {
|
|
81
|
+
name: string;
|
|
82
|
+
operatingSystem?: string;
|
|
83
|
+
applicationCategory?: string;
|
|
84
|
+
offers?: Obj;
|
|
85
|
+
aggregateRating?: Obj;
|
|
86
|
+
} & Obj) => Obj;
|
|
87
|
+
export declare const jsonld: {
|
|
88
|
+
custom: (type: string, data?: Obj) => Obj;
|
|
89
|
+
organization: (d: {
|
|
90
|
+
name: string;
|
|
91
|
+
url?: string;
|
|
92
|
+
logo?: string;
|
|
93
|
+
sameAs?: string[];
|
|
94
|
+
} & Obj) => Obj;
|
|
95
|
+
person: (d: {
|
|
96
|
+
name: string;
|
|
97
|
+
url?: string;
|
|
98
|
+
image?: string;
|
|
99
|
+
jobTitle?: string;
|
|
100
|
+
sameAs?: string[];
|
|
101
|
+
} & Obj) => Obj;
|
|
102
|
+
website: (d: {
|
|
103
|
+
name: string;
|
|
104
|
+
url: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
searchUrl?: string;
|
|
107
|
+
} & Obj) => Obj;
|
|
108
|
+
webPage: (d: {
|
|
109
|
+
name: string;
|
|
110
|
+
url?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
} & Obj) => Obj;
|
|
113
|
+
searchAction: (d: {
|
|
114
|
+
target: string;
|
|
115
|
+
queryInput?: string;
|
|
116
|
+
}) => Obj;
|
|
117
|
+
article: (d: {
|
|
118
|
+
headline: string;
|
|
119
|
+
image?: string | string[];
|
|
120
|
+
author?: string | Obj;
|
|
121
|
+
datePublished?: string;
|
|
122
|
+
dateModified?: string;
|
|
123
|
+
description?: string;
|
|
124
|
+
} & Obj) => Obj;
|
|
125
|
+
blogPosting: (d: Parameters<typeof article>[0]) => Obj;
|
|
126
|
+
faq: (items: Array<{
|
|
127
|
+
question: string;
|
|
128
|
+
answer: string;
|
|
129
|
+
}>) => Obj;
|
|
130
|
+
product: (d: {
|
|
131
|
+
name: string;
|
|
132
|
+
image?: string | string[];
|
|
133
|
+
description?: string;
|
|
134
|
+
sku?: string;
|
|
135
|
+
brand?: string;
|
|
136
|
+
offers?: Obj;
|
|
137
|
+
} & Obj) => Obj;
|
|
138
|
+
breadcrumb: (items: Array<{
|
|
139
|
+
name: string;
|
|
140
|
+
url: string;
|
|
141
|
+
}>) => Obj;
|
|
142
|
+
videoObject: (d: {
|
|
143
|
+
name: string;
|
|
144
|
+
description?: string;
|
|
145
|
+
thumbnailUrl?: string | string[];
|
|
146
|
+
uploadDate?: string;
|
|
147
|
+
contentUrl?: string;
|
|
148
|
+
embedUrl?: string;
|
|
149
|
+
duration?: string;
|
|
150
|
+
} & Obj) => Obj;
|
|
151
|
+
event: (d: {
|
|
152
|
+
name: string;
|
|
153
|
+
startDate: string;
|
|
154
|
+
endDate?: string;
|
|
155
|
+
location?: string | Obj;
|
|
156
|
+
description?: string;
|
|
157
|
+
} & Obj) => Obj;
|
|
158
|
+
localBusiness: (d: {
|
|
159
|
+
name: string;
|
|
160
|
+
address?: string | Obj;
|
|
161
|
+
telephone?: string;
|
|
162
|
+
url?: string;
|
|
163
|
+
image?: string;
|
|
164
|
+
priceRange?: string;
|
|
165
|
+
} & Obj) => Obj;
|
|
166
|
+
softwareApplication: (d: {
|
|
167
|
+
name: string;
|
|
168
|
+
operatingSystem?: string;
|
|
169
|
+
applicationCategory?: string;
|
|
170
|
+
offers?: Obj;
|
|
171
|
+
aggregateRating?: Obj;
|
|
172
|
+
} & Obj) => Obj;
|
|
173
|
+
};
|
|
174
|
+
export {};
|
|
175
|
+
//# sourceMappingURL=jsonld.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonld.d.ts","sourceRoot":"","sources":["../src/jsonld.ts"],"names":[],"mappings":"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"}
|
package/dist/jsonld.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed schema.org JSON-LD builders. Each returns a plain object ready for `seo({ jsonLd })` or
|
|
3
|
+
* `<JsonLD data={…} />`. Pass-through extra fields are allowed; `custom()` builds an arbitrary type.
|
|
4
|
+
*/
|
|
5
|
+
const CTX = 'https://schema.org';
|
|
6
|
+
const clean = (o) => {
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const [k, v] of Object.entries(o))
|
|
9
|
+
if (v !== undefined)
|
|
10
|
+
out[k] = v;
|
|
11
|
+
return out;
|
|
12
|
+
};
|
|
13
|
+
export const custom = (type, data = {}) => clean({ '@context': CTX, '@type': type, ...data });
|
|
14
|
+
export const organization = (d) => custom('Organization', d);
|
|
15
|
+
export const person = (d) => custom('Person', d);
|
|
16
|
+
export const website = (d) => {
|
|
17
|
+
const { searchUrl, ...rest } = d;
|
|
18
|
+
return custom('WebSite', {
|
|
19
|
+
...rest,
|
|
20
|
+
...(searchUrl
|
|
21
|
+
? {
|
|
22
|
+
potentialAction: {
|
|
23
|
+
'@type': 'SearchAction',
|
|
24
|
+
target: { '@type': 'EntryPoint', urlTemplate: `${searchUrl}{search_term_string}` },
|
|
25
|
+
'query-input': 'required name=search_term_string',
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
: {}),
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
export const webPage = (d) => custom('WebPage', d);
|
|
32
|
+
export const searchAction = (d) => clean({ '@type': 'SearchAction', target: { '@type': 'EntryPoint', urlTemplate: d.target }, 'query-input': d.queryInput ?? 'required name=search_term_string' });
|
|
33
|
+
export const article = (d) => custom('Article', { ...d, author: typeof d.author === 'string' ? { '@type': 'Person', name: d.author } : d.author });
|
|
34
|
+
export const blogPosting = (d) => ({ ...article(d), '@type': 'BlogPosting' });
|
|
35
|
+
export const faq = (items) => custom('FAQPage', {
|
|
36
|
+
mainEntity: items.map((i) => ({ '@type': 'Question', name: i.question, acceptedAnswer: { '@type': 'Answer', text: i.answer } })),
|
|
37
|
+
});
|
|
38
|
+
export const product = (d) => custom('Product', { ...d, brand: typeof d.brand === 'string' ? { '@type': 'Brand', name: d.brand } : d.brand });
|
|
39
|
+
export const breadcrumb = (items) => custom('BreadcrumbList', {
|
|
40
|
+
itemListElement: items.map((it, i) => ({ '@type': 'ListItem', position: i + 1, name: it.name, item: it.url })),
|
|
41
|
+
});
|
|
42
|
+
export const videoObject = (d) => custom('VideoObject', d);
|
|
43
|
+
export const event = (d) => custom('Event', { ...d, location: typeof d.location === 'string' ? { '@type': 'Place', name: d.location } : d.location });
|
|
44
|
+
export const localBusiness = (d) => custom('LocalBusiness', d);
|
|
45
|
+
export const softwareApplication = (d) => custom('SoftwareApplication', d);
|
|
46
|
+
export const jsonld = {
|
|
47
|
+
custom,
|
|
48
|
+
organization,
|
|
49
|
+
person,
|
|
50
|
+
website,
|
|
51
|
+
webPage,
|
|
52
|
+
searchAction,
|
|
53
|
+
article,
|
|
54
|
+
blogPosting,
|
|
55
|
+
faq,
|
|
56
|
+
product,
|
|
57
|
+
breadcrumb,
|
|
58
|
+
videoObject,
|
|
59
|
+
event,
|
|
60
|
+
localBusiness,
|
|
61
|
+
softwareApplication,
|
|
62
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type HeadRegistry } from './core.js';
|
|
2
|
+
/**
|
|
3
|
+
* Mount reactive head management on the client. Resolves the registry inside an effect so any
|
|
4
|
+
* signal read while resolving re-runs reconciliation — `document.title`, meta, canonical, JSON-LD
|
|
5
|
+
* etc. update live with no reload and no hydration mismatch (server tags are reused by key).
|
|
6
|
+
* Returns a disposer.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mountHead(head: HeadRegistry): () => void;
|
|
9
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AA2D3D;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,IAAI,CAUxD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createRoot, createEffect, onCleanup } from '@fluixi/reactive/signal';
|
|
2
|
+
import { resolveHead } from './core.js';
|
|
3
|
+
import { MANAGED_ATTR, KEY_ATTR } from './serialize.js';
|
|
4
|
+
function setAttrs(el, attrs) {
|
|
5
|
+
// Remove attrs no longer present (except our markers).
|
|
6
|
+
for (const a of Array.from(el.attributes)) {
|
|
7
|
+
if (a.name === MANAGED_ATTR || a.name === KEY_ATTR)
|
|
8
|
+
continue;
|
|
9
|
+
if (!(a.name in attrs))
|
|
10
|
+
el.removeAttribute(a.name);
|
|
11
|
+
}
|
|
12
|
+
for (const [k, v] of Object.entries(attrs))
|
|
13
|
+
if (el.getAttribute(k) !== v)
|
|
14
|
+
el.setAttribute(k, v);
|
|
15
|
+
}
|
|
16
|
+
function createEl(t) {
|
|
17
|
+
const el = document.createElement(t.tag);
|
|
18
|
+
setAttrs(el, t.attrs);
|
|
19
|
+
el.setAttribute(MANAGED_ATTR, '');
|
|
20
|
+
el.setAttribute(KEY_ATTR, t.key);
|
|
21
|
+
if (t.tag === 'script' && t.children != null)
|
|
22
|
+
el.textContent = t.children;
|
|
23
|
+
return el;
|
|
24
|
+
}
|
|
25
|
+
/** Reconcile the resolved tags into <head>, reusing the server-rendered managed nodes by key. */
|
|
26
|
+
function apply(tags, htmlAttrs) {
|
|
27
|
+
const head = document.head;
|
|
28
|
+
// Index existing managed nodes by key.
|
|
29
|
+
const existing = new Map();
|
|
30
|
+
for (const el of Array.from(head.querySelectorAll(`[${MANAGED_ATTR}]`))) {
|
|
31
|
+
const key = el.getAttribute(KEY_ATTR);
|
|
32
|
+
if (el.tagName === 'TITLE')
|
|
33
|
+
continue; // title handled via document.title
|
|
34
|
+
if (key)
|
|
35
|
+
existing.set(key, el);
|
|
36
|
+
}
|
|
37
|
+
let title = null;
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
for (const t of tags) {
|
|
40
|
+
if (t.tag === 'title') {
|
|
41
|
+
title = t.children;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (t.tag === 'htmlAttr')
|
|
45
|
+
continue; // applied to <html>, not <head> (and filtered upstream)
|
|
46
|
+
seen.add(t.key);
|
|
47
|
+
const cur = existing.get(t.key);
|
|
48
|
+
if (cur && cur.tagName === t.tag.toUpperCase()) {
|
|
49
|
+
setAttrs(cur, t.attrs);
|
|
50
|
+
if (t.tag === 'script' && (cur.textContent ?? '') !== (t.children ?? ''))
|
|
51
|
+
cur.textContent = t.children ?? '';
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (cur)
|
|
55
|
+
cur.remove();
|
|
56
|
+
head.appendChild(createEl(t));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Remove managed nodes that are no longer wanted.
|
|
60
|
+
for (const [key, el] of existing)
|
|
61
|
+
if (!seen.has(key))
|
|
62
|
+
el.remove();
|
|
63
|
+
if (title != null && document.title !== title)
|
|
64
|
+
document.title = title;
|
|
65
|
+
for (const [k, v] of Object.entries(htmlAttrs)) {
|
|
66
|
+
if (document.documentElement.getAttribute(k) !== v)
|
|
67
|
+
document.documentElement.setAttribute(k, v);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Mount reactive head management on the client. Resolves the registry inside an effect so any
|
|
72
|
+
* signal read while resolving re-runs reconciliation — `document.title`, meta, canonical, JSON-LD
|
|
73
|
+
* etc. update live with no reload and no hydration mismatch (server tags are reused by key).
|
|
74
|
+
* Returns a disposer.
|
|
75
|
+
*/
|
|
76
|
+
export function mountHead(head) {
|
|
77
|
+
return createRoot((dispose) => {
|
|
78
|
+
createEffect(() => {
|
|
79
|
+
head.version(); // re-run when sources are added/removed (route transitions)
|
|
80
|
+
const { tags, htmlAttrs } = resolveHead(head);
|
|
81
|
+
apply(tags, htmlAttrs);
|
|
82
|
+
});
|
|
83
|
+
onCleanup(() => { });
|
|
84
|
+
return dispose;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HeadRegistry } from './core.js';
|
|
2
|
+
/** The attribute marking a head tag as managed by @fluixi/head (so the client reconciler owns it). */
|
|
3
|
+
export declare const MANAGED_ATTR = "data-fh";
|
|
4
|
+
export declare const KEY_ATTR = "data-fh-key";
|
|
5
|
+
export interface SerializedHead {
|
|
6
|
+
/** Markup to inject inside <head>. */
|
|
7
|
+
headHtml: string;
|
|
8
|
+
/** Attributes to apply to <html> (e.g. lang). */
|
|
9
|
+
htmlAttrs: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/** Render the registry's resolved tags to SSR head markup + <html> attributes. */
|
|
12
|
+
export declare function renderHead(head: HeadRegistry): SerializedHead;
|
|
13
|
+
/** Render just the <html> attribute string (e.g. ` lang="en"`) for templating. */
|
|
14
|
+
export declare function renderHtmlAttrs(htmlAttrs: Record<string, string>): string;
|
|
15
|
+
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAe3D,sGAAsG;AACtG,eAAO,MAAM,YAAY,YAAY,CAAC;AACtC,eAAO,MAAM,QAAQ,gBAAgB,CAAC;AA4BtC,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,cAAc,CAM7D;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAIzE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { resolveHead } from './core.js';
|
|
2
|
+
const VOID = new Set(['meta', 'link', 'base']);
|
|
3
|
+
function escAttr(s) {
|
|
4
|
+
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
5
|
+
}
|
|
6
|
+
function escText(s) {
|
|
7
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
8
|
+
}
|
|
9
|
+
/** Inline <script> bodies (incl. JSON-LD) must not break out of the element. */
|
|
10
|
+
function escScript(s) {
|
|
11
|
+
return s.replace(/<\//g, '<\\/').replace(/<!--/g, '<\\!--');
|
|
12
|
+
}
|
|
13
|
+
/** The attribute marking a head tag as managed by @fluixi/head (so the client reconciler owns it). */
|
|
14
|
+
export const MANAGED_ATTR = 'data-fh';
|
|
15
|
+
export const KEY_ATTR = 'data-fh-key';
|
|
16
|
+
function renderAttrs(attrs) {
|
|
17
|
+
let s = '';
|
|
18
|
+
for (const [k, v] of Object.entries(attrs))
|
|
19
|
+
s += ` ${k}="${escAttr(v)}"`;
|
|
20
|
+
return s;
|
|
21
|
+
}
|
|
22
|
+
function renderTag(t) {
|
|
23
|
+
switch (t.tag) {
|
|
24
|
+
case 'title':
|
|
25
|
+
return `<title ${MANAGED_ATTR}>${escText(t.children)}</title>`;
|
|
26
|
+
case 'meta':
|
|
27
|
+
case 'link':
|
|
28
|
+
case 'base': {
|
|
29
|
+
const marker = ` ${MANAGED_ATTR} ${KEY_ATTR}="${escAttr(t.key)}"`;
|
|
30
|
+
return `<${t.tag}${renderAttrs(t.attrs)}${marker}>`;
|
|
31
|
+
}
|
|
32
|
+
case 'script': {
|
|
33
|
+
const marker = ` ${MANAGED_ATTR} ${KEY_ATTR}="${escAttr(t.key)}"`;
|
|
34
|
+
const body = t.children ? escScript(t.children) : '';
|
|
35
|
+
return `<script${renderAttrs(t.attrs)}${marker}>${body}</script>`;
|
|
36
|
+
}
|
|
37
|
+
default:
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Render the registry's resolved tags to SSR head markup + <html> attributes. */
|
|
42
|
+
export function renderHead(head) {
|
|
43
|
+
const { tags, htmlAttrs } = resolveHead(head);
|
|
44
|
+
// Stable, predictable order: title, base, meta, link, script.
|
|
45
|
+
const order = { title: 0, base: 1, meta: 2, link: 3, script: 4 };
|
|
46
|
+
const sorted = [...tags].sort((a, b) => (order[a.tag] ?? 9) - (order[b.tag] ?? 9));
|
|
47
|
+
return { headHtml: sorted.map(renderTag).join(''), htmlAttrs };
|
|
48
|
+
}
|
|
49
|
+
/** Render just the <html> attribute string (e.g. ` lang="en"`) for templating. */
|
|
50
|
+
export function renderHtmlAttrs(htmlAttrs) {
|
|
51
|
+
return Object.entries(htmlAttrs)
|
|
52
|
+
.map(([k, v]) => ` ${k}="${escAttr(v)}"`)
|
|
53
|
+
.join('');
|
|
54
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** sitemap.xml + robots.txt generators. Pure functions — the build/server wires routes into them. */
|
|
2
|
+
export interface SitemapImage {
|
|
3
|
+
loc: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
caption?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SitemapVideo {
|
|
8
|
+
thumbnailLoc: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
contentLoc?: string;
|
|
12
|
+
playerLoc?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SitemapEntry {
|
|
15
|
+
loc: string;
|
|
16
|
+
lastmod?: string | Date;
|
|
17
|
+
changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
18
|
+
priority?: number;
|
|
19
|
+
/** hreflang alternates. */
|
|
20
|
+
alternates?: Array<{
|
|
21
|
+
hreflang: string;
|
|
22
|
+
href: string;
|
|
23
|
+
}>;
|
|
24
|
+
images?: SitemapImage[];
|
|
25
|
+
videos?: SitemapVideo[];
|
|
26
|
+
}
|
|
27
|
+
/** Build a urlset sitemap. Pass absolute `loc`s. */
|
|
28
|
+
export declare function generateSitemap(entries: SitemapEntry[]): string;
|
|
29
|
+
/** Split a large set into ≤`limit` chunks (default 50k, the sitemap spec cap). */
|
|
30
|
+
export declare function chunkSitemap(entries: SitemapEntry[], limit?: number): SitemapEntry[][];
|
|
31
|
+
/** Build a sitemap index referencing child sitemaps. */
|
|
32
|
+
export declare function generateSitemapIndex(sitemaps: Array<{
|
|
33
|
+
loc: string;
|
|
34
|
+
lastmod?: string | Date;
|
|
35
|
+
}>): string;
|
|
36
|
+
export interface RobotsRule {
|
|
37
|
+
userAgent: string | string[];
|
|
38
|
+
allow?: string | string[];
|
|
39
|
+
disallow?: string | string[];
|
|
40
|
+
crawlDelay?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface RobotsOptions {
|
|
43
|
+
rules?: RobotsRule[];
|
|
44
|
+
host?: string;
|
|
45
|
+
sitemaps?: string[];
|
|
46
|
+
/** When false (e.g. non-production), emit a disallow-all robots so previews aren't indexed. */
|
|
47
|
+
production?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function generateRobots(opts?: RobotsOptions): string;
|
|
50
|
+
//# sourceMappingURL=sitemap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../src/sitemap.ts"],"names":[],"mappings":"AAAA,qGAAqG;AAErG,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AACD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAiCD,oDAAoD;AACpD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAO/D;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,SAAQ,GAAG,YAAY,EAAE,EAAE,CAIrF;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,GAAG,MAAM,CAQtG;AAMD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AACD,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,+FAA+F;IAC/F,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,cAAc,CAAC,IAAI,GAAE,aAAkB,GAAG,MAAM,CAgB/D"}
|
package/dist/sitemap.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** sitemap.xml + robots.txt generators. Pure functions — the build/server wires routes into them. */
|
|
2
|
+
function esc(s) {
|
|
3
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
4
|
+
}
|
|
5
|
+
function isoDate(d) {
|
|
6
|
+
return d instanceof Date ? d.toISOString() : d;
|
|
7
|
+
}
|
|
8
|
+
function renderEntry(e) {
|
|
9
|
+
let s = ' <url>\n';
|
|
10
|
+
s += ` <loc>${esc(e.loc)}</loc>\n`;
|
|
11
|
+
if (e.lastmod != null)
|
|
12
|
+
s += ` <lastmod>${esc(isoDate(e.lastmod))}</lastmod>\n`;
|
|
13
|
+
if (e.changefreq)
|
|
14
|
+
s += ` <changefreq>${e.changefreq}</changefreq>\n`;
|
|
15
|
+
if (e.priority != null)
|
|
16
|
+
s += ` <priority>${e.priority.toFixed(1)}</priority>\n`;
|
|
17
|
+
for (const a of e.alternates ?? [])
|
|
18
|
+
s += ` <xhtml:link rel="alternate" hreflang="${esc(a.hreflang)}" href="${esc(a.href)}"/>\n`;
|
|
19
|
+
for (const img of e.images ?? []) {
|
|
20
|
+
s += ` <image:image>\n <image:loc>${esc(img.loc)}</image:loc>\n`;
|
|
21
|
+
if (img.title)
|
|
22
|
+
s += ` <image:title>${esc(img.title)}</image:title>\n`;
|
|
23
|
+
if (img.caption)
|
|
24
|
+
s += ` <image:caption>${esc(img.caption)}</image:caption>\n`;
|
|
25
|
+
s += ` </image:image>\n`;
|
|
26
|
+
}
|
|
27
|
+
for (const v of e.videos ?? []) {
|
|
28
|
+
s += ` <video:video>\n <video:thumbnail_loc>${esc(v.thumbnailLoc)}</video:thumbnail_loc>\n`;
|
|
29
|
+
s += ` <video:title>${esc(v.title)}</video:title>\n <video:description>${esc(v.description)}</video:description>\n`;
|
|
30
|
+
if (v.contentLoc)
|
|
31
|
+
s += ` <video:content_loc>${esc(v.contentLoc)}</video:content_loc>\n`;
|
|
32
|
+
if (v.playerLoc)
|
|
33
|
+
s += ` <video:player_loc>${esc(v.playerLoc)}</video:player_loc>\n`;
|
|
34
|
+
s += ` </video:video>\n`;
|
|
35
|
+
}
|
|
36
|
+
s += ' </url>\n';
|
|
37
|
+
return s;
|
|
38
|
+
}
|
|
39
|
+
/** Build a urlset sitemap. Pass absolute `loc`s. */
|
|
40
|
+
export function generateSitemap(entries) {
|
|
41
|
+
const ns = 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ' +
|
|
42
|
+
'xmlns:xhtml="http://www.w3.org/1999/xhtml" ' +
|
|
43
|
+
'xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" ' +
|
|
44
|
+
'xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"';
|
|
45
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset ${ns}>\n${entries.map(renderEntry).join('')}</urlset>\n`;
|
|
46
|
+
}
|
|
47
|
+
/** Split a large set into ≤`limit` chunks (default 50k, the sitemap spec cap). */
|
|
48
|
+
export function chunkSitemap(entries, limit = 50000) {
|
|
49
|
+
const out = [];
|
|
50
|
+
for (let i = 0; i < entries.length; i += limit)
|
|
51
|
+
out.push(entries.slice(i, i + limit));
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** Build a sitemap index referencing child sitemaps. */
|
|
55
|
+
export function generateSitemapIndex(sitemaps) {
|
|
56
|
+
let s = '<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
|
|
57
|
+
for (const sm of sitemaps) {
|
|
58
|
+
s += ` <sitemap>\n <loc>${esc(sm.loc)}</loc>\n`;
|
|
59
|
+
if (sm.lastmod != null)
|
|
60
|
+
s += ` <lastmod>${esc(isoDate(sm.lastmod))}</lastmod>\n`;
|
|
61
|
+
s += ' </sitemap>\n';
|
|
62
|
+
}
|
|
63
|
+
return s + '</sitemapindex>\n';
|
|
64
|
+
}
|
|
65
|
+
export function generateRobots(opts = {}) {
|
|
66
|
+
if (opts.production === false) {
|
|
67
|
+
return 'User-agent: *\nDisallow: /\n';
|
|
68
|
+
}
|
|
69
|
+
const lines = [];
|
|
70
|
+
const rules = opts.rules ?? [{ userAgent: '*', allow: '/' }];
|
|
71
|
+
for (const r of rules) {
|
|
72
|
+
for (const ua of [].concat(r.userAgent))
|
|
73
|
+
lines.push(`User-agent: ${ua}`);
|
|
74
|
+
for (const a of [].concat(r.allow ?? []))
|
|
75
|
+
lines.push(`Allow: ${a}`);
|
|
76
|
+
for (const d of [].concat(r.disallow ?? []))
|
|
77
|
+
lines.push(`Disallow: ${d}`);
|
|
78
|
+
if (r.crawlDelay != null)
|
|
79
|
+
lines.push(`Crawl-delay: ${r.crawlDelay}`);
|
|
80
|
+
lines.push('');
|
|
81
|
+
}
|
|
82
|
+
if (opts.host)
|
|
83
|
+
lines.push(`Host: ${opts.host}`);
|
|
84
|
+
for (const sm of opts.sitemaps ?? [])
|
|
85
|
+
lines.push(`Sitemap: ${sm}`);
|
|
86
|
+
return lines.join('\n').replace(/\n+$/, '\n');
|
|
87
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SerializedHead } from './serialize.js';
|
|
2
|
+
/** Encode the serialized head into a marker to prepend to the rendered body. */
|
|
3
|
+
export declare function encodeHeadMarker(head: SerializedHead): string;
|
|
4
|
+
/**
|
|
5
|
+
* Client-side safety net: remove any `<!--fh:…-->` transport comment left inside the hydration
|
|
6
|
+
* container. A correct adapter/prerender/dev server lifts the marker into <head> before it reaches
|
|
7
|
+
* the browser, but a streaming or custom server can leave it in the body. Since encodeHeadMarker
|
|
8
|
+
* prepends it, an un-extracted marker is the mount root's first child — where it strands the
|
|
9
|
+
* hydration cursor (the walk expects the app's first element, finds a comment, and rebuilds the
|
|
10
|
+
* whole tree next to the server DOM → duplicate). The client rebuilds the head from seo()/useHead
|
|
11
|
+
* during hydration, so the marker's payload isn't needed here — just drop the node.
|
|
12
|
+
*/
|
|
13
|
+
export declare function stripHeadMarker(container: ParentNode): void;
|
|
14
|
+
/** Pull the head marker out of rendered HTML, returning the head and the cleaned body. */
|
|
15
|
+
export declare function extractHeadMarker(html: string): {
|
|
16
|
+
head: SerializedHead | null;
|
|
17
|
+
body: string;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAmBrD,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAG7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,UAAU,GAAG,IAAI,CAO3D;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAU7F"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The server can't change the render's return type, so the collected head rides back inside the
|
|
2
|
+
// rendered HTML as one inert comment marker; the adapter/prerender extracts it into <head>. If a
|
|
3
|
+
// custom server doesn't extract it, it's just an invisible comment in the body — no breakage.
|
|
4
|
+
const RE = /<!--fh:([A-Za-z0-9+/=]+)-->/;
|
|
5
|
+
function b64encode(s) {
|
|
6
|
+
const g = globalThis;
|
|
7
|
+
if (g.Buffer)
|
|
8
|
+
return g.Buffer.from(s, 'utf-8').toString('base64');
|
|
9
|
+
return g.btoa(unescape(encodeURIComponent(s)));
|
|
10
|
+
}
|
|
11
|
+
function b64decode(s) {
|
|
12
|
+
const g = globalThis;
|
|
13
|
+
if (g.Buffer)
|
|
14
|
+
return g.Buffer.from(s, 'base64').toString('utf-8');
|
|
15
|
+
return decodeURIComponent(escape(g.atob(s)));
|
|
16
|
+
}
|
|
17
|
+
/** Encode the serialized head into a marker to prepend to the rendered body. */
|
|
18
|
+
export function encodeHeadMarker(head) {
|
|
19
|
+
if (!head.headHtml && Object.keys(head.htmlAttrs).length === 0)
|
|
20
|
+
return '';
|
|
21
|
+
return `<!--fh:${b64encode(JSON.stringify(head))}-->`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Client-side safety net: remove any `<!--fh:…-->` transport comment left inside the hydration
|
|
25
|
+
* container. A correct adapter/prerender/dev server lifts the marker into <head> before it reaches
|
|
26
|
+
* the browser, but a streaming or custom server can leave it in the body. Since encodeHeadMarker
|
|
27
|
+
* prepends it, an un-extracted marker is the mount root's first child — where it strands the
|
|
28
|
+
* hydration cursor (the walk expects the app's first element, finds a comment, and rebuilds the
|
|
29
|
+
* whole tree next to the server DOM → duplicate). The client rebuilds the head from seo()/useHead
|
|
30
|
+
* during hydration, so the marker's payload isn't needed here — just drop the node.
|
|
31
|
+
*/
|
|
32
|
+
export function stripHeadMarker(container) {
|
|
33
|
+
for (let n = container.firstChild; n; n = n.nextSibling) {
|
|
34
|
+
if (n.nodeType === 8 && n.data.startsWith('fh:')) {
|
|
35
|
+
n.parentNode?.removeChild(n);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Pull the head marker out of rendered HTML, returning the head and the cleaned body. */
|
|
41
|
+
export function extractHeadMarker(html) {
|
|
42
|
+
const m = html.match(RE);
|
|
43
|
+
if (!m)
|
|
44
|
+
return { head: null, body: html };
|
|
45
|
+
let head = null;
|
|
46
|
+
try {
|
|
47
|
+
head = JSON.parse(b64decode(m[1]));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
head = null;
|
|
51
|
+
}
|
|
52
|
+
return { head, body: html.replace(m[0], '') };
|
|
53
|
+
}
|