@uniweb/build 0.3.0 → 0.3.1

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/package.json +3 -3
  2. package/src/prerender.js +136 -63
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "optionalDependencies": {
53
53
  "@uniweb/content-reader": "1.0.7",
54
- "@uniweb/runtime": "0.3.1"
54
+ "@uniweb/runtime": "0.4.2"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -60,7 +60,7 @@
60
60
  "@tailwindcss/vite": "^4.0.0",
61
61
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
62
62
  "vite-plugin-svgr": "^4.0.0",
63
- "@uniweb/core": "0.2.3"
63
+ "@uniweb/core": "0.2.4"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "vite": {
package/src/prerender.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { readFile, writeFile, mkdir } from 'node:fs/promises'
12
- import { existsSync } from 'node:fs'
12
+ import { existsSync, readdirSync, statSync } from 'node:fs'
13
13
  import { join, dirname, resolve } from 'node:path'
14
14
  import { pathToFileURL } from 'node:url'
15
15
  import { createRequire } from 'node:module'
@@ -381,6 +381,62 @@ function createPageElement(page, website) {
381
381
  )
382
382
  }
383
383
 
384
+ /**
385
+ * Discover all locale content files in the dist directory
386
+ * Returns an array of { locale, contentPath, htmlPath, isDefault }
387
+ *
388
+ * @param {string} distDir - Path to dist directory
389
+ * @param {Object} defaultContent - Default site content (to get default locale)
390
+ * @returns {Array} Locale configurations
391
+ */
392
+ async function discoverLocaleContents(distDir, defaultContent) {
393
+ const locales = []
394
+ const defaultLocale = defaultContent.config?.defaultLanguage || 'en'
395
+
396
+ // Add the default locale (root level)
397
+ locales.push({
398
+ locale: defaultLocale,
399
+ contentPath: join(distDir, 'site-content.json'),
400
+ htmlPath: join(distDir, 'index.html'),
401
+ isDefault: true,
402
+ routePrefix: ''
403
+ })
404
+
405
+ // Check for locale subdirectories with site-content.json
406
+ try {
407
+ const entries = readdirSync(distDir)
408
+ for (const entry of entries) {
409
+ const entryPath = join(distDir, entry)
410
+ // Skip if not a directory
411
+ if (!statSync(entryPath).isDirectory()) continue
412
+
413
+ // Check if this looks like a locale code (2-3 letter code)
414
+ if (!/^[a-z]{2,3}(-[A-Z]{2})?$/.test(entry)) continue
415
+
416
+ // Check if it has a site-content.json
417
+ const localeContentPath = join(entryPath, 'site-content.json')
418
+ const localeHtmlPath = join(entryPath, 'index.html')
419
+
420
+ if (existsSync(localeContentPath)) {
421
+ locales.push({
422
+ locale: entry,
423
+ contentPath: localeContentPath,
424
+ htmlPath: localeHtmlPath,
425
+ isDefault: false,
426
+ routePrefix: `/${entry}`
427
+ })
428
+ }
429
+ }
430
+ } catch (err) {
431
+ // Ignore errors reading directory
432
+ if (process.env.DEBUG) {
433
+ console.error('Error discovering locale contents:', err.message)
434
+ }
435
+ }
436
+
437
+ return locales
438
+ }
439
+
384
440
  /**
385
441
  * Pre-render all pages in a built site to static HTML
386
442
  *
@@ -407,33 +463,21 @@ export async function prerenderSite(siteDir, options = {}) {
407
463
  onProgress('Loading dependencies...')
408
464
  await loadDependencies(siteDir)
409
465
 
410
- // Load site content
466
+ // Load default site content
411
467
  onProgress('Loading site content...')
412
468
  const contentPath = join(distDir, 'site-content.json')
413
469
  if (!existsSync(contentPath)) {
414
470
  throw new Error(`site-content.json not found at: ${contentPath}`)
415
471
  }
416
- const siteContent = JSON.parse(await readFile(contentPath, 'utf8'))
417
-
418
- // Execute data fetches (site, page, section levels)
419
- onProgress('Executing data fetches...')
420
- const { siteCascadedData, pageFetchedData } = await executeAllFetches(siteContent, siteDir, onProgress)
421
-
422
- // Expand dynamic pages (e.g., /blog/:slug → /blog/post-1, /blog/post-2)
423
- if (siteContent.pages?.some(p => p.isDynamic)) {
424
- onProgress('Expanding dynamic routes...')
425
- siteContent.pages = expandDynamicPages(siteContent.pages, pageFetchedData, onProgress)
426
- }
472
+ const defaultSiteContent = JSON.parse(await readFile(contentPath, 'utf8'))
427
473
 
428
- // Load the HTML shell
429
- onProgress('Loading HTML shell...')
430
- const shellPath = join(distDir, 'index.html')
431
- if (!existsSync(shellPath)) {
432
- throw new Error(`index.html not found at: ${shellPath}`)
474
+ // Discover all locale content files
475
+ const localeConfigs = await discoverLocaleContents(distDir, defaultSiteContent)
476
+ if (localeConfigs.length > 1) {
477
+ onProgress(`Found ${localeConfigs.length} locales: ${localeConfigs.map(l => l.locale).join(', ')}`)
433
478
  }
434
- const htmlShell = await readFile(shellPath, 'utf8')
435
479
 
436
- // Load the foundation module
480
+ // Load the foundation module (shared across all locales)
437
481
  onProgress('Loading foundation...')
438
482
  const foundationPath = join(foundationDir, 'dist', 'foundation.js')
439
483
  if (!existsSync(foundationPath)) {
@@ -442,58 +486,87 @@ export async function prerenderSite(siteDir, options = {}) {
442
486
  const foundationUrl = pathToFileURL(foundationPath).href
443
487
  const foundation = await import(foundationUrl)
444
488
 
445
- // Initialize the Uniweb runtime (this sets globalThis.uniweb)
446
- onProgress('Initializing runtime...')
447
- const uniweb = createUniweb(siteContent)
448
- uniweb.setFoundation(foundation)
489
+ // Pre-render each locale
490
+ const renderedFiles = []
449
491
 
450
- // Set foundation capabilities (Layout, props, etc.)
451
- if (foundation.capabilities) {
452
- uniweb.setFoundationConfig(foundation.capabilities)
453
- }
492
+ for (const localeConfig of localeConfigs) {
493
+ const { locale, contentPath: localeContentPath, htmlPath, isDefault, routePrefix } = localeConfig
454
494
 
455
- // Pre-render each page
456
- const renderedFiles = []
457
- const pages = uniweb.activeWebsite.pages
458
- const website = uniweb.activeWebsite
495
+ onProgress(`\nRendering ${isDefault ? 'default' : locale} locale...`)
459
496
 
460
- for (const page of pages) {
461
- // Each page has a single canonical route
462
- // Index pages already have their parent route as their canonical route
463
- onProgress(`Rendering ${page.route}...`)
464
-
465
- // Set this as the active page
466
- uniweb.activeWebsite.setActivePage(page.route)
467
-
468
- // Create the page element using inline SSR rendering
469
- // (uses React from prerender's scope to avoid module resolution issues)
470
- const element = createPageElement(page, website)
471
-
472
- // Render to HTML string
473
- let renderedContent
474
- try {
475
- renderedContent = renderToString(element)
476
- } catch (err) {
477
- console.warn(`Warning: Failed to render ${page.route}: ${err.message}`)
478
- if (process.env.DEBUG) {
479
- console.error(err.stack)
480
- }
481
- continue
497
+ // Load locale-specific content
498
+ const siteContent = JSON.parse(await readFile(localeContentPath, 'utf8'))
499
+
500
+ // Set the active locale in the content
501
+ siteContent.config = siteContent.config || {}
502
+ siteContent.config.activeLocale = locale
503
+
504
+ // Execute data fetches (site, page, section levels)
505
+ onProgress('Executing data fetches...')
506
+ const { siteCascadedData, pageFetchedData } = await executeAllFetches(siteContent, siteDir, onProgress)
507
+
508
+ // Expand dynamic pages (e.g., /blog/:slug → /blog/post-1, /blog/post-2)
509
+ if (siteContent.pages?.some(p => p.isDynamic)) {
510
+ onProgress('Expanding dynamic routes...')
511
+ siteContent.pages = expandDynamicPages(siteContent.pages, pageFetchedData, onProgress)
512
+ }
513
+
514
+ // Load the HTML shell for this locale
515
+ const shellPath = existsSync(htmlPath) ? htmlPath : join(distDir, 'index.html')
516
+ const htmlShell = await readFile(shellPath, 'utf8')
517
+
518
+ // Initialize the Uniweb runtime for this locale
519
+ onProgress('Initializing runtime...')
520
+ const uniweb = createUniweb(siteContent)
521
+ uniweb.setFoundation(foundation)
522
+
523
+ // Set foundation capabilities (Layout, props, etc.)
524
+ if (foundation.capabilities) {
525
+ uniweb.setFoundationConfig(foundation.capabilities)
482
526
  }
483
527
 
484
- // Inject into shell
485
- const html = injectContent(htmlShell, renderedContent, page, siteContent)
528
+ // Pre-render each page
529
+ const pages = uniweb.activeWebsite.pages
530
+ const website = uniweb.activeWebsite
486
531
 
487
- // Output to the canonical route
488
- const outputPath = getOutputPath(distDir, page.route)
489
- await mkdir(dirname(outputPath), { recursive: true })
490
- await writeFile(outputPath, html)
532
+ for (const page of pages) {
533
+ // Build the output route with locale prefix
534
+ const outputRoute = routePrefix + page.route
491
535
 
492
- renderedFiles.push(outputPath)
493
- onProgress(` → ${outputPath.replace(distDir, 'dist')}`)
536
+ onProgress(`Rendering ${outputRoute}...`)
537
+
538
+ // Set this as the active page
539
+ uniweb.activeWebsite.setActivePage(page.route)
540
+
541
+ // Create the page element using inline SSR rendering
542
+ const element = createPageElement(page, website)
543
+
544
+ // Render to HTML string
545
+ let renderedContent
546
+ try {
547
+ renderedContent = renderToString(element)
548
+ } catch (err) {
549
+ console.warn(`Warning: Failed to render ${outputRoute}: ${err.message}`)
550
+ if (process.env.DEBUG) {
551
+ console.error(err.stack)
552
+ }
553
+ continue
554
+ }
555
+
556
+ // Inject into shell
557
+ const html = injectContent(htmlShell, renderedContent, page, siteContent)
558
+
559
+ // Output to the locale-prefixed route
560
+ const outputPath = getOutputPath(distDir, outputRoute)
561
+ await mkdir(dirname(outputPath), { recursive: true })
562
+ await writeFile(outputPath, html)
563
+
564
+ renderedFiles.push(outputPath)
565
+ onProgress(` → ${outputPath.replace(distDir, 'dist')}`)
566
+ }
494
567
  }
495
568
 
496
- onProgress(`Pre-rendered ${renderedFiles.length} pages`)
569
+ onProgress(`\nPre-rendered ${renderedFiles.length} pages across ${localeConfigs.length} locale(s)`)
497
570
 
498
571
  return {
499
572
  pages: renderedFiles.length,