@uniweb/build 0.3.0 → 0.3.2
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/package.json +4 -4
- package/src/prerender.js +137 -65
- package/src/site/content-collector.js +12 -3
- package/src/site/plugin.js +11 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"sharp": "^0.33.2"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@uniweb/content-reader": "1.0.
|
|
54
|
-
"@uniweb/runtime": "0.3
|
|
53
|
+
"@uniweb/content-reader": "1.0.8",
|
|
54
|
+
"@uniweb/runtime": "0.4.3"
|
|
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.
|
|
63
|
+
"@uniweb/core": "0.2.5"
|
|
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'
|
|
@@ -301,8 +301,7 @@ function renderBlock(block) {
|
|
|
301
301
|
const componentProps = {
|
|
302
302
|
content,
|
|
303
303
|
params,
|
|
304
|
-
block
|
|
305
|
-
input: block.input
|
|
304
|
+
block
|
|
306
305
|
}
|
|
307
306
|
|
|
308
307
|
// Wrapper props
|
|
@@ -381,6 +380,62 @@ function createPageElement(page, website) {
|
|
|
381
380
|
)
|
|
382
381
|
}
|
|
383
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Discover all locale content files in the dist directory
|
|
385
|
+
* Returns an array of { locale, contentPath, htmlPath, isDefault }
|
|
386
|
+
*
|
|
387
|
+
* @param {string} distDir - Path to dist directory
|
|
388
|
+
* @param {Object} defaultContent - Default site content (to get default locale)
|
|
389
|
+
* @returns {Array} Locale configurations
|
|
390
|
+
*/
|
|
391
|
+
async function discoverLocaleContents(distDir, defaultContent) {
|
|
392
|
+
const locales = []
|
|
393
|
+
const defaultLocale = defaultContent.config?.defaultLanguage || 'en'
|
|
394
|
+
|
|
395
|
+
// Add the default locale (root level)
|
|
396
|
+
locales.push({
|
|
397
|
+
locale: defaultLocale,
|
|
398
|
+
contentPath: join(distDir, 'site-content.json'),
|
|
399
|
+
htmlPath: join(distDir, 'index.html'),
|
|
400
|
+
isDefault: true,
|
|
401
|
+
routePrefix: ''
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
// Check for locale subdirectories with site-content.json
|
|
405
|
+
try {
|
|
406
|
+
const entries = readdirSync(distDir)
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
const entryPath = join(distDir, entry)
|
|
409
|
+
// Skip if not a directory
|
|
410
|
+
if (!statSync(entryPath).isDirectory()) continue
|
|
411
|
+
|
|
412
|
+
// Check if this looks like a locale code (2-3 letter code)
|
|
413
|
+
if (!/^[a-z]{2,3}(-[A-Z]{2})?$/.test(entry)) continue
|
|
414
|
+
|
|
415
|
+
// Check if it has a site-content.json
|
|
416
|
+
const localeContentPath = join(entryPath, 'site-content.json')
|
|
417
|
+
const localeHtmlPath = join(entryPath, 'index.html')
|
|
418
|
+
|
|
419
|
+
if (existsSync(localeContentPath)) {
|
|
420
|
+
locales.push({
|
|
421
|
+
locale: entry,
|
|
422
|
+
contentPath: localeContentPath,
|
|
423
|
+
htmlPath: localeHtmlPath,
|
|
424
|
+
isDefault: false,
|
|
425
|
+
routePrefix: `/${entry}`
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} catch (err) {
|
|
430
|
+
// Ignore errors reading directory
|
|
431
|
+
if (process.env.DEBUG) {
|
|
432
|
+
console.error('Error discovering locale contents:', err.message)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return locales
|
|
437
|
+
}
|
|
438
|
+
|
|
384
439
|
/**
|
|
385
440
|
* Pre-render all pages in a built site to static HTML
|
|
386
441
|
*
|
|
@@ -407,33 +462,21 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
407
462
|
onProgress('Loading dependencies...')
|
|
408
463
|
await loadDependencies(siteDir)
|
|
409
464
|
|
|
410
|
-
// Load site content
|
|
465
|
+
// Load default site content
|
|
411
466
|
onProgress('Loading site content...')
|
|
412
467
|
const contentPath = join(distDir, 'site-content.json')
|
|
413
468
|
if (!existsSync(contentPath)) {
|
|
414
469
|
throw new Error(`site-content.json not found at: ${contentPath}`)
|
|
415
470
|
}
|
|
416
|
-
const
|
|
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
|
-
}
|
|
471
|
+
const defaultSiteContent = JSON.parse(await readFile(contentPath, 'utf8'))
|
|
427
472
|
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
throw new Error(`index.html not found at: ${shellPath}`)
|
|
473
|
+
// Discover all locale content files
|
|
474
|
+
const localeConfigs = await discoverLocaleContents(distDir, defaultSiteContent)
|
|
475
|
+
if (localeConfigs.length > 1) {
|
|
476
|
+
onProgress(`Found ${localeConfigs.length} locales: ${localeConfigs.map(l => l.locale).join(', ')}`)
|
|
433
477
|
}
|
|
434
|
-
const htmlShell = await readFile(shellPath, 'utf8')
|
|
435
478
|
|
|
436
|
-
// Load the foundation module
|
|
479
|
+
// Load the foundation module (shared across all locales)
|
|
437
480
|
onProgress('Loading foundation...')
|
|
438
481
|
const foundationPath = join(foundationDir, 'dist', 'foundation.js')
|
|
439
482
|
if (!existsSync(foundationPath)) {
|
|
@@ -442,58 +485,87 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
442
485
|
const foundationUrl = pathToFileURL(foundationPath).href
|
|
443
486
|
const foundation = await import(foundationUrl)
|
|
444
487
|
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
const uniweb = createUniweb(siteContent)
|
|
448
|
-
uniweb.setFoundation(foundation)
|
|
488
|
+
// Pre-render each locale
|
|
489
|
+
const renderedFiles = []
|
|
449
490
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
uniweb.setFoundationConfig(foundation.capabilities)
|
|
453
|
-
}
|
|
491
|
+
for (const localeConfig of localeConfigs) {
|
|
492
|
+
const { locale, contentPath: localeContentPath, htmlPath, isDefault, routePrefix } = localeConfig
|
|
454
493
|
|
|
455
|
-
|
|
456
|
-
const renderedFiles = []
|
|
457
|
-
const pages = uniweb.activeWebsite.pages
|
|
458
|
-
const website = uniweb.activeWebsite
|
|
494
|
+
onProgress(`\nRendering ${isDefault ? 'default' : locale} locale...`)
|
|
459
495
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
496
|
+
// Load locale-specific content
|
|
497
|
+
const siteContent = JSON.parse(await readFile(localeContentPath, 'utf8'))
|
|
498
|
+
|
|
499
|
+
// Set the active locale in the content
|
|
500
|
+
siteContent.config = siteContent.config || {}
|
|
501
|
+
siteContent.config.activeLocale = locale
|
|
502
|
+
|
|
503
|
+
// Execute data fetches (site, page, section levels)
|
|
504
|
+
onProgress('Executing data fetches...')
|
|
505
|
+
const { siteCascadedData, pageFetchedData } = await executeAllFetches(siteContent, siteDir, onProgress)
|
|
506
|
+
|
|
507
|
+
// Expand dynamic pages (e.g., /blog/:slug → /blog/post-1, /blog/post-2)
|
|
508
|
+
if (siteContent.pages?.some(p => p.isDynamic)) {
|
|
509
|
+
onProgress('Expanding dynamic routes...')
|
|
510
|
+
siteContent.pages = expandDynamicPages(siteContent.pages, pageFetchedData, onProgress)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Load the HTML shell for this locale
|
|
514
|
+
const shellPath = existsSync(htmlPath) ? htmlPath : join(distDir, 'index.html')
|
|
515
|
+
const htmlShell = await readFile(shellPath, 'utf8')
|
|
516
|
+
|
|
517
|
+
// Initialize the Uniweb runtime for this locale
|
|
518
|
+
onProgress('Initializing runtime...')
|
|
519
|
+
const uniweb = createUniweb(siteContent)
|
|
520
|
+
uniweb.setFoundation(foundation)
|
|
521
|
+
|
|
522
|
+
// Set foundation capabilities (Layout, props, etc.)
|
|
523
|
+
if (foundation.capabilities) {
|
|
524
|
+
uniweb.setFoundationConfig(foundation.capabilities)
|
|
482
525
|
}
|
|
483
526
|
|
|
484
|
-
//
|
|
485
|
-
const
|
|
527
|
+
// Pre-render each page
|
|
528
|
+
const pages = uniweb.activeWebsite.pages
|
|
529
|
+
const website = uniweb.activeWebsite
|
|
486
530
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
await writeFile(outputPath, html)
|
|
531
|
+
for (const page of pages) {
|
|
532
|
+
// Build the output route with locale prefix
|
|
533
|
+
const outputRoute = routePrefix + page.route
|
|
491
534
|
|
|
492
|
-
|
|
493
|
-
|
|
535
|
+
onProgress(`Rendering ${outputRoute}...`)
|
|
536
|
+
|
|
537
|
+
// Set this as the active page
|
|
538
|
+
uniweb.activeWebsite.setActivePage(page.route)
|
|
539
|
+
|
|
540
|
+
// Create the page element using inline SSR rendering
|
|
541
|
+
const element = createPageElement(page, website)
|
|
542
|
+
|
|
543
|
+
// Render to HTML string
|
|
544
|
+
let renderedContent
|
|
545
|
+
try {
|
|
546
|
+
renderedContent = renderToString(element)
|
|
547
|
+
} catch (err) {
|
|
548
|
+
console.warn(`Warning: Failed to render ${outputRoute}: ${err.message}`)
|
|
549
|
+
if (process.env.DEBUG) {
|
|
550
|
+
console.error(err.stack)
|
|
551
|
+
}
|
|
552
|
+
continue
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Inject into shell
|
|
556
|
+
const html = injectContent(htmlShell, renderedContent, page, siteContent)
|
|
557
|
+
|
|
558
|
+
// Output to the locale-prefixed route
|
|
559
|
+
const outputPath = getOutputPath(distDir, outputRoute)
|
|
560
|
+
await mkdir(dirname(outputPath), { recursive: true })
|
|
561
|
+
await writeFile(outputPath, html)
|
|
562
|
+
|
|
563
|
+
renderedFiles.push(outputPath)
|
|
564
|
+
onProgress(` → ${outputPath.replace(distDir, 'dist')}`)
|
|
565
|
+
}
|
|
494
566
|
}
|
|
495
567
|
|
|
496
|
-
onProgress(
|
|
568
|
+
onProgress(`\nPre-rendered ${renderedFiles.length} pages across ${localeConfigs.length} locale(s)`)
|
|
497
569
|
|
|
498
570
|
return {
|
|
499
571
|
pages: renderedFiles.length,
|
|
@@ -523,7 +523,6 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
523
523
|
title: pageConfig.title || pageName,
|
|
524
524
|
description: pageConfig.description || '',
|
|
525
525
|
label: pageConfig.label || null, // Short label for navigation (defaults to title)
|
|
526
|
-
order: pageConfig.order,
|
|
527
526
|
lastModified: lastModified?.toISOString(),
|
|
528
527
|
|
|
529
528
|
// Dynamic route metadata
|
|
@@ -599,8 +598,9 @@ function determineIndexPage(orderConfig, availableFolders) {
|
|
|
599
598
|
|
|
600
599
|
const sorted = [...staticFolders].sort((a, b) => {
|
|
601
600
|
// Sort by order (lower first), then alphabetically
|
|
602
|
-
|
|
603
|
-
const
|
|
601
|
+
// Pages without explicit order come after ordered pages
|
|
602
|
+
const orderA = a.order ?? Infinity
|
|
603
|
+
const orderB = b.order ?? Infinity
|
|
604
604
|
if (orderA !== orderB) return orderA - orderB
|
|
605
605
|
return a.name.localeCompare(b.name)
|
|
606
606
|
})
|
|
@@ -655,6 +655,15 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
655
655
|
})
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
+
// Sort page folders by order (ascending), then alphabetically
|
|
659
|
+
// Pages without explicit order come after ordered pages (order ?? Infinity)
|
|
660
|
+
pageFolders.sort((a, b) => {
|
|
661
|
+
const orderA = a.order ?? Infinity
|
|
662
|
+
const orderB = b.order ?? Infinity
|
|
663
|
+
if (orderA !== orderB) return orderA - orderB
|
|
664
|
+
return a.name.localeCompare(b.name)
|
|
665
|
+
})
|
|
666
|
+
|
|
658
667
|
// Check if this directory contains version folders (versioned section)
|
|
659
668
|
const folderNames = pageFolders.map(f => f.name)
|
|
660
669
|
const detectedVersions = detectVersions(folderNames)
|
package/src/site/plugin.js
CHANGED
|
@@ -93,16 +93,20 @@ async function processDevSectionFetches(sections, cascadedData, fetchOptions) {
|
|
|
93
93
|
if (sectionFetch) {
|
|
94
94
|
const result = await executeFetch(sectionFetch, fetchOptions)
|
|
95
95
|
if (result.data && !result.error) {
|
|
96
|
-
// Merge fetched data into
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
// Merge fetched data into section's parsedContent (not cascadedData)
|
|
97
|
+
// This matches prerender behavior - section's own fetch goes to content.data
|
|
98
|
+
section.parsedContent = mergeDataIntoContent(
|
|
99
|
+
section.parsedContent || {},
|
|
100
|
+
result.data,
|
|
101
|
+
sectionFetch.schema,
|
|
102
|
+
sectionFetch.merge
|
|
103
|
+
)
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
// Attach cascaded data
|
|
105
|
-
|
|
107
|
+
// Attach cascaded data for components with inheritData
|
|
108
|
+
// Note: cascadedData is from page/site level only, not section's own fetch
|
|
109
|
+
section.cascadedData = cascadedData
|
|
106
110
|
|
|
107
111
|
// Process subsections recursively
|
|
108
112
|
if (section.subsections && section.subsections.length > 0) {
|