mnfst-render 0.1.8 → 0.2.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.
Files changed (2) hide show
  1. package/manifest.render.mjs +59 -12
  2. package/package.json +1 -1
@@ -313,31 +313,55 @@ function discoverDataPaths(manifest, rootDir, wildcardBases = [], locales = [])
313
313
  return wildcardBases.some((base) => rest.startsWith(base + '/'));
314
314
  }
315
315
 
316
- function addFilePaths(value) {
316
+ function expandCandidates(rawPath, sourceKey) {
317
+ const p = String(rawPath || '').replace(/^\/+|\/+$/g, '');
318
+ if (!p) return [];
319
+ const candidates = [p];
320
+ if (wildcardBases.length === 0) return candidates;
321
+ if (!sourceKey || !wildcardBases.includes(sourceKey)) return candidates;
322
+ const parts = p.split('/');
323
+ const hasLocalePrefix = parts.length > 1 && localeSet.has(parts[0].toLowerCase());
324
+ if (hasLocalePrefix) {
325
+ const locale = parts[0];
326
+ const rest = parts.slice(1).join('/');
327
+ if (rest && !rest.startsWith(sourceKey + '/')) candidates.push(`${locale}/${sourceKey}/${rest}`);
328
+ } else if (!p.startsWith(sourceKey + '/')) {
329
+ candidates.push(`${sourceKey}/${p}`);
330
+ }
331
+ return candidates;
332
+ }
333
+
334
+ function addFilePaths(value, sourceKey) {
317
335
  if (typeof value !== 'string' || !value.startsWith('/')) return;
318
336
  const filePath = join(rootDir, value.slice(1));
319
337
  if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
320
338
  parseYamlPaths(filePath).forEach((p) => {
321
- if (shouldIncludeDataPath(p)) paths.add('/' + p);
339
+ for (const c of expandCandidates(p, sourceKey)) {
340
+ if (shouldIncludeDataPath(c)) paths.add('/' + c);
341
+ }
322
342
  });
323
343
  } else if (filePath.endsWith('.json')) {
324
344
  parseJsonPaths(filePath).forEach((p) => {
325
345
  const normalized = p.startsWith('/') ? p.slice(1) : p;
326
- if (shouldIncludeDataPath(normalized)) paths.add('/' + normalized);
346
+ for (const c of expandCandidates(normalized, sourceKey)) {
347
+ if (shouldIncludeDataPath(c)) paths.add('/' + c);
348
+ }
327
349
  });
328
350
  } else if (filePath.endsWith('.csv')) {
329
351
  parseCsvPaths(filePath).forEach((p) => {
330
352
  const normalized = p.startsWith('/') ? p.slice(1) : p;
331
- if (shouldIncludeDataPath(normalized)) paths.add('/' + normalized);
353
+ for (const c of expandCandidates(normalized, sourceKey)) {
354
+ if (shouldIncludeDataPath(c)) paths.add('/' + c);
355
+ }
332
356
  });
333
357
  }
334
358
  }
335
359
 
336
- for (const value of Object.values(data)) {
337
- if (typeof value === 'string') addFilePaths(value);
360
+ for (const [sourceKey, value] of Object.entries(data)) {
361
+ if (typeof value === 'string') addFilePaths(value, sourceKey);
338
362
  else if (value && typeof value === 'object') {
339
363
  for (const v of Object.values(value)) {
340
- if (typeof v === 'string') addFilePaths(v);
364
+ if (typeof v === 'string') addFilePaths(v, sourceKey);
341
365
  }
342
366
  }
343
367
  }
@@ -540,9 +564,8 @@ function runTailwindCliForPrerender(rootDir, outputDir, pre) {
540
564
  const outputBasename = basename(outputDir);
541
565
  const defaultContent = [
542
566
  '**/*.html',
543
- '**/*.{js,mjs,css}',
544
- '**/*.json',
545
567
  '!**/node_modules/**',
568
+ '!**/dist/**',
546
569
  `!**/${outputBasename}/**`,
547
570
  ];
548
571
  const contentGlobs = Array.isArray(pre?.tailwindContent) && pre.tailwindContent.length > 0
@@ -679,12 +702,14 @@ function stripPrerenderedXDataDirectives(html) {
679
702
  function stripPrerenderDynamicBindings(html) {
680
703
  return html.replace(/<(\w+)([^>]*)>/g, (match, tagName, attrsStr) => {
681
704
  if (tagName.toLowerCase() === 'script') return match;
705
+ const isAnchor = tagName.toLowerCase() === 'a';
682
706
  const toStrip = new Set();
683
707
  const bindingRegex = /(?:^|\s)(?::|x-bind:)(\w+)=(?:"([^"]*)"|'([^']*)')/g;
684
708
  let m;
685
709
  while ((m = bindingRegex.exec(attrsStr)) !== null) {
686
710
  const attrName = (m[1] || '').toLowerCase();
687
- if (attrName === 'class' || attrName === 'style') continue;
711
+ // Keep href on anchors so prerendered static navigation stays valid.
712
+ if (attrName === 'class' || attrName === 'style' || (isAnchor && attrName === 'href')) continue;
688
713
  const val = (m[2] !== undefined ? m[2] : m[3]) || '';
689
714
  if (val.indexOf('$x') === -1) toStrip.add(attrName);
690
715
  }
@@ -1084,8 +1109,12 @@ async function main() {
1084
1109
  await new Promise((res) => staticServer.close(res));
1085
1110
  }
1086
1111
  }
1087
- const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
1088
- process.stdout.write(`prerender: total time ${secs}s\n`);
1112
+ const elapsedMs = Date.now() - startedAt;
1113
+ const totalSeconds = Math.floor(elapsedMs / 1000);
1114
+ const hours = Math.floor(totalSeconds / 3600);
1115
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
1116
+ const seconds = totalSeconds % 60;
1117
+ process.stdout.write(`prerender: total time ${hours}h ${minutes}m ${seconds}s\n`);
1089
1118
  }
1090
1119
 
1091
1120
  async function runPrerender(config) {
@@ -1258,6 +1287,24 @@ async function runPrerender(config) {
1258
1287
  });
1259
1288
  }).catch(() => { });
1260
1289
 
1290
+ // Ensure $route-dependent expressions are recalculated after locale/data stores settle.
1291
+ // This helps localized dynamic pages (e.g. /ko/articles/slug) compute prev/next links correctly.
1292
+ await page.evaluate(() => {
1293
+ try {
1294
+ const to = window.ManifestRoutingNavigation?.getCurrentRoute?.() ?? window.location.pathname;
1295
+ window.dispatchEvent(new CustomEvent('manifest:route-change', {
1296
+ detail: {
1297
+ from: to,
1298
+ to,
1299
+ normalizedPath: (typeof to === 'string' && to !== '/') ? to.replace(/^\/|\/$/g, '') : '/'
1300
+ }
1301
+ }));
1302
+ window.dispatchEvent(new PopStateEvent('popstate'));
1303
+ } catch {
1304
+ // no-op
1305
+ }
1306
+ }).catch(() => { });
1307
+
1261
1308
  // Optional extra delay so in-page async (e.g. fetch() in x-init for client logos) can complete before snapshot.
1262
1309
  if (config.waitAfterIdle > 0) {
1263
1310
  await new Promise((r) => setTimeout(r, config.waitAfterIdle));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {