mnfst-render 0.4.3 → 0.4.4
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 +72 -18
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -238,6 +238,11 @@ function resolveConfig() {
|
|
|
238
238
|
localeSubstitutionExclude: Array.isArray(pre.localeSubstitutionExclude)
|
|
239
239
|
? pre.localeSubstitutionExclude.map(String)
|
|
240
240
|
: [],
|
|
241
|
+
/** Explicit locale-neutral paths to render in addition to those discovered automatically.
|
|
242
|
+
* Each entry is expanded to all locale variants (e.g. "legal/privacy" → "cs/legal/privacy", ...) */
|
|
243
|
+
paths: Array.isArray(pre.paths)
|
|
244
|
+
? pre.paths.map((p) => String(p).replace(/^\/+|\/+$/g, '')).filter(Boolean)
|
|
245
|
+
: [],
|
|
241
246
|
dryRun: !!cli.dryRun,
|
|
242
247
|
debugPrerender: !!(cli.debugPrerender ?? pre.debugPrerender),
|
|
243
248
|
pipelineTimeout: Math.max(3000, Number(pre.pipelineTimeout) || 25000),
|
|
@@ -1127,32 +1132,71 @@ function loadAllLocaleContentData(manifest, rootDir, locales) {
|
|
|
1127
1132
|
const data = manifest?.data;
|
|
1128
1133
|
if (!data || typeof data !== 'object') return new Map();
|
|
1129
1134
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1135
|
+
// Lazy-load js-yaml for parsing per-locale YAML files
|
|
1136
|
+
let jsYaml = null;
|
|
1137
|
+
try { jsYaml = require('js-yaml'); } catch { /* yaml not available; YAML locale files will be skipped */ }
|
|
1138
|
+
|
|
1139
|
+
// Deep-merge source into target (for combining multiple data sources per locale)
|
|
1140
|
+
function deepMerge(target, source) {
|
|
1141
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) return;
|
|
1142
|
+
for (const key of Object.keys(source)) {
|
|
1143
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
1144
|
+
target[key] = (target[key] && typeof target[key] === 'object') ? target[key] : {};
|
|
1145
|
+
deepMerge(target[key], source[key]);
|
|
1146
|
+
} else {
|
|
1147
|
+
target[key] = source[key];
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const result = new Map();
|
|
1153
|
+
for (const locale of locales) result.set(locale, {});
|
|
1137
1154
|
|
|
1138
1155
|
for (const [, value] of Object.entries(data)) {
|
|
1139
|
-
if (typeof value === 'string') {
|
|
1140
|
-
|
|
1156
|
+
if (typeof value === 'string') {
|
|
1157
|
+
// Single CSV with locale columns (all locales in one file)
|
|
1158
|
+
if (value.endsWith('.csv')) {
|
|
1159
|
+
const csvPath = join(rootDir, value.startsWith('/') ? value.slice(1) : value);
|
|
1160
|
+
for (const locale of locales) {
|
|
1161
|
+
deepMerge(result.get(locale), parseCsvToKeyValue(csvPath, locale));
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
1141
1165
|
if (value.locales) {
|
|
1166
|
+
// { locales: "/path/to/multi-locale.csv" } format
|
|
1142
1167
|
const refs = Array.isArray(value.locales) ? value.locales : [value.locales];
|
|
1143
|
-
refs
|
|
1168
|
+
for (const ref of refs) {
|
|
1169
|
+
if (typeof ref !== 'string' || !ref.endsWith('.csv')) continue;
|
|
1170
|
+
const csvPath = join(rootDir, ref.startsWith('/') ? ref.slice(1) : ref);
|
|
1171
|
+
for (const locale of locales) {
|
|
1172
|
+
deepMerge(result.get(locale), parseCsvToKeyValue(csvPath, locale));
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
} else {
|
|
1176
|
+
// Per-locale files: { "en": "/data/content.en.yaml", "fr": "/data/content.fr.yaml", ... }
|
|
1177
|
+
for (const [localeKey, filePath] of Object.entries(value)) {
|
|
1178
|
+
if (!locales.includes(localeKey) || typeof filePath !== 'string') continue;
|
|
1179
|
+
const fullPath = join(rootDir, filePath.startsWith('/') ? filePath.slice(1) : filePath);
|
|
1180
|
+
if (!existsSync(fullPath)) continue;
|
|
1181
|
+
let localeData = null;
|
|
1182
|
+
try {
|
|
1183
|
+
const raw = readFileSync(fullPath, 'utf8');
|
|
1184
|
+
if ((filePath.endsWith('.yaml') || filePath.endsWith('.yml')) && jsYaml) {
|
|
1185
|
+
localeData = jsYaml.load(raw);
|
|
1186
|
+
} else if (filePath.endsWith('.json')) {
|
|
1187
|
+
localeData = JSON.parse(raw);
|
|
1188
|
+
} else if (filePath.endsWith('.csv')) {
|
|
1189
|
+
localeData = parseCsvToKeyValue(fullPath, localeKey);
|
|
1190
|
+
}
|
|
1191
|
+
} catch { /* ignore parse errors for individual locale files */ }
|
|
1192
|
+
if (localeData && typeof localeData === 'object') {
|
|
1193
|
+
deepMerge(result.get(localeKey), localeData);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1144
1196
|
}
|
|
1145
1197
|
}
|
|
1146
1198
|
}
|
|
1147
1199
|
|
|
1148
|
-
const result = new Map();
|
|
1149
|
-
for (const locale of locales) {
|
|
1150
|
-
const merged = {};
|
|
1151
|
-
for (const csvPath of csvFiles) {
|
|
1152
|
-
Object.assign(merged, parseCsvToKeyValue(csvPath, locale));
|
|
1153
|
-
}
|
|
1154
|
-
result.set(locale, merged);
|
|
1155
|
-
}
|
|
1156
1200
|
return result;
|
|
1157
1201
|
}
|
|
1158
1202
|
|
|
@@ -1599,6 +1643,14 @@ async function runPrerender(config) {
|
|
|
1599
1643
|
|
|
1600
1644
|
const defaultLocale = locales[0] ?? null;
|
|
1601
1645
|
const routeSegments = discoverRoutes(manifest, config.root);
|
|
1646
|
+
// Merge any explicitly configured paths (manifest.prerender.paths) into the discovered segments.
|
|
1647
|
+
// These are treated as locale-neutral and get full locale-expansion like all other discovered paths.
|
|
1648
|
+
if (config.paths && config.paths.length > 0) {
|
|
1649
|
+
const segSet = new Set(routeSegments);
|
|
1650
|
+
for (const p of config.paths) {
|
|
1651
|
+
if (!segSet.has(p)) { routeSegments.push(p); segSet.add(p); }
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1602
1654
|
const localeSet = new Set(locales.map((l) => String(l).toLowerCase()));
|
|
1603
1655
|
const localeNeutralSegments = routeSegments.filter((seg) => {
|
|
1604
1656
|
if (!seg) return true;
|
|
@@ -2112,6 +2164,8 @@ async function runPrerender(config) {
|
|
|
2112
2164
|
const stripLoopBindings = (el, itemVar, indexVar) => {
|
|
2113
2165
|
const nodes = [el, ...Array.from(el.querySelectorAll('*'))];
|
|
2114
2166
|
for (const node of nodes) {
|
|
2167
|
+
// Skip elements inside data-hydrate islands — their bindings must remain live
|
|
2168
|
+
if (node.hasAttribute('data-prerender-hydrate') || node.closest('[data-prerender-hydrate]')) continue;
|
|
2115
2169
|
const attrs = node.attributes ? Array.from(node.attributes) : [];
|
|
2116
2170
|
for (const attr of attrs) {
|
|
2117
2171
|
if (!bindingAttrRegex.test(attr.name)) continue;
|