@uniweb/build 0.1.20 → 0.1.22
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 +2 -2
- package/src/prerender.js +22 -10
- package/src/site/content-collector.js +101 -25
- package/src/site/plugin.js +26 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@tailwindcss/vite": "^4.0.0",
|
|
60
60
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
61
61
|
"vite-plugin-svgr": "^4.0.0",
|
|
62
|
-
"@uniweb/core": "0.1.
|
|
62
|
+
"@uniweb/core": "0.1.10"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"vite": {
|
package/src/prerender.js
CHANGED
|
@@ -118,11 +118,21 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
118
118
|
const pages = uniweb.activeWebsite.pages
|
|
119
119
|
|
|
120
120
|
for (const page of pages) {
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// Determine which routes to render this page at
|
|
122
|
+
// Index pages are rendered at both their actual route and their nav route
|
|
123
|
+
const routesToRender = [page.route]
|
|
124
|
+
if (page.isIndex) {
|
|
125
|
+
const navRoute = page.getNavRoute()
|
|
126
|
+
if (navRoute !== page.route) {
|
|
127
|
+
routesToRender.push(navRoute)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Render once, output to multiple paths
|
|
132
|
+
onProgress(`Rendering ${routesToRender[0]}...`)
|
|
123
133
|
|
|
124
134
|
// Set this as the active page
|
|
125
|
-
uniweb.activeWebsite.setActivePage(route)
|
|
135
|
+
uniweb.activeWebsite.setActivePage(page.route)
|
|
126
136
|
|
|
127
137
|
// Create the page element
|
|
128
138
|
// Note: We don't need StaticRouter for SSG since we're just rendering
|
|
@@ -134,7 +144,7 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
134
144
|
try {
|
|
135
145
|
renderedContent = renderToString(element)
|
|
136
146
|
} catch (err) {
|
|
137
|
-
console.warn(`Warning: Failed to render ${route}: ${err.message}`)
|
|
147
|
+
console.warn(`Warning: Failed to render ${page.route}: ${err.message}`)
|
|
138
148
|
if (process.env.DEBUG) {
|
|
139
149
|
console.error(err.stack)
|
|
140
150
|
}
|
|
@@ -144,13 +154,15 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
144
154
|
// Inject into shell
|
|
145
155
|
const html = injectContent(htmlShell, renderedContent, page, siteContent)
|
|
146
156
|
|
|
147
|
-
//
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
// Output to all routes for this page
|
|
158
|
+
for (const route of routesToRender) {
|
|
159
|
+
const outputPath = getOutputPath(distDir, route)
|
|
160
|
+
await mkdir(dirname(outputPath), { recursive: true })
|
|
161
|
+
await writeFile(outputPath, html)
|
|
151
162
|
|
|
152
|
-
|
|
153
|
-
|
|
163
|
+
renderedFiles.push(outputPath)
|
|
164
|
+
onProgress(` → ${outputPath.replace(distDir, 'dist')}`)
|
|
165
|
+
}
|
|
154
166
|
}
|
|
155
167
|
|
|
156
168
|
onProgress(`Pre-rendered ${renderedFiles.length} pages`)
|
|
@@ -288,11 +288,14 @@ async function processExplicitSections(sectionsConfig, pagePath, siteRoot, paren
|
|
|
288
288
|
* Process a page directory
|
|
289
289
|
*
|
|
290
290
|
* @param {string} pagePath - Path to page directory
|
|
291
|
-
* @param {string} pageName - Name of the page
|
|
291
|
+
* @param {string} pageName - Name of the page (folder name, not full path)
|
|
292
292
|
* @param {string} siteRoot - Site root directory for asset resolution
|
|
293
|
+
* @param {Object} options - Route options
|
|
294
|
+
* @param {boolean} options.isIndex - Whether this page is the index for its parent route
|
|
295
|
+
* @param {string} options.parentRoute - The parent route (e.g., '/' or '/docs')
|
|
293
296
|
* @returns {Object} Page data with assets manifest
|
|
294
297
|
*/
|
|
295
|
-
async function processPage(pagePath, pageName, siteRoot) {
|
|
298
|
+
async function processPage(pagePath, pageName, siteRoot, { isIndex = false, parentRoute = '/' } = {}) {
|
|
296
299
|
const pageConfig = await readYamlFile(join(pagePath, 'page.yml'))
|
|
297
300
|
|
|
298
301
|
// Note: We no longer skip hidden pages here - they still exist as valid pages,
|
|
@@ -348,11 +351,15 @@ async function processPage(pagePath, pageName, siteRoot) {
|
|
|
348
351
|
}
|
|
349
352
|
|
|
350
353
|
// Determine route
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
// All pages get their actual folder-based route (no special treatment for index)
|
|
355
|
+
// The isIndex flag marks which page should also be accessible at the parent route
|
|
356
|
+
let route
|
|
357
|
+
if (pageName.startsWith('@')) {
|
|
358
|
+
// Special pages (layout areas) keep their @ prefix
|
|
359
|
+
route = parentRoute === '/' ? `/@${pageName.slice(1)}` : `${parentRoute}/@${pageName.slice(1)}`
|
|
360
|
+
} else {
|
|
361
|
+
// Normal pages get parent + their name
|
|
362
|
+
route = parentRoute === '/' ? `/${pageName}` : `${parentRoute}/${pageName}`
|
|
356
363
|
}
|
|
357
364
|
|
|
358
365
|
// Extract configuration
|
|
@@ -361,6 +368,7 @@ async function processPage(pagePath, pageName, siteRoot) {
|
|
|
361
368
|
return {
|
|
362
369
|
page: {
|
|
363
370
|
route,
|
|
371
|
+
isIndex, // Marks this page as the index for its parent route (accessible at parentRoute)
|
|
364
372
|
title: pageConfig.title || pageName,
|
|
365
373
|
description: pageConfig.description || '',
|
|
366
374
|
label: pageConfig.label || null, // Short label for navigation (defaults to title)
|
|
@@ -392,15 +400,50 @@ async function processPage(pagePath, pageName, siteRoot) {
|
|
|
392
400
|
}
|
|
393
401
|
}
|
|
394
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Determine the index page name from ordering config
|
|
405
|
+
*
|
|
406
|
+
* @param {Object} orderConfig - { pages: [...], index: 'name' } from parent
|
|
407
|
+
* @param {Array} availableFolders - Array of { name, order } for folders at this level
|
|
408
|
+
* @returns {string|null} The folder name that should be the index, or null
|
|
409
|
+
*/
|
|
410
|
+
function determineIndexPage(orderConfig, availableFolders) {
|
|
411
|
+
const { pages: pagesArray, index: indexName } = orderConfig || {}
|
|
412
|
+
|
|
413
|
+
// 1. Explicit pages array - first item is index
|
|
414
|
+
if (Array.isArray(pagesArray) && pagesArray.length > 0) {
|
|
415
|
+
return pagesArray[0]
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 2. Explicit index property
|
|
419
|
+
if (indexName) {
|
|
420
|
+
return indexName
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// 3. Fallback: lowest order value, or first alphabetically
|
|
424
|
+
if (availableFolders.length === 0) return null
|
|
425
|
+
|
|
426
|
+
const sorted = [...availableFolders].sort((a, b) => {
|
|
427
|
+
// Sort by order (lower first), then alphabetically
|
|
428
|
+
const orderA = a.order ?? 999
|
|
429
|
+
const orderB = b.order ?? 999
|
|
430
|
+
if (orderA !== orderB) return orderA - orderB
|
|
431
|
+
return a.name.localeCompare(b.name)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
return sorted[0].name
|
|
435
|
+
}
|
|
436
|
+
|
|
395
437
|
/**
|
|
396
438
|
* Recursively collect pages from a directory
|
|
397
439
|
*
|
|
398
440
|
* @param {string} dirPath - Directory to scan
|
|
399
|
-
* @param {string}
|
|
441
|
+
* @param {string} parentRoute - Parent route (e.g., '/' or '/docs')
|
|
400
442
|
* @param {string} siteRoot - Site root directory for asset resolution
|
|
443
|
+
* @param {Object} orderConfig - { pages: [...], index: 'name' } from parent's config
|
|
401
444
|
* @returns {Promise<Object>} { pages, assetCollection, header, footer, left, right }
|
|
402
445
|
*/
|
|
403
|
-
async function collectPagesRecursive(dirPath,
|
|
446
|
+
async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig = {}) {
|
|
404
447
|
const entries = await readdir(dirPath)
|
|
405
448
|
const pages = []
|
|
406
449
|
let assetCollection = {
|
|
@@ -413,30 +456,55 @@ async function collectPagesRecursive(dirPath, routePrefix, siteRoot) {
|
|
|
413
456
|
let left = null
|
|
414
457
|
let right = null
|
|
415
458
|
|
|
459
|
+
// First pass: discover all page folders and read their order values
|
|
460
|
+
const pageFolders = []
|
|
416
461
|
for (const entry of entries) {
|
|
417
462
|
const entryPath = join(dirPath, entry)
|
|
418
463
|
const stats = await stat(entryPath)
|
|
419
|
-
|
|
420
464
|
if (!stats.isDirectory()) continue
|
|
421
465
|
|
|
422
|
-
//
|
|
423
|
-
const
|
|
466
|
+
// Read page.yml to get order and child page config
|
|
467
|
+
const pageConfig = await readYamlFile(join(entryPath, 'page.yml'))
|
|
468
|
+
pageFolders.push({
|
|
469
|
+
name: entry,
|
|
470
|
+
path: entryPath,
|
|
471
|
+
order: pageConfig.order,
|
|
472
|
+
childOrderConfig: {
|
|
473
|
+
pages: pageConfig.pages,
|
|
474
|
+
index: pageConfig.index
|
|
475
|
+
}
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Determine which page is the index for this level
|
|
480
|
+
const regularFolders = pageFolders.filter(f => !f.name.startsWith('@'))
|
|
481
|
+
const indexPageName = determineIndexPage(orderConfig, regularFolders)
|
|
482
|
+
|
|
483
|
+
// Second pass: process each page folder
|
|
484
|
+
for (const folder of pageFolders) {
|
|
485
|
+
const { name: entry, path: entryPath, childOrderConfig } = folder
|
|
486
|
+
const isIndex = entry === indexPageName
|
|
487
|
+
const isSpecial = entry.startsWith('@')
|
|
424
488
|
|
|
425
489
|
// Process this directory as a page
|
|
426
|
-
const result = await processPage(entryPath,
|
|
490
|
+
const result = await processPage(entryPath, entry, siteRoot, {
|
|
491
|
+
isIndex: isIndex && !isSpecial,
|
|
492
|
+
parentRoute
|
|
493
|
+
})
|
|
494
|
+
|
|
427
495
|
if (result) {
|
|
428
496
|
const { page, assetCollection: pageAssets } = result
|
|
429
497
|
assetCollection = mergeAssetCollections(assetCollection, pageAssets)
|
|
430
498
|
|
|
431
499
|
// Handle special pages (layout areas) - only at root level
|
|
432
|
-
if (
|
|
433
|
-
if (entry === '@header'
|
|
500
|
+
if (parentRoute === '/') {
|
|
501
|
+
if (entry === '@header') {
|
|
434
502
|
header = page
|
|
435
|
-
} else if (entry === '@footer'
|
|
503
|
+
} else if (entry === '@footer') {
|
|
436
504
|
footer = page
|
|
437
|
-
} else if (entry === '@left'
|
|
505
|
+
} else if (entry === '@left') {
|
|
438
506
|
left = page
|
|
439
|
-
} else if (entry === '@right'
|
|
507
|
+
} else if (entry === '@right') {
|
|
440
508
|
right = page
|
|
441
509
|
} else {
|
|
442
510
|
pages.push(page)
|
|
@@ -444,13 +512,15 @@ async function collectPagesRecursive(dirPath, routePrefix, siteRoot) {
|
|
|
444
512
|
} else {
|
|
445
513
|
pages.push(page)
|
|
446
514
|
}
|
|
447
|
-
}
|
|
448
515
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
516
|
+
// Recursively process subdirectories (but not special @ directories)
|
|
517
|
+
if (!isSpecial) {
|
|
518
|
+
// The child route depends on whether this page is the index
|
|
519
|
+
const childParentRoute = isIndex ? parentRoute : page.route
|
|
520
|
+
const subResult = await collectPagesRecursive(entryPath, childParentRoute, siteRoot, childOrderConfig)
|
|
521
|
+
pages.push(...subResult.pages)
|
|
522
|
+
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
523
|
+
}
|
|
454
524
|
}
|
|
455
525
|
}
|
|
456
526
|
|
|
@@ -480,9 +550,15 @@ export async function collectSiteContent(sitePath) {
|
|
|
480
550
|
}
|
|
481
551
|
}
|
|
482
552
|
|
|
553
|
+
// Extract page ordering config from site.yml
|
|
554
|
+
const siteOrderConfig = {
|
|
555
|
+
pages: siteConfig.pages,
|
|
556
|
+
index: siteConfig.index
|
|
557
|
+
}
|
|
558
|
+
|
|
483
559
|
// Recursively collect all pages
|
|
484
560
|
const { pages, assetCollection, header, footer, left, right } =
|
|
485
|
-
await collectPagesRecursive(pagesPath, '', sitePath)
|
|
561
|
+
await collectPagesRecursive(pagesPath, '/', sitePath, siteOrderConfig)
|
|
486
562
|
|
|
487
563
|
// Sort pages by order
|
|
488
564
|
pages.sort((a, b) => (a.order ?? 999) - (b.order ?? 999))
|
package/src/site/plugin.js
CHANGED
|
@@ -295,7 +295,9 @@ export function siteContentPlugin(options = {}) {
|
|
|
295
295
|
|
|
296
296
|
// Watch for content changes in dev mode
|
|
297
297
|
if (shouldWatch) {
|
|
298
|
-
const
|
|
298
|
+
const pagesPath = resolve(resolvedSitePath, pagesDir)
|
|
299
|
+
const siteYmlPath = resolve(resolvedSitePath, 'site.yml')
|
|
300
|
+
const themeYmlPath = resolve(resolvedSitePath, 'theme.yml')
|
|
299
301
|
|
|
300
302
|
// Debounce rebuilds
|
|
301
303
|
let rebuildTimeout = null
|
|
@@ -315,12 +317,33 @@ export function siteContentPlugin(options = {}) {
|
|
|
315
317
|
}, 100)
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
// Track all watchers for cleanup
|
|
321
|
+
const watchers = []
|
|
322
|
+
|
|
323
|
+
// Watch pages directory
|
|
318
324
|
try {
|
|
319
|
-
|
|
320
|
-
console.log(`[site-content] Watching ${
|
|
325
|
+
watchers.push(watch(pagesPath, { recursive: true }, scheduleRebuild))
|
|
326
|
+
console.log(`[site-content] Watching ${pagesPath}`)
|
|
321
327
|
} catch (err) {
|
|
322
328
|
console.warn('[site-content] Could not watch pages directory:', err.message)
|
|
323
329
|
}
|
|
330
|
+
|
|
331
|
+
// Watch site.yml
|
|
332
|
+
try {
|
|
333
|
+
watchers.push(watch(siteYmlPath, scheduleRebuild))
|
|
334
|
+
} catch (err) {
|
|
335
|
+
// site.yml may not exist, that's ok
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Watch theme.yml
|
|
339
|
+
try {
|
|
340
|
+
watchers.push(watch(themeYmlPath, scheduleRebuild))
|
|
341
|
+
} catch (err) {
|
|
342
|
+
// theme.yml may not exist, that's ok
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Store watchers for cleanup
|
|
346
|
+
watcher = { close: () => watchers.forEach(w => w.close()) }
|
|
324
347
|
}
|
|
325
348
|
|
|
326
349
|
// Serve content and SEO files
|