@uniweb/build 0.11.8 → 0.11.9

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.11.8",
3
+ "version": "0.11.9",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -58,9 +58,9 @@
58
58
  "@uniweb/theming": "0.1.3"
59
59
  },
60
60
  "optionalDependencies": {
61
- "@uniweb/content-reader": "1.1.9",
61
+ "@uniweb/schemas": "0.2.1",
62
62
  "@uniweb/runtime": "0.8.9",
63
- "@uniweb/schemas": "0.2.1"
63
+ "@uniweb/content-reader": "1.1.9"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -7,7 +7,8 @@
7
7
  * - Processing preview images for presets
8
8
  */
9
9
 
10
- import { writeFile, mkdir } from 'node:fs/promises'
10
+ import { writeFile, mkdir, readFile } from 'node:fs/promises'
11
+ import { existsSync } from 'node:fs'
11
12
  import { join, resolve } from 'node:path'
12
13
  import { buildSchema } from './schema.js'
13
14
  import { generateEntryPoint, shouldRegenerateForFile } from './generate-entry.js'
@@ -41,6 +42,119 @@ async function buildSchemaWithPreviews(srcDir, outDir, isProduction, sectionPath
41
42
  */
42
43
  let _buildingSSRBundle = false
43
44
 
45
+ /**
46
+ * Emit dist/runtime-pin.json declaring the @uniweb/runtime version this
47
+ * foundation was built against. Read by the edge isolate (under the
48
+ * Strategy S split-bundle path) to decide which runtime/{ver}/ssr.js to
49
+ * side-load from R2. See kb/platform/plans/edge-ssr-bundling-strategy.md
50
+ * and kb/platform/operations/release-workflow.md.
51
+ *
52
+ * Reads the resolved version from the foundation's node_modules/@uniweb/
53
+ * runtime/package.json so the pin reflects what was actually linked at
54
+ * build time, not what the foundation's own package.json range happens
55
+ * to allow.
56
+ *
57
+ * Silently no-ops when @uniweb/runtime isn't resolvable (e.g., the
58
+ * foundation depends on the runtime via a workspace alias that puts it
59
+ * elsewhere). The edge resolver treats the absence of a pin as the
60
+ * legacy single-bundle path, so omitting the pin is harmless during the
61
+ * dual-mode window.
62
+ *
63
+ * Optional foundation-author override: a `uniweb.runtimePolicy` field
64
+ * in the foundation's own package.json gets recorded alongside the
65
+ * runtime version so the registry's semver resolver can apply it.
66
+ *
67
+ * @param {string} outDir - dist/ directory to write to.
68
+ * @param {string} projectRoot - foundation project root (where package.json lives).
69
+ */
70
+ async function emitRuntimePin(outDir, projectRoot) {
71
+ // Resolve @uniweb/runtime via two strategies, in order:
72
+ // 1. createRequire from this plugin's location (catches the runtime
73
+ // pulled in transitively through @uniweb/build, @uniweb/core, etc.).
74
+ // 2. Walk up node_modules from the project root (catches the case
75
+ // where the foundation depends on runtime directly).
76
+ // The first covers the common case (foundations don't typically depend
77
+ // on runtime directly — it's the host environment, not a foundation
78
+ // import); the second is a safety net.
79
+ let runtimePkgPath = null
80
+
81
+ try {
82
+ // Resolve via an exported subpath, not 'package.json' directly —
83
+ // @uniweb/runtime's `exports` map doesn't include package.json, so
84
+ // require.resolve on it throws ERR_PACKAGE_PATH_NOT_EXPORTED.
85
+ // Walking back from the resolved subpath finds the package root.
86
+ const { createRequire } = await import('node:module')
87
+ const { dirname: pathDirname } = await import('node:path')
88
+ const pluginRequire = createRequire(import.meta.url)
89
+ const ssrEntry = pluginRequire.resolve('@uniweb/runtime/ssr')
90
+ let dir = pathDirname(ssrEntry)
91
+ for (let i = 0; i < 5; i++) {
92
+ const candidate = join(dir, 'package.json')
93
+ if (existsSync(candidate)) {
94
+ const pkg = JSON.parse(await readFile(candidate, 'utf-8'))
95
+ if (pkg.name === '@uniweb/runtime') {
96
+ runtimePkgPath = candidate
97
+ break
98
+ }
99
+ }
100
+ const parent = resolve(dir, '..')
101
+ if (parent === dir) break
102
+ dir = parent
103
+ }
104
+ } catch {
105
+ // Fall through to the walk-up search.
106
+ }
107
+
108
+ if (!runtimePkgPath) {
109
+ let dir = projectRoot
110
+ for (let i = 0; i < 10; i++) {
111
+ const candidate = join(dir, 'node_modules', '@uniweb', 'runtime', 'package.json')
112
+ if (existsSync(candidate)) {
113
+ runtimePkgPath = candidate
114
+ break
115
+ }
116
+ const parent = resolve(dir, '..')
117
+ if (parent === dir) break
118
+ dir = parent
119
+ }
120
+ }
121
+
122
+ if (!runtimePkgPath) {
123
+ // No runtime resolvable. Skip emission — edge will treat as legacy.
124
+ return
125
+ }
126
+
127
+ let runtimeVersion
128
+ try {
129
+ const pkg = JSON.parse(await readFile(runtimePkgPath, 'utf-8'))
130
+ runtimeVersion = pkg.version
131
+ } catch {
132
+ return
133
+ }
134
+ if (!runtimeVersion) return
135
+
136
+ // Read foundation's own package.json for an optional runtimePolicy
137
+ // field. Default policy (auto-patch) lives at the registry layer; we
138
+ // only record the foundation's override if explicitly set.
139
+ let policy = null
140
+ try {
141
+ const foundationPkgPath = join(projectRoot, 'package.json')
142
+ if (existsSync(foundationPkgPath)) {
143
+ const foundationPkg = JSON.parse(await readFile(foundationPkgPath, 'utf-8'))
144
+ policy = foundationPkg?.uniweb?.runtimePolicy ?? null
145
+ }
146
+ } catch {
147
+ // Foundation package.json malformed; skip policy. Pin still emits.
148
+ }
149
+
150
+ const pin = { runtime: runtimeVersion }
151
+ if (policy) pin.policy = policy
152
+
153
+ const pinPath = join(outDir, 'runtime-pin.json')
154
+ await writeFile(pinPath, JSON.stringify(pin, null, 2) + '\n', 'utf-8')
155
+ console.log(`Generated runtime-pin.json (runtime ${runtimeVersion}${policy ? `, policy ${policy}` : ''})`)
156
+ }
157
+
44
158
  /**
45
159
  * Build a self-contained ESM bundle for edge SSR (Cloudflare Dynamic Workers).
46
160
  *
@@ -64,7 +178,7 @@ let _buildingSSRBundle = false
64
178
  * file in, everything resolves. A future refactor can switch to side-
65
179
  * loading runtime + React + core into the modules map separately so
66
180
  * foundations stop carrying runtime in their bundles — see
67
- * `kb/platform/plans/edge-runtime-side-loading.md`. Until then, this
181
+ * `kb/platform/plans/edge-ssr-bundling-strategy.md`. Until then, this
68
182
  * build pre-bundles for the isolate's contract.
69
183
  *
70
184
  * @param {string} outDir - Path to dist/ directory containing foundation.js
@@ -118,8 +232,16 @@ async function buildSSRBundle(outDir) {
118
232
  // - @uniweb/theming (buildSectionOverrides, used by runtime/ssr)
119
233
  //
120
234
  // All in a single file so the Dynamic Worker isolate has one React instance.
235
+ // L2/L3 helpers from @uniweb/runtime/ssr that worker SSR + framework SSG
236
+ // both depend on. Keep this list in sync with runtime/src/ssr-renderer.js
237
+ // exports — missing one here makes the foundation bundle fail to import
238
+ // it ("module does not provide an export named X") inside the SSR isolate.
121
239
  const ssrExports = runtimeSSRPath
122
- ? `export { initPrerender, renderPage, injectPageContent, prefetchIcons } from "${runtimeSSRPath.replace(/\\/g, '/')}";`
240
+ ? `export {
241
+ initPrerender, initPrerenderForLocale,
242
+ renderPage, injectPageContent, prefetchIcons,
243
+ sliceContentForLocale, hydrateDataStore,
244
+ } from "${runtimeSSRPath.replace(/\\/g, '/')}";`
123
245
  : ''
124
246
 
125
247
  // Resolve React to a single package directory to avoid duplicate instances
@@ -191,6 +313,7 @@ export function foundationBuildPlugin(options = {}) {
191
313
 
192
314
  let resolvedSrcDir
193
315
  let resolvedOutDir
316
+ let resolvedRoot
194
317
  let isProduction
195
318
 
196
319
  return {
@@ -209,6 +332,7 @@ export function foundationBuildPlugin(options = {}) {
209
332
  async configResolved(config) {
210
333
  resolvedSrcDir = resolve(config.root, srcDir)
211
334
  resolvedOutDir = config.build.outDir
335
+ resolvedRoot = config.root
212
336
  isProduction = config.mode === 'production'
213
337
  },
214
338
 
@@ -235,7 +359,17 @@ export function foundationBuildPlugin(options = {}) {
235
359
 
236
360
  console.log(`Generated meta/schema.json with ${Object.keys(schema).length - 1} components`)
237
361
 
238
- // Build self-contained SSR bundle for edge rendering (Dynamic Workers)
362
+ // Emit runtime-pin.json so the edge isolate (under Strategy S) can
363
+ // side-load the matching runtime/{ver}/ssr.js. Lands silently before
364
+ // the dual-mode resolver ships; foundations published in the dual-mode
365
+ // window already have the pin and start using the split-bundle path
366
+ // automatically once the edge is updated.
367
+ await emitRuntimePin(outDir, resolvedRoot)
368
+
369
+ // Build self-contained SSR bundle for edge rendering (Dynamic Workers).
370
+ // Stays in the build until Strategy S Phase 2 — dual-mode edge
371
+ // resolver continues to fall back to it for foundations without
372
+ // a runtime pin.
239
373
  await buildSSRBundle(outDir)
240
374
  },
241
375