mnfst-render 0.3.8 → 0.4.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/manifest.render.mjs +37 -16
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -897,6 +897,10 @@ function stripDuplicatedLoopDirectives(html) {
|
|
|
897
897
|
return html;
|
|
898
898
|
}
|
|
899
899
|
|
|
900
|
+
function isHydrateMarkedAttrs(attrsStr) {
|
|
901
|
+
return /\sdata-prerender-hydrate(?:\s*=|[\s>])/i.test(attrsStr || '');
|
|
902
|
+
}
|
|
903
|
+
|
|
900
904
|
// --- Strip x-text and x-html that reference $x when static/SEO (content already in snapshot).
|
|
901
905
|
// Do NOT strip when expression is user-driven: $route(, $search, $query. Those stay so Alpine can update.
|
|
902
906
|
// Same rule for :attr in stripPrerenderDynamicBindings: bindings with $x are kept (content stays for SEO).
|
|
@@ -906,9 +910,13 @@ function stripPrerenderedXDataDirectives(html) {
|
|
|
906
910
|
if (expr.includes('$search') || expr.includes('$query')) return false;
|
|
907
911
|
return true;
|
|
908
912
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
913
|
+
return html.replace(/<(\w+)([^>]*)>/g, (full, tag, attrs) => {
|
|
914
|
+
if (isHydrateMarkedAttrs(attrs)) return full;
|
|
915
|
+
let outAttrs = attrs;
|
|
916
|
+
outAttrs = outAttrs.replace(/\s+x-text="([^"]*\$x[^"]*)"/g, (match, expr) => (isStatic(expr) ? '' : match));
|
|
917
|
+
outAttrs = outAttrs.replace(/\s+x-html="([^"]*\$x[^"]*)"/g, (match, expr) => (isStatic(expr) ? '' : match));
|
|
918
|
+
return `<${tag}${outAttrs}>`;
|
|
919
|
+
});
|
|
912
920
|
}
|
|
913
921
|
|
|
914
922
|
// --- Don't bake Alpine-only state into the snapshot; only $x-driven content should be prerendered.
|
|
@@ -921,6 +929,7 @@ function stripPrerenderedXDataDirectives(html) {
|
|
|
921
929
|
function stripPrerenderDynamicBindings(html) {
|
|
922
930
|
return html.replace(/<(\w+)([^>]*)>/g, (match, tagName, attrsStr) => {
|
|
923
931
|
if (tagName.toLowerCase() === 'script') return match;
|
|
932
|
+
if (isHydrateMarkedAttrs(attrsStr)) return match;
|
|
924
933
|
const isAnchor = tagName.toLowerCase() === 'a';
|
|
925
934
|
const isImg = tagName.toLowerCase() === 'img';
|
|
926
935
|
let workAttrs = attrsStr;
|
|
@@ -960,6 +969,7 @@ function stripPrerenderDynamicBindings(html) {
|
|
|
960
969
|
// Drop :src / x-bind:src when img already has a baked src= (x-for / iterator expressions break hydrate).
|
|
961
970
|
function stripRedundantImgSrcBindings(html) {
|
|
962
971
|
return html.replace(/<img\b([^>]*)>/gi, (full, attrs) => {
|
|
972
|
+
if (isHydrateMarkedAttrs(attrs)) return full;
|
|
963
973
|
const srcM = attrs.match(/\ssrc=(["'])([\s\S]*?)\1/i);
|
|
964
974
|
if (!srcM || !String(srcM[2] || '').trim()) return full;
|
|
965
975
|
if (!/\s:src\s*=|\sx-bind:src\s*=/i.test(attrs)) return full;
|
|
@@ -978,6 +988,7 @@ function stripRedundantImgSrcBindings(html) {
|
|
|
978
988
|
// loop/item expressions while the attribute remains for CSS (e.g. inline layout that keys off [x-icon]).
|
|
979
989
|
function stripResolvedXIconDirectives(html) {
|
|
980
990
|
return html.replace(/<i\b([^>]*)>([\s\S]*?)<\/i>/gi, (full, attrs, inner) => {
|
|
991
|
+
if (isHydrateMarkedAttrs(attrs)) return full;
|
|
981
992
|
if (!/\sx-icon\s*=/i.test(attrs)) return full;
|
|
982
993
|
if (!/<svg\b/i.test(inner) || !/\bdata-icon\s*=/i.test(inner)) return full;
|
|
983
994
|
const cleaned = attrs
|
|
@@ -989,6 +1000,10 @@ function stripResolvedXIconDirectives(html) {
|
|
|
989
1000
|
});
|
|
990
1001
|
}
|
|
991
1002
|
|
|
1003
|
+
function stripPrerenderHydrateMarkers(html) {
|
|
1004
|
+
return html.replace(/\sdata-prerender-hydrate(?:=(?:"[^"]*"|'[^']*'|[^\s>]+))?/gi, '');
|
|
1005
|
+
}
|
|
1006
|
+
|
|
992
1007
|
function markPrerenderedManifestComponents(html) {
|
|
993
1008
|
return html.replace(/<(x-[a-z][\w-]*)([^>]*)>/gi, (full, tag, attrs) => {
|
|
994
1009
|
const a = attrs || '';
|
|
@@ -1385,7 +1400,7 @@ function startStaticServer(rootDir) {
|
|
|
1385
1400
|
// --- Copy project into output so website is self-contained (e.g. for Appwrite). ---
|
|
1386
1401
|
const COPY_EXCLUDE = new Set([
|
|
1387
1402
|
'node_modules', '.git', 'package.json', 'package-lock.json',
|
|
1388
|
-
'index.html', 'prerender.mjs', 'prerender.js',
|
|
1403
|
+
'index.html', 'prerender.mjs', 'prerender.js', '_redirects',
|
|
1389
1404
|
]);
|
|
1390
1405
|
|
|
1391
1406
|
function copyProjectIntoDist(rootResolved, outputResolved) {
|
|
@@ -1795,15 +1810,27 @@ async function runPrerender(config) {
|
|
|
1795
1810
|
});
|
|
1796
1811
|
});
|
|
1797
1812
|
|
|
1813
|
+
// Mark data-hydrate islands so static compile transforms skip them.
|
|
1814
|
+
await page.evaluate(() => {
|
|
1815
|
+
document.querySelectorAll('[data-hydrate]').forEach((root) => {
|
|
1816
|
+
root.setAttribute('data-prerender-hydrate', '1');
|
|
1817
|
+
root.querySelectorAll('*').forEach((el) => el.setAttribute('data-prerender-hydrate', '1'));
|
|
1818
|
+
});
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1798
1821
|
// x-for lists: keep static lists in the HTML for SEO; collapse only dynamic lists so Alpine re-renders.
|
|
1799
|
-
// Explicit: data-
|
|
1822
|
+
// Explicit: data-prerender="dynamic"|"skip". Inferred: x-for uses $search/$query,
|
|
1800
1823
|
// $url, $auth, or iterates over getter names (filtered*, results, searchResults). See docs prerender + local.data.
|
|
1801
1824
|
await page.evaluate(() => {
|
|
1802
1825
|
document.querySelectorAll('template[x-for]').forEach((tpl) => {
|
|
1826
|
+
if (tpl.hasAttribute('data-prerender-hydrate') || tpl.closest('[data-prerender-hydrate]')) {
|
|
1827
|
+
tpl.removeAttribute('data-prerender-collapsed');
|
|
1828
|
+
tpl.removeAttribute('data-prerender-static-generated');
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1803
1831
|
const xFor = (tpl.getAttribute('x-for') || '').trim();
|
|
1804
1832
|
const prerender = (tpl.getAttribute('data-prerender') || '').toLowerCase();
|
|
1805
|
-
const
|
|
1806
|
-
const explicit = hasDataDynamic || prerender === 'dynamic' || prerender === 'skip';
|
|
1833
|
+
const explicit = prerender === 'dynamic' || prerender === 'skip';
|
|
1807
1834
|
const inferred = xFor.includes('$search') || xFor.includes('$query') ||
|
|
1808
1835
|
xFor.includes('$url') || xFor.includes('$auth') ||
|
|
1809
1836
|
/\bin\s+(filtered\w*|results|searchResults)\b/.test(xFor);
|
|
@@ -1917,6 +1944,7 @@ async function runPrerender(config) {
|
|
|
1917
1944
|
};
|
|
1918
1945
|
|
|
1919
1946
|
document.querySelectorAll('template[x-for]').forEach((tpl) => {
|
|
1947
|
+
if (tpl.hasAttribute('data-prerender-hydrate') || tpl.closest('[data-prerender-hydrate]')) return;
|
|
1920
1948
|
const xFor = (tpl.getAttribute('x-for') || '').trim();
|
|
1921
1949
|
const m = xFor.match(loopVarRegex);
|
|
1922
1950
|
const itemVar = m ? (m[1] || m[3] || '') : '';
|
|
@@ -1945,6 +1973,7 @@ async function runPrerender(config) {
|
|
|
1945
1973
|
const runBatch = typeof A?.mutateDom === 'function' ? (fn) => A.mutateDom(fn) : (fn) => fn();
|
|
1946
1974
|
runBatch(() => {
|
|
1947
1975
|
document.querySelectorAll('template[x-for][data-prerender-static-generated="1"]').forEach((tpl) => {
|
|
1976
|
+
if (tpl.hasAttribute('data-prerender-hydrate') || tpl.closest('[data-prerender-hydrate]')) return;
|
|
1948
1977
|
const parent = tpl.parentNode;
|
|
1949
1978
|
if (!parent) {
|
|
1950
1979
|
tpl.remove();
|
|
@@ -2017,15 +2046,6 @@ async function runPrerender(config) {
|
|
|
2017
2046
|
});
|
|
2018
2047
|
});
|
|
2019
2048
|
|
|
2020
|
-
// Remove elements marked data-dynamic (so they are not in static HTML; client will render them).
|
|
2021
|
-
// Skip <template> since we only collapse those above; other elements and their subtree are removed.
|
|
2022
|
-
await page.evaluate(() => {
|
|
2023
|
-
const toRemove = Array.from(document.querySelectorAll('[data-dynamic]')).filter((el) => el.tagName !== 'TEMPLATE');
|
|
2024
|
-
const depth = (el) => { let d = 0; let n = el; while (n && n !== document.body) { d++; n = n.parentElement; } return d; };
|
|
2025
|
-
toRemove.sort((a, b) => depth(a) - depth(b));
|
|
2026
|
-
toRemove.forEach((el) => { if (document.contains(el)) el.remove(); });
|
|
2027
|
-
});
|
|
2028
|
-
|
|
2029
2049
|
const visibilityNormalizedPath = logicalPathToVisibilityNormalizedPath(pathSeg, locales);
|
|
2030
2050
|
await page.evaluate((np) => {
|
|
2031
2051
|
try {
|
|
@@ -2081,6 +2101,7 @@ async function runPrerender(config) {
|
|
|
2081
2101
|
html = stripRedundantImgSrcBindings(html);
|
|
2082
2102
|
html = stripEmptyInlineMaskStyles(html);
|
|
2083
2103
|
html = stripResolvedXIconDirectives(html);
|
|
2104
|
+
html = stripPrerenderHydrateMarkers(html);
|
|
2084
2105
|
html = rewriteHtmlAssetPaths(html, fileSegments.length);
|
|
2085
2106
|
const liveBase = config.liveUrl.replace(/\/$/, '');
|
|
2086
2107
|
const canonicalHreflang = buildCanonicalAndHreflang(is404 ? '' : pathSeg, locales, defaultLocale, liveBase);
|