dh-remixer-sdk 0.0.38 → 0.0.39-2e1e75f
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/package.json
CHANGED
|
@@ -64,6 +64,9 @@ const setForce = (obj, k, v) => {
|
|
|
64
64
|
obj[k] = s;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
const isHelmetOwned = (attrs) =>
|
|
68
|
+
"data-rh" in attrs || "data-react-helmet" in attrs;
|
|
69
|
+
|
|
67
70
|
const normalizeDomain = (d) => {
|
|
68
71
|
const s = String(d || "").trim();
|
|
69
72
|
if (!s) return "";
|
|
@@ -185,6 +188,7 @@ export async function injectMetadata(
|
|
|
185
188
|
// metas
|
|
186
189
|
for (const tag of metaTags) {
|
|
187
190
|
const attrs = parseAttrs(tag);
|
|
191
|
+
const set = isHelmetOwned(attrs) ? setForce : setFirst;
|
|
188
192
|
|
|
189
193
|
if (attrs.charset) {
|
|
190
194
|
setFirst(meta, "charset", decodeHtml(attrs.charset));
|
|
@@ -196,20 +200,20 @@ export async function injectMetadata(
|
|
|
196
200
|
const content = decodeHtml(attrs.content ?? "");
|
|
197
201
|
|
|
198
202
|
if (name === "description") {
|
|
199
|
-
|
|
203
|
+
set(meta, "description", content);
|
|
200
204
|
} else if (name === "robots") {
|
|
201
|
-
|
|
205
|
+
set(meta, "robots", content);
|
|
202
206
|
} else if (name === "viewport") {
|
|
203
|
-
|
|
207
|
+
set(meta, "viewport", content);
|
|
204
208
|
} else if (name === "theme-color") {
|
|
205
|
-
|
|
209
|
+
set(meta, "theme-color", content);
|
|
206
210
|
} else if (name && name in meta) {
|
|
207
|
-
|
|
211
|
+
set(meta, name, content);
|
|
208
212
|
}
|
|
209
213
|
|
|
210
|
-
if (prop && prop in meta)
|
|
214
|
+
if (prop && prop in meta) set(meta, prop, content);
|
|
211
215
|
|
|
212
|
-
if (httpEquiv === "content-language")
|
|
216
|
+
if (httpEquiv === "content-language") set(meta, "og:locale", content);
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
// canonical from <link rel="canonical"...> if present in source
|
|
@@ -39,3 +39,68 @@ test("description uses the name attribute", async () => {
|
|
|
39
39
|
const out = await injectMetadata(SAMPLE, "/", true);
|
|
40
40
|
assert.match(out, /<meta\s+name="description"/);
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
// A real prerendered subpage: index.html's static, site-wide metas (built from
|
|
44
|
+
// VITE_METADATA_*) sit first in <head>, then react-helmet-async appends the
|
|
45
|
+
// page-level ones with data-rh="true" on hydration.
|
|
46
|
+
const SITE_DESCRIPTION = "Site-wide description from VITE_METADATA_DESCRIPTION";
|
|
47
|
+
const PAGE_DESCRIPTION = "Page-specific description from SEOHead";
|
|
48
|
+
const HYDRATED_SUBPAGE = `<!doctype html>
|
|
49
|
+
<html lang="en">
|
|
50
|
+
<head>
|
|
51
|
+
<meta charset="UTF-8" />
|
|
52
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
53
|
+
<title>Services</title>
|
|
54
|
+
<meta name="description" content="${SITE_DESCRIPTION}" />
|
|
55
|
+
<meta name="description" content="${PAGE_DESCRIPTION}" data-rh="true" />
|
|
56
|
+
<meta property="og:description" content="${PAGE_DESCRIPTION}" data-rh="true" />
|
|
57
|
+
</head>
|
|
58
|
+
<body class="antialiased"><div id="root"></div></body>
|
|
59
|
+
</html>`;
|
|
60
|
+
|
|
61
|
+
test("page-level helmet description wins over the static one (AI-7092)", async () => {
|
|
62
|
+
const out = await injectMetadata(HYDRATED_SUBPAGE, "/services", true);
|
|
63
|
+
assert.match(
|
|
64
|
+
out,
|
|
65
|
+
new RegExp(`<meta\\s+name="description"\\s+content="${PAGE_DESCRIPTION}"`),
|
|
66
|
+
);
|
|
67
|
+
assert.doesNotMatch(out, new RegExp(SITE_DESCRIPTION));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("only one description tag is emitted", async () => {
|
|
71
|
+
const out = await injectMetadata(HYDRATED_SUBPAGE, "/services", true);
|
|
72
|
+
assert.equal(out.match(/<meta\s+name="description"/g)?.length, 1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("JSON-LD description follows the page-level description (AI-7092)", async () => {
|
|
76
|
+
const out = await injectMetadata(HYDRATED_SUBPAGE, "/services", true);
|
|
77
|
+
const ldJson = out.match(
|
|
78
|
+
/<script type="application\/ld\+json">([\s\S]*?)<\/script>/,
|
|
79
|
+
)?.[1];
|
|
80
|
+
assert.ok(ldJson, "expected a JSON-LD block");
|
|
81
|
+
assert.equal(JSON.parse(ldJson).description, PAGE_DESCRIPTION);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("a head with only the static description still keeps it", async () => {
|
|
85
|
+
const out = await injectMetadata(SAMPLE, "/", true);
|
|
86
|
+
assert.match(out, /<meta\s+name="description"\s+content="A sample description"/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// og:locale is derived from http-equiv="content-language", which follows the
|
|
90
|
+
// same precedence rule as every other meta: a helmet-owned tag beats the static one.
|
|
91
|
+
const LOCALIZED_PAGE = `<!doctype html>
|
|
92
|
+
<html lang="en">
|
|
93
|
+
<head>
|
|
94
|
+
<meta charset="UTF-8" />
|
|
95
|
+
<meta http-equiv="content-language" content="en_US" />
|
|
96
|
+
<meta http-equiv="content-language" content="fr_FR" data-rh="true" />
|
|
97
|
+
<title>Services</title>
|
|
98
|
+
</head>
|
|
99
|
+
<body class="antialiased"><div id="root"></div></body>
|
|
100
|
+
</html>`;
|
|
101
|
+
|
|
102
|
+
test("helmet content-language wins over the static one for og:locale", async () => {
|
|
103
|
+
const out = await injectMetadata(LOCALIZED_PAGE, "/services", true);
|
|
104
|
+
assert.match(out, /<meta\s+property="og:locale"\s+content="fr_FR"/);
|
|
105
|
+
assert.doesNotMatch(out, /content="en_US"/);
|
|
106
|
+
});
|