@pie-players/pie-players-shared 0.3.47 → 0.3.49

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 (65) hide show
  1. package/README.md +30 -0
  2. package/dist/i18n/simple-i18n.js.map +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/instrumentation/debug-panel-stream.js.map +1 -1
  6. package/dist/instrumentation/providers/CompositeInstrumentationProvider.js.map +1 -1
  7. package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js +2 -1
  8. package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js.map +1 -1
  9. package/dist/loaders/ElementLoader.js +2 -1
  10. package/dist/loaders/ElementLoader.js.map +1 -1
  11. package/dist/loaders/element-loader.js +28 -4
  12. package/dist/loaders/element-loader.js.map +1 -1
  13. package/dist/loaders/esm-adapter.d.ts +22 -5
  14. package/dist/loaders/esm-adapter.js +320 -60
  15. package/dist/loaders/esm-adapter.js.map +1 -1
  16. package/dist/loaders/iife-adapter.js +1 -2
  17. package/dist/loaders/iife-adapter.js.map +1 -1
  18. package/dist/loaders/index.d.ts +1 -1
  19. package/dist/loaders/index.js.map +1 -1
  20. package/dist/pie/authoring.js +4 -2
  21. package/dist/pie/authoring.js.map +1 -1
  22. package/dist/pie/config.js +4 -1
  23. package/dist/pie/config.js.map +1 -1
  24. package/dist/pie/configure-initialization.js.map +1 -1
  25. package/dist/pie/correct-response-env.js.map +1 -1
  26. package/dist/pie/index.d.ts +1 -1
  27. package/dist/pie/index.js +1 -1
  28. package/dist/pie/index.js.map +1 -1
  29. package/dist/pie/initialization.js +2 -1
  30. package/dist/pie/initialization.js.map +1 -1
  31. package/dist/pie/instrumentation-event-bridge.js.map +1 -1
  32. package/dist/pie/instrumentation-event-map.js.map +1 -1
  33. package/dist/pie/instrumentation-provider-resolution.js.map +1 -1
  34. package/dist/pie/item-controller-storage.js.map +1 -1
  35. package/dist/pie/item-controller.d.ts +15 -0
  36. package/dist/pie/item-controller.js +22 -2
  37. package/dist/pie/item-controller.js.map +1 -1
  38. package/dist/pie/item-session-contract.d.ts +1 -0
  39. package/dist/pie/item-session-contract.js +28 -13
  40. package/dist/pie/item-session-contract.js.map +1 -1
  41. package/dist/pie/math-rendering.js.map +1 -1
  42. package/dist/pie/overrides.js +2 -1
  43. package/dist/pie/overrides.js.map +1 -1
  44. package/dist/pie/resource-monitor.js.map +1 -1
  45. package/dist/pie/stage-tracker.js.map +1 -1
  46. package/dist/pie/types.d.ts +7 -0
  47. package/dist/pie/types.js.map +1 -1
  48. package/dist/pie/updates.d.ts +3 -3
  49. package/dist/pie/updates.js +48 -25
  50. package/dist/pie/updates.js.map +1 -1
  51. package/dist/security/index.d.ts +1 -0
  52. package/dist/security/index.js +1 -0
  53. package/dist/security/index.js.map +1 -1
  54. package/dist/security/sanitize-item-markup.js.map +1 -1
  55. package/dist/security/wrap-model-rich-content.d.ts +1 -0
  56. package/dist/security/wrap-model-rich-content.js +41 -0
  57. package/dist/security/wrap-model-rich-content.js.map +1 -0
  58. package/dist/security/wrap-overwide-images.js +1 -2
  59. package/dist/security/wrap-overwide-images.js.map +1 -1
  60. package/dist/security/wrap-overwide-tables.js +1 -2
  61. package/dist/security/wrap-overwide-tables.js.map +1 -1
  62. package/dist/server/npm-registry.js.map +1 -1
  63. package/dist/types/index.d.ts +15 -3
  64. package/dist/types/index.js.map +1 -1
  65. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * ESM backend adapter for the ElementLoader primitive.
3
3
  *
4
- * Loads PIE elements via dynamic `import()` from an ESM CDN (esm.sh,
5
- * jsDelivr, etc.), optionally resolving bare specifiers through an
6
- * `<script type="importmap">` injected into the host document. On any
7
- * per-tag failure (module load, non-constructor element class, define
8
- * failure), throws `AdapterFailure` with a structured `reasons` map.
4
+ * Loads PIE elements via dynamic `import()` from static browser ESM files on a
5
+ * CDN, optionally resolving bare specifiers through an import map injected into
6
+ * the host document. On any per-tag failure (module load, non-constructor
7
+ * element class, define failure), throws `AdapterFailure` with a structured
8
+ * `reasons` map.
9
9
  *
10
10
  * Like the IIFE adapter, this module does not gate its own promise on
11
11
  * `customElements.whenDefined`; the primitive performs that verification
@@ -13,6 +13,7 @@
13
13
  * call `customElements.define`.
14
14
  */
15
15
  import { defineCustomElementSafely } from "../pie/custom-element-define.js";
16
+ import { isInstrumentationProvider } from "../instrumentation/provider-guards.js";
16
17
  import { pieRegistry } from "../pie/registry.js";
17
18
  import { validateCustomElementTag } from "../pie/tag-names.js";
18
19
  import { isCustomElementConstructor, Status } from "../pie/types.js";
@@ -24,14 +25,16 @@ export const BUILT_IN_VIEWS = {
24
25
  };
25
26
  export function createEsmBackend(config) {
26
27
  const cdnBaseUrl = config.cdnBaseUrl.replace(/\/+$/, "");
28
+ const cdnProvider = resolveCdnProvider(config.cdnProvider, cdnBaseUrl);
27
29
  const moduleResolution = config.moduleResolution ?? "url";
28
30
  const view = config.view ?? "delivery";
29
31
  const loadControllers = config.loadControllers ?? true;
30
- const viewConfig = config.viewConfig ??
31
- BUILT_IN_VIEWS[view] ??
32
- BUILT_IN_VIEWS.delivery;
33
- const injectedPackages = new Set();
32
+ const viewConfig = config.viewConfig ?? BUILT_IN_VIEWS[view] ?? BUILT_IN_VIEWS.delivery;
33
+ const injectedPackageVersions = new Set();
34
+ const importMappedPackageVersions = new Map();
35
+ let sharedDependencyVersions = {};
34
36
  let importer = defaultImporter;
37
+ let packageMetadataLoader = defaultPackageMetadataLoader;
35
38
  let importMapObserver;
36
39
  const __seams = {
37
40
  replaceImporter(fn) {
@@ -40,10 +43,16 @@ export function createEsmBackend(config) {
40
43
  observeImportMapInjection(cb) {
41
44
  importMapObserver = cb;
42
45
  },
46
+ replacePackageMetadataLoader(fn) {
47
+ packageMetadataLoader = fn;
48
+ },
43
49
  restore() {
44
50
  importer = defaultImporter;
51
+ packageMetadataLoader = defaultPackageMetadataLoader;
45
52
  importMapObserver = undefined;
46
- injectedPackages.clear();
53
+ injectedPackageVersions.clear();
54
+ importMappedPackageVersions.clear();
55
+ sharedDependencyVersions = {};
47
56
  },
48
57
  };
49
58
  async function load(elements, context) {
@@ -51,19 +60,42 @@ export function createEsmBackend(config) {
51
60
  return;
52
61
  if (moduleResolution === "import-map") {
53
62
  assertImportMapSupported();
54
- const newEntries = {};
55
- for (const [tag, pkg] of Object.entries(elements)) {
56
- const packageName = extractPackageName(pkg);
57
- if (!injectedPackages.has(packageName)) {
58
- newEntries[tag] = pkg;
59
- injectedPackages.add(packageName);
63
+ }
64
+ const newEntries = {};
65
+ for (const [tag, pkg] of Object.entries(elements)) {
66
+ const packageName = extractPackageName(pkg);
67
+ if (moduleResolution === "import-map") {
68
+ const existingVersion = importMappedPackageVersions.get(packageName);
69
+ if (existingVersion && existingVersion !== pkg) {
70
+ throw new Error(`Conflicting browser ESM package version for ${packageName}: ${existingVersion} is already mapped, but ${pkg} was requested`);
60
71
  }
61
72
  }
62
- if (Object.keys(newEntries).length > 0) {
63
- const json = buildImportMapJson(newEntries, viewConfig, cdnBaseUrl, loadControllers);
73
+ if (!injectedPackageVersions.has(pkg)) {
74
+ newEntries[tag] = pkg;
75
+ }
76
+ }
77
+ if (Object.keys(newEntries).length > 0) {
78
+ let importMapResult;
79
+ try {
80
+ importMapResult = await buildImportMapJson(newEntries, viewConfig, cdnProvider, loadControllers, moduleResolution === "import-map", packageMetadataLoader, sharedDependencyVersions, reportSharedDependencyConflict);
81
+ }
82
+ catch (err) {
83
+ reportSharedDependencyError(err);
84
+ throw err;
85
+ }
86
+ const json = importMapResult.json;
87
+ if (json) {
88
+ assertImportMapSupported();
64
89
  injectImportMap(json, context.doc);
65
90
  importMapObserver?.(json, context.doc);
66
91
  }
92
+ sharedDependencyVersions = importMapResult.sharedDependencyVersions;
93
+ for (const pkg of Object.values(newEntries)) {
94
+ injectedPackageVersions.add(pkg);
95
+ if (moduleResolution === "import-map") {
96
+ importMappedPackageVersions.set(extractPackageName(pkg), pkg);
97
+ }
98
+ }
67
99
  }
68
100
  const reasons = new Map();
69
101
  const registry = pieRegistry();
@@ -81,7 +113,7 @@ export function createEsmBackend(config) {
81
113
  });
82
114
  return;
83
115
  }
84
- const specifier = resolveElementSpecifier(packageName, packageVersion, viewConfig, moduleResolution, cdnBaseUrl);
116
+ const specifier = resolveElementSpecifier(packageName, packageVersion, viewConfig, moduleResolution, cdnProvider, view);
85
117
  let module;
86
118
  try {
87
119
  module = await importer(specifier);
@@ -89,7 +121,7 @@ export function createEsmBackend(config) {
89
121
  catch (err) {
90
122
  if (viewConfig.fallback) {
91
123
  const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback] ?? BUILT_IN_VIEWS.delivery;
92
- const fallbackSpecifier = resolveElementSpecifier(packageName, packageVersion, fallbackConfig, moduleResolution, cdnBaseUrl);
124
+ const fallbackSpecifier = resolveElementSpecifier(packageName, packageVersion, fallbackConfig, moduleResolution, cdnProvider, viewConfig.fallback);
93
125
  try {
94
126
  module = await importer(fallbackSpecifier);
95
127
  }
@@ -146,7 +178,7 @@ export function createEsmBackend(config) {
146
178
  }
147
179
  let controller = null;
148
180
  if (loadControllers) {
149
- const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnBaseUrl);
181
+ const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider);
150
182
  try {
151
183
  const controllerModule = await importer(controllerSpecifier);
152
184
  controller = controllerModule?.default ?? controllerModule;
@@ -170,6 +202,48 @@ export function createEsmBackend(config) {
170
202
  throw new AdapterFailure(reasons);
171
203
  }
172
204
  }
205
+ function getInstrumentationProvider() {
206
+ if (!config.trackPageActions)
207
+ return undefined;
208
+ const provider = config.instrumentationProvider;
209
+ if (!isInstrumentationProvider(provider))
210
+ return undefined;
211
+ if (!provider.isReady())
212
+ return undefined;
213
+ return provider;
214
+ }
215
+ function reportSharedDependencyConflict(attributes) {
216
+ if (typeof console !== "undefined" && console.warn) {
217
+ console.warn(`[pie-esm] Shared dependency version conflict resolved for ${String(attributes.dependencyName)}`, attributes);
218
+ }
219
+ const provider = getInstrumentationProvider();
220
+ if (!provider)
221
+ return;
222
+ try {
223
+ provider.trackEvent("pie-esm-shared-dependency-conflict", attributes);
224
+ }
225
+ catch {
226
+ // Swallow: instrumentation must never break loading.
227
+ }
228
+ }
229
+ function reportSharedDependencyError(error) {
230
+ const err = error instanceof Error ? error : new Error(String(error));
231
+ if (typeof console !== "undefined" && console.error) {
232
+ console.error("[pie-esm] Shared dependency resolution failed", err);
233
+ }
234
+ const provider = getInstrumentationProvider();
235
+ if (!provider)
236
+ return;
237
+ try {
238
+ provider.trackError(err, {
239
+ component: "esm-adapter",
240
+ errorType: "EsmSharedDependencyError",
241
+ });
242
+ }
243
+ catch {
244
+ // Swallow: instrumentation must never break loading.
245
+ }
246
+ }
173
247
  return {
174
248
  load,
175
249
  get __seams() {
@@ -182,47 +256,133 @@ function defaultImporter(specifier) {
182
256
  // @vite-ignore — dynamic import resolved at runtime.
183
257
  return import(/* @vite-ignore */ specifier);
184
258
  }
259
+ async function defaultPackageMetadataLoader(packageVersion, packageJsonUrl) {
260
+ const browserFetch = typeof window !== "undefined" &&
261
+ typeof window.fetch === "function"
262
+ ? window.fetch.bind(window)
263
+ : undefined;
264
+ if (!browserFetch) {
265
+ return null;
266
+ }
267
+ const response = await browserFetch(packageJsonUrl);
268
+ if (!response.ok) {
269
+ return null;
270
+ }
271
+ return (await response.json());
272
+ }
273
+ function resolveCdnProvider(provider, cdnBaseUrl) {
274
+ if (isEsmCdnProvider(provider))
275
+ return provider;
276
+ const resolvedProviderName = provider ?? (cdnBaseUrl.includes("esm.sh") ? "esm.sh" : "jsdelivr");
277
+ if (resolvedProviderName === "esm.sh") {
278
+ return createEsmShProvider(cdnBaseUrl);
279
+ }
280
+ return createJsDelivrProvider(cdnBaseUrl, resolvedProviderName);
281
+ }
282
+ function isEsmCdnProvider(value) {
283
+ if (!value || typeof value !== "object")
284
+ return false;
285
+ const candidate = value;
286
+ return (typeof candidate.name === "string" &&
287
+ typeof candidate.packageJsonUrl === "function" &&
288
+ typeof candidate.browserViewUrl === "function" &&
289
+ typeof candidate.browserControllerUrl === "function" &&
290
+ typeof candidate.sharedDependencyUrl === "function");
291
+ }
292
+ function createJsDelivrProvider(cdnBaseUrl, name = "jsdelivr") {
293
+ return {
294
+ name,
295
+ packageJsonUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/package.json`,
296
+ browserViewUrl: (packageVersion, view) => `${cdnBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
297
+ browserControllerUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
298
+ sharedDependencyUrl: (dependencyName, version, subpath) => {
299
+ const suffix = subpath ? `/${subpath}/+esm` : "/+esm";
300
+ return `${cdnBaseUrl}/${dependencyName}@${version}${suffix}`;
301
+ },
302
+ };
303
+ }
304
+ function createEsmShProvider(cdnBaseUrl) {
305
+ const esmBaseUrl = cdnBaseUrl.replace(/\/+$/, "");
306
+ const rawBaseUrl = toRawEsmShBaseUrl(esmBaseUrl);
307
+ return {
308
+ name: "esm.sh",
309
+ packageJsonUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/package.json`,
310
+ browserViewUrl: (packageVersion, view) => `${rawBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
311
+ browserControllerUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
312
+ sharedDependencyUrl: (dependencyName, version, subpath) => {
313
+ const suffix = subpath ? `/${subpath}` : "";
314
+ return `${esmBaseUrl}/${dependencyName}@${version}${suffix}`;
315
+ },
316
+ };
317
+ }
318
+ function toRawEsmShBaseUrl(cdnBaseUrl) {
319
+ try {
320
+ const url = new URL(cdnBaseUrl);
321
+ if (url.hostname === "raw.esm.sh")
322
+ return url.toString().replace(/\/+$/, "");
323
+ if (url.hostname === "esm.sh") {
324
+ url.hostname = "raw.esm.sh";
325
+ return url.toString().replace(/\/+$/, "");
326
+ }
327
+ }
328
+ catch {
329
+ // Fall through to the public raw endpoint for non-URL test fixtures.
330
+ }
331
+ return "https://raw.esm.sh";
332
+ }
333
+ function assertBrowserEsmExports(metadata, packageVersion, viewConfig, loadControllers, viewName) {
334
+ if (!metadata?.exports)
335
+ return;
336
+ const requiredExports = new Set([
337
+ `./browser/${browserViewName(viewConfig, viewName)}`,
338
+ ]);
339
+ if (viewConfig.fallback) {
340
+ const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback];
341
+ if (fallbackConfig) {
342
+ requiredExports.add(`./browser/${browserViewName(fallbackConfig, viewConfig.fallback)}`);
343
+ }
344
+ }
345
+ if (loadControllers) {
346
+ requiredExports.add("./browser/controller");
347
+ }
348
+ for (const exportKey of requiredExports) {
349
+ if (!(exportKey in metadata.exports)) {
350
+ throw new Error(`${packageVersion} does not publish browser ESM export ${exportKey}; use IIFE/preloaded mode or publish browser ESM artifacts first`);
351
+ }
352
+ }
353
+ }
185
354
  function extractPackageName(packageVersion) {
186
355
  const parts = packageVersion.split("@");
187
356
  return parts.length >= 3 ? `@${parts[1]}` : parts[0];
188
357
  }
189
- function isJsDelivrNpm(cdnBaseUrl) {
190
- return cdnBaseUrl.includes("cdn.jsdelivr.net/npm");
358
+ function cleanViewSubpath(subpath) {
359
+ const cleanSubpath = subpath.replace(/^\/+|\/+$/g, "");
360
+ return cleanSubpath.length > 0 ? cleanSubpath : null;
191
361
  }
192
- function resolvePackageUrl(packageVersion, cdnBaseUrl) {
193
- if (isJsDelivrNpm(cdnBaseUrl)) {
194
- return `${cdnBaseUrl}/${packageVersion}/+esm`;
195
- }
196
- return `${cdnBaseUrl}/${packageVersion}`;
362
+ function browserViewName(viewConfig, viewName = "delivery") {
363
+ const subpathView = cleanViewSubpath(viewConfig.subpath);
364
+ return subpathView ?? viewName;
197
365
  }
198
- function resolveSubpathUrl(packageVersion, subpath, cdnBaseUrl) {
199
- const cleanSubpath = subpath.startsWith("/") ? subpath.slice(1) : subpath;
200
- if (isJsDelivrNpm(cdnBaseUrl)) {
201
- return `${cdnBaseUrl}/${packageVersion}/${cleanSubpath}/+esm`;
202
- }
203
- return `${cdnBaseUrl}/${packageVersion}/${cleanSubpath}`;
366
+ function resolveBrowserViewUrl(packageVersion, viewConfig, cdnProvider, viewName = "delivery") {
367
+ const view = browserViewName(viewConfig, viewName);
368
+ return cdnProvider.browserViewUrl(packageVersion, view);
204
369
  }
205
- function resolveControllerUrl(packageVersion, cdnBaseUrl) {
206
- if (isJsDelivrNpm(cdnBaseUrl)) {
207
- return `${cdnBaseUrl}/${packageVersion}/controller/+esm`;
208
- }
209
- return `${cdnBaseUrl}/${packageVersion}/controller`;
370
+ function resolveBrowserControllerUrl(packageVersion, cdnProvider) {
371
+ return cdnProvider.browserControllerUrl(packageVersion);
210
372
  }
211
- function resolveElementSpecifier(packageName, packageVersion, viewConfig, moduleResolution, cdnBaseUrl) {
373
+ function resolveElementSpecifier(packageName, packageVersion, viewConfig, moduleResolution, cdnProvider, viewName) {
212
374
  if (moduleResolution === "import-map") {
213
375
  return viewConfig.subpath
214
376
  ? `${packageName}${viewConfig.subpath}`
215
377
  : packageName;
216
378
  }
217
- return viewConfig.subpath
218
- ? resolveSubpathUrl(packageVersion, viewConfig.subpath, cdnBaseUrl)
219
- : resolvePackageUrl(packageVersion, cdnBaseUrl);
379
+ return resolveBrowserViewUrl(packageVersion, viewConfig, cdnProvider, viewName);
220
380
  }
221
- function resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnBaseUrl) {
381
+ function resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider) {
222
382
  if (moduleResolution === "import-map") {
223
383
  return `${packageName}/controller`;
224
384
  }
225
- return resolveControllerUrl(packageVersion, cdnBaseUrl);
385
+ return resolveBrowserControllerUrl(packageVersion, cdnProvider);
226
386
  }
227
387
  function pickElementClass(module, view) {
228
388
  if (!module || typeof module !== "object")
@@ -235,25 +395,127 @@ function pickElementClass(module, view) {
235
395
  }
236
396
  return module.default ?? module.Element;
237
397
  }
238
- function buildImportMapJson(elements, viewConfig, cdnBaseUrl, loadControllers) {
398
+ const SHARED_BROWSER_DEPENDENCIES = ["react", "react-dom"];
399
+ function declaredSharedDependencyVersion(metadata, dependencyName, packageVersion) {
400
+ const version = metadata?.pie?.browserSharedDependencies?.[dependencyName];
401
+ if (!version) {
402
+ throw new Error(`${packageVersion} is missing required pie.browserSharedDependencies.${dependencyName}`);
403
+ }
404
+ if (!isExactVersion(version)) {
405
+ throw new Error(`${packageVersion} pie.browserSharedDependencies.${dependencyName} must be an exact version; received "${version}"`);
406
+ }
407
+ return version;
408
+ }
409
+ function packageUsesSharedDependency(metadata, dependencyName) {
410
+ return Boolean(metadata?.pie?.browserSharedDependencies?.[dependencyName] ||
411
+ metadata?.peerDependencies?.[dependencyName] ||
412
+ metadata?.dependencies?.[dependencyName] ||
413
+ metadata?.optionalDependencies?.[dependencyName]);
414
+ }
415
+ function addSharedDependencyImports(imports, selectedVersions, lockedVersions, dependencyName, version, cdnProvider, packageVersion, onConflict) {
416
+ if (!version) {
417
+ return;
418
+ }
419
+ const currentVersion = selectedVersions[dependencyName];
420
+ let resolvedVersion = version;
421
+ if (currentVersion && currentVersion !== version) {
422
+ resolvedVersion = resolveSharedDependencyVersion(dependencyName, currentVersion, version, packageVersion, lockedVersions.has(dependencyName));
423
+ onConflict({
424
+ dependencyName,
425
+ existingVersion: currentVersion,
426
+ requestedVersion: version,
427
+ resolvedVersion,
428
+ packageVersion,
429
+ });
430
+ }
431
+ selectedVersions[dependencyName] = resolvedVersion;
432
+ imports[dependencyName] = cdnProvider.sharedDependencyUrl(dependencyName, resolvedVersion);
433
+ if (dependencyName === "react") {
434
+ imports["react/jsx-runtime"] = cdnProvider.sharedDependencyUrl("react", resolvedVersion, "jsx-runtime");
435
+ imports["react/jsx-dev-runtime"] = cdnProvider.sharedDependencyUrl("react", resolvedVersion, "jsx-dev-runtime");
436
+ }
437
+ if (dependencyName === "react-dom") {
438
+ imports["react-dom/client"] = cdnProvider.sharedDependencyUrl("react-dom", resolvedVersion, "client");
439
+ }
440
+ }
441
+ function isExactVersion(version) {
442
+ return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version);
443
+ }
444
+ function parseVersion(version) {
445
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
446
+ if (!match) {
447
+ throw new Error(`Invalid shared browser dependency version "${version}"`);
448
+ }
449
+ return {
450
+ major: Number(match[1]),
451
+ minor: Number(match[2]),
452
+ patch: Number(match[3]),
453
+ };
454
+ }
455
+ function compareVersions(a, b) {
456
+ const parsedA = parseVersion(a);
457
+ const parsedB = parseVersion(b);
458
+ if (parsedA.major !== parsedB.major)
459
+ return parsedA.major - parsedB.major;
460
+ if (parsedA.minor !== parsedB.minor)
461
+ return parsedA.minor - parsedB.minor;
462
+ return parsedA.patch - parsedB.patch;
463
+ }
464
+ function resolveSharedDependencyVersion(dependencyName, currentVersion, requestedVersion, packageVersion, isLocked) {
465
+ const current = parseVersion(currentVersion);
466
+ const requested = parseVersion(requestedVersion);
467
+ if (current.major !== requested.major) {
468
+ throw new Error(`Conflicting shared browser dependency ${dependencyName}: ${currentVersion} vs ${requestedVersion} from ${packageVersion}; different major versions cannot share one browser singleton`);
469
+ }
470
+ if (isLocked && compareVersions(requestedVersion, currentVersion) > 0) {
471
+ throw new Error(`Conflicting shared browser dependency ${dependencyName}: ${currentVersion} is already selected, but ${packageVersion} requires higher version ${requestedVersion}; the browser singleton cannot be upgraded after import-map injection`);
472
+ }
473
+ return compareVersions(currentVersion, requestedVersion) >= 0
474
+ ? currentVersion
475
+ : requestedVersion;
476
+ }
477
+ async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControllers, includeElementImports, packageMetadataLoader, currentSharedDependencyVersions, onConflict) {
239
478
  const imports = {};
479
+ const selectedVersions = {
480
+ ...currentSharedDependencyVersions,
481
+ };
482
+ const lockedVersions = new Set(Object.keys(currentSharedDependencyVersions));
240
483
  for (const [, pkg] of Object.entries(elements)) {
241
484
  const packageName = extractPackageName(pkg);
242
- imports[packageName] = resolvePackageUrl(pkg, cdnBaseUrl);
243
- if (viewConfig.subpath) {
244
- imports[`${packageName}${viewConfig.subpath}`] = resolveSubpathUrl(pkg, viewConfig.subpath, cdnBaseUrl);
245
- }
246
- if (loadControllers) {
247
- imports[`${packageName}/controller`] = resolveControllerUrl(pkg, cdnBaseUrl);
485
+ if (includeElementImports) {
486
+ imports[packageName] = resolveBrowserViewUrl(pkg, BUILT_IN_VIEWS.delivery, cdnProvider, "delivery");
487
+ if (viewConfig.subpath) {
488
+ imports[`${packageName}${viewConfig.subpath}`] = resolveBrowserViewUrl(pkg, viewConfig, cdnProvider, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
489
+ }
490
+ if (loadControllers) {
491
+ imports[`${packageName}/controller`] = resolveBrowserControllerUrl(pkg, cdnProvider);
492
+ }
493
+ if (viewConfig.fallback) {
494
+ const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback];
495
+ if (fallbackConfig) {
496
+ const fallbackSpecifier = fallbackConfig.subpath
497
+ ? `${packageName}${fallbackConfig.subpath}`
498
+ : packageName;
499
+ imports[fallbackSpecifier] = resolveBrowserViewUrl(pkg, fallbackConfig, cdnProvider, viewConfig.fallback);
500
+ }
501
+ }
248
502
  }
249
- if (viewConfig.fallback) {
250
- const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback];
251
- if (fallbackConfig?.subpath) {
252
- imports[`${packageName}${fallbackConfig.subpath}`] = resolveSubpathUrl(pkg, fallbackConfig.subpath, cdnBaseUrl);
503
+ const metadata = await packageMetadataLoader(pkg, cdnProvider.packageJsonUrl(pkg));
504
+ assertBrowserEsmExports(metadata, pkg, viewConfig, loadControllers, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
505
+ for (const dependencyName of SHARED_BROWSER_DEPENDENCIES) {
506
+ if (!packageUsesSharedDependency(metadata, dependencyName)) {
507
+ continue;
253
508
  }
509
+ addSharedDependencyImports(imports, selectedVersions, lockedVersions, dependencyName, declaredSharedDependencyVersion(metadata, dependencyName, pkg), cdnProvider, pkg, onConflict);
254
510
  }
255
511
  }
256
- return JSON.stringify({ imports }, null, 2);
512
+ if (Object.keys(imports).length === 0) {
513
+ return { json: null, sharedDependencyVersions: selectedVersions };
514
+ }
515
+ return {
516
+ json: JSON.stringify({ imports }, null, 2),
517
+ sharedDependencyVersions: selectedVersions,
518
+ };
257
519
  }
258
520
  function injectImportMap(json, doc) {
259
521
  const script = doc.createElement("script");
@@ -262,9 +524,7 @@ function injectImportMap(json, doc) {
262
524
  doc.head.appendChild(script);
263
525
  }
264
526
  function assertImportMapSupported() {
265
- const htmlScriptElement = (typeof HTMLScriptElement !== "undefined"
266
- ? HTMLScriptElement
267
- : undefined);
527
+ const htmlScriptElement = (typeof HTMLScriptElement !== "undefined" ? HTMLScriptElement : undefined);
268
528
  const supports = typeof htmlScriptElement?.supports === "function" &&
269
529
  htmlScriptElement.supports("importmap");
270
530
  if (!supports) {