@uniweb/build 0.64.1 → 0.65.0
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 +9 -9
- package/src/i18n/records.js +25 -12
- package/src/import-map-plugin.js +13 -0
- package/src/prerender.js +9 -4
- package/src/resolve-data-schema.js +4 -2
- package/src/runtime-schema.js +15 -18
- package/src/site/config.js +59 -5
- package/src/site/entity-pool.js +2 -2
- package/src/site/foundation-ref.js +21 -2
- package/src/site/index.js +2 -0
- package/src/site/preview-media.js +90 -0
- package/src/site/preview.js +84 -0
- package/src/site/queries-config.js +46 -83
- package/src/site/query-processor.js +251 -144
- package/src/site/record-file.js +35 -0
- package/src/site/records-config.js +21 -0
- package/src/site/registered-foundation.js +3 -2
- package/src/uwx/backfill.js +9 -7
- package/src/uwx/data-schema.js +8 -24
- package/src/uwx/entity-source.js +9 -20
- package/src/uwx/queries-config.js +0 -1
- package/src/uwx/record-layout.js +1 -1
- package/src/uwx/records-project.js +17 -94
- package/src/uwx/records.js +1 -26
- package/src/uwx/site-project.js +2 -5
- package/src/uwx/site.js +6 -6
- package/src/validate-data.js +3 -2
- package/src/vite-foundation-plugin.js +39 -193
|
@@ -39,9 +39,8 @@ async function buildSchemaWithPreviews(srcDir, outDir, isProduction, sectionPath
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Module-level guard
|
|
43
|
-
*
|
|
44
|
-
* the foundation plugin's writeBundle hook.
|
|
42
|
+
* Module-level guard around the `entry-ssr.js` sub-build (buildEntrySSR): while
|
|
43
|
+
* it runs, the foundation plugin's writeBundle hook does nothing.
|
|
45
44
|
*/
|
|
46
45
|
let _buildingSSRBundle = false
|
|
47
46
|
|
|
@@ -68,11 +67,8 @@ let _buildingSSRBundle = false
|
|
|
68
67
|
* ⚠️ **This used to say "the selector is `site.yml::runtime`". It is not.** That
|
|
69
68
|
* key is an operator-level override, not the authoring surface: a link-mode site
|
|
70
69
|
* is CODELESS, so it has nothing that binds to a runtime version and no basis for
|
|
71
|
-
* an opinion about one. The selector is
|
|
72
|
-
* → the site's current → a deployment default → newest installed). What a
|
|
70
|
+
* an opinion about one. The selector is whatever serves the site. What a
|
|
73
71
|
* foundation declares here is the CONSTRAINT on that choice, never the choice.
|
|
74
|
-
* the site/foundation/runtime model, § "who gets to say
|
|
75
|
-
* whether a site accepts a newer runtime" is the authority.
|
|
76
72
|
*
|
|
77
73
|
* Reads the resolved version from the foundation's node_modules/@uniweb/
|
|
78
74
|
* runtime/package.json so the pin reflects what was actually linked at
|
|
@@ -101,13 +97,9 @@ let _buildingSSRBundle = false
|
|
|
101
97
|
* about every host the framework serves and framework can substantiate it for
|
|
102
98
|
* none of them.
|
|
103
99
|
*
|
|
104
|
-
* 📌 **The check
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* mirror, runtime host and capability worker**, and its runtime version comes
|
|
108
|
-
* solely from published site metadata and is part of a render-cache key, so a
|
|
109
|
-
* request-time policy would collide with its invalidation model. **That is one
|
|
110
|
-
* host on one date; a static host or a foreign backend is a separate question.**
|
|
100
|
+
* 📌 **The check, so this is not a claim with no expiry:** grep a host's serving
|
|
101
|
+
* code for `runtime-pin` / `runtimePolicy`. An answer is about one host on one
|
|
102
|
+
* date; a static host or a foreign backend is a separate question.
|
|
111
103
|
*
|
|
112
104
|
* ⚖️ **This is a field with standing and no consumer — NOT a mistake to delete.**
|
|
113
105
|
* The architecture assigns this declaration to the foundation on principle: each
|
|
@@ -116,7 +108,7 @@ let _buildingSSRBundle = false
|
|
|
116
108
|
* core. So this is the designated home for the answer; nothing has asked for the
|
|
117
109
|
* answer yet.
|
|
118
110
|
*
|
|
119
|
-
* ⇒ **The event that gives it a consumer
|
|
111
|
+
* ⇒ **The event that gives it a consumer:**
|
|
120
112
|
* when the runtime declares what it supplies — replacing today's `>=` version
|
|
121
113
|
* compare, which is a proxy that holds only while the runtime's version number
|
|
122
114
|
* tracks its externals contract — **or** when the publish path starts consulting
|
|
@@ -180,7 +172,8 @@ async function emitRuntimePin(outDir, projectRoot) {
|
|
|
180
172
|
}
|
|
181
173
|
|
|
182
174
|
if (!runtimePkgPath) {
|
|
183
|
-
// No runtime resolvable. Skip emission —
|
|
175
|
+
// No runtime resolvable. Skip emission — the foundation then states no
|
|
176
|
+
// floor (see the header).
|
|
184
177
|
return
|
|
185
178
|
}
|
|
186
179
|
|
|
@@ -194,10 +187,10 @@ async function emitRuntimePin(outDir, projectRoot) {
|
|
|
194
187
|
if (!runtimeVersion) return
|
|
195
188
|
|
|
196
189
|
// Read foundation's own package.json for an optional runtimePolicy
|
|
197
|
-
// field.
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
190
|
+
// field, recorded only when explicitly set. What a host does when it is
|
|
191
|
+
// omitted is that host's to say (see the header). See
|
|
192
|
+
// framework/docs/reference/foundation-config.md for the full set of
|
|
193
|
+
// `uniweb.*` fields foundations can declare.
|
|
201
194
|
let policy = null
|
|
202
195
|
try {
|
|
203
196
|
const foundationPkgPath = join(projectRoot, 'package.json')
|
|
@@ -227,150 +220,6 @@ async function emitRuntimePin(outDir, projectRoot) {
|
|
|
227
220
|
}
|
|
228
221
|
}
|
|
229
222
|
|
|
230
|
-
/**
|
|
231
|
-
* @deprecated 2026-04-27 — Strategy S Phase 2.
|
|
232
|
-
*
|
|
233
|
-
* Foundations no longer carry their own runtime bundle. Runtime + React +
|
|
234
|
-
* core + theming now live in R2 under `runtime/{version}/worker-runtime.js`,
|
|
235
|
-
* published by the platform's `/deploy-runtime` skill, and side-loaded
|
|
236
|
-
* by the Cloudflare isolate alongside `dist/entry.js`.
|
|
237
|
-
*
|
|
238
|
-
* The invocation in `writeBundle()` is commented out; this function
|
|
239
|
-
* definition is kept for the rollout window so it can be flipped back
|
|
240
|
-
* on with one line if Phase 1's edge dispatcher misbehaves in production.
|
|
241
|
-
* Phase 3 cleanup deletes this function entirely once the new path is
|
|
242
|
-
* proven healthy.
|
|
243
|
-
*
|
|
244
|
-
* Original purpose (preserved for context):
|
|
245
|
-
* Build a self-contained ESM bundle for edge SSR (Cloudflare Dynamic Workers).
|
|
246
|
-
* Produces `ssr-worker-bundle.js` — a single ESM file with React,
|
|
247
|
-
* ReactDOM/server, `@uniweb/core`, `@uniweb/runtime/ssr`,
|
|
248
|
-
* `@uniweb/theming`, and the foundation's components all inlined. No
|
|
249
|
-
* external imports. The artifact was bundled this way because the
|
|
250
|
-
* Dynamic Worker LOADER accepts a closed `modules` map at isolate
|
|
251
|
-
* construction. The Phase -1 prototype (2026-04-27) verified the LOADER
|
|
252
|
-
* actually deduplicates shared modules across multiple ESM bundles, so
|
|
253
|
-
* a multi-entry modules map became viable — that's what Strategy S uses.
|
|
254
|
-
*
|
|
255
|
-
* @param {string} outDir - Path to dist/ directory containing entry.js
|
|
256
|
-
*/
|
|
257
|
-
async function buildSSRBundle(outDir) {
|
|
258
|
-
if (_buildingSSRBundle) return
|
|
259
|
-
_buildingSSRBundle = true
|
|
260
|
-
|
|
261
|
-
const entryPath = join(outDir, 'entry.js')
|
|
262
|
-
try {
|
|
263
|
-
const { build: esbuild } = await import('esbuild')
|
|
264
|
-
const { statSync } = await import('node:fs')
|
|
265
|
-
|
|
266
|
-
// Collect all node_modules directories up the tree (pnpm hoists to workspace root)
|
|
267
|
-
const { existsSync } = await import('node:fs')
|
|
268
|
-
let searchDir = resolve(outDir, '..')
|
|
269
|
-
let nodePaths = []
|
|
270
|
-
for (let i = 0; i < 10; i++) {
|
|
271
|
-
const candidate = join(searchDir, 'node_modules')
|
|
272
|
-
if (existsSync(candidate)) {
|
|
273
|
-
nodePaths.push(candidate)
|
|
274
|
-
}
|
|
275
|
-
const parent = resolve(searchDir, '..')
|
|
276
|
-
if (parent === searchDir) break
|
|
277
|
-
searchDir = parent
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Resolve workspace packages that esbuild can't find via node_modules
|
|
281
|
-
// (pnpm workspace symlinks aren't in node_modules for the foundation project)
|
|
282
|
-
const { createRequire } = await import('node:module')
|
|
283
|
-
const pluginRequire = createRequire(import.meta.url)
|
|
284
|
-
let runtimeSSRPath
|
|
285
|
-
try {
|
|
286
|
-
runtimeSSRPath = pluginRequire.resolve('@uniweb/runtime/ssr')
|
|
287
|
-
} catch {
|
|
288
|
-
// Fallback: try to find it relative to the workspace root
|
|
289
|
-
for (const np of nodePaths) {
|
|
290
|
-
const candidate = join(np, '@uniweb', 'runtime', 'dist', 'ssr.js')
|
|
291
|
-
if (existsSync(candidate)) {
|
|
292
|
-
runtimeSSRPath = candidate
|
|
293
|
-
break
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Build a self-contained ESM bundle including:
|
|
299
|
-
// - Foundation components (from the just-built ESM output)
|
|
300
|
-
// - React + ReactDOM/server (browser version, no Node.js built-ins)
|
|
301
|
-
// - @uniweb/core (Website, Page, Block classes)
|
|
302
|
-
// - @uniweb/runtime/ssr (initPrerender, renderPage, injectPageContent)
|
|
303
|
-
// - @uniweb/theming (buildSectionOverrides, used by runtime/ssr)
|
|
304
|
-
//
|
|
305
|
-
// All in a single file so the Dynamic Worker isolate has one React instance.
|
|
306
|
-
// L2/L3 helpers from @uniweb/runtime/ssr that worker SSR + framework SSG
|
|
307
|
-
// both depend on. Keep this list in sync with runtime/src/ssr-renderer.js
|
|
308
|
-
// exports — missing one here makes the foundation bundle fail to import
|
|
309
|
-
// it ("module does not provide an export named X") inside the SSR isolate.
|
|
310
|
-
const ssrExports = runtimeSSRPath
|
|
311
|
-
? `export {
|
|
312
|
-
initPrerender, initPrerenderForLocale,
|
|
313
|
-
renderPage, injectPageContent, prefetchIcons,
|
|
314
|
-
sliceContentForLocale, hydrateDataStore,
|
|
315
|
-
} from "${runtimeSSRPath.replace(/\\/g, '/')}";`
|
|
316
|
-
: ''
|
|
317
|
-
|
|
318
|
-
// Resolve React to a single package directory to avoid duplicate instances
|
|
319
|
-
// (entry.js and runtime/ssr may resolve to different copies)
|
|
320
|
-
const { dirname } = await import('node:path')
|
|
321
|
-
let reactDir
|
|
322
|
-
try {
|
|
323
|
-
reactDir = dirname(pluginRequire.resolve('react/package.json'))
|
|
324
|
-
} catch {
|
|
325
|
-
// Fall back to nodePaths resolution
|
|
326
|
-
}
|
|
327
|
-
const alias = {}
|
|
328
|
-
if (reactDir) {
|
|
329
|
-
alias['react'] = reactDir
|
|
330
|
-
// Force react-dom/server imports to the browser version (no Node.js built-ins)
|
|
331
|
-
const reactDomDir = dirname(pluginRequire.resolve('react-dom/package.json'))
|
|
332
|
-
alias['react-dom'] = reactDomDir
|
|
333
|
-
alias['react-dom/server'] = join(reactDomDir, 'server.browser.js')
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const foundationPath = entryPath.replace(/\\/g, '/')
|
|
337
|
-
await esbuild({
|
|
338
|
-
stdin: {
|
|
339
|
-
contents: [
|
|
340
|
-
// Foundation components (named + default export)
|
|
341
|
-
`export * from "${foundationPath}";`,
|
|
342
|
-
`export { default } from "${foundationPath}";`,
|
|
343
|
-
// React SSR
|
|
344
|
-
`export { renderToString } from "react-dom/server.browser";`,
|
|
345
|
-
`export { createElement } from "react";`,
|
|
346
|
-
// Runtime SSR functions (initPrerender, renderPage, etc.)
|
|
347
|
-
ssrExports,
|
|
348
|
-
].join('\n'),
|
|
349
|
-
resolveDir: outDir,
|
|
350
|
-
loader: 'js',
|
|
351
|
-
},
|
|
352
|
-
bundle: true,
|
|
353
|
-
format: 'esm',
|
|
354
|
-
platform: 'browser',
|
|
355
|
-
outfile: join(outDir, 'ssr-worker-bundle.js'),
|
|
356
|
-
minify: false,
|
|
357
|
-
external: [],
|
|
358
|
-
nodePaths,
|
|
359
|
-
alias,
|
|
360
|
-
conditions: ['browser', 'module'],
|
|
361
|
-
logLevel: 'warning',
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
const ssrFile = join(outDir, 'ssr-worker-bundle.js')
|
|
365
|
-
const size = (statSync(ssrFile).size / 1024).toFixed(1)
|
|
366
|
-
console.log(`Generated ssr-worker-bundle.js (${size} KB)`)
|
|
367
|
-
} catch (err) {
|
|
368
|
-
console.warn(`Warning: SSR bundle build failed: ${err.message}`)
|
|
369
|
-
} finally {
|
|
370
|
-
_buildingSSRBundle = false
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
223
|
/**
|
|
375
224
|
* Append the foundation's theme-variable DEFAULTS as a `:root{}` baseline to the
|
|
376
225
|
* built CSS (`assets/style.css`), so a runtime-loaded foundation carries its own
|
|
@@ -414,18 +263,18 @@ async function emitFoundationVarsCss(outDir, schema) {
|
|
|
414
263
|
* Externals for the SSR bundle (`dist/entry-ssr.js`).
|
|
415
264
|
*
|
|
416
265
|
* Same set the browser foundation build externalizes, and now literally the
|
|
417
|
-
* same array — `DEFAULT_EXTERNALS`, imported rather than re-typed.
|
|
418
|
-
* resolves these to the
|
|
419
|
-
*
|
|
266
|
+
* same array — `DEFAULT_EXTERNALS`, imported rather than re-typed. A host
|
|
267
|
+
* rendering the SSR bundle resolves these to the runtime's shared React/core,
|
|
268
|
+
* so React stays deduped and runtime patches still propagate without a
|
|
420
269
|
* foundation rebuild. "Same set" was a comment three copies made a promise
|
|
421
270
|
* rather than a fact; deriving it makes drift unrepresentable.
|
|
422
271
|
*
|
|
423
272
|
* PLUS the client-only library kit code-splits via dynamic import:
|
|
424
273
|
* - shiki / shiki/bundle/full — syntax highlighting (kit Code renderer)
|
|
425
|
-
* It hydrates in the browser and never runs during renderToString
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
274
|
+
* It hydrates in the browser and never runs during renderToString. Keeping it
|
|
275
|
+
* external drops the ~10 MB Shiki language graph from the SSR bundle and leaves
|
|
276
|
+
* it a DORMANT dynamic import that server rendering never awaits — so a host
|
|
277
|
+
* has nothing to supply for it.
|
|
429
278
|
*
|
|
430
279
|
* ⛔ `fuse.js` was listed here too, until the local search ranker became
|
|
431
280
|
* `@uniweb/projections/search` (2026-09-06). Nothing imports fuse now, so the
|
|
@@ -447,20 +296,20 @@ function isSSRExternal(id) {
|
|
|
447
296
|
*
|
|
448
297
|
* The modern browser `entry.js` is a facade that re-exports from
|
|
449
298
|
* `_entry.generated-*.js` and lazily code-splits kit's client-only features
|
|
450
|
-
* (Shiki) into hundreds of chunks — a graph
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
299
|
+
* (Shiki) into hundreds of chunks — a graph that a request-time renderer loading
|
|
300
|
+
* the foundation as a single module can't resolve. This builds the SAME source
|
|
301
|
+
* entry into ONE file, inlining the foundation's own graph and externalizing the
|
|
302
|
+
* runtime/React set (→ the runtime's shared build) and the client-only Shiki
|
|
303
|
+
* lib. Result: a ~foundation-sized ESM module (no React, no Shiki) that a host
|
|
304
|
+
* can load for request-time SSR.
|
|
456
305
|
*
|
|
457
306
|
* Built from source (not by re-bundling the built `entry.js`, whose Shiki
|
|
458
307
|
* specifier is already rewritten to a relative chunk path that couldn't be
|
|
459
308
|
* externalized) via a secondary Vite build into a temp dir; only the JS is
|
|
460
309
|
* copied out (the throwaway CSS is discarded — the SSR bundle needs no styles).
|
|
461
310
|
*
|
|
462
|
-
* Best-effort: a failure warns and emits nothing
|
|
463
|
-
*
|
|
311
|
+
* Best-effort: a failure warns and emits nothing. The browser `entry.js` is
|
|
312
|
+
* unaffected, so the foundation still renders client-side.
|
|
464
313
|
*
|
|
465
314
|
* @param {string} foundationRoot - foundation project root (vite `root`).
|
|
466
315
|
* @param {string} entrySourcePath - absolute path to `_entry.generated.js`.
|
|
@@ -589,7 +438,7 @@ export function foundationBuildPlugin(options = {}) {
|
|
|
589
438
|
},
|
|
590
439
|
|
|
591
440
|
async writeBundle(_options, bundle) {
|
|
592
|
-
// Skip
|
|
441
|
+
// Skip while the entry-ssr.js sub-build (buildEntrySSR) is running
|
|
593
442
|
if (_buildingSSRBundle) return
|
|
594
443
|
|
|
595
444
|
// What host services this foundation actually reaches for, read off the
|
|
@@ -637,26 +486,23 @@ export function foundationBuildPlugin(options = {}) {
|
|
|
637
486
|
// site build's theme.css — this is harmless redundancy there.
|
|
638
487
|
await emitFoundationVarsCss(outDir, schema)
|
|
639
488
|
|
|
640
|
-
// Record which @uniweb/runtime this build linked against
|
|
641
|
-
// floor
|
|
642
|
-
//
|
|
489
|
+
// Record which @uniweb/runtime this build linked against: a compatibility
|
|
490
|
+
// floor that `register` carries as `info.runtime` and that nothing in the
|
|
491
|
+
// framework enforces — see emitRuntimePin's header before assuming more.
|
|
643
492
|
await emitRuntimePin(outDir, resolvedRoot)
|
|
644
493
|
|
|
645
494
|
// Emit dist/entry-ssr.js — the single-file SSR twin of the (code-split)
|
|
646
|
-
// browser dist/entry.js — for
|
|
647
|
-
// runtime stay externalized (resolved to the
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
// `foundation` module for request-time SSR, gated on its presence.
|
|
495
|
+
// browser dist/entry.js — for a host that renders at request time. React
|
|
496
|
+
// + the runtime stay externalized (resolved to the runtime's shared build,
|
|
497
|
+
// so runtime patches propagate without a foundation rebuild); the
|
|
498
|
+
// client-only Shiki lib is externalized so the ~10 MB Shiki graph stays
|
|
499
|
+
// out.
|
|
652
500
|
//
|
|
653
|
-
// (The legacy self-contained buildSSRBundle() — React + runtime INLINED,
|
|
654
|
-
// ~14 MB with Shiki — is retained below, unused, for reference only.)
|
|
655
501
|
// Skipped on a DEV rebuild: this is a second full Vite pass, it runs on
|
|
656
502
|
// every save, and nothing in the dev loop reads its output. The local SSR
|
|
657
503
|
// lanes load `entry.js` (SSG prerender via `import()`, unipress the same);
|
|
658
|
-
// only
|
|
659
|
-
// to
|
|
504
|
+
// only a request-time host needs the single-file twin, and dev ships
|
|
505
|
+
// nothing to one.
|
|
660
506
|
//
|
|
661
507
|
// Shipping lanes are unaffected — `uniweb build`, and `register`/`publish`
|
|
662
508
|
// which build through it. `register`'s build-if-stale check also requires
|