@uniweb/build 0.1.28 → 0.1.30
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/site/config.js +46 -1
- package/src/site/plugin.js +112 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
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.4",
|
|
54
|
-
"@uniweb/runtime": "0.2.
|
|
54
|
+
"@uniweb/runtime": "0.2.17"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
package/src/site/config.js
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { existsSync, readFileSync } from 'node:fs'
|
|
24
|
-
import { resolve, dirname } from 'node:path'
|
|
24
|
+
import { resolve, dirname, join } from 'node:path'
|
|
25
25
|
import yaml from 'js-yaml'
|
|
26
|
+
import { generateEntryPoint } from '../generate-entry.js'
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Detect foundation type from the foundation config value
|
|
@@ -177,8 +178,52 @@ export async function defineSiteConfig(options = {}) {
|
|
|
177
178
|
foundationDevPlugin = modules[3].foundationDevPlugin
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
// Plugin to ensure foundation entry file exists (for bundled mode with local foundation)
|
|
182
|
+
const ensureFoundationEntryPlugin = !isRuntimeMode && foundationInfo.type === 'local' ? {
|
|
183
|
+
name: 'uniweb:ensure-foundation-entry',
|
|
184
|
+
async config() {
|
|
185
|
+
const srcDir = join(foundationInfo.path, 'src')
|
|
186
|
+
const entryPath = join(srcDir, '_entry.generated.js')
|
|
187
|
+
|
|
188
|
+
// Always regenerate on dev start to ensure it's current
|
|
189
|
+
// This handles new components being added
|
|
190
|
+
if (existsSync(srcDir)) {
|
|
191
|
+
console.log('[site] Ensuring foundation entry is up to date...')
|
|
192
|
+
try {
|
|
193
|
+
await generateEntryPoint(srcDir, entryPath)
|
|
194
|
+
} catch (err) {
|
|
195
|
+
console.warn('[site] Failed to generate foundation entry:', err.message)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
configureServer(server) {
|
|
201
|
+
// Watch foundation src for meta.js changes to regenerate entry
|
|
202
|
+
const srcDir = join(foundationInfo.path, 'src')
|
|
203
|
+
const entryPath = join(srcDir, '_entry.generated.js')
|
|
204
|
+
|
|
205
|
+
server.watcher.add(join(srcDir, '**', 'meta.js'))
|
|
206
|
+
|
|
207
|
+
server.watcher.on('all', async (event, path) => {
|
|
208
|
+
// Regenerate entry when meta.js files change (new/deleted components)
|
|
209
|
+
if (path.includes(srcDir) && path.endsWith('meta.js')) {
|
|
210
|
+
console.log(`[site] Foundation meta.js changed, regenerating entry...`)
|
|
211
|
+
try {
|
|
212
|
+
await generateEntryPoint(srcDir, entryPath)
|
|
213
|
+
server.ws.send({ type: 'full-reload' })
|
|
214
|
+
} catch (err) {
|
|
215
|
+
console.warn('[site] Failed to regenerate foundation entry:', err.message)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
} : null
|
|
221
|
+
|
|
180
222
|
// Build the plugins array
|
|
181
223
|
const plugins = [
|
|
224
|
+
// Ensure foundation entry exists first (bundled mode only)
|
|
225
|
+
ensureFoundationEntryPlugin,
|
|
226
|
+
|
|
182
227
|
// Standard plugins
|
|
183
228
|
tailwind && tailwindcss(),
|
|
184
229
|
react(),
|
package/src/site/plugin.js
CHANGED
|
@@ -37,6 +37,79 @@ import { collectSiteContent } from './content-collector.js'
|
|
|
37
37
|
import { processAssets, rewriteSiteContentPaths } from './asset-processor.js'
|
|
38
38
|
import { processAdvancedAssets } from './advanced-processors.js'
|
|
39
39
|
import { processCollections, writeCollectionFiles } from './collection-processor.js'
|
|
40
|
+
import { executeFetch, mergeDataIntoContent } from './data-fetcher.js'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Execute all fetches for site content (used in dev mode)
|
|
44
|
+
* Populates section.cascadedData with fetched data
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} siteContent - The collected site content
|
|
47
|
+
* @param {string} siteDir - Path to site directory
|
|
48
|
+
*/
|
|
49
|
+
async function executeDevFetches(siteContent, siteDir) {
|
|
50
|
+
const fetchOptions = { siteRoot: siteDir, publicDir: 'public' }
|
|
51
|
+
|
|
52
|
+
// Site-level fetch
|
|
53
|
+
let siteCascadedData = {}
|
|
54
|
+
const siteFetch = siteContent.config?.fetch
|
|
55
|
+
if (siteFetch) {
|
|
56
|
+
const result = await executeFetch(siteFetch, fetchOptions)
|
|
57
|
+
if (result.data && !result.error) {
|
|
58
|
+
siteCascadedData[siteFetch.schema] = result.data
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Process each page
|
|
63
|
+
for (const page of siteContent.pages || []) {
|
|
64
|
+
let pageCascadedData = { ...siteCascadedData }
|
|
65
|
+
|
|
66
|
+
// Page-level fetch
|
|
67
|
+
const pageFetch = page.fetch
|
|
68
|
+
if (pageFetch) {
|
|
69
|
+
const result = await executeFetch(pageFetch, fetchOptions)
|
|
70
|
+
if (result.data && !result.error) {
|
|
71
|
+
pageCascadedData[pageFetch.schema] = result.data
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Process sections
|
|
76
|
+
await processDevSectionFetches(page.sections, pageCascadedData, fetchOptions)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Process fetches for sections recursively
|
|
82
|
+
*
|
|
83
|
+
* @param {Array} sections - Sections to process
|
|
84
|
+
* @param {Object} cascadedData - Data from parent levels
|
|
85
|
+
* @param {Object} fetchOptions - Options for executeFetch
|
|
86
|
+
*/
|
|
87
|
+
async function processDevSectionFetches(sections, cascadedData, fetchOptions) {
|
|
88
|
+
if (!sections || !Array.isArray(sections)) return
|
|
89
|
+
|
|
90
|
+
for (const section of sections) {
|
|
91
|
+
// Execute section-level fetch
|
|
92
|
+
const sectionFetch = section.fetch
|
|
93
|
+
if (sectionFetch) {
|
|
94
|
+
const result = await executeFetch(sectionFetch, fetchOptions)
|
|
95
|
+
if (result.data && !result.error) {
|
|
96
|
+
// Merge fetched data into cascadedData for this section
|
|
97
|
+
cascadedData = {
|
|
98
|
+
...cascadedData,
|
|
99
|
+
[sectionFetch.schema]: result.data
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Attach cascaded data to section
|
|
105
|
+
section.cascadedData = { ...cascadedData }
|
|
106
|
+
|
|
107
|
+
// Process subsections recursively
|
|
108
|
+
if (section.subsections && section.subsections.length > 0) {
|
|
109
|
+
await processDevSectionFetches(section.subsections, cascadedData, fetchOptions)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
40
113
|
import { generateSearchIndex, isSearchEnabled, getSearchIndexFilename } from '../search/index.js'
|
|
41
114
|
import { mergeTranslations } from '../i18n/merge.js'
|
|
42
115
|
|
|
@@ -274,6 +347,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
274
347
|
let server = null
|
|
275
348
|
let localeTranslations = {} // Cache: { locale: translations }
|
|
276
349
|
let localesDir = 'locales' // Default, updated from site config
|
|
350
|
+
let collectionsConfig = null // Cached for watcher setup
|
|
277
351
|
|
|
278
352
|
/**
|
|
279
353
|
* Load translations for a specific locale
|
|
@@ -332,10 +406,28 @@ export function siteContentPlugin(options = {}) {
|
|
|
332
406
|
return {
|
|
333
407
|
name: 'uniweb:site-content',
|
|
334
408
|
|
|
335
|
-
configResolved(config) {
|
|
409
|
+
async configResolved(config) {
|
|
336
410
|
resolvedSitePath = resolve(config.root, sitePath)
|
|
337
411
|
resolvedOutDir = resolve(config.root, config.build.outDir)
|
|
338
412
|
isProduction = config.command === 'build'
|
|
413
|
+
|
|
414
|
+
// In dev mode, process collections early so JSON files exist before server starts
|
|
415
|
+
// This runs before configureServer, ensuring data is available immediately
|
|
416
|
+
if (!isProduction) {
|
|
417
|
+
try {
|
|
418
|
+
// Do an early content collection to get the collections config
|
|
419
|
+
const earlyContent = await collectSiteContent(resolvedSitePath)
|
|
420
|
+
collectionsConfig = earlyContent.config?.collections
|
|
421
|
+
|
|
422
|
+
if (collectionsConfig) {
|
|
423
|
+
console.log('[site-content] Processing content collections...')
|
|
424
|
+
const collections = await processCollections(resolvedSitePath, collectionsConfig)
|
|
425
|
+
await writeCollectionFiles(resolvedSitePath, collections)
|
|
426
|
+
}
|
|
427
|
+
} catch (err) {
|
|
428
|
+
console.warn('[site-content] Early collection processing failed:', err.message)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
339
431
|
},
|
|
340
432
|
|
|
341
433
|
async buildStart() {
|
|
@@ -345,13 +437,20 @@ export function siteContentPlugin(options = {}) {
|
|
|
345
437
|
console.log(`[site-content] Collected ${siteContent.pages?.length || 0} pages`)
|
|
346
438
|
|
|
347
439
|
// Process content collections if defined in site.yml
|
|
348
|
-
//
|
|
349
|
-
|
|
440
|
+
// In dev mode, this was already done in configResolved (before server starts)
|
|
441
|
+
// In production, do it here
|
|
442
|
+
if (isProduction && siteContent.config?.collections) {
|
|
350
443
|
console.log('[site-content] Processing content collections...')
|
|
351
444
|
const collections = await processCollections(resolvedSitePath, siteContent.config.collections)
|
|
352
445
|
await writeCollectionFiles(resolvedSitePath, collections)
|
|
353
446
|
}
|
|
354
447
|
|
|
448
|
+
// Execute data fetches in dev mode
|
|
449
|
+
// In production, prerender handles this
|
|
450
|
+
if (!isProduction) {
|
|
451
|
+
await executeDevFetches(siteContent, resolvedSitePath)
|
|
452
|
+
}
|
|
453
|
+
|
|
355
454
|
// Update localesDir from site config
|
|
356
455
|
if (siteContent.config?.i18n?.localesDir) {
|
|
357
456
|
localesDir = siteContent.config.i18n.localesDir
|
|
@@ -382,6 +481,8 @@ export function siteContentPlugin(options = {}) {
|
|
|
382
481
|
console.log('[site-content] Content changed, rebuilding...')
|
|
383
482
|
try {
|
|
384
483
|
siteContent = await collectSiteContent(resolvedSitePath)
|
|
484
|
+
// Execute fetches for the updated content
|
|
485
|
+
await executeDevFetches(siteContent, resolvedSitePath)
|
|
385
486
|
console.log(`[site-content] Rebuilt ${siteContent.pages?.length || 0} pages`)
|
|
386
487
|
|
|
387
488
|
// Send full reload to client
|
|
@@ -399,9 +500,11 @@ export function siteContentPlugin(options = {}) {
|
|
|
399
500
|
collectionRebuildTimeout = setTimeout(async () => {
|
|
400
501
|
console.log('[site-content] Collection content changed, regenerating JSON...')
|
|
401
502
|
try {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
503
|
+
// Use collectionsConfig (cached from configResolved) or siteContent
|
|
504
|
+
const collections = collectionsConfig || siteContent?.config?.collections
|
|
505
|
+
if (collections) {
|
|
506
|
+
const processed = await processCollections(resolvedSitePath, collections)
|
|
507
|
+
await writeCollectionFiles(resolvedSitePath, processed)
|
|
405
508
|
}
|
|
406
509
|
// Send full reload to client
|
|
407
510
|
server.ws.send({ type: 'full-reload' })
|
|
@@ -437,9 +540,10 @@ export function siteContentPlugin(options = {}) {
|
|
|
437
540
|
}
|
|
438
541
|
|
|
439
542
|
// Watch content/ folder for collection changes
|
|
440
|
-
|
|
543
|
+
// Use collectionsConfig cached from configResolved (siteContent may be null here)
|
|
544
|
+
if (collectionsConfig) {
|
|
441
545
|
const contentPaths = new Set()
|
|
442
|
-
for (const config of Object.values(
|
|
546
|
+
for (const config of Object.values(collectionsConfig)) {
|
|
443
547
|
const collectionPath = typeof config === 'string' ? config : config.path
|
|
444
548
|
if (collectionPath) {
|
|
445
549
|
contentPaths.add(resolve(resolvedSitePath, collectionPath))
|