@uniweb/runtime 0.5.15 → 0.5.17

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.jsx +59 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -31,7 +31,7 @@
31
31
  "node": ">=20.19"
32
32
  },
33
33
  "dependencies": {
34
- "@uniweb/core": "0.4.3"
34
+ "@uniweb/core": "0.4.4"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@vitejs/plugin-react": "^4.5.2",
package/src/index.jsx CHANGED
@@ -130,6 +130,42 @@ async function loadFoundation(source) {
130
130
  }
131
131
  }
132
132
 
133
+ /**
134
+ * Load extensions (secondary foundations) in parallel
135
+ * @param {Array<string|Object>} urls - Extension URLs or {url, cssUrl} objects
136
+ * @param {Object} uniwebInstance - The Uniweb instance to register extensions on
137
+ */
138
+ async function loadExtensions(urls, uniwebInstance) {
139
+ if (!urls?.length) return
140
+
141
+ // Resolve extension URLs against base path for subdirectory deployments
142
+ // e.g., /effects/foundation.js → /templates/extensions/effects/foundation.js
143
+ const basePath = import.meta.env?.BASE_URL || '/'
144
+ function resolveUrl(source) {
145
+ if (basePath === '/') return source
146
+ if (typeof source === 'string' && source.startsWith('/')) {
147
+ return basePath + source.slice(1)
148
+ }
149
+ if (typeof source === 'object' && source.url?.startsWith('/')) {
150
+ return { ...source, url: basePath + source.url.slice(1) }
151
+ }
152
+ return source
153
+ }
154
+
155
+ const results = await Promise.allSettled(
156
+ urls.map(url => loadFoundation(resolveUrl(url)))
157
+ )
158
+
159
+ for (let i = 0; i < results.length; i++) {
160
+ if (results[i].status === 'fulfilled') {
161
+ uniwebInstance.registerExtension(results[i].value)
162
+ console.log(`[Runtime] Extension loaded: ${urls[i]}`)
163
+ } else {
164
+ console.warn(`[Runtime] Extension failed to load: ${urls[i]}`, results[i].reason)
165
+ }
166
+ }
167
+ }
168
+
133
169
  /**
134
170
  * Map friendly family names to react-icons codes
135
171
  * The existing CDN uses react-icons structure: /{familyCode}/{familyCode}-{name}.svg
@@ -411,6 +447,12 @@ async function initRuntime(foundationSource, options = {}) {
411
447
  uniwebInstance.setFoundationConfig(foundation.capabilities)
412
448
  }
413
449
 
450
+ // Load extensions (secondary foundations)
451
+ const extensions = configData?.config?.extensions
452
+ if (extensions?.length) {
453
+ await loadExtensions(extensions, uniwebInstance)
454
+ }
455
+
414
456
  // Render the app
415
457
  render({ development, basename })
416
458
 
@@ -452,15 +494,23 @@ async function initRuntime(foundationSource, options = {}) {
452
494
  /**
453
495
  * Simplified entry point for sites
454
496
  *
455
- * Reads configuration from (in order of priority):
456
- * 1. __DATA__ element (dynamic backends) - combined foundation config + site content
457
- * 2. Build-time __FOUNDATION_CONFIG__ (static builds) - foundation config only
497
+ * Template main.js calls this with config + foundation/styles promises:
498
+ *
499
+ * start({
500
+ * config: __FOUNDATION_CONFIG__,
501
+ * styles: import('#foundation/styles'),
502
+ * foundation: import('#foundation')
503
+ * })
504
+ *
505
+ * In runtime mode, the foundation/styles promises resolve to noop modules
506
+ * (configured by the site Vite plugin) and are ignored.
458
507
  *
459
508
  * @param {Object} options
509
+ * @param {Object} options.config - Build-time config from __FOUNDATION_CONFIG__
460
510
  * @param {Promise} options.foundation - Promise from import('#foundation')
461
511
  * @param {Promise} options.styles - Promise from import('#foundation/styles')
462
512
  */
463
- async function start({ foundation, styles } = {}) {
513
+ async function start({ config, foundation, styles } = {}) {
464
514
  // Try __DATA__ first (dynamic backends inject combined config + content)
465
515
  const data = await decodeData()
466
516
 
@@ -472,15 +522,14 @@ async function start({ foundation, styles } = {}) {
472
522
  )
473
523
  }
474
524
 
475
- // Static build mode - use build-time config
476
- const config =
477
- typeof __FOUNDATION_CONFIG__ !== 'undefined' ? __FOUNDATION_CONFIG__ : { mode: 'bundled' }
525
+ // Use provided config, or fall back to bundled mode
526
+ const mode = config?.mode ?? 'bundled'
478
527
 
479
- if (config.mode === 'runtime') {
480
- // Runtime mode (foundation URL in site.yml)
528
+ if (mode === 'runtime') {
529
+ // Runtime mode - foundation loaded from URL
481
530
  return initRuntime({ url: config.url, cssUrl: config.cssUrl })
482
531
  } else {
483
- // Bundled mode - foundation included in build
532
+ // Bundled mode - use the provided foundation/styles promises
484
533
  const [foundationModule] = await Promise.all([foundation, styles])
485
534
  return initRuntime(foundationModule)
486
535
  }