@uniweb/build 0.9.3 → 0.9.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -54,7 +54,7 @@
54
54
  "@uniweb/theming": "0.1.3"
55
55
  },
56
56
  "optionalDependencies": {
57
- "@uniweb/runtime": "0.7.3",
57
+ "@uniweb/runtime": "0.7.5",
58
58
  "@uniweb/content-reader": "1.1.4",
59
59
  "@uniweb/schemas": "0.2.1"
60
60
  },
@@ -65,7 +65,7 @@
65
65
  "@tailwindcss/vite": "^4.0.0",
66
66
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
67
67
  "vite-plugin-svgr": "^4.0.0",
68
- "@uniweb/core": "0.6.1"
68
+ "@uniweb/core": "0.6.2"
69
69
  },
70
70
  "peerDependenciesMeta": {
71
71
  "vite": {
package/src/prerender.js CHANGED
@@ -11,6 +11,7 @@ import { readFile, writeFile, mkdir } from 'node:fs/promises'
11
11
  import { existsSync, readdirSync, statSync } from 'node:fs'
12
12
  import { join, dirname, resolve } from 'node:path'
13
13
  import { pathToFileURL } from 'node:url'
14
+ import { deriveCacheKey } from '@uniweb/core'
14
15
  import { executeFetch, mergeDataIntoContent, singularize } from './site/data-fetcher.js'
15
16
  import { shouldSplitContent } from './site/split-content.js'
16
17
 
@@ -509,27 +510,19 @@ export async function prerenderSite(siteDir, options = {}) {
509
510
  const shellPath = existsSync(htmlPath) ? htmlPath : join(distDir, 'index.html')
510
511
  const htmlShell = await readFile(shellPath, 'utf8')
511
512
 
512
- // Initialize the Uniweb runtime using the shared SSR module
513
- const uniweb = initPrerender(siteContent, foundation, { onProgress })
514
-
515
- // Build-specific: pre-populate DataStore so EntityStore can resolve data during prerender
516
- if (fetchedData.length > 0 && uniweb.activeWebsite?.dataStore) {
517
- for (const entry of fetchedData) {
518
- uniweb.activeWebsite.dataStore.set(entry.config, entry.data)
519
- }
520
- }
521
-
522
- // Build-specific: load extensions (secondary foundations via URL)
523
- const extensions = siteContent.config?.extensions
524
- if (extensions?.length) {
525
- onProgress(`Loading ${extensions.length} extension(s)...`)
513
+ // Build-specific: load extensions (secondary foundations via URL) BEFORE
514
+ // initPrerender so the Website's FetcherDispatcher sees their routes.
515
+ const extensionSources = siteContent.config?.extensions
516
+ const loadedExtensions = []
517
+ if (extensionSources?.length) {
518
+ onProgress(`Loading ${extensionSources.length} extension(s)...`)
526
519
  const projectRoot = join(siteDir, '..')
527
- for (const ext of extensions) {
520
+ for (const ext of extensionSources) {
528
521
  try {
529
522
  const url = typeof ext === 'string' ? ext : ext.url
530
523
  const extPath = resolveExtensionPath(url, distDir, projectRoot)
531
524
  const extModule = await import(pathToFileURL(extPath).href)
532
- uniweb.registerExtension(extModule)
525
+ loadedExtensions.push(extModule)
533
526
  onProgress(` Extension loaded: ${url}`)
534
527
  } catch (err) {
535
528
  onProgress(` Warning: Extension failed to load: ${ext} (${err.message})`)
@@ -537,6 +530,17 @@ export async function prerenderSite(siteDir, options = {}) {
537
530
  }
538
531
  }
539
532
 
533
+ // Initialize the Uniweb runtime using the shared SSR module
534
+ const uniweb = initPrerender(siteContent, foundation, loadedExtensions, { onProgress })
535
+
536
+ // Build-specific: pre-populate DataStore so EntityStore can resolve data during prerender.
537
+ // Use the framework's default cache key so runtime probes hit the same entries.
538
+ if (fetchedData.length > 0 && uniweb.activeWebsite?.dataStore) {
539
+ for (const entry of fetchedData) {
540
+ uniweb.activeWebsite.dataStore.set(deriveCacheKey(entry.config), { data: entry.data })
541
+ }
542
+ }
543
+
540
544
  // Pre-fetch icons for SSR embedding
541
545
  await prefetchIcons(siteContent, uniweb, onProgress)
542
546
 
@@ -10,7 +10,18 @@
10
10
  * - defaults: param default values
11
11
  * - context: static capabilities for cross-block coordination
12
12
  * - initialState: initial values for mutable block state
13
- * - inheritData: boolean or array for cascaded data from page/site fetches
13
+ * - inheritData: internal flag for cascaded data delivery
14
+ * true → deliver all data available at ancestor levels (default)
15
+ * false → deliver nothing (component opted out with `data: false`)
16
+ *
17
+ * Data delivery is default-on: a component without any `data:` field
18
+ * receives all data cascaded from its ancestor levels (block → page →
19
+ * parent page → site) via `content.data.{schema}`. A component that
20
+ * genuinely cannot tolerate ambient data declares `data: false`.
21
+ *
22
+ * `data: { entity: 'articles' }` is a **declaration**, not a gate. It
23
+ * tells the editor and prepare-props what shape the component expects,
24
+ * but does not restrict delivery.
14
25
  *
15
26
  * Full metadata (titles, descriptions, hints, etc.) stays in schema.json
16
27
  * for the visual editor.
@@ -202,39 +213,64 @@ export function extractRuntimeSchema(fullMeta) {
202
213
  }
203
214
 
204
215
  // Data binding (CMS entities)
205
- // Supports both old format (data: 'person:6') and new consolidated format
206
- // (data: { entity: 'person:6', schemas: {...}, inherit: [...] })
207
- if (fullMeta.data) {
208
- if (typeof fullMeta.data === 'string') {
209
- // Old format: data: 'person:6'
210
- const parsed = parseDataString(fullMeta.data)
216
+ //
217
+ // Supported forms:
218
+ // data: false → explicit opt-out
219
+ // data: { entity: 'person:6' } → declaration + shape hints
220
+ // data: { schemas: {...} } → validation / default shapes
221
+ // data: 'person:6' → legacy string form (declaration only)
222
+ //
223
+ // Deprecated forms (accepted with dev-mode warning, removed in next release):
224
+ // data: { inherit: true | false | [...] } — component-side gating is gone;
225
+ // delivery is default-on
226
+ // data: { detail, limit } — moved to block-level fetch
227
+ if (fullMeta.data === false) {
228
+ // Explicit opt-out — deliver nothing to this component.
229
+ runtime.inheritData = false
230
+ } else if (typeof fullMeta.data === 'string') {
231
+ // Legacy string form: data: 'person:6'
232
+ const parsed = parseDataString(fullMeta.data)
233
+ if (parsed) {
234
+ runtime.data = parsed
235
+ }
236
+ } else if (fullMeta.data && typeof fullMeta.data === 'object') {
237
+ if (fullMeta.data.entity) {
238
+ const parsed = parseDataString(fullMeta.data.entity)
211
239
  if (parsed) {
212
240
  runtime.data = parsed
213
241
  }
214
- } else if (typeof fullMeta.data === 'object') {
215
- // New format: data: { entity, schemas, inherit }
216
- if (fullMeta.data.entity) {
217
- const parsed = parseDataString(fullMeta.data.entity)
218
- if (parsed) {
219
- runtime.data = parsed
220
- }
221
- }
222
- if (fullMeta.data.schemas) {
223
- const schemas = extractSchemas(fullMeta.data.schemas)
224
- if (schemas) {
225
- runtime.schemas = schemas
226
- }
242
+ }
243
+ if (fullMeta.data.schemas) {
244
+ const schemas = extractSchemas(fullMeta.data.schemas)
245
+ if (schemas) {
246
+ runtime.schemas = schemas
227
247
  }
228
- if (fullMeta.data.inherit !== undefined) {
229
- runtime.inheritData = fullMeta.data.inherit
248
+ }
249
+ // Deprecated: data.inherit is a no-op under default-on delivery. The
250
+ // only behavior we still honor is `inherit: false` → treat as opt-out,
251
+ // so existing foundations that wrote "don't deliver" still don't.
252
+ // Array and `true` forms are ignored — delivery happens regardless.
253
+ if (fullMeta.data.inherit === false) {
254
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
255
+ console.warn(
256
+ '[uniweb] `data: { inherit: false }` is deprecated; use `data: false` instead.'
257
+ )
230
258
  }
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
259
+ runtime.inheritData = false
260
+ } else if (fullMeta.data.inherit !== undefined) {
261
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
262
+ console.warn(
263
+ '[uniweb] `data: { inherit: ... }` is deprecated; delivery is default-on. Remove the `inherit` field.'
264
+ )
235
265
  }
236
- if (fullMeta.data.limit !== undefined) {
237
- runtime.inheritLimit = fullMeta.data.limit
266
+ }
267
+ // Deprecated: detail/limit on the component side. Block-level
268
+ // `fetch: { inherit: true, detail, limit }` is where these belong now.
269
+ if (fullMeta.data.detail !== undefined || fullMeta.data.limit !== undefined) {
270
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
271
+ console.warn(
272
+ '[uniweb] `data: { detail, limit }` on the component side is deprecated; set these on a block-level `fetch: { inherit: true, ... }` instead.'
273
+ )
238
274
  }
239
275
  }
240
276
  }
@@ -267,18 +303,17 @@ export function extractRuntimeSchema(fullMeta) {
267
303
  }
268
304
  }
269
305
 
270
- // Data inheritance - component receives cascaded data from page/site level fetches
271
- // Can be: true (inherit all), false (inherit none), or ['schema1', 'schema2'] (selective)
272
- // Top-level inheritData supported for backwards compat (lower priority than data.inherit)
273
- if (fullMeta.inheritData !== undefined && runtime.inheritData === undefined) {
274
- runtime.inheritData = fullMeta.inheritData
306
+ // Top-level inheritData (legacy, pre-`data.*` format) honored only as opt-out.
307
+ // Truthy values and arrays are ignored; delivery is default-on.
308
+ if (fullMeta.inheritData === false && runtime.inheritData === undefined) {
309
+ runtime.inheritData = false
275
310
  }
276
311
 
277
- // Auto-derive inheritData from entity type when no explicit inherit is set.
278
- // data: { entity: 'articles' } implies inheritData: ['articles']
279
- if (runtime.data && runtime.inheritData === undefined) {
280
- runtime.inheritData = [runtime.data.type]
281
- }
312
+ // Data delivery is default-on. `runtime.inheritData` stays undefined unless
313
+ // the component explicitly opts out (runtime.inheritData === false), in
314
+ // which case EntityStore delivers nothing. The declaration `data.entity`
315
+ // no longer implies a gate — it's a hint consumed by prepare-props and
316
+ // the editor.
282
317
 
283
318
  return Object.keys(runtime).length > 0 ? runtime : null
284
319
  }
@@ -225,11 +225,25 @@ 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) {
228
+ // Refine config: { refine: true, detail: false, limit: 3 }
229
+ // No URL — merges with the parent fetch config at runtime; only carries
230
+ // override props. The legacy spelling `inherit: true` is accepted for one
231
+ // release with a warning, then removed.
232
+ //
233
+ // Note on build-vs-runtime scope: this parser passes `sort` and `filter`
234
+ // through on refine configs, but the runtime EntityStore only applies
235
+ // `detail`, `limit`, and `order` overrides. `sort` / `filter` on a refine
236
+ // block are currently accepted by the parser but not honored at runtime.
237
+ // Preserved as-is in this rename commit; revisit separately if needed.
238
+ if (fetch.refine === true || fetch.inherit === true) {
239
+ if (fetch.inherit === true && fetch.refine !== true) {
240
+ console.warn(
241
+ "[uniweb] 'fetch: { inherit: true }' is deprecated; rename to 'fetch: { refine: true }'. " +
242
+ 'Accepted for one release; will be removed in the next minor.'
243
+ )
244
+ }
231
245
  return {
232
- inherit: true,
246
+ refine: true,
233
247
  ...(fetch.detail !== undefined ? { detail: fetch.detail } : {}),
234
248
  ...(fetch.limit !== undefined ? { limit: fetch.limit } : {}),
235
249
  ...(fetch.sort !== undefined ? { sort: fetch.sort } : {}),