@uniweb/build 0.8.20 → 0.8.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 +5 -4
- package/src/prerender.js +15 -0
- package/src/runtime-schema.js +8 -0
- package/src/site/content-collector.js +19 -3
- package/src/site/data-fetcher.js +12 -0
- package/src/vite-foundation-plugin.js +133 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.22",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -48,14 +48,15 @@
|
|
|
48
48
|
"jest": "^29.7.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
+
"esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0",
|
|
51
52
|
"js-yaml": "^4.1.0",
|
|
52
53
|
"sharp": "^0.33.2",
|
|
53
54
|
"@uniweb/theming": "0.1.2"
|
|
54
55
|
},
|
|
55
56
|
"optionalDependencies": {
|
|
56
57
|
"@uniweb/content-reader": "1.1.4",
|
|
57
|
-
"@uniweb/
|
|
58
|
-
"@uniweb/
|
|
58
|
+
"@uniweb/schemas": "0.2.1",
|
|
59
|
+
"@uniweb/runtime": "0.6.18"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
62
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"@tailwindcss/vite": "^4.0.0",
|
|
65
66
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
66
67
|
"vite-plugin-svgr": "^4.0.0",
|
|
67
|
-
"@uniweb/core": "0.5.
|
|
68
|
+
"@uniweb/core": "0.5.15"
|
|
68
69
|
},
|
|
69
70
|
"peerDependenciesMeta": {
|
|
70
71
|
"vite": {
|
package/src/prerender.js
CHANGED
|
@@ -403,6 +403,7 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
403
403
|
prefetchIcons,
|
|
404
404
|
renderPage,
|
|
405
405
|
injectPageContent,
|
|
406
|
+
generate404Html,
|
|
406
407
|
} = await import('@uniweb/runtime/ssr')
|
|
407
408
|
|
|
408
409
|
// Load default site content
|
|
@@ -545,6 +546,20 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
545
546
|
renderedFiles.push(outputPath)
|
|
546
547
|
onProgress(` → ${outputPath.replace(distDir, 'dist')}`)
|
|
547
548
|
}
|
|
549
|
+
|
|
550
|
+
// Write 404.html — shared logic from @uniweb/runtime/ssr
|
|
551
|
+
const fallbackBaseHtml = injectBuildData(htmlShell, siteContent)
|
|
552
|
+
const { html: notFoundHtml, hasNotFoundPage } = generate404Html({
|
|
553
|
+
baseHtml: fallbackBaseHtml,
|
|
554
|
+
website,
|
|
555
|
+
siteContent,
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
const fallbackDir = routePrefix ? join(distDir, routePrefix.replace(/^\//, '')) : distDir
|
|
559
|
+
await mkdir(fallbackDir, { recursive: true })
|
|
560
|
+
await writeFile(join(fallbackDir, '404.html'), notFoundHtml)
|
|
561
|
+
const fallbackNote = hasNotFoundPage ? '404 page + SPA fallback' : 'SPA fallback'
|
|
562
|
+
onProgress(` → ${routePrefix || ''}404.html (${fallbackNote})`)
|
|
548
563
|
}
|
|
549
564
|
|
|
550
565
|
onProgress(`\nPre-rendered ${renderedFiles.length} pages across ${localeConfigs.length} locale(s)`)
|
package/src/runtime-schema.js
CHANGED
|
@@ -228,6 +228,14 @@ export function extractRuntimeSchema(fullMeta) {
|
|
|
228
228
|
if (fullMeta.data.inherit !== undefined) {
|
|
229
229
|
runtime.inheritData = fullMeta.data.inherit
|
|
230
230
|
}
|
|
231
|
+
// detail: false → opt out of single-item resolution on dynamic pages,
|
|
232
|
+
// returning the collection instead (minus the active item)
|
|
233
|
+
if (fullMeta.data.detail !== undefined) {
|
|
234
|
+
runtime.inheritDetail = fullMeta.data.detail
|
|
235
|
+
}
|
|
236
|
+
if (fullMeta.data.limit !== undefined) {
|
|
237
|
+
runtime.inheritLimit = fullMeta.data.limit
|
|
238
|
+
}
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
241
|
|
|
@@ -1425,6 +1425,14 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1425
1425
|
const { page, assetCollection: pageAssets, iconCollection: pageIcons } = result
|
|
1426
1426
|
assetCollection = mergeAssetCollections(assetCollection, pageAssets)
|
|
1427
1427
|
iconCollection = mergeIconCollections(iconCollection, pageIcons)
|
|
1428
|
+
|
|
1429
|
+
// Modern pattern: blog/index/ (isIndex) inherits the container's fetch config
|
|
1430
|
+
// when it has no fetch of its own. Without this, EntityStore can't find the
|
|
1431
|
+
// fetch config for sections on the index page (page.parent is null for /blog).
|
|
1432
|
+
if (isIndex && !page.fetch && parentFetch) {
|
|
1433
|
+
page.fetch = parentFetch
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1428
1436
|
pages.push(page)
|
|
1429
1437
|
|
|
1430
1438
|
// Recurse into subdirectories (page mode)
|
|
@@ -1477,16 +1485,17 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1477
1485
|
changefreq: dirConfig.seo?.changefreq || null,
|
|
1478
1486
|
priority: dirConfig.seo?.priority || null
|
|
1479
1487
|
},
|
|
1480
|
-
fetch: null,
|
|
1488
|
+
fetch: parseFetchConfig(dirConfig.fetch) || null,
|
|
1481
1489
|
sections: [],
|
|
1482
1490
|
order: typeof dirConfig.order === 'number' ? dirConfig.order : undefined
|
|
1483
1491
|
}
|
|
1484
1492
|
|
|
1485
1493
|
pages.push(containerPage)
|
|
1486
1494
|
|
|
1487
|
-
// Recurse in folder mode
|
|
1495
|
+
// Recurse in folder mode — pass container's own fetch config (or fall back to parent's)
|
|
1488
1496
|
const childDirPath = mounts?.get(entry) || entryPath
|
|
1489
|
-
const
|
|
1497
|
+
const containerFetch = containerPage.fetch || parentFetch
|
|
1498
|
+
const subResult = await collectPagesRecursive(childDirPath, containerRoute, siteRoot, childOrderConfig, containerFetch, versionContext, 'pages', null, effectiveLayout)
|
|
1490
1499
|
pages.push(...subResult.pages)
|
|
1491
1500
|
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
1492
1501
|
iconCollection = mergeIconCollections(iconCollection, subResult.iconCollection)
|
|
@@ -1595,6 +1604,13 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1595
1604
|
assetCollection = mergeAssetCollections(assetCollection, pageAssets)
|
|
1596
1605
|
iconCollection = mergeIconCollections(iconCollection, pageIcons)
|
|
1597
1606
|
|
|
1607
|
+
// Modern pattern: articles/index/ (isIndex) inherits the container's fetch config
|
|
1608
|
+
// when it has no fetch of its own. Without this, EntityStore can't find the
|
|
1609
|
+
// fetch config for sections on the index page.
|
|
1610
|
+
if (isIndex && !page.fetch && parentFetch) {
|
|
1611
|
+
page.fetch = parentFetch
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1598
1614
|
// Handle 404 page - only at root level
|
|
1599
1615
|
if (parentRoute === '/' && entry === '404') {
|
|
1600
1616
|
notFound = page
|
package/src/site/data-fetcher.js
CHANGED
|
@@ -225,6 +225,18 @@ export function parseFetchConfig(fetch) {
|
|
|
225
225
|
// Full config object
|
|
226
226
|
if (typeof fetch !== 'object') return null
|
|
227
227
|
|
|
228
|
+
// Inherit-merge config: { inherit: true, detail: false, limit: 3 }
|
|
229
|
+
// No URL — merges with the parent fetch config at runtime; only carries override props.
|
|
230
|
+
if (fetch.inherit === true) {
|
|
231
|
+
return {
|
|
232
|
+
inherit: true,
|
|
233
|
+
...(fetch.detail !== undefined ? { detail: fetch.detail } : {}),
|
|
234
|
+
...(fetch.limit !== undefined ? { limit: fetch.limit } : {}),
|
|
235
|
+
...(fetch.sort !== undefined ? { sort: fetch.sort } : {}),
|
|
236
|
+
...(fetch.filter !== undefined ? { filter: fetch.filter } : {}),
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
228
240
|
// Collection reference: { collection: 'articles', limit: 3 }
|
|
229
241
|
if (fetch.collection) {
|
|
230
242
|
return {
|
|
@@ -34,6 +34,133 @@ async function buildSchemaWithPreviews(srcDir, outDir, isProduction, sectionPath
|
|
|
34
34
|
return schemaWithImages
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Module-level guard to prevent recursive SSR bundle builds.
|
|
39
|
+
* When buildSSRBundle calls esbuild, it should not re-trigger
|
|
40
|
+
* the foundation plugin's writeBundle hook.
|
|
41
|
+
*/
|
|
42
|
+
let _buildingSSRBundle = false
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build a self-contained ESM bundle for edge SSR (Dynamic Workers).
|
|
46
|
+
*
|
|
47
|
+
* Produces foundation.ssr.js — a single ESM file with React, ReactDOM/server,
|
|
48
|
+
* @uniweb/core, and the foundation components all inlined. No external imports.
|
|
49
|
+
*
|
|
50
|
+
* This bundle is loaded into a Cloudflare Dynamic Worker isolate at request time
|
|
51
|
+
* via env.LOADER.get(). The isolate caches the bundle per foundation version.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} outDir - Path to dist/ directory containing foundation.js
|
|
54
|
+
*/
|
|
55
|
+
async function buildSSRBundle(outDir) {
|
|
56
|
+
if (_buildingSSRBundle) return
|
|
57
|
+
_buildingSSRBundle = true
|
|
58
|
+
|
|
59
|
+
const entryPath = join(outDir, 'foundation.js')
|
|
60
|
+
try {
|
|
61
|
+
const { build: esbuild } = await import('esbuild')
|
|
62
|
+
const { statSync } = await import('node:fs')
|
|
63
|
+
|
|
64
|
+
// Collect all node_modules directories up the tree (pnpm hoists to workspace root)
|
|
65
|
+
const { existsSync } = await import('node:fs')
|
|
66
|
+
let searchDir = resolve(outDir, '..')
|
|
67
|
+
let nodePaths = []
|
|
68
|
+
for (let i = 0; i < 10; i++) {
|
|
69
|
+
const candidate = join(searchDir, 'node_modules')
|
|
70
|
+
if (existsSync(candidate)) {
|
|
71
|
+
nodePaths.push(candidate)
|
|
72
|
+
}
|
|
73
|
+
const parent = resolve(searchDir, '..')
|
|
74
|
+
if (parent === searchDir) break
|
|
75
|
+
searchDir = parent
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Resolve workspace packages that esbuild can't find via node_modules
|
|
79
|
+
// (pnpm workspace symlinks aren't in node_modules for the foundation project)
|
|
80
|
+
const { createRequire } = await import('node:module')
|
|
81
|
+
const pluginRequire = createRequire(import.meta.url)
|
|
82
|
+
let runtimeSSRPath
|
|
83
|
+
try {
|
|
84
|
+
runtimeSSRPath = pluginRequire.resolve('@uniweb/runtime/ssr')
|
|
85
|
+
} catch {
|
|
86
|
+
// Fallback: try to find it relative to the workspace root
|
|
87
|
+
for (const np of nodePaths) {
|
|
88
|
+
const candidate = join(np, '@uniweb', 'runtime', 'dist', 'ssr.js')
|
|
89
|
+
if (existsSync(candidate)) {
|
|
90
|
+
runtimeSSRPath = candidate
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Build a self-contained ESM bundle including:
|
|
97
|
+
// - Foundation components (from the just-built ESM output)
|
|
98
|
+
// - React + ReactDOM/server (browser version, no Node.js built-ins)
|
|
99
|
+
// - @uniweb/core (Website, Page, Block classes)
|
|
100
|
+
// - @uniweb/runtime/ssr (initPrerender, renderPage, injectPageContent)
|
|
101
|
+
// - @uniweb/theming (buildSectionOverrides, used by runtime/ssr)
|
|
102
|
+
//
|
|
103
|
+
// All in a single file so the Dynamic Worker isolate has one React instance.
|
|
104
|
+
const ssrExports = runtimeSSRPath
|
|
105
|
+
? `export { initPrerender, renderPage, injectPageContent, prefetchIcons } from "${runtimeSSRPath.replace(/\\/g, '/')}";`
|
|
106
|
+
: ''
|
|
107
|
+
|
|
108
|
+
// Resolve React to a single package directory to avoid duplicate instances
|
|
109
|
+
// (foundation.js and runtime/ssr may resolve to different copies)
|
|
110
|
+
const { dirname } = await import('node:path')
|
|
111
|
+
let reactDir
|
|
112
|
+
try {
|
|
113
|
+
reactDir = dirname(pluginRequire.resolve('react/package.json'))
|
|
114
|
+
} catch {
|
|
115
|
+
// Fall back to nodePaths resolution
|
|
116
|
+
}
|
|
117
|
+
const alias = {}
|
|
118
|
+
if (reactDir) {
|
|
119
|
+
alias['react'] = reactDir
|
|
120
|
+
// Force react-dom/server imports to the browser version (no Node.js built-ins)
|
|
121
|
+
const reactDomDir = dirname(pluginRequire.resolve('react-dom/package.json'))
|
|
122
|
+
alias['react-dom'] = reactDomDir
|
|
123
|
+
alias['react-dom/server'] = join(reactDomDir, 'server.browser.js')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const foundationPath = entryPath.replace(/\\/g, '/')
|
|
127
|
+
await esbuild({
|
|
128
|
+
stdin: {
|
|
129
|
+
contents: [
|
|
130
|
+
// Foundation components (named + default export)
|
|
131
|
+
`export * from "${foundationPath}";`,
|
|
132
|
+
`export { default } from "${foundationPath}";`,
|
|
133
|
+
// React SSR
|
|
134
|
+
`export { renderToString } from "react-dom/server.browser";`,
|
|
135
|
+
`export { createElement } from "react";`,
|
|
136
|
+
// Runtime SSR functions (initPrerender, renderPage, etc.)
|
|
137
|
+
ssrExports,
|
|
138
|
+
].join('\n'),
|
|
139
|
+
resolveDir: outDir,
|
|
140
|
+
loader: 'js',
|
|
141
|
+
},
|
|
142
|
+
bundle: true,
|
|
143
|
+
format: 'esm',
|
|
144
|
+
platform: 'browser',
|
|
145
|
+
outfile: join(outDir, 'foundation.ssr.js'),
|
|
146
|
+
minify: false,
|
|
147
|
+
external: [],
|
|
148
|
+
nodePaths,
|
|
149
|
+
alias,
|
|
150
|
+
conditions: ['browser', 'module'],
|
|
151
|
+
logLevel: 'warning',
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const ssrFile = join(outDir, 'foundation.ssr.js')
|
|
155
|
+
const size = (statSync(ssrFile).size / 1024).toFixed(1)
|
|
156
|
+
console.log(`Generated foundation.ssr.js (${size} KB)`)
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.warn(`Warning: SSR bundle build failed: ${err.message}`)
|
|
159
|
+
} finally {
|
|
160
|
+
_buildingSSRBundle = false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
37
164
|
/**
|
|
38
165
|
* Vite plugin for foundation builds
|
|
39
166
|
*/
|
|
@@ -69,6 +196,9 @@ export function foundationBuildPlugin(options = {}) {
|
|
|
69
196
|
},
|
|
70
197
|
|
|
71
198
|
async writeBundle() {
|
|
199
|
+
// Skip if this is a recursive call from buildSSRBundle
|
|
200
|
+
if (_buildingSSRBundle) return
|
|
201
|
+
|
|
72
202
|
// After bundle is written, generate schema.json in meta folder
|
|
73
203
|
const outDir = resolve(resolvedOutDir)
|
|
74
204
|
const metaDir = join(outDir, 'meta')
|
|
@@ -87,6 +217,9 @@ export function foundationBuildPlugin(options = {}) {
|
|
|
87
217
|
await writeFile(schemaPath, JSON.stringify(schema, null, 2), 'utf-8')
|
|
88
218
|
|
|
89
219
|
console.log(`Generated meta/schema.json with ${Object.keys(schema).length - 1} components`)
|
|
220
|
+
|
|
221
|
+
// Build self-contained SSR bundle for edge rendering (Dynamic Workers)
|
|
222
|
+
await buildSSRBundle(outDir)
|
|
90
223
|
},
|
|
91
224
|
}
|
|
92
225
|
}
|