snappy-prerender 2.6.0 → 2.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/CHANGELOG.md +51 -0
- package/README.md +122 -101
- package/docs/limitations.md +58 -0
- package/docs/react-snap-migration.md +45 -0
- package/docs/report.md +102 -0
- package/package.json +2 -1
- package/src/cli-init.js +205 -0
- package/src/cli.js +73 -168
- package/src/core/artifacts.js +2 -0
- package/src/core/audit/index.js +114 -0
- package/src/core/audit/links.js +63 -0
- package/src/core/audit/metadata.js +88 -0
- package/src/core/audit/sitemap.js +39 -0
- package/src/core/config.js +129 -5
- package/src/core/crawl.js +300 -0
- package/src/core/csp.js +127 -0
- package/src/core/frameworks.js +94 -0
- package/src/core/hydration-diff.js +93 -0
- package/src/core/init.js +143 -30
- package/src/core/normalize.js +7 -0
- package/src/core/page-meta.js +108 -0
- package/src/core/preload-links.js +120 -0
- package/src/core/render.js +87 -5
- package/src/core/report.js +108 -0
- package/src/core/resources.js +5 -1
- package/src/core/route-config.js +17 -0
- package/src/core/route-sources.js +209 -0
- package/src/core/run.js +68 -113
- package/src/core/sitemap.js +90 -14
- package/src/core/verify.js +92 -48
- package/src/core/write.js +92 -7
- package/src/head/index.js +2 -2
- package/src/head/manager.js +54 -13
- package/src/head/tags.js +52 -6
package/src/head/tags.js
CHANGED
|
@@ -11,12 +11,19 @@ const SINGLETON_LINK_RELS = new Set(['canonical']);
|
|
|
11
11
|
* caller did not supply are simply absent, so the component never invents metadata the app
|
|
12
12
|
* did not ask for.
|
|
13
13
|
*/
|
|
14
|
-
export function buildHeadEntries(props = {}, defaults = {}) {
|
|
14
|
+
export function buildHeadEntries(props = {}, defaults = {}, id = 'default') {
|
|
15
15
|
const entries = [];
|
|
16
16
|
const template = props.titleTemplate ?? defaults.titleTemplate ?? null;
|
|
17
17
|
const siteName = props.siteName ?? defaults.siteName ?? null;
|
|
18
18
|
const image = props.image ?? defaults.defaultImage ?? null;
|
|
19
19
|
|
|
20
|
+
if (typeof props.lang === 'string' && props.lang.length > 0) {
|
|
21
|
+
entries.push({ kind: 'html', key: 'lang', value: props.lang });
|
|
22
|
+
}
|
|
23
|
+
if (props.dir === 'ltr' || props.dir === 'rtl' || props.dir === 'auto') {
|
|
24
|
+
entries.push({ kind: 'html', key: 'dir', value: props.dir });
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
const title = props.title ? applyTitleTemplate(props.title, template, siteName) : null;
|
|
21
28
|
if (title) entries.push({ kind: 'title', text: title });
|
|
22
29
|
if (title && props.openGraph !== false) entries.push(metaProp('og:title', title));
|
|
@@ -50,6 +57,24 @@ export function buildHeadEntries(props = {}, defaults = {}) {
|
|
|
50
57
|
entries.push({ kind: 'link', key: 'rel:canonical', attrs: { rel: 'canonical', href: url } });
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
for (const alternate of props.alternates ?? []) {
|
|
61
|
+
const href = pageUrl(alternate.href, defaults);
|
|
62
|
+
const hreflang = alternate.hreflang ?? alternate.lang;
|
|
63
|
+
if (!href || !hreflang) continue;
|
|
64
|
+
const attrs = { rel: 'alternate', hreflang, href };
|
|
65
|
+
if (alternate.type) attrs.type = alternate.type;
|
|
66
|
+
entries.push({ kind: 'link', key: `rel:alternate|${hreflang}|${href}`, attrs });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (props.structuredData !== undefined && props.structuredData !== null) {
|
|
70
|
+
entries.push({
|
|
71
|
+
kind: 'script',
|
|
72
|
+
key: `script:ld+json:${id}`,
|
|
73
|
+
attrs: { type: 'application/ld+json' },
|
|
74
|
+
text: serializeStructuredData(props.structuredData),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
for (const extra of props.extra ?? []) entries.push(extraEntry(extra));
|
|
54
79
|
|
|
55
80
|
return entries;
|
|
@@ -77,6 +102,8 @@ export function mergeHeadEntries(stack = []) {
|
|
|
77
102
|
*/
|
|
78
103
|
export function headKey(entry) {
|
|
79
104
|
if (entry.kind === 'title') return 'title';
|
|
105
|
+
if (entry.kind === 'html') return `html:${entry.key}`;
|
|
106
|
+
if (entry.kind === 'script') return entry.key;
|
|
80
107
|
if (entry.kind === 'link') return `link:${entry.key ?? attrsKey(entry.attrs)}`;
|
|
81
108
|
if (entry.key) return `meta:${entry.key}`;
|
|
82
109
|
const name = attrsValue(entry.attrs, 'name');
|
|
@@ -195,10 +222,29 @@ function attrsValue(attrs = {}, name) {
|
|
|
195
222
|
return attrs[name];
|
|
196
223
|
}
|
|
197
224
|
|
|
198
|
-
//
|
|
225
|
+
// Serialises JSON-LD so a value can never close the script element. The escapes remain
|
|
226
|
+
// valid JSON, so crawlers parse the same object the caller supplied.
|
|
227
|
+
function serializeStructuredData(value) {
|
|
228
|
+
return JSON.stringify(value).replace(
|
|
229
|
+
/[<>&\u2028\u2029]/g,
|
|
230
|
+
(char) =>
|
|
231
|
+
({
|
|
232
|
+
'<': '\\u003C',
|
|
233
|
+
'>': '\\u003E',
|
|
234
|
+
'&': '\\u0026',
|
|
235
|
+
'\u2028': '\\u2028',
|
|
236
|
+
'\u2029': '\\u2029',
|
|
237
|
+
})[char],
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Keeps output deterministic: title, metas, links, JSON-LD scripts, then html attributes.
|
|
199
242
|
function sortMetasFirst(entries) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
243
|
+
const rank = (entry) => {
|
|
244
|
+
if (entry.kind === 'meta') return 0;
|
|
245
|
+
if (entry.kind === 'link') return 1;
|
|
246
|
+
if (entry.kind === 'script') return 2;
|
|
247
|
+
return 3;
|
|
248
|
+
};
|
|
249
|
+
return [...entries].sort((a, b) => rank(a) - rank(b));
|
|
204
250
|
}
|