mnfst-render 0.3.6 → 0.3.8
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 +64 -13
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -252,6 +252,13 @@ function loadConfig(rootDir) {
|
|
|
252
252
|
return manifest;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
function normalizeLocaleRouteExclude(val) {
|
|
256
|
+
if (val == null) return [];
|
|
257
|
+
if (Array.isArray(val)) return val.map((s) => String(s).trim()).filter(Boolean);
|
|
258
|
+
if (typeof val === 'string') return val.split(',').map((s) => s.trim()).filter(Boolean);
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
|
|
255
262
|
function resolveConfig() {
|
|
256
263
|
const cli = parseArgs();
|
|
257
264
|
const cwd = process.cwd();
|
|
@@ -274,6 +281,10 @@ function resolveConfig() {
|
|
|
274
281
|
output: resolve(root, cli.output ?? pre.output ?? 'website'),
|
|
275
282
|
root,
|
|
276
283
|
routerBase: pre.routerBase ?? null,
|
|
284
|
+
/** Logical path prefixes (after locale) that skip sticky locale prefix; see manifest:locale-route-exclude */
|
|
285
|
+
localeRouteExclude: normalizeLocaleRouteExclude(
|
|
286
|
+
pre.localeRouteExclude ?? pre.localeStickyExclude
|
|
287
|
+
),
|
|
277
288
|
locales: pre.locales,
|
|
278
289
|
redirects: Array.isArray(pre.redirects) ? pre.redirects : [],
|
|
279
290
|
wait: cli.wait ?? pre.wait ?? null,
|
|
@@ -853,19 +864,26 @@ function depthFromOutputRoot(outputDir, filePath) {
|
|
|
853
864
|
return rel.split(sep).filter(Boolean).length;
|
|
854
865
|
}
|
|
855
866
|
|
|
856
|
-
/**
|
|
857
|
-
function
|
|
867
|
+
/** Root-absolute path for prerender bundles (same URL from every page depth; supports manifest:router-base). */
|
|
868
|
+
function buildRootAssetPath(routerBasePath, filename) {
|
|
869
|
+
const base = String(routerBasePath || '').replace(/^\/+|\/+$/g, '');
|
|
870
|
+
const name = String(filename || '').replace(/^\/+/, '');
|
|
871
|
+
const path = base ? `${base}/${name}` : name;
|
|
872
|
+
return '/' + path.replace(/\/{2,}/g, '/');
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** Inject stylesheet link with root-absolute href (avoids ../ resolving under locale segments like /en/page/). */
|
|
876
|
+
function postProcessInjectStylesheetLink(outputDir, filename, routerBasePath) {
|
|
858
877
|
const cssPath = join(outputDir, filename);
|
|
859
878
|
if (!existsSync(cssPath)) return;
|
|
860
879
|
const stat = statSync(cssPath);
|
|
861
880
|
if (stat.size === 0) return;
|
|
862
881
|
|
|
882
|
+
const href = buildRootAssetPath(routerBasePath, filename);
|
|
883
|
+
const tag = `<link rel="stylesheet" href="${href}">`;
|
|
863
884
|
const files = walkHtmlFiles(outputDir);
|
|
864
885
|
for (const file of files) {
|
|
865
886
|
let html = readFileSync(file, 'utf8');
|
|
866
|
-
const depth = depthFromOutputRoot(outputDir, file);
|
|
867
|
-
const prefix = depth ? '../'.repeat(depth) : '';
|
|
868
|
-
const tag = `<link rel="stylesheet" href="${prefix}${filename}">`;
|
|
869
887
|
html = injectBeforeHeadClose(html, tag);
|
|
870
888
|
writeFileSync(file, html, 'utf8');
|
|
871
889
|
}
|
|
@@ -956,14 +974,15 @@ function stripRedundantImgSrcBindings(html) {
|
|
|
956
974
|
* prerender-baked markup (stripped :style, expanded lists, etc.). Tag opens with data-pre-rendered
|
|
957
975
|
* are skipped by manifest.components.processor — required for static prerender output to hydrate correctly.
|
|
958
976
|
*/
|
|
959
|
-
// Prerender inlined Iconify SVG under <i x-icon="iterator.icon">;
|
|
977
|
+
// Prerender inlined Iconify SVG under <i x-icon="iterator.icon">; clear x-icon value so Alpine does not evaluate
|
|
978
|
+
// loop/item expressions while the attribute remains for CSS (e.g. inline layout that keys off [x-icon]).
|
|
960
979
|
function stripResolvedXIconDirectives(html) {
|
|
961
980
|
return html.replace(/<i\b([^>]*)>([\s\S]*?)<\/i>/gi, (full, attrs, inner) => {
|
|
962
981
|
if (!/\sx-icon\s*=/i.test(attrs)) return full;
|
|
963
982
|
if (!/<svg\b/i.test(inner) || !/\bdata-icon\s*=/i.test(inner)) return full;
|
|
964
983
|
const cleaned = attrs
|
|
965
|
-
.replace(/\s+x-icon\s*=\s*"[^"]*"/gi, '')
|
|
966
|
-
.replace(/\s+x-icon\s*=\s*'[^']*'/gi, '')
|
|
984
|
+
.replace(/\s+x-icon\s*=\s*"[^"]*"/gi, ' x-icon=""')
|
|
985
|
+
.replace(/\s+x-icon\s*=\s*'[^']*'/gi, ' x-icon=""')
|
|
967
986
|
.trim();
|
|
968
987
|
const sp = cleaned ? ' ' : '';
|
|
969
988
|
return `<i${sp}${cleaned}>${inner}</i>`;
|
|
@@ -1006,6 +1025,10 @@ function stripEmptyInlineMaskStyles(html) {
|
|
|
1006
1025
|
// All project assets are copied into output, so root-relative paths become relative within output.
|
|
1007
1026
|
// Do NOT rewrite href on <a> tags (navigation links); only rewrite link/script/img so router gets clean paths.
|
|
1008
1027
|
|
|
1028
|
+
function isPrerenderBundleAssetPath(pathAfterSlash) {
|
|
1029
|
+
return /(^|\/)prerender\.(tailwind|utilities)\.css$/.test(pathAfterSlash);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1009
1032
|
function rewriteHtmlAssetPaths(html, depthWithinOutput) {
|
|
1010
1033
|
const prefix = depthWithinOutput > 0 ? '../'.repeat(depthWithinOutput) : '';
|
|
1011
1034
|
if (!prefix) return html;
|
|
@@ -1015,9 +1038,10 @@ function rewriteHtmlAssetPaths(html, depthWithinOutput) {
|
|
|
1015
1038
|
const tag = htmlBeforeMatch.slice(lastOpen + 1).match(/^(\w+)/);
|
|
1016
1039
|
return tag && tag[1].toLowerCase() === 'a';
|
|
1017
1040
|
}
|
|
1018
|
-
let out = html.replace(/(\s(href|src)=["'])\/(?!\/)/g, (match, lead,
|
|
1041
|
+
let out = html.replace(/(\s(href|src)=["'])\/(?!\/)([^'"]*)/g, (match, lead, _attr, rest, offset, fullString) => {
|
|
1019
1042
|
if (isAnchorTag(fullString.slice(0, offset))) return match;
|
|
1020
|
-
|
|
1043
|
+
if (isPrerenderBundleAssetPath(rest)) return match;
|
|
1044
|
+
return lead + prefix + rest;
|
|
1021
1045
|
});
|
|
1022
1046
|
out = out.replace(/(\s(href|src)=["'])(\.\.\/)+/g, (match, lead, attr, dots, offset, fullString) => {
|
|
1023
1047
|
if (isAnchorTag(fullString.slice(0, offset))) return match;
|
|
@@ -1026,6 +1050,17 @@ function rewriteHtmlAssetPaths(html, depthWithinOutput) {
|
|
|
1026
1050
|
return out;
|
|
1027
1051
|
}
|
|
1028
1052
|
|
|
1053
|
+
// Alpine x-data drives radio state; baked checked="" from the live DOM (e.g. yearly) fights monthly defaults.
|
|
1054
|
+
function stripPrerenderBakedRadioCheckedForXModel(html) {
|
|
1055
|
+
return html.replace(/<input\b([^>]*)>/gi, (full, attrs) => {
|
|
1056
|
+
if (!/\btype\s*=\s*["']radio["']/i.test(attrs)) return full;
|
|
1057
|
+
if (!/\bx-model\s*=/i.test(attrs)) return full;
|
|
1058
|
+
const next = attrs.replace(/\s+checked(?:\s*=\s*["'][^"']*["']|\s*=\s*[^\s>]+)?/gi, '');
|
|
1059
|
+
if (next === attrs) return full;
|
|
1060
|
+
return `<input${next}>`;
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1029
1064
|
// --- Canonical and hreflang (per-page injection) ---
|
|
1030
1065
|
|
|
1031
1066
|
function buildCanonicalAndHreflang(pathSeg, locales, defaultLocale, base) {
|
|
@@ -1861,6 +1896,10 @@ async function runPrerender(config) {
|
|
|
1861
1896
|
node.removeAttribute(name);
|
|
1862
1897
|
continue;
|
|
1863
1898
|
}
|
|
1899
|
+
if (name === 'x-icon') {
|
|
1900
|
+
node.setAttribute('x-icon', '');
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1864
1903
|
let boundAttr = '';
|
|
1865
1904
|
if (name.startsWith(':')) boundAttr = name.slice(1);
|
|
1866
1905
|
else if (name.startsWith('x-bind:')) boundAttr = name.slice('x-bind:'.length);
|
|
@@ -2027,7 +2066,10 @@ async function runPrerender(config) {
|
|
|
2027
2066
|
for (const b of extracted.blocks) utilityBlocks.push(b);
|
|
2028
2067
|
}
|
|
2029
2068
|
if (tailwindBuilt) {
|
|
2030
|
-
html = injectBeforeHeadClose(
|
|
2069
|
+
html = injectBeforeHeadClose(
|
|
2070
|
+
html,
|
|
2071
|
+
`<link rel="stylesheet" href="${buildRootAssetPath(routerBasePath, 'prerender.tailwind.css')}">`
|
|
2072
|
+
);
|
|
2031
2073
|
}
|
|
2032
2074
|
html = stripDuplicatedLoopDirectives(html);
|
|
2033
2075
|
html = stripPrerenderedXDataDirectives(html);
|
|
@@ -2035,6 +2077,7 @@ async function runPrerender(config) {
|
|
|
2035
2077
|
const xData = { manifest, content };
|
|
2036
2078
|
html = resolveHeadXBindings(html, xData);
|
|
2037
2079
|
html = stripPrerenderDynamicBindings(html);
|
|
2080
|
+
html = stripPrerenderBakedRadioCheckedForXModel(html);
|
|
2038
2081
|
html = stripRedundantImgSrcBindings(html);
|
|
2039
2082
|
html = stripEmptyInlineMaskStyles(html);
|
|
2040
2083
|
html = stripResolvedXIconDirectives(html);
|
|
@@ -2045,9 +2088,17 @@ async function runPrerender(config) {
|
|
|
2045
2088
|
const injectOgLocale = ogLocale && hasOtherOgMeta(html);
|
|
2046
2089
|
if (injectOgLocale) html = stripOgLocaleFromHead(html);
|
|
2047
2090
|
const baseMeta = routerBasePath !== null ? `<meta name="manifest:router-base" content="${String(routerBasePath).replace(/"/g, '"')}">\n` : '';
|
|
2091
|
+
const routeEx = config.localeRouteExclude || [];
|
|
2092
|
+
const routeMeta =
|
|
2093
|
+
routeEx.length > 0
|
|
2094
|
+
? `<meta name="manifest:locale-route-exclude" content="${JSON.stringify(routeEx).replace(/"/g, '"')}">\n`
|
|
2095
|
+
: '';
|
|
2048
2096
|
const routeDepth = fileSegments.length;
|
|
2049
2097
|
const prerenderedMeta = `<meta name="manifest:prerendered" content="1">\n`;
|
|
2050
|
-
html = html.replace(
|
|
2098
|
+
html = html.replace(
|
|
2099
|
+
'</head>',
|
|
2100
|
+
`${canonicalHreflang}${injectOgLocale ? ogLocale : ''}${routeMeta}${baseMeta}${prerenderedMeta}<meta name="manifest:router-base-depth" content="${routeDepth}">\n</head>`
|
|
2101
|
+
);
|
|
2051
2102
|
html = markPrerenderedManifestComponents(html);
|
|
2052
2103
|
mkdirSync(outDir, { recursive: true });
|
|
2053
2104
|
writeFileSync(outFile, html, 'utf8');
|
|
@@ -2108,7 +2159,7 @@ async function runPrerender(config) {
|
|
|
2108
2159
|
if (utilMerged.trim()) {
|
|
2109
2160
|
writeFileSync(join(outputResolved, 'prerender.utilities.css'), `${utilMerged}\n`, 'utf8');
|
|
2110
2161
|
process.stdout.write('prerender: wrote prerender.utilities.css (Manifest custom utilities)\n');
|
|
2111
|
-
postProcessInjectStylesheetLink(outputResolved, 'prerender.utilities.css');
|
|
2162
|
+
postProcessInjectStylesheetLink(outputResolved, 'prerender.utilities.css', routerBasePath || '');
|
|
2112
2163
|
}
|
|
2113
2164
|
}
|
|
2114
2165
|
|