@uniweb/build 0.7.2 → 0.7.4
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/README.md +1 -1
- package/package.json +4 -4
- package/src/generate-entry.js +100 -56
- package/src/index.js +0 -1
- package/src/prerender.js +7 -2
- package/src/site/config.js +17 -7
- package/src/vite-foundation-plugin.js +5 -28
package/README.md
CHANGED
|
@@ -387,7 +387,7 @@ The generated `_entry.generated.js` file exports:
|
|
|
387
387
|
|--------|-------------|
|
|
388
388
|
| `components` | Object map of component name → React component |
|
|
389
389
|
| Named exports | Each component exported by name (e.g., `Hero`, `Features`) |
|
|
390
|
-
| `capabilities` | Custom Layout and props from `src/
|
|
390
|
+
| `capabilities` | Custom Layout and props from `src/foundation.js` (or `null`) |
|
|
391
391
|
| `meta` | Runtime metadata extracted from component `meta.js` files |
|
|
392
392
|
|
|
393
393
|
#### Runtime Metadata (`meta` export)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"sharp": "^0.33.2"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@uniweb/
|
|
53
|
+
"@uniweb/content-reader": "1.1.2",
|
|
54
54
|
"@uniweb/schemas": "0.2.1",
|
|
55
|
-
"@uniweb/
|
|
55
|
+
"@uniweb/runtime": "0.6.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@tailwindcss/vite": "^4.0.0",
|
|
62
62
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
63
63
|
"vite-plugin-svgr": "^4.0.0",
|
|
64
|
-
"@uniweb/core": "0.5.
|
|
64
|
+
"@uniweb/core": "0.5.1"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"vite": {
|
package/src/generate-entry.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Exports:
|
|
7
7
|
* - `components` - Object map of component name -> React component
|
|
8
|
-
* - `capabilities` - Custom Layout and props from src/
|
|
8
|
+
* - `capabilities` - Custom Layout and props from src/foundation.js (if present)
|
|
9
9
|
* - `meta` - Per-component runtime metadata extracted from meta.js files
|
|
10
10
|
*
|
|
11
11
|
* The `meta` export contains only properties needed at runtime:
|
|
@@ -19,18 +19,16 @@
|
|
|
19
19
|
* Foundation identity (name, description) comes from package.json in the editor schema.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { writeFile, mkdir } from 'node:fs/promises'
|
|
22
|
+
import { writeFile, readFile, mkdir } from 'node:fs/promises'
|
|
23
23
|
import { existsSync } from 'node:fs'
|
|
24
24
|
import { join, dirname } from 'node:path'
|
|
25
25
|
import { discoverComponents, discoverLayoutsInPath } from './schema.js'
|
|
26
26
|
import { extractAllRuntimeSchemas, extractAllLayoutRuntimeSchemas } from './runtime-schema.js'
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Detect foundation config
|
|
29
|
+
* Detect foundation config file (for props, vars, etc.)
|
|
30
30
|
*
|
|
31
|
-
* Looks for
|
|
32
|
-
* 1. foundation.js - New consolidated format
|
|
33
|
-
* 2. exports.js - Legacy format (for backward compatibility)
|
|
31
|
+
* Looks for: foundation.js or foundation.jsx
|
|
34
32
|
*
|
|
35
33
|
* The file should export:
|
|
36
34
|
* - props (optional) - Foundation-wide props
|
|
@@ -40,29 +38,14 @@ import { extractAllRuntimeSchemas, extractAllLayoutRuntimeSchemas } from './runt
|
|
|
40
38
|
* Note: Layout components are now discovered from src/layouts/
|
|
41
39
|
*/
|
|
42
40
|
function detectFoundationExports(srcDir) {
|
|
43
|
-
|
|
44
|
-
const foundationCandidates = [
|
|
41
|
+
const candidates = [
|
|
45
42
|
{ path: 'foundation.js', ext: 'js' },
|
|
46
43
|
{ path: 'foundation.jsx', ext: 'jsx' },
|
|
47
44
|
]
|
|
48
45
|
|
|
49
|
-
for (const { path, ext } of
|
|
46
|
+
for (const { path, ext } of candidates) {
|
|
50
47
|
if (existsSync(join(srcDir, path))) {
|
|
51
|
-
return { path: `./${path}`, ext
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Fall back to exports.js (legacy format)
|
|
56
|
-
const legacyCandidates = [
|
|
57
|
-
{ path: 'exports.js', ext: 'js' },
|
|
58
|
-
{ path: 'exports.jsx', ext: 'jsx' },
|
|
59
|
-
{ path: 'exports/index.js', ext: 'js' },
|
|
60
|
-
{ path: 'exports/index.jsx', ext: 'jsx' },
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
for (const { path, ext } of legacyCandidates) {
|
|
64
|
-
if (existsSync(join(srcDir, path))) {
|
|
65
|
-
return { path: `./${path.replace(/\/index\.(js|jsx)$/, '')}`, ext, isFoundationJs: false }
|
|
48
|
+
return { path: `./${path}`, ext }
|
|
66
49
|
}
|
|
67
50
|
}
|
|
68
51
|
return null
|
|
@@ -132,13 +115,9 @@ function generateEntrySource(components, options = {}) {
|
|
|
132
115
|
|
|
133
116
|
lines.push('')
|
|
134
117
|
|
|
135
|
-
//
|
|
118
|
+
// Named exports — one per component
|
|
136
119
|
if (componentNames.length > 0) {
|
|
137
|
-
lines.push(`export const components = { ${componentNames.join(', ')} }`)
|
|
138
|
-
lines.push('')
|
|
139
120
|
lines.push(`export { ${componentNames.join(', ')} }`)
|
|
140
|
-
} else {
|
|
141
|
-
lines.push('export const components = {}')
|
|
142
121
|
}
|
|
143
122
|
|
|
144
123
|
// Foundation capabilities (props, vars, etc. + discovered layouts)
|
|
@@ -152,31 +131,23 @@ function generateEntrySource(components, options = {}) {
|
|
|
152
131
|
capParts.push(`layouts: { ${layoutNames.join(', ')} }`)
|
|
153
132
|
}
|
|
154
133
|
lines.push(`const capabilities = { ${capParts.join(', ')} }`)
|
|
155
|
-
lines.push('export { capabilities }')
|
|
156
134
|
} else {
|
|
157
|
-
lines.push('
|
|
135
|
+
lines.push('const capabilities = null')
|
|
158
136
|
}
|
|
159
137
|
|
|
160
|
-
// Per-component metadata (defaults, context, initialState, background, data)
|
|
138
|
+
// Per-component runtime metadata (defaults, context, initialState, background, data)
|
|
161
139
|
lines.push('')
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
lines.push(`// Per-component runtime metadata (from meta.js)`)
|
|
165
|
-
lines.push(`export const meta = ${metaJson}`)
|
|
166
|
-
} else {
|
|
167
|
-
lines.push('export const meta = {}')
|
|
168
|
-
}
|
|
140
|
+
const metaJson = JSON.stringify(Object.keys(meta).length > 0 ? meta : {}, null, 2)
|
|
141
|
+
lines.push(`const meta = ${metaJson}`)
|
|
169
142
|
|
|
170
143
|
// Per-layout runtime metadata (areas, transitions, defaults)
|
|
171
144
|
lines.push('')
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
lines.push(`// Per-layout runtime metadata (from meta.js)`)
|
|
175
|
-
lines.push(`export const layoutMeta = ${layoutMetaJson}`)
|
|
176
|
-
} else {
|
|
177
|
-
lines.push('export const layoutMeta = {}')
|
|
178
|
-
}
|
|
145
|
+
const layoutMetaJson = JSON.stringify(Object.keys(layoutMeta).length > 0 ? layoutMeta : {}, null, 2)
|
|
146
|
+
lines.push(`const layoutMeta = ${layoutMetaJson}`)
|
|
179
147
|
|
|
148
|
+
// Default export — non-component data (naturally unforgeable key)
|
|
149
|
+
lines.push('')
|
|
150
|
+
lines.push('export default { meta, capabilities, layoutMeta }')
|
|
180
151
|
lines.push('')
|
|
181
152
|
|
|
182
153
|
return lines.join('\n')
|
|
@@ -277,12 +248,23 @@ export async function generateEntryPoint(srcDir, outputPath = null, options = {}
|
|
|
277
248
|
layoutMeta,
|
|
278
249
|
})
|
|
279
250
|
|
|
280
|
-
// Write to file
|
|
251
|
+
// Write to file (skip if content unchanged to avoid unnecessary watcher triggers)
|
|
281
252
|
const output = outputPath || join(srcDir, '_entry.generated.js')
|
|
282
253
|
await mkdir(dirname(output), { recursive: true })
|
|
283
|
-
await writeFile(output, source, 'utf-8')
|
|
284
254
|
|
|
285
|
-
|
|
255
|
+
let written = false
|
|
256
|
+
if (existsSync(output)) {
|
|
257
|
+
const existing = await readFile(output, 'utf-8')
|
|
258
|
+
if (existing !== source) {
|
|
259
|
+
await writeFile(output, source, 'utf-8')
|
|
260
|
+
written = true
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
await writeFile(output, source, 'utf-8')
|
|
264
|
+
written = true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
console.log(`${written ? 'Generated' : 'Unchanged'} entry point: ${output}`)
|
|
286
268
|
console.log(` - ${componentNames.length} components: ${componentNames.join(', ')}`)
|
|
287
269
|
if (layoutNames.length > 0) {
|
|
288
270
|
console.log(` - ${layoutNames.length} layouts: ${layoutNames.join(', ')}`)
|
|
@@ -302,13 +284,75 @@ export async function generateEntryPoint(srcDir, outputPath = null, options = {}
|
|
|
302
284
|
}
|
|
303
285
|
|
|
304
286
|
/**
|
|
305
|
-
* Check if entry point
|
|
306
|
-
*
|
|
287
|
+
* Check if a file change should trigger entry point regeneration.
|
|
288
|
+
*
|
|
289
|
+
* Used by both the foundation dev plugin and the site's bundled-mode plugin
|
|
290
|
+
* to decide when to re-run generateEntryPoint().
|
|
291
|
+
*
|
|
292
|
+
* The content-comparison guard in generateEntryPoint() makes false positives
|
|
293
|
+
* cheap (discovery runs but no write), so we err on the side of regenerating.
|
|
294
|
+
*
|
|
295
|
+
* @param {string} file - Absolute path of the changed file
|
|
296
|
+
* @param {string} srcDir - Foundation source directory (absolute)
|
|
297
|
+
* @returns {string|null} Reason string if regeneration needed, null otherwise
|
|
307
298
|
*/
|
|
308
|
-
export
|
|
309
|
-
if (!
|
|
299
|
+
export function shouldRegenerateForFile(file, srcDir) {
|
|
300
|
+
if (!file.startsWith(srcDir + '/')) return null
|
|
301
|
+
|
|
302
|
+
const rel = file.slice(srcDir.length + 1)
|
|
310
303
|
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
304
|
+
// meta.js anywhere — affects runtime metadata
|
|
305
|
+
if (rel.endsWith('/meta.js') || rel === 'meta.js') {
|
|
306
|
+
return 'meta.js changed'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// foundation.js / foundation.jsx at root — affects capabilities import
|
|
310
|
+
if (/^foundation\.(js|jsx)$/.test(rel)) {
|
|
311
|
+
return 'foundation config changed'
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// styles.css / index.css at root — affects CSS import line
|
|
315
|
+
if (/^(styles|index)\.css$/.test(rel)) {
|
|
316
|
+
return 'foundation styles changed'
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// sections/ — relaxed discovery (bare files + entry files in PascalCase dirs)
|
|
320
|
+
if (rel.startsWith('sections/')) {
|
|
321
|
+
const inner = rel.slice('sections/'.length)
|
|
322
|
+
const parts = inner.split('/')
|
|
323
|
+
|
|
324
|
+
// Bare file at sections root: sections/Hero.jsx
|
|
325
|
+
if (parts.length === 1 && /^[A-Z].*\.(jsx|tsx|js|ts)$/.test(parts[0])) {
|
|
326
|
+
return `section file: ${parts[0]}`
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Entry file in a PascalCase directory: sections/Hero/index.jsx or sections/Hero/Hero.jsx
|
|
330
|
+
if (parts.length === 2 && /^[A-Z]/.test(parts[0]) && /\.(jsx|tsx|js|ts)$/.test(parts[1])) {
|
|
331
|
+
const base = parts[1].replace(/\.(jsx|tsx|js|ts)$/, '')
|
|
332
|
+
if (base === 'index' || base === parts[0]) {
|
|
333
|
+
return `section entry: ${inner}`
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// layouts/ — bare files and entry files
|
|
339
|
+
if (rel.startsWith('layouts/')) {
|
|
340
|
+
const inner = rel.slice('layouts/'.length)
|
|
341
|
+
const parts = inner.split('/')
|
|
342
|
+
|
|
343
|
+
// Bare file at layouts root: layouts/docs.jsx
|
|
344
|
+
if (parts.length === 1 && /\.(jsx|tsx|js|ts)$/.test(parts[0])) {
|
|
345
|
+
return `layout file: ${parts[0]}`
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Entry file in a directory: layouts/docs/index.jsx or layouts/docs/docs.jsx
|
|
349
|
+
if (parts.length === 2 && /\.(jsx|tsx|js|ts)$/.test(parts[1])) {
|
|
350
|
+
const base = parts[1].replace(/\.(jsx|tsx|js|ts)$/, '')
|
|
351
|
+
if (base === 'index' || base === parts[0]) {
|
|
352
|
+
return `layout entry: ${inner}`
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return null
|
|
314
358
|
}
|
package/src/index.js
CHANGED
package/src/prerender.js
CHANGED
|
@@ -786,8 +786,13 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
786
786
|
}
|
|
787
787
|
|
|
788
788
|
// Set foundation capabilities (Layout, props, etc.)
|
|
789
|
-
if (foundation.capabilities) {
|
|
790
|
-
uniweb.setFoundationConfig(foundation.capabilities)
|
|
789
|
+
if (foundation.default?.capabilities) {
|
|
790
|
+
uniweb.setFoundationConfig(foundation.default.capabilities)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Attach layout metadata (areas, transitions, defaults)
|
|
794
|
+
if (foundation.default?.layoutMeta && uniweb.foundationConfig) {
|
|
795
|
+
uniweb.foundationConfig.layoutMeta = foundation.default.layoutMeta
|
|
791
796
|
}
|
|
792
797
|
|
|
793
798
|
// Pre-fetch icons for SSR embedding
|
package/src/site/config.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { existsSync, readFileSync } from 'node:fs'
|
|
24
24
|
import { resolve, dirname, join } from 'node:path'
|
|
25
25
|
import yaml from 'js-yaml'
|
|
26
|
-
import { generateEntryPoint } from '../generate-entry.js'
|
|
26
|
+
import { generateEntryPoint, shouldRegenerateForFile } from '../generate-entry.js'
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Normalize a base path for Vite compatibility
|
|
@@ -241,16 +241,16 @@ export async function defineSiteConfig(options = {}) {
|
|
|
241
241
|
},
|
|
242
242
|
|
|
243
243
|
configureServer(server) {
|
|
244
|
-
// Watch foundation src for
|
|
244
|
+
// Watch foundation src for structural changes that affect the entry
|
|
245
245
|
const srcDir = join(foundationInfo.path, 'src')
|
|
246
246
|
const entryPath = join(srcDir, '_entry.generated.js')
|
|
247
247
|
|
|
248
|
-
server.watcher.add(
|
|
248
|
+
server.watcher.add(srcDir)
|
|
249
249
|
|
|
250
250
|
server.watcher.on('all', async (event, path) => {
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
console.log(`[site] Foundation
|
|
251
|
+
const reason = shouldRegenerateForFile(path, srcDir)
|
|
252
|
+
if (reason) {
|
|
253
|
+
console.log(`[site] Foundation ${reason}, regenerating entry...`)
|
|
254
254
|
try {
|
|
255
255
|
await generateEntryPoint(srcDir, entryPath)
|
|
256
256
|
server.ws.send({ type: 'full-reload' })
|
|
@@ -348,8 +348,18 @@ export async function defineSiteConfig(options = {}) {
|
|
|
348
348
|
isBuild = config.command === 'build'
|
|
349
349
|
},
|
|
350
350
|
|
|
351
|
-
resolveId(id) {
|
|
351
|
+
resolveId(id, importer) {
|
|
352
352
|
if (id.startsWith(IMPORT_MAP_PREFIX)) return id
|
|
353
|
+
// Bare specifiers inside our virtual modules (e.g. '@uniweb/core' re-exported
|
|
354
|
+
// from '\0importmap:@uniweb/core') can't be resolved by Rollup because virtual
|
|
355
|
+
// modules have no filesystem context. Resolve from the foundation directory where
|
|
356
|
+
// @uniweb/core is a direct dependency (the site may not have it under pnpm strict).
|
|
357
|
+
if (importer?.startsWith(IMPORT_MAP_PREFIX) && IMPORT_MAP_EXTERNALS.includes(id)) {
|
|
358
|
+
const resolveFrom = foundationInfo.path
|
|
359
|
+
? resolve(foundationInfo.path, 'package.json')
|
|
360
|
+
: resolve(siteRoot, 'main.js')
|
|
361
|
+
return this.resolve(id, resolveFrom, { skipSelf: true })
|
|
362
|
+
}
|
|
353
363
|
},
|
|
354
364
|
|
|
355
365
|
async load(id) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { writeFile, mkdir } from 'node:fs/promises'
|
|
11
11
|
import { join, resolve } from 'node:path'
|
|
12
12
|
import { buildSchema } from './schema.js'
|
|
13
|
-
import { generateEntryPoint } from './generate-entry.js'
|
|
13
|
+
import { generateEntryPoint, shouldRegenerateForFile } from './generate-entry.js'
|
|
14
14
|
import { processAllPreviews } from './images.js'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -120,33 +120,10 @@ export function foundationDevPlugin(options = {}) {
|
|
|
120
120
|
},
|
|
121
121
|
|
|
122
122
|
async handleHotUpdate({ file, server }) {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
console.log('Component meta.js changed, regenerating entry...')
|
|
128
|
-
await generateEntryPoint(resolvedSrcDir, entryPath, { componentPaths })
|
|
129
|
-
server.ws.send({ type: 'full-reload' })
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Regenerate when component files are added/removed in sections/ root
|
|
134
|
-
// (bare file discovery means any .jsx/.tsx/.js/.ts at sections root is a section type)
|
|
135
|
-
const sectionsDir = join(resolvedSrcDir, 'sections')
|
|
136
|
-
if (file.startsWith(sectionsDir)) {
|
|
137
|
-
const relative = file.slice(sectionsDir.length + 1)
|
|
138
|
-
// Direct child of sections/ (no further slashes) — could be a new/removed bare file
|
|
139
|
-
if (!relative.includes('/') && /\.(jsx|tsx|js|ts)$/.test(relative)) {
|
|
140
|
-
console.log('Section file changed, regenerating entry...')
|
|
141
|
-
await generateEntryPoint(resolvedSrcDir, entryPath, { componentPaths })
|
|
142
|
-
server.ws.send({ type: 'full-reload' })
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Also regenerate if exports.js changes
|
|
148
|
-
if (file.endsWith('/exports.js') || file.endsWith('/exports.jsx')) {
|
|
149
|
-
console.log('Foundation exports changed, regenerating entry...')
|
|
123
|
+
const reason = shouldRegenerateForFile(file, resolvedSrcDir)
|
|
124
|
+
if (reason) {
|
|
125
|
+
console.log(`[foundation] ${reason}, regenerating entry...`)
|
|
126
|
+
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
150
127
|
await generateEntryPoint(resolvedSrcDir, entryPath, { componentPaths })
|
|
151
128
|
server.ws.send({ type: 'full-reload' })
|
|
152
129
|
}
|