@uniweb/build 0.1.14 → 0.1.15
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/foundation/config.js +5 -2
- package/src/generate-entry.js +40 -27
- package/src/prerender.js +5 -4
- package/src/schema.js +26 -175
- package/src/site/content-collector.js +73 -43
- package/src/vite-foundation-plugin.js +37 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
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.7"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"vite": {
|
package/src/foundation/config.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { resolve } from 'node:path'
|
|
27
|
+
import { foundationPlugin } from '../vite-foundation-plugin.js'
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Default externals for foundations
|
|
@@ -45,7 +46,7 @@ const DEFAULT_EXTERNALS = [
|
|
|
45
46
|
* Create a complete Vite configuration for a Uniweb foundation
|
|
46
47
|
*
|
|
47
48
|
* @param {Object} [options={}] - Configuration options
|
|
48
|
-
* @param {string} [options.entry] - Entry point path (default: 'src/
|
|
49
|
+
* @param {string} [options.entry] - Entry point path (default: 'src/_entry.generated.js')
|
|
49
50
|
* @param {string} [options.fileName] - Output file name (default: 'foundation')
|
|
50
51
|
* @param {string[]} [options.externals] - Additional packages to externalize
|
|
51
52
|
* @param {boolean} [options.includeDefaultExternals] - Include default externals (default: true)
|
|
@@ -57,7 +58,7 @@ const DEFAULT_EXTERNALS = [
|
|
|
57
58
|
*/
|
|
58
59
|
export async function defineFoundationConfig(options = {}) {
|
|
59
60
|
const {
|
|
60
|
-
entry = 'src/
|
|
61
|
+
entry = 'src/_entry.generated.js',
|
|
61
62
|
fileName = 'foundation',
|
|
62
63
|
externals: additionalExternals = [],
|
|
63
64
|
includeDefaultExternals = true,
|
|
@@ -101,7 +102,9 @@ export async function defineFoundationConfig(options = {}) {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
// Build the plugins array
|
|
105
|
+
// foundationPlugin handles entry generation and schema building
|
|
104
106
|
const plugins = [
|
|
107
|
+
foundationPlugin({ srcDir: 'src' }),
|
|
105
108
|
tailwind && tailwindcss(),
|
|
106
109
|
react(),
|
|
107
110
|
svgr(),
|
package/src/generate-entry.js
CHANGED
|
@@ -15,15 +15,15 @@ import {
|
|
|
15
15
|
} from './schema.js'
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Detect
|
|
19
|
-
* Looks for: src/
|
|
18
|
+
* Detect runtime configuration file (for custom Layout, props, etc.)
|
|
19
|
+
* Looks for: src/runtime.js, src/runtime.jsx, src/runtime/index.js, src/runtime/index.jsx
|
|
20
20
|
*/
|
|
21
|
-
function
|
|
21
|
+
function detectRuntimeExports(srcDir) {
|
|
22
22
|
const candidates = [
|
|
23
|
-
{ path: '
|
|
24
|
-
{ path: '
|
|
25
|
-
{ path: '
|
|
26
|
-
{ path: '
|
|
23
|
+
{ path: 'runtime.js', ext: 'js' },
|
|
24
|
+
{ path: 'runtime.jsx', ext: 'jsx' },
|
|
25
|
+
{ path: 'runtime/index.js', ext: 'js' },
|
|
26
|
+
{ path: 'runtime/index.jsx', ext: 'jsx' },
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
for (const { path, ext } of candidates) {
|
|
@@ -34,23 +34,36 @@ function detectSiteConfig(srcDir) {
|
|
|
34
34
|
return null
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Detect CSS file
|
|
39
|
+
* Looks for: src/styles.css, src/index.css
|
|
40
|
+
*/
|
|
41
|
+
function detectCssFile(srcDir) {
|
|
42
|
+
const candidates = ['styles.css', 'index.css']
|
|
43
|
+
for (const file of candidates) {
|
|
44
|
+
if (existsSync(join(srcDir, file))) {
|
|
45
|
+
return `./${file}`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
/**
|
|
38
52
|
* Generate the entry point source code
|
|
39
53
|
*/
|
|
40
54
|
function generateEntrySource(componentNames, runtimeConfig, options = {}) {
|
|
41
|
-
const {
|
|
55
|
+
const { cssPath = null, componentExtensions = {}, runtimeExports = null } = options
|
|
42
56
|
|
|
43
57
|
const imports = []
|
|
44
|
-
const exports = []
|
|
45
58
|
|
|
46
59
|
// CSS import
|
|
47
|
-
if (
|
|
60
|
+
if (cssPath) {
|
|
48
61
|
imports.push(`import '${cssPath}'`)
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
//
|
|
52
|
-
if (
|
|
53
|
-
imports.push(`import
|
|
64
|
+
// Runtime exports import (for custom Layout, props, etc.)
|
|
65
|
+
if (runtimeExports) {
|
|
66
|
+
imports.push(`import runtime from '${runtimeExports.path}'`)
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
// Component imports (use detected extension or default to .js)
|
|
@@ -130,10 +143,10 @@ export function getSchema(name) {
|
|
|
130
143
|
? `\n// Named exports for direct imports\nexport { ${componentNames.join(', ')} }`
|
|
131
144
|
: ''
|
|
132
145
|
|
|
133
|
-
//
|
|
134
|
-
const
|
|
135
|
-
? `\n//
|
|
136
|
-
: `\n// No
|
|
146
|
+
// Runtime exports (Layout, props, etc.)
|
|
147
|
+
const runtimeExport = runtimeExports
|
|
148
|
+
? `\n// Runtime exports (Layout, props, etc.)\nexport { runtime }`
|
|
149
|
+
: `\n// No runtime exports provided\nexport const runtime = null`
|
|
137
150
|
|
|
138
151
|
return `// Auto-generated foundation entry point
|
|
139
152
|
// DO NOT EDIT - This file is regenerated during build
|
|
@@ -144,7 +157,7 @@ ${componentsObj}
|
|
|
144
157
|
${runtimeConfigBlock}
|
|
145
158
|
${exportFunctions}
|
|
146
159
|
${namedExports}
|
|
147
|
-
${
|
|
160
|
+
${runtimeExport}
|
|
148
161
|
`
|
|
149
162
|
}
|
|
150
163
|
|
|
@@ -194,17 +207,17 @@ export async function generateEntryPoint(srcDir, outputPath = null) {
|
|
|
194
207
|
}
|
|
195
208
|
}
|
|
196
209
|
|
|
197
|
-
// Check
|
|
198
|
-
const
|
|
210
|
+
// Check for CSS file
|
|
211
|
+
const cssPath = detectCssFile(srcDir)
|
|
199
212
|
|
|
200
|
-
// Check for
|
|
201
|
-
const
|
|
213
|
+
// Check for runtime exports (custom Layout, props, etc.)
|
|
214
|
+
const runtimeExports = detectRuntimeExports(srcDir)
|
|
202
215
|
|
|
203
216
|
// Generate source
|
|
204
217
|
const source = generateEntrySource(componentNames, runtimeConfig, {
|
|
205
|
-
|
|
218
|
+
cssPath,
|
|
206
219
|
componentExtensions,
|
|
207
|
-
|
|
220
|
+
runtimeExports,
|
|
208
221
|
})
|
|
209
222
|
|
|
210
223
|
// Write to file
|
|
@@ -214,15 +227,15 @@ export async function generateEntryPoint(srcDir, outputPath = null) {
|
|
|
214
227
|
|
|
215
228
|
console.log(`Generated entry point: ${output}`)
|
|
216
229
|
console.log(` - ${componentNames.length} components: ${componentNames.join(', ')}`)
|
|
217
|
-
if (
|
|
218
|
-
console.log(` -
|
|
230
|
+
if (runtimeExports) {
|
|
231
|
+
console.log(` - Runtime exports found: ${runtimeExports.path}`)
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
return {
|
|
222
235
|
outputPath: output,
|
|
223
236
|
componentNames,
|
|
224
237
|
runtimeConfig,
|
|
225
|
-
|
|
238
|
+
runtimeExports,
|
|
226
239
|
}
|
|
227
240
|
}
|
|
228
241
|
|
package/src/prerender.js
CHANGED
|
@@ -108,8 +108,9 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
108
108
|
const uniweb = createUniweb(siteContent)
|
|
109
109
|
uniweb.setFoundation(foundation)
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
// Check for foundation config (runtime is the new name, config/site are legacy)
|
|
112
|
+
if (foundation.runtime || foundation.config || foundation.site) {
|
|
113
|
+
uniweb.setFoundationConfig(foundation.runtime || foundation.config || foundation.site)
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
// Pre-render each page
|
|
@@ -187,8 +188,8 @@ function DefaultLayout({ header, body, footer }) {
|
|
|
187
188
|
* Supports foundation-provided custom Layout via site.Layout
|
|
188
189
|
*/
|
|
189
190
|
function Layout({ page, website, foundation }) {
|
|
190
|
-
// Check if foundation provides a custom Layout
|
|
191
|
-
const RemoteLayout = foundation.site?.Layout || null
|
|
191
|
+
// Check if foundation provides a custom Layout (runtime is the new name, site is legacy)
|
|
192
|
+
const RemoteLayout = foundation.runtime?.Layout || foundation.site?.Layout || null
|
|
192
193
|
|
|
193
194
|
// Get block groups from page
|
|
194
195
|
const headerBlocks = page.getHeaderBlocks()
|
package/src/schema.js
CHANGED
|
@@ -5,169 +5,24 @@
|
|
|
5
5
|
* runtime-relevant configuration.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readdir
|
|
8
|
+
import { readdir } from 'node:fs/promises'
|
|
9
9
|
import { existsSync } from 'node:fs'
|
|
10
10
|
import { join, basename } from 'node:path'
|
|
11
11
|
import { pathToFileURL } from 'node:url'
|
|
12
12
|
|
|
13
|
-
// Meta file
|
|
14
|
-
const
|
|
13
|
+
// Meta file name (standardized to meta.js)
|
|
14
|
+
const META_FILE_NAME = 'meta.js'
|
|
15
15
|
|
|
16
16
|
// Keys that should be extracted for runtime (embedded in foundation.js)
|
|
17
17
|
const RUNTIME_KEYS = ['input', 'props']
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
* Supports basic key-value, nested objects, and arrays
|
|
22
|
-
*/
|
|
23
|
-
function parseYaml(content) {
|
|
24
|
-
const lines = content.split('\n')
|
|
25
|
-
return parseYamlLines(lines, 0).value
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getIndent(line) {
|
|
29
|
-
const match = line.match(/^(\s*)/)
|
|
30
|
-
return match ? match[1].length : 0
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function parseYamlValue(value) {
|
|
34
|
-
const trimmed = value.trim()
|
|
35
|
-
if (!trimmed) return null
|
|
36
|
-
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
37
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
38
|
-
return trimmed.slice(1, -1)
|
|
39
|
-
}
|
|
40
|
-
if (trimmed === 'true') return true
|
|
41
|
-
if (trimmed === 'false') return false
|
|
42
|
-
if (!isNaN(Number(trimmed)) && trimmed !== '') return Number(trimmed)
|
|
43
|
-
return trimmed
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function parseYamlLines(lines, startIndex, baseIndent = 0) {
|
|
47
|
-
const result = {}
|
|
48
|
-
let i = startIndex
|
|
49
|
-
|
|
50
|
-
while (i < lines.length) {
|
|
51
|
-
const line = lines[i]
|
|
52
|
-
const trimmed = line.trim()
|
|
53
|
-
|
|
54
|
-
if (!trimmed || trimmed.startsWith('#')) {
|
|
55
|
-
i++
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const indent = getIndent(line)
|
|
60
|
-
if (indent < baseIndent && i > startIndex) break
|
|
61
|
-
if (trimmed.startsWith('- ')) {
|
|
62
|
-
i++
|
|
63
|
-
continue
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const colonIndex = trimmed.indexOf(':')
|
|
67
|
-
if (colonIndex === -1) {
|
|
68
|
-
i++
|
|
69
|
-
continue
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const key = trimmed.slice(0, colonIndex).trim()
|
|
73
|
-
const valueAfterColon = trimmed.slice(colonIndex + 1).trim()
|
|
74
|
-
|
|
75
|
-
const nextLine = lines[i + 1]
|
|
76
|
-
const nextTrimmed = nextLine?.trim()
|
|
77
|
-
const nextIndent = nextLine ? getIndent(nextLine) : 0
|
|
78
|
-
|
|
79
|
-
if (nextTrimmed?.startsWith('- ') && nextIndent > indent) {
|
|
80
|
-
const arrayResult = parseYamlArray(lines, i + 1, nextIndent)
|
|
81
|
-
result[key] = arrayResult.value
|
|
82
|
-
i = arrayResult.endIndex
|
|
83
|
-
} else if (!valueAfterColon && nextIndent > indent) {
|
|
84
|
-
const nestedResult = parseYamlLines(lines, i + 1, nextIndent)
|
|
85
|
-
result[key] = nestedResult.value
|
|
86
|
-
i = nestedResult.endIndex
|
|
87
|
-
} else {
|
|
88
|
-
result[key] = parseYamlValue(valueAfterColon)
|
|
89
|
-
i++
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { value: result, endIndex: i }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function parseYamlArray(lines, startIndex, baseIndent) {
|
|
97
|
-
const result = []
|
|
98
|
-
let i = startIndex
|
|
99
|
-
|
|
100
|
-
while (i < lines.length) {
|
|
101
|
-
const line = lines[i]
|
|
102
|
-
const trimmed = line.trim()
|
|
103
|
-
|
|
104
|
-
if (!trimmed || trimmed.startsWith('#')) {
|
|
105
|
-
i++
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const indent = getIndent(line)
|
|
110
|
-
if (indent < baseIndent) break
|
|
111
|
-
|
|
112
|
-
if (trimmed.startsWith('- ')) {
|
|
113
|
-
const afterDash = trimmed.slice(2)
|
|
114
|
-
const colonIndex = afterDash.indexOf(':')
|
|
115
|
-
|
|
116
|
-
if (colonIndex !== -1) {
|
|
117
|
-
const key = afterDash.slice(0, colonIndex).trim()
|
|
118
|
-
const value = afterDash.slice(colonIndex + 1).trim()
|
|
119
|
-
const obj = { [key]: parseYamlValue(value) }
|
|
120
|
-
const itemIndent = indent + 2
|
|
121
|
-
i++
|
|
122
|
-
|
|
123
|
-
while (i < lines.length) {
|
|
124
|
-
const propLine = lines[i]
|
|
125
|
-
const propTrimmed = propLine?.trim()
|
|
126
|
-
|
|
127
|
-
if (!propTrimmed || propTrimmed.startsWith('#')) {
|
|
128
|
-
i++
|
|
129
|
-
continue
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const propIndent = getIndent(propLine)
|
|
133
|
-
if (propIndent < itemIndent || propTrimmed.startsWith('- ')) break
|
|
134
|
-
|
|
135
|
-
const propColonIndex = propTrimmed.indexOf(':')
|
|
136
|
-
if (propColonIndex !== -1) {
|
|
137
|
-
const propKey = propTrimmed.slice(0, propColonIndex).trim()
|
|
138
|
-
const propValue = propTrimmed.slice(propColonIndex + 1).trim()
|
|
139
|
-
obj[propKey] = parseYamlValue(propValue)
|
|
140
|
-
}
|
|
141
|
-
i++
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
result.push(obj)
|
|
145
|
-
} else {
|
|
146
|
-
result.push(parseYamlValue(afterDash))
|
|
147
|
-
i++
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
break
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return { value: result, endIndex: i }
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Load a meta file (JS or YAML)
|
|
20
|
+
* Load a meta.js file via dynamic import
|
|
159
21
|
*/
|
|
160
22
|
async function loadMetaFile(filePath) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const module = await import(fileUrl)
|
|
165
|
-
return module.default
|
|
166
|
-
} else {
|
|
167
|
-
// Parse YAML
|
|
168
|
-
const content = await readFile(filePath, 'utf-8')
|
|
169
|
-
return parseYaml(content)
|
|
170
|
-
}
|
|
23
|
+
const fileUrl = pathToFileURL(filePath).href
|
|
24
|
+
const module = await import(fileUrl)
|
|
25
|
+
return module.default
|
|
171
26
|
}
|
|
172
27
|
|
|
173
28
|
/**
|
|
@@ -175,37 +30,33 @@ async function loadMetaFile(filePath) {
|
|
|
175
30
|
* Returns null if no meta file found
|
|
176
31
|
*/
|
|
177
32
|
export async function loadComponentMeta(componentDir) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
33
|
+
const filePath = join(componentDir, META_FILE_NAME)
|
|
34
|
+
if (!existsSync(filePath)) {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const meta = await loadMetaFile(filePath)
|
|
39
|
+
return { meta, fileName: META_FILE_NAME, filePath }
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn(`Warning: Failed to load ${filePath}:`, error.message)
|
|
42
|
+
return null
|
|
189
43
|
}
|
|
190
|
-
return null
|
|
191
44
|
}
|
|
192
45
|
|
|
193
46
|
/**
|
|
194
47
|
* Load foundation-level meta file
|
|
195
48
|
*/
|
|
196
49
|
export async function loadFoundationMeta(srcDir) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
50
|
+
const filePath = join(srcDir, META_FILE_NAME)
|
|
51
|
+
if (!existsSync(filePath)) {
|
|
52
|
+
return {}
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return await loadMetaFile(filePath)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.warn(`Warning: Failed to load foundation meta ${filePath}:`, error.message)
|
|
58
|
+
return {}
|
|
207
59
|
}
|
|
208
|
-
return {}
|
|
209
60
|
}
|
|
210
61
|
|
|
211
62
|
/**
|
|
@@ -381,6 +381,71 @@ async function processPage(pagePath, pageName, siteRoot) {
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Recursively collect pages from a directory
|
|
386
|
+
*
|
|
387
|
+
* @param {string} dirPath - Directory to scan
|
|
388
|
+
* @param {string} routePrefix - Route prefix for nested pages
|
|
389
|
+
* @param {string} siteRoot - Site root directory for asset resolution
|
|
390
|
+
* @returns {Promise<Object>} { pages, assetCollection, header, footer, left, right }
|
|
391
|
+
*/
|
|
392
|
+
async function collectPagesRecursive(dirPath, routePrefix, siteRoot) {
|
|
393
|
+
const entries = await readdir(dirPath)
|
|
394
|
+
const pages = []
|
|
395
|
+
let assetCollection = {
|
|
396
|
+
assets: {},
|
|
397
|
+
hasExplicitPoster: new Set(),
|
|
398
|
+
hasExplicitPreview: new Set()
|
|
399
|
+
}
|
|
400
|
+
let header = null
|
|
401
|
+
let footer = null
|
|
402
|
+
let left = null
|
|
403
|
+
let right = null
|
|
404
|
+
|
|
405
|
+
for (const entry of entries) {
|
|
406
|
+
const entryPath = join(dirPath, entry)
|
|
407
|
+
const stats = await stat(entryPath)
|
|
408
|
+
|
|
409
|
+
if (!stats.isDirectory()) continue
|
|
410
|
+
|
|
411
|
+
// Build the page name/route
|
|
412
|
+
const pageName = routePrefix ? `${routePrefix}/${entry}` : entry
|
|
413
|
+
|
|
414
|
+
// Process this directory as a page
|
|
415
|
+
const result = await processPage(entryPath, pageName, siteRoot)
|
|
416
|
+
if (result) {
|
|
417
|
+
const { page, assetCollection: pageAssets } = result
|
|
418
|
+
assetCollection = mergeAssetCollections(assetCollection, pageAssets)
|
|
419
|
+
|
|
420
|
+
// Handle special pages (layout areas) - only at root level
|
|
421
|
+
if (!routePrefix) {
|
|
422
|
+
if (entry === '@header' || page.route === '/@header') {
|
|
423
|
+
header = page
|
|
424
|
+
} else if (entry === '@footer' || page.route === '/@footer') {
|
|
425
|
+
footer = page
|
|
426
|
+
} else if (entry === '@left' || page.route === '/@left') {
|
|
427
|
+
left = page
|
|
428
|
+
} else if (entry === '@right' || page.route === '/@right') {
|
|
429
|
+
right = page
|
|
430
|
+
} else {
|
|
431
|
+
pages.push(page)
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
pages.push(page)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Recursively process subdirectories (but not special @ directories)
|
|
439
|
+
if (!entry.startsWith('@')) {
|
|
440
|
+
const subResult = await collectPagesRecursive(entryPath, pageName, siteRoot)
|
|
441
|
+
pages.push(...subResult.pages)
|
|
442
|
+
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return { pages, assetCollection, header, footer, left, right }
|
|
447
|
+
}
|
|
448
|
+
|
|
384
449
|
/**
|
|
385
450
|
* Collect all site content
|
|
386
451
|
*
|
|
@@ -404,51 +469,16 @@ export async function collectSiteContent(sitePath) {
|
|
|
404
469
|
}
|
|
405
470
|
}
|
|
406
471
|
|
|
407
|
-
//
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
let siteAssetCollection = {
|
|
411
|
-
assets: {},
|
|
412
|
-
hasExplicitPoster: new Set(),
|
|
413
|
-
hasExplicitPreview: new Set()
|
|
414
|
-
}
|
|
415
|
-
let header = null
|
|
416
|
-
let footer = null
|
|
417
|
-
let left = null
|
|
418
|
-
let right = null
|
|
419
|
-
|
|
420
|
-
for (const entry of entries) {
|
|
421
|
-
const entryPath = join(pagesPath, entry)
|
|
422
|
-
const stats = await stat(entryPath)
|
|
423
|
-
|
|
424
|
-
if (!stats.isDirectory()) continue
|
|
425
|
-
|
|
426
|
-
const result = await processPage(entryPath, entry, sitePath)
|
|
427
|
-
if (!result) continue
|
|
428
|
-
|
|
429
|
-
const { page, assetCollection } = result
|
|
430
|
-
siteAssetCollection = mergeAssetCollections(siteAssetCollection, assetCollection)
|
|
431
|
-
|
|
432
|
-
// Handle special pages (layout areas)
|
|
433
|
-
if (entry === '@header' || page.route === '/@header') {
|
|
434
|
-
header = page
|
|
435
|
-
} else if (entry === '@footer' || page.route === '/@footer') {
|
|
436
|
-
footer = page
|
|
437
|
-
} else if (entry === '@left' || page.route === '/@left') {
|
|
438
|
-
left = page
|
|
439
|
-
} else if (entry === '@right' || page.route === '/@right') {
|
|
440
|
-
right = page
|
|
441
|
-
} else {
|
|
442
|
-
pages.push(page)
|
|
443
|
-
}
|
|
444
|
-
}
|
|
472
|
+
// Recursively collect all pages
|
|
473
|
+
const { pages, assetCollection, header, footer, left, right } =
|
|
474
|
+
await collectPagesRecursive(pagesPath, '', sitePath)
|
|
445
475
|
|
|
446
476
|
// Sort pages by order
|
|
447
477
|
pages.sort((a, b) => (a.order ?? 999) - (b.order ?? 999))
|
|
448
478
|
|
|
449
479
|
// Log asset summary
|
|
450
|
-
const assetCount = Object.keys(
|
|
451
|
-
const explicitCount =
|
|
480
|
+
const assetCount = Object.keys(assetCollection.assets).length
|
|
481
|
+
const explicitCount = assetCollection.hasExplicitPoster.size + assetCollection.hasExplicitPreview.size
|
|
452
482
|
if (assetCount > 0) {
|
|
453
483
|
console.log(`[content-collector] Found ${assetCount} asset references${explicitCount > 0 ? ` (${explicitCount} with explicit poster/preview)` : ''}`)
|
|
454
484
|
}
|
|
@@ -461,9 +491,9 @@ export async function collectSiteContent(sitePath) {
|
|
|
461
491
|
footer,
|
|
462
492
|
left,
|
|
463
493
|
right,
|
|
464
|
-
assets:
|
|
465
|
-
hasExplicitPoster:
|
|
466
|
-
hasExplicitPreview:
|
|
494
|
+
assets: assetCollection.assets,
|
|
495
|
+
hasExplicitPoster: assetCollection.hasExplicitPoster,
|
|
496
|
+
hasExplicitPreview: assetCollection.hasExplicitPreview
|
|
467
497
|
}
|
|
468
498
|
}
|
|
469
499
|
|
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
* - Processing preview images for presets
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { writeFile,
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { buildSchema, discoverComponents } from './schema.js'
|
|
10
|
+
import { writeFile, mkdir } from 'node:fs/promises'
|
|
11
|
+
import { join, resolve } from 'node:path'
|
|
12
|
+
import { buildSchema } from './schema.js'
|
|
14
13
|
import { generateEntryPoint } from './generate-entry.js'
|
|
15
14
|
import { processAllPreviews } from './images.js'
|
|
16
15
|
|
|
@@ -52,20 +51,22 @@ export function foundationBuildPlugin(options = {}) {
|
|
|
52
51
|
return {
|
|
53
52
|
name: 'uniweb-foundation-build',
|
|
54
53
|
|
|
54
|
+
// Generate entry before config resolution (entry must exist for Vite to resolve it)
|
|
55
|
+
async config(config) {
|
|
56
|
+
if (!generateEntry) return
|
|
57
|
+
|
|
58
|
+
const root = config.root || process.cwd()
|
|
59
|
+
const srcPath = resolve(root, srcDir)
|
|
60
|
+
const entryPath = join(srcPath, entryFileName)
|
|
61
|
+
await generateEntryPoint(srcPath, entryPath)
|
|
62
|
+
},
|
|
63
|
+
|
|
55
64
|
async configResolved(config) {
|
|
56
65
|
resolvedSrcDir = resolve(config.root, srcDir)
|
|
57
66
|
resolvedOutDir = config.build.outDir
|
|
58
67
|
isProduction = config.mode === 'production'
|
|
59
68
|
},
|
|
60
69
|
|
|
61
|
-
async buildStart() {
|
|
62
|
-
if (!generateEntry) return
|
|
63
|
-
|
|
64
|
-
// Generate entry point before build starts
|
|
65
|
-
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
66
|
-
await generateEntryPoint(resolvedSrcDir, entryPath)
|
|
67
|
-
},
|
|
68
|
-
|
|
69
70
|
async writeBundle() {
|
|
70
71
|
// After bundle is written, generate schema.json in meta folder
|
|
71
72
|
const outDir = resolve(resolvedOutDir)
|
|
@@ -103,26 +104,36 @@ export function foundationDevPlugin(options = {}) {
|
|
|
103
104
|
return {
|
|
104
105
|
name: 'uniweb-foundation-dev',
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
// Generate entry before config resolution
|
|
108
|
+
async config(config) {
|
|
109
|
+
const root = config.root || process.cwd()
|
|
110
|
+
const srcPath = resolve(root, srcDir)
|
|
111
|
+
const entryPath = join(srcPath, entryFileName)
|
|
112
|
+
await generateEntryPoint(srcPath, entryPath)
|
|
108
113
|
},
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
113
|
-
await generateEntryPoint(resolvedSrcDir, entryPath)
|
|
115
|
+
configResolved(config) {
|
|
116
|
+
resolvedSrcDir = resolve(config.root, srcDir)
|
|
114
117
|
},
|
|
115
118
|
|
|
116
119
|
async handleHotUpdate({ file, server }) {
|
|
117
|
-
// Regenerate entry when meta files change
|
|
118
|
-
if (file.includes('/components/') && file.
|
|
119
|
-
console.log('
|
|
120
|
+
// Regenerate entry when meta.js files change
|
|
121
|
+
if (file.includes('/components/') && file.endsWith('/meta.js')) {
|
|
122
|
+
console.log('Component meta.js changed, regenerating entry...')
|
|
120
123
|
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
121
124
|
await generateEntryPoint(resolvedSrcDir, entryPath)
|
|
122
125
|
|
|
123
126
|
// Trigger full reload since entry changed
|
|
124
127
|
server.ws.send({ type: 'full-reload' })
|
|
125
128
|
}
|
|
129
|
+
|
|
130
|
+
// Also regenerate if runtime.js changes
|
|
131
|
+
if (file.endsWith('/runtime.js') || file.endsWith('/runtime.jsx')) {
|
|
132
|
+
console.log('Runtime exports changed, regenerating entry...')
|
|
133
|
+
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
134
|
+
await generateEntryPoint(resolvedSrcDir, entryPath)
|
|
135
|
+
server.ws.send({ type: 'full-reload' })
|
|
136
|
+
}
|
|
126
137
|
},
|
|
127
138
|
}
|
|
128
139
|
}
|
|
@@ -137,15 +148,16 @@ export function foundationPlugin(options = {}) {
|
|
|
137
148
|
return {
|
|
138
149
|
name: 'uniweb-foundation',
|
|
139
150
|
|
|
151
|
+
async config(config) {
|
|
152
|
+
// Only need to call once - devPlugin.config generates the entry
|
|
153
|
+
await devPlugin.config?.(config)
|
|
154
|
+
},
|
|
155
|
+
|
|
140
156
|
configResolved(config) {
|
|
141
157
|
buildPlugin.configResolved?.(config)
|
|
142
158
|
devPlugin.configResolved?.(config)
|
|
143
159
|
},
|
|
144
160
|
|
|
145
|
-
async buildStart() {
|
|
146
|
-
await devPlugin.buildStart?.()
|
|
147
|
-
},
|
|
148
|
-
|
|
149
161
|
async writeBundle(...args) {
|
|
150
162
|
await buildPlugin.writeBundle?.(...args)
|
|
151
163
|
},
|