@pie-players/pie-players-shared 0.3.72 → 0.3.74

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 (123) hide show
  1. package/README.md +27 -1
  2. package/dist/components/vendor/nds/README.md +7 -5
  3. package/dist/config/profile.d.ts +0 -2
  4. package/dist/config/profile.js +2 -8
  5. package/dist/i18n/index.d.ts +2 -3
  6. package/dist/i18n/index.js +2 -3
  7. package/dist/i18n/messages/en-US.d.ts +10 -0
  8. package/dist/i18n/messages/en-US.js +12 -2
  9. package/dist/i18n/messages/nl-NL.d.ts +6 -0
  10. package/dist/i18n/messages/nl-NL.js +8 -2
  11. package/dist/i18n/provider.d.ts +4 -5
  12. package/dist/i18n/provider.js +4 -5
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +3 -1
  15. package/dist/instrumentation/index.d.ts +5 -4
  16. package/dist/instrumentation/index.js +5 -4
  17. package/dist/instrumentation/new-relic-agent.d.ts +20 -0
  18. package/dist/instrumentation/new-relic-agent.js +24 -0
  19. package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
  20. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
  21. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
  22. package/dist/instrumentation/types.d.ts +7 -3
  23. package/dist/loader-config.d.ts +16 -6
  24. package/dist/loaders/controller-loading.d.ts +14 -0
  25. package/dist/loaders/controller-loading.js +12 -0
  26. package/dist/loaders/defaults.d.ts +10 -0
  27. package/dist/loaders/defaults.js +10 -0
  28. package/dist/loaders/element-loader.d.ts +20 -8
  29. package/dist/loaders/element-loader.js +102 -36
  30. package/dist/loaders/element-package-policy.d.ts +2 -0
  31. package/dist/loaders/element-package-policy.js +2 -1
  32. package/dist/loaders/esm-adapter.d.ts +18 -0
  33. package/dist/loaders/esm-adapter.js +617 -102
  34. package/dist/loaders/iife-adapter.d.ts +0 -5
  35. package/dist/loaders/iife-adapter.js +3 -9
  36. package/dist/loaders/index.d.ts +10 -3
  37. package/dist/loaders/index.js +8 -3
  38. package/dist/loaders/mathjax-version-conflict.d.ts +17 -0
  39. package/dist/loaders/mathjax-version-conflict.js +51 -0
  40. package/dist/loaders/module-shim.d.ts +12 -0
  41. package/dist/loaders/module-shim.js +12 -0
  42. package/dist/loaders/preloaded-alignment.d.ts +24 -0
  43. package/dist/loaders/preloaded-alignment.js +57 -0
  44. package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
  45. package/dist/loaders/preloaded-authored-tags.js +69 -0
  46. package/dist/loaders/preloaded-registration.d.ts +56 -0
  47. package/dist/loaders/preloaded-registration.js +129 -0
  48. package/dist/media/index.d.ts +69 -0
  49. package/dist/media/index.js +194 -0
  50. package/dist/pie/authoring.d.ts +1 -1
  51. package/dist/pie/authoring.js +25 -18
  52. package/dist/pie/element-announcements.d.ts +39 -0
  53. package/dist/pie/element-announcements.js +67 -0
  54. package/dist/pie/element-observer.d.ts +3 -1
  55. package/dist/pie/element-observer.js +1 -0
  56. package/dist/pie/index.d.ts +6 -4
  57. package/dist/pie/index.js +6 -3
  58. package/dist/pie/initialization.js +13 -22
  59. package/dist/pie/initialize-element.d.ts +3 -0
  60. package/dist/pie/initialize-element.js +3 -4
  61. package/dist/pie/instrumentation-event-map.js +2 -9
  62. package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
  63. package/dist/pie/instrumentation-provider-resolution.js +0 -1
  64. package/dist/pie/item-session-contract.d.ts +47 -0
  65. package/dist/pie/item-session-contract.js +196 -9
  66. package/dist/pie/markup-math.d.ts +20 -0
  67. package/dist/pie/markup-math.js +118 -0
  68. package/dist/pie/math-rendering.d.ts +10 -21
  69. package/dist/pie/math-rendering.js +50 -35
  70. package/dist/pie/registry.d.ts +13 -1
  71. package/dist/pie/registry.js +39 -1
  72. package/dist/pie/resource-monitor.d.ts +6 -33
  73. package/dist/pie/resource-monitor.js +14 -213
  74. package/dist/pie/scoring.d.ts +11 -2
  75. package/dist/pie/scoring.js +18 -16
  76. package/dist/pie/session-commit.d.ts +121 -0
  77. package/dist/pie/session-commit.js +420 -0
  78. package/dist/pie/session-snapshot.d.ts +80 -0
  79. package/dist/pie/session-snapshot.js +166 -0
  80. package/dist/pie/tag-names.d.ts +1 -0
  81. package/dist/pie/tag-names.js +1 -0
  82. package/dist/pie/types.d.ts +4 -5
  83. package/dist/pie/types.js +1 -0
  84. package/dist/pie/updates.d.ts +2 -2
  85. package/dist/pie/updates.js +33 -15
  86. package/dist/pie/versioned-tag.d.ts +4 -0
  87. package/dist/pie/versioned-tag.js +4 -0
  88. package/dist/security/index.d.ts +2 -3
  89. package/dist/security/index.js +2 -3
  90. package/dist/security/sanitize-item-markup.d.ts +0 -2
  91. package/dist/security/sanitize-item-markup.js +0 -4
  92. package/dist/security/sanitize-svg-icon.d.ts +0 -2
  93. package/dist/security/sanitize-svg-icon.js +0 -5
  94. package/dist/security/wrap-overwide.d.ts +5 -0
  95. package/dist/security/wrap-overwide.js +16 -0
  96. package/dist/timed-media/policy.js +5 -16
  97. package/dist/types/index.d.ts +30 -311
  98. package/dist/types/index.js +0 -68
  99. package/dist/ui/attribute-coercion.d.ts +12 -0
  100. package/dist/ui/attribute-coercion.js +33 -0
  101. package/dist/ui/content-styles.d.ts +18 -16
  102. package/dist/ui/content-styles.js +67 -36
  103. package/dist/ui/overlay-containment.d.ts +31 -0
  104. package/dist/ui/overlay-containment.js +63 -0
  105. package/dist/ui/overlay-placement.d.ts +110 -0
  106. package/dist/ui/overlay-placement.js +222 -0
  107. package/dist/ui/pointer-gesture.d.ts +32 -0
  108. package/dist/ui/pointer-gesture.js +56 -0
  109. package/dist/ui/pointer-rotate.d.ts +29 -0
  110. package/dist/ui/pointer-rotate.js +39 -0
  111. package/package.json +21 -17
  112. package/dist/pie/component-context.d.ts +0 -21
  113. package/dist/pie/component-context.js +0 -29
  114. package/dist/pie/player-initializer.d.ts +0 -54
  115. package/dist/pie/player-initializer.js +0 -122
  116. package/dist/security/wrap-model-rich-content.d.ts +0 -1
  117. package/dist/security/wrap-model-rich-content.js +0 -40
  118. package/dist/server/npm-auth-env.d.ts +0 -13
  119. package/dist/server/npm-auth-env.js +0 -64
  120. package/dist/server/npm-registry.d.ts +0 -7
  121. package/dist/server/npm-registry.js +0 -59
  122. package/dist/ui/debug-panel-persistence.d.ts +0 -17
  123. package/dist/ui/debug-panel-persistence.js +0 -37
@@ -12,12 +12,16 @@
12
12
  * uniformly. Here the adapter's job is narrowly to fetch, extract, and
13
13
  * call `customElements.define`.
14
14
  */
15
+ import { compare as compareSemver, major as semverMajor, minor as semverMinor, } from "semver";
15
16
  import { defineCustomElementSafely } from "../pie/custom-element-define.js";
16
17
  import { isInstrumentationProvider } from "../instrumentation/provider-guards.js";
17
- import { pieRegistry } from "../pie/registry.js";
18
+ import { writeRegistryEntry } from "../pie/registry.js";
18
19
  import { validateCustomElementTag } from "../pie/tag-names.js";
19
- import { isCustomElementConstructor, Status } from "../pie/types.js";
20
+ import { BundleType, isCustomElementConstructor, Status, } from "../pie/types.js";
21
+ import { parsePackageName } from "../pie/utils.js";
20
22
  import { AdapterFailure, } from "./element-loader-types.js";
23
+ import { isExactSemver } from "./element-package-policy.js";
24
+ import { forwardMathjaxVersionConflicts } from "./mathjax-version-conflict.js";
21
25
  export const BUILT_IN_VIEWS = {
22
26
  delivery: { subpath: "", tagSuffix: "" },
23
27
  author: { subpath: "/author", tagSuffix: "-config", fallback: "delivery" },
@@ -30,12 +34,21 @@ export function createEsmBackend(config) {
30
34
  const view = config.view ?? "delivery";
31
35
  const loadControllers = config.loadControllers ?? true;
32
36
  const viewConfig = resolveEsmViewConfig(view, config.viewConfig);
37
+ const browserView = browserViewName(viewConfig, view);
33
38
  const injectedPackageVersions = new Set();
34
39
  const importMappedPackageVersions = new Map();
40
+ /** Packages whose editor-runtime variant this backend loads. */
41
+ const editorRuntimeVariants = new Map();
42
+ /** Packages that do not publish the browser ESM exports this backend loads. */
43
+ const unpublishedPackages = new Map();
44
+ /** Windows whose MathJax version conflicts this backend forwards. */
45
+ const mathjaxConflictViews = new WeakSet();
35
46
  let sharedDependencyVersions = {};
36
47
  let importer = defaultImporter;
37
48
  let packageMetadataLoader = defaultPackageMetadataLoader;
38
49
  let importMapObserver;
50
+ let importShimLoader = defaultImportShimLoader;
51
+ let importShimRequest;
39
52
  const __seams = {
40
53
  replaceImporter(fn) {
41
54
  importer = fn;
@@ -46,12 +59,20 @@ export function createEsmBackend(config) {
46
59
  replacePackageMetadataLoader(fn) {
47
60
  packageMetadataLoader = fn;
48
61
  },
62
+ replaceImportShimLoader(fn) {
63
+ importShimLoader = fn;
64
+ importShimRequest = undefined;
65
+ },
49
66
  restore() {
50
67
  importer = defaultImporter;
51
68
  packageMetadataLoader = defaultPackageMetadataLoader;
69
+ importShimLoader = defaultImportShimLoader;
70
+ importShimRequest = undefined;
52
71
  importMapObserver = undefined;
53
72
  injectedPackageVersions.clear();
54
73
  importMappedPackageVersions.clear();
74
+ editorRuntimeVariants.clear();
75
+ unpublishedPackages.clear();
55
76
  sharedDependencyVersions = {};
56
77
  },
57
78
  };
@@ -61,9 +82,10 @@ export function createEsmBackend(config) {
61
82
  if (moduleResolution === "import-map") {
62
83
  assertImportMapSupported();
63
84
  }
85
+ forwardMathjaxVersionConflictsOf(context.doc);
64
86
  const newEntries = {};
65
87
  for (const [tag, pkg] of Object.entries(elements)) {
66
- const packageName = extractPackageName(pkg);
88
+ const packageName = parsePackageName(pkg).name;
67
89
  if (moduleResolution === "import-map") {
68
90
  const existingVersion = importMappedPackageVersions.get(packageName);
69
91
  if (existingVersion && existingVersion !== pkg) {
@@ -77,30 +99,51 @@ export function createEsmBackend(config) {
77
99
  if (Object.keys(newEntries).length > 0) {
78
100
  let importMapResult;
79
101
  try {
80
- importMapResult = await buildImportMapJson(newEntries, viewConfig, cdnProvider, loadControllers, moduleResolution === "import-map", packageMetadataLoader, sharedDependencyVersions, reportSharedDependencyConflict);
102
+ importMapResult = await buildImportMap(newEntries, viewConfig, cdnProvider, loadControllers, moduleResolution === "import-map", packageMetadataLoader, sharedDependencyVersions, reportSharedDependencyConflict);
81
103
  }
82
104
  catch (err) {
83
105
  reportSharedDependencyError(err);
84
106
  throw err;
85
107
  }
86
- const json = importMapResult.json;
87
- if (json) {
108
+ for (const [pkg, cause] of importMapResult.unpublished) {
109
+ unpublishedPackages.set(pkg, cause);
110
+ if (typeof console !== "undefined" && console.error) {
111
+ console.error(`[pie-esm] ${cause}`);
112
+ }
113
+ }
114
+ const editorRuntime = moduleResolution === "url"
115
+ ? await prepareEditorRuntime(importMapResult.metadata, context.doc)
116
+ : undefined;
117
+ // Synchronous from here through the injection, so a loader that runs
118
+ // next on this page reads the runtime this one maps.
119
+ const runtimePlan = editorRuntime
120
+ ? planEditorRuntime(editorRuntime, context.doc)
121
+ : undefined;
122
+ const imports = withoutMappedSpecifiers({ ...importMapResult.imports, ...runtimePlan?.imports }, context.doc);
123
+ let injectedMap;
124
+ if (Object.keys(imports).length > 0) {
125
+ const json = JSON.stringify({ imports }, null, 2);
88
126
  assertImportMapSupported();
89
- injectImportMap(json, context.doc);
127
+ injectedMap = injectImportMap(json, context.doc, runtimePlan?.mappedRuntime);
90
128
  importMapObserver?.(json, context.doc);
91
129
  }
130
+ for (const [pkg, variant] of runtimePlan?.served ?? []) {
131
+ editorRuntimeVariants.set(pkg, variant);
132
+ }
92
133
  sharedDependencyVersions = importMapResult.sharedDependencyVersions;
93
134
  for (const pkg of Object.values(newEntries)) {
94
135
  injectedPackageVersions.add(pkg);
95
136
  if (moduleResolution === "import-map") {
96
- importMappedPackageVersions.set(extractPackageName(pkg), pkg);
137
+ importMappedPackageVersions.set(parsePackageName(pkg).name, pkg);
97
138
  }
98
139
  }
140
+ if (injectedMap) {
141
+ await applyImportMap(imports, injectedMap, context.doc);
142
+ }
99
143
  }
100
144
  const reasons = new Map();
101
- const registry = pieRegistry();
102
145
  await Promise.all(Object.entries(elements).map(async ([tag, packageVersion]) => {
103
- const packageName = extractPackageName(packageVersion);
146
+ const packageName = parsePackageName(packageVersion).name;
104
147
  let actualTag;
105
148
  try {
106
149
  actualTag = validateCustomElementTag(tag, `element tag for ${packageName}`);
@@ -114,48 +157,65 @@ export function createEsmBackend(config) {
114
157
  return;
115
158
  }
116
159
  const specifier = resolveElementSpecifier(packageName, packageVersion, viewConfig, moduleResolution, cdnProvider, view);
117
- let module;
118
- try {
119
- module = await importer(specifier);
160
+ const unpublished = unpublishedPackages.get(packageVersion);
161
+ if (unpublished) {
162
+ reasons.set(tag, {
163
+ kind: "module-load-failed",
164
+ tag,
165
+ specifier,
166
+ cause: unpublished,
167
+ });
168
+ return;
120
169
  }
121
- catch (err) {
122
- if (viewConfig.fallback) {
123
- const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback] ?? BUILT_IN_VIEWS.delivery;
124
- const fallbackSpecifier = resolveElementSpecifier(packageName, packageVersion, fallbackConfig, moduleResolution, cdnProvider, viewConfig.fallback);
125
- try {
126
- module = await importer(fallbackSpecifier);
170
+ const variant = editorRuntimeVariants.get(packageVersion);
171
+ let ElementClass;
172
+ if (variant) {
173
+ ElementClass = await importVariantElementClass(packageVersion, variant, context.doc);
174
+ }
175
+ if (!ElementClass) {
176
+ let module;
177
+ try {
178
+ module = await importModule(specifier, context.doc);
179
+ }
180
+ catch (err) {
181
+ if (viewConfig.fallback) {
182
+ const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback] ?? BUILT_IN_VIEWS.delivery;
183
+ const fallbackSpecifier = resolveElementSpecifier(packageName, packageVersion, fallbackConfig, moduleResolution, cdnProvider, viewConfig.fallback);
184
+ try {
185
+ module = await importModule(fallbackSpecifier, context.doc);
186
+ }
187
+ catch (fallbackErr) {
188
+ reasons.set(tag, {
189
+ kind: "module-load-failed",
190
+ tag,
191
+ specifier: fallbackSpecifier,
192
+ cause: fallbackErr instanceof Error
193
+ ? fallbackErr.message
194
+ : String(fallbackErr),
195
+ });
196
+ return;
197
+ }
127
198
  }
128
- catch (fallbackErr) {
199
+ else {
129
200
  reasons.set(tag, {
130
201
  kind: "module-load-failed",
131
202
  tag,
132
- specifier: fallbackSpecifier,
133
- cause: fallbackErr instanceof Error
134
- ? fallbackErr.message
135
- : String(fallbackErr),
203
+ specifier,
204
+ cause: err instanceof Error ? err.message : String(err),
136
205
  });
137
206
  return;
138
207
  }
139
208
  }
140
- else {
209
+ ElementClass = pickElementClass(module, view);
210
+ if (!ElementClass) {
141
211
  reasons.set(tag, {
142
- kind: "module-load-failed",
212
+ kind: "no-element-class",
143
213
  tag,
144
- specifier,
145
- cause: err instanceof Error ? err.message : String(err),
214
+ packageName,
146
215
  });
147
216
  return;
148
217
  }
149
218
  }
150
- const ElementClass = pickElementClass(module, view);
151
- if (!ElementClass) {
152
- reasons.set(tag, {
153
- kind: "no-element-class",
154
- tag,
155
- packageName,
156
- });
157
- return;
158
- }
159
219
  if (!isCustomElementConstructor(ElementClass)) {
160
220
  reasons.set(tag, {
161
221
  kind: "not-a-constructor",
@@ -178,30 +238,239 @@ export function createEsmBackend(config) {
178
238
  }
179
239
  let controller = null;
180
240
  if (loadControllers) {
181
- const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider);
182
- try {
183
- const controllerModule = await importer(controllerSpecifier);
184
- controller = controllerModule?.default ?? controllerModule;
241
+ const servedVariant = editorRuntimeVariants.get(packageVersion);
242
+ const variantController = servedVariant?.views.controller;
243
+ if (servedVariant && variantController) {
244
+ try {
245
+ const controllerModule = await importModule(cdnProvider.browserViewUrl(packageVersion, variantController), context.doc);
246
+ controller = controllerModule?.default ?? controllerModule;
247
+ }
248
+ catch (err) {
249
+ reportEditorRuntimeFallback(packageVersion, servedVariant.declaration, servedVariant.runtime, `its editor-runtime controller failed to load: ${errorMessage(err)}`);
250
+ }
185
251
  }
186
- catch {
187
- // Controllers are best-effort; element registration is what the
188
- // primitive verifies.
252
+ if (!controller) {
253
+ const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider);
254
+ try {
255
+ const controllerModule = await importModule(controllerSpecifier, context.doc);
256
+ controller = controllerModule?.default ?? controllerModule;
257
+ }
258
+ catch {
259
+ // Controllers are best-effort; element registration is what the
260
+ // primitive verifies.
261
+ }
189
262
  }
190
263
  }
191
- registry[actualTag] = {
264
+ writeRegistryEntry({
192
265
  package: packageVersion,
193
266
  status: Status.loaded,
194
267
  tagName: actualTag,
195
268
  element: ElementClass,
196
269
  controller,
197
270
  config: null,
198
- bundleType: "esm",
199
- };
271
+ bundleType: BundleType.esm,
272
+ });
200
273
  }));
201
274
  if (reasons.size > 0) {
202
275
  throw new AdapterFailure(reasons);
203
276
  }
204
277
  }
278
+ /**
279
+ * The element class of a package's editor-runtime variant, or `undefined`
280
+ * after reporting why it cannot load, so the caller loads `./browser/*`.
281
+ */
282
+ async function importVariantElementClass(packageVersion, variant, doc) {
283
+ const specifier = cdnProvider.browserViewUrl(packageVersion, variant.views[browserView]);
284
+ try {
285
+ const ElementClass = pickElementClass(await importModule(specifier, doc), view);
286
+ if (isCustomElementConstructor(ElementClass))
287
+ return ElementClass;
288
+ throw new Error(`${specifier} exports no custom element class`);
289
+ }
290
+ catch (err) {
291
+ editorRuntimeVariants.delete(packageVersion);
292
+ reportEditorRuntimeFallback(packageVersion, variant.declaration, variant.runtime, `its editor-runtime ${browserView} view failed to load: ${errorMessage(err)}`);
293
+ return undefined;
294
+ }
295
+ }
296
+ /**
297
+ * Imports through the browser, or through es-module-shims once the document
298
+ * has rejected an import map, after every map a backend added has settled.
299
+ */
300
+ async function importModule(specifier, doc) {
301
+ await importMapsSettled(doc);
302
+ if (!documentUsesImportShim(doc))
303
+ return importer(specifier);
304
+ return (await importShim(doc))(specifier);
305
+ }
306
+ /**
307
+ * Settles the map this backend just added: es-module-shims takes it in a
308
+ * document that already uses it, and otherwise the browser has either
309
+ * applied it or rejected it.
310
+ */
311
+ async function applyImportMap(imports, script, doc) {
312
+ let state = "applied";
313
+ try {
314
+ if (script.type === SHIM_IMPORT_MAP_TYPE) {
315
+ (await importShim(doc)).addImportMap({ imports });
316
+ }
317
+ else if (await importMapRejected(imports)) {
318
+ state = "rejected";
319
+ await importShim(doc);
320
+ }
321
+ }
322
+ finally {
323
+ settleImportMap(script, state);
324
+ }
325
+ }
326
+ /**
327
+ * Whether the browser rejected a map this backend added. Firefox rejects
328
+ * every import map added after the page's first module load, so a specifier
329
+ * the map defines fails to resolve while the module it maps to loads. When
330
+ * that module fails too, the map is not the cause, and the element imports
331
+ * report the failure.
332
+ */
333
+ async function importMapRejected(imports) {
334
+ const specifier = "react" in imports ? "react" : Object.keys(imports)[0];
335
+ try {
336
+ await importer(specifier);
337
+ return false;
338
+ }
339
+ catch {
340
+ // Resolved below: the map, or the module it maps to.
341
+ }
342
+ try {
343
+ await importer(imports[specifier]);
344
+ }
345
+ catch {
346
+ return false;
347
+ }
348
+ return true;
349
+ }
350
+ /**
351
+ * es-module-shims, holding every map in `doc`: the rejected ones, and any
352
+ * the browser applied before, so both resolve the same way.
353
+ */
354
+ function importShim(doc) {
355
+ importShimRequest ??= importShimLoader().then((shim) => {
356
+ shim.addImportMap({ imports: documentImports(doc) });
357
+ return shim;
358
+ }, (err) => {
359
+ importShimRequest = undefined;
360
+ throw err;
361
+ });
362
+ return importShimRequest;
363
+ }
364
+ /**
365
+ * Collect the packages whose variant covers this backend's view and, when
366
+ * the page maps no editor runtime yet, fetch the metadata of the one to map.
367
+ */
368
+ async function prepareEditorRuntime(metadataByPackage, doc) {
369
+ const candidates = [];
370
+ for (const [packageVersion, metadata] of metadataByPackage) {
371
+ const declaration = readEditorRuntimeDeclaration(metadata);
372
+ if (declaration === undefined)
373
+ continue;
374
+ if (declaration === null) {
375
+ reportEditorRuntimeFallback(packageVersion, undefined, undefined, "its pie.browserEditorRuntime declaration is invalid");
376
+ continue;
377
+ }
378
+ if (declaration.views[browserView]) {
379
+ candidates.push({ packageVersion, declaration });
380
+ }
381
+ }
382
+ if (candidates.length === 0)
383
+ return { candidates };
384
+ if (!importMapsSupported()) {
385
+ for (const { packageVersion, declaration } of candidates) {
386
+ reportEditorRuntimeFallback(packageVersion, declaration, undefined, "this browser does not support import maps");
387
+ }
388
+ return { candidates: [] };
389
+ }
390
+ if (readMappedEditorRuntime(doc))
391
+ return { candidates };
392
+ const runtime = highestDeclaredEditorRuntime(candidates);
393
+ return {
394
+ candidates,
395
+ target: { runtime, ...(await loadEditorRuntimeModules(runtime)) },
396
+ };
397
+ }
398
+ async function loadEditorRuntimeModules(runtime) {
399
+ const runtimeVersion = `${runtime.name}@${runtime.version}`;
400
+ let metadata;
401
+ try {
402
+ metadata = await packageMetadataLoader(runtimeVersion, cdnProvider.packageJsonUrl(runtimeVersion));
403
+ }
404
+ catch (err) {
405
+ return {
406
+ failure: `the package.json of ${runtimeVersion} could not be loaded: ${errorMessage(err)}`,
407
+ };
408
+ }
409
+ if (!metadata) {
410
+ return {
411
+ failure: `the package.json of ${runtimeVersion} could not be loaded`,
412
+ };
413
+ }
414
+ const modules = readBrowserModules(metadata);
415
+ return modules
416
+ ? { modules }
417
+ : { failure: `${runtimeVersion} declares no valid pie.browserModules` };
418
+ }
419
+ /**
420
+ * Decide which packages the page's editor runtime serves, and the entries
421
+ * that map it when the page maps none yet. Runs synchronously up to the
422
+ * import-map injection, so it reads the runtime any other loader mapped
423
+ * while this one awaited metadata.
424
+ */
425
+ function planEditorRuntime(prepared, doc) {
426
+ const plan = { imports: {}, served: new Map() };
427
+ if (prepared.candidates.length === 0)
428
+ return plan;
429
+ let runtime = readMappedEditorRuntime(doc);
430
+ if (!runtime) {
431
+ const { target } = prepared;
432
+ let failure = target?.failure;
433
+ if (target?.modules) {
434
+ const taken = documentImports(doc);
435
+ const mapped = Object.keys(target.modules).find((specifier) => specifier in taken);
436
+ if (mapped)
437
+ failure = `the page already maps ${mapped}`;
438
+ }
439
+ if (failure || !target?.modules) {
440
+ for (const { packageVersion, declaration } of prepared.candidates) {
441
+ reportEditorRuntimeFallback(packageVersion, declaration, undefined, failure ?? "the page no longer maps its editor runtime");
442
+ }
443
+ return plan;
444
+ }
445
+ runtime = target.runtime;
446
+ const runtimeVersion = `${runtime.name}@${runtime.version}`;
447
+ for (const [specifier, runtimeView] of Object.entries(target.modules)) {
448
+ plan.imports[specifier] = cdnProvider.browserViewUrl(runtimeVersion, runtimeView);
449
+ }
450
+ plan.mappedRuntime = runtimeVersion;
451
+ }
452
+ for (const { packageVersion, declaration } of prepared.candidates) {
453
+ if (!canServeEditorRuntime(runtime, declaration)) {
454
+ reportEditorRuntimeFallback(packageVersion, declaration, runtime, `the page maps ${runtime.name}@${runtime.version}, which cannot serve ${declaration.name}@${declaration.version}`);
455
+ continue;
456
+ }
457
+ plan.served.set(packageVersion, {
458
+ views: declaration.views,
459
+ declaration,
460
+ runtime,
461
+ });
462
+ if (declaration.version !== runtime.version) {
463
+ reportSharedDependencyConflict({
464
+ dependencyName: runtime.name,
465
+ existingVersion: runtime.version,
466
+ requestedVersion: declaration.version,
467
+ resolvedVersion: runtime.version,
468
+ packageVersion,
469
+ });
470
+ }
471
+ }
472
+ return plan;
473
+ }
205
474
  function getInstrumentationProvider() {
206
475
  if (!config.trackPageActions)
207
476
  return undefined;
@@ -212,9 +481,16 @@ export function createEsmBackend(config) {
212
481
  return undefined;
213
482
  return provider;
214
483
  }
215
- function reportSharedDependencyConflict(attributes) {
484
+ function forwardMathjaxVersionConflictsOf(doc) {
485
+ const view = doc.defaultView;
486
+ if (!view || mathjaxConflictViews.has(view))
487
+ return;
488
+ mathjaxConflictViews.add(view);
489
+ forwardMathjaxVersionConflicts(view, getInstrumentationProvider);
490
+ }
491
+ function reportSharedDependencyConflict(attributes, message = `[pie-esm] Shared dependency version conflict resolved for ${String(attributes.dependencyName)}`) {
216
492
  if (typeof console !== "undefined" && console.warn) {
217
- console.warn(`[pie-esm] Shared dependency version conflict resolved for ${String(attributes.dependencyName)}`, attributes);
493
+ console.warn(message, attributes);
218
494
  }
219
495
  const provider = getInstrumentationProvider();
220
496
  if (!provider)
@@ -226,6 +502,17 @@ export function createEsmBackend(config) {
226
502
  // Swallow: instrumentation must never break loading.
227
503
  }
228
504
  }
505
+ /** Report a package that loads `./browser/*` although it declares a variant. */
506
+ function reportEditorRuntimeFallback(packageVersion, declaration, runtime, reason) {
507
+ reportSharedDependencyConflict({
508
+ dependencyName: declaration?.name,
509
+ existingVersion: runtime?.version,
510
+ requestedVersion: declaration?.version,
511
+ packageVersion,
512
+ fallback: "./browser/*",
513
+ reason,
514
+ }, `[pie-esm] ${packageVersion} loads ./browser/* in place of its editor-runtime variant: ${reason}`);
515
+ }
229
516
  function reportSharedDependencyError(error) {
230
517
  const err = error instanceof Error ? error : new Error(String(error));
231
518
  if (typeof console !== "undefined" && console.error) {
@@ -261,9 +548,37 @@ export function mapEsmViewElements(elements, view = "delivery", viewConfig) {
261
548
  }
262
549
  function defaultImporter(specifier) {
263
550
  // @vite-ignore — dynamic import resolved at runtime.
264
- return import(/* @vite-ignore */ specifier);
551
+ return import(/* webpackIgnore: true */ /* @vite-ignore */ specifier);
552
+ }
553
+ /**
554
+ * One request per package.json URL, shared by every backend on the page: the
555
+ * players on a page read the metadata of the same element versions and of the
556
+ * same editor runtime. A request that fails or finds no metadata is dropped
557
+ * once it settles, so a later load asks again.
558
+ */
559
+ const packageMetadataRequests = new Map();
560
+ /** @internal Test-only: drops every shared package.json request. */
561
+ export function clearPackageMetadataRequests() {
562
+ packageMetadataRequests.clear();
265
563
  }
266
- async function defaultPackageMetadataLoader(packageVersion, packageJsonUrl) {
564
+ function defaultPackageMetadataLoader(packageVersion, packageJsonUrl) {
565
+ const pending = packageMetadataRequests.get(packageJsonUrl);
566
+ if (pending)
567
+ return pending;
568
+ const request = fetchPackageMetadata(packageJsonUrl);
569
+ packageMetadataRequests.set(packageJsonUrl, request);
570
+ const forget = () => {
571
+ if (packageMetadataRequests.get(packageJsonUrl) === request) {
572
+ packageMetadataRequests.delete(packageJsonUrl);
573
+ }
574
+ };
575
+ request.then((metadata) => {
576
+ if (metadata === null)
577
+ forget();
578
+ }, forget);
579
+ return request;
580
+ }
581
+ async function fetchPackageMetadata(packageJsonUrl) {
267
582
  const browserFetch = typeof window !== "undefined" &&
268
583
  typeof window.fetch === "function"
269
584
  ? window.fetch.bind(window)
@@ -277,6 +592,22 @@ async function defaultPackageMetadataLoader(packageVersion, packageJsonUrl) {
277
592
  }
278
593
  return (await response.json());
279
594
  }
595
+ const RUNTIME_SUPPORT_PATH = "dist/runtime-support.js";
596
+ /**
597
+ * URL of a package's `./runtime-support` module on the ESM CDN the players load
598
+ * elements from, or `undefined` when a custom provider names no package root.
599
+ */
600
+ export function resolveEsmRuntimeSupportUrl(packageVersion, options) {
601
+ const cdnBaseUrl = options.cdnBaseUrl.replace(/\/+$/, "");
602
+ const provider = resolveCdnProvider(options.cdnProvider, cdnBaseUrl);
603
+ if (typeof provider.runtimeSupportUrl === "function") {
604
+ return provider.runtimeSupportUrl(packageVersion);
605
+ }
606
+ const packageJsonUrl = provider.packageJsonUrl(packageVersion);
607
+ return packageJsonUrl.endsWith("/package.json")
608
+ ? `${packageJsonUrl.slice(0, -"package.json".length)}${RUNTIME_SUPPORT_PATH}`
609
+ : undefined;
610
+ }
280
611
  function resolveCdnProvider(provider, cdnBaseUrl) {
281
612
  if (isEsmCdnProvider(provider))
282
613
  return provider;
@@ -302,6 +633,7 @@ function createJsDelivrProvider(cdnBaseUrl, name = "jsdelivr") {
302
633
  packageJsonUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/package.json`,
303
634
  browserViewUrl: (packageVersion, view) => `${cdnBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
304
635
  browserControllerUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
636
+ runtimeSupportUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/${RUNTIME_SUPPORT_PATH}`,
305
637
  sharedDependencyUrl: (dependencyName, version, subpath) => {
306
638
  const suffix = subpath ? `/${subpath}/+esm` : "/+esm";
307
639
  return `${cdnBaseUrl}/${dependencyName}@${version}${suffix}`;
@@ -316,6 +648,7 @@ function createEsmShProvider(cdnBaseUrl) {
316
648
  packageJsonUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/package.json`,
317
649
  browserViewUrl: (packageVersion, view) => `${rawBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
318
650
  browserControllerUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
651
+ runtimeSupportUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/${RUNTIME_SUPPORT_PATH}`,
319
652
  sharedDependencyUrl: (dependencyName, version, subpath) => {
320
653
  const suffix = subpath ? `/${subpath}` : "";
321
654
  return `${esmBaseUrl}/${dependencyName}@${version}${suffix}`;
@@ -340,9 +673,13 @@ function toRawEsmShBaseUrl(cdnBaseUrl) {
340
673
  function resolveEsmViewConfig(view, viewConfig) {
341
674
  return viewConfig ?? BUILT_IN_VIEWS[view] ?? BUILT_IN_VIEWS.delivery;
342
675
  }
343
- function assertBrowserEsmExports(metadata, packageVersion, viewConfig, loadControllers, viewName) {
676
+ /**
677
+ * Why a package cannot load this view as browser ESM, or `undefined` when its
678
+ * metadata publishes every export the view needs or names no exports.
679
+ */
680
+ function missingBrowserEsmExport(metadata, packageVersion, viewConfig, loadControllers, viewName) {
344
681
  if (!metadata?.exports)
345
- return;
682
+ return undefined;
346
683
  const requiredExports = new Set([
347
684
  `./browser/${browserViewName(viewConfig, viewName)}`,
348
685
  ]);
@@ -357,13 +694,10 @@ function assertBrowserEsmExports(metadata, packageVersion, viewConfig, loadContr
357
694
  }
358
695
  for (const exportKey of requiredExports) {
359
696
  if (!(exportKey in metadata.exports)) {
360
- throw new Error(`${packageVersion} does not publish browser ESM export ${exportKey}; use IIFE/preloaded mode or publish browser ESM artifacts first`);
697
+ return `${packageVersion} does not publish browser ESM export ${exportKey}; use IIFE/preloaded mode or publish browser ESM artifacts first`;
361
698
  }
362
699
  }
363
- }
364
- function extractPackageName(packageVersion) {
365
- const parts = packageVersion.split("@");
366
- return parts.length >= 3 ? `@${parts[1]}` : parts[0];
700
+ return undefined;
367
701
  }
368
702
  function cleanViewSubpath(subpath) {
369
703
  const cleanSubpath = subpath.replace(/^\/+|\/+$/g, "");
@@ -394,7 +728,7 @@ function resolveControllerSpecifier(packageName, packageVersion, moduleResolutio
394
728
  }
395
729
  return resolveBrowserControllerUrl(packageVersion, cdnProvider);
396
730
  }
397
- function pickElementClass(module, view) {
731
+ export function pickElementClass(module, view) {
398
732
  if (!module || typeof module !== "object")
399
733
  return undefined;
400
734
  if (view === "author") {
@@ -411,7 +745,7 @@ function declaredSharedDependencyVersion(metadata, dependencyName, packageVersio
411
745
  if (!version) {
412
746
  throw new Error(`${packageVersion} is missing required pie.browserSharedDependencies.${dependencyName}`);
413
747
  }
414
- if (!isExactVersion(version)) {
748
+ if (!isExactSemver(version)) {
415
749
  throw new Error(`${packageVersion} pie.browserSharedDependencies.${dependencyName} must be an exact version; received "${version}"`);
416
750
  }
417
751
  return version;
@@ -448,50 +782,35 @@ function addSharedDependencyImports(imports, selectedVersions, lockedVersions, d
448
782
  imports["react-dom/client"] = cdnProvider.sharedDependencyUrl("react-dom", resolvedVersion, "client");
449
783
  }
450
784
  }
451
- function isExactVersion(version) {
452
- return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version);
453
- }
454
- function parseVersion(version) {
455
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
456
- if (!match) {
457
- throw new Error(`Invalid shared browser dependency version "${version}"`);
458
- }
459
- return {
460
- major: Number(match[1]),
461
- minor: Number(match[2]),
462
- patch: Number(match[3]),
463
- };
464
- }
465
- function compareVersions(a, b) {
466
- const parsedA = parseVersion(a);
467
- const parsedB = parseVersion(b);
468
- if (parsedA.major !== parsedB.major)
469
- return parsedA.major - parsedB.major;
470
- if (parsedA.minor !== parsedB.minor)
471
- return parsedA.minor - parsedB.minor;
472
- return parsedA.patch - parsedB.patch;
473
- }
785
+ /** Versions are exact semver, validated before they are compared. */
474
786
  function resolveSharedDependencyVersion(dependencyName, currentVersion, requestedVersion, packageVersion, isLocked) {
475
- const current = parseVersion(currentVersion);
476
- const requested = parseVersion(requestedVersion);
477
- if (current.major !== requested.major) {
787
+ if (semverMajor(currentVersion) !== semverMajor(requestedVersion)) {
478
788
  throw new Error(`Conflicting shared browser dependency ${dependencyName}: ${currentVersion} vs ${requestedVersion} from ${packageVersion}; different major versions cannot share one browser singleton`);
479
789
  }
480
- if (isLocked && compareVersions(requestedVersion, currentVersion) > 0) {
790
+ if (isLocked && compareSemver(requestedVersion, currentVersion) > 0) {
481
791
  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`);
482
792
  }
483
- return compareVersions(currentVersion, requestedVersion) >= 0
793
+ return compareSemver(currentVersion, requestedVersion) >= 0
484
794
  ? currentVersion
485
795
  : requestedVersion;
486
796
  }
487
- async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControllers, includeElementImports, packageMetadataLoader, currentSharedDependencyVersions, onConflict) {
797
+ async function buildImportMap(elements, viewConfig, cdnProvider, loadControllers, includeElementImports, packageMetadataLoader, currentSharedDependencyVersions, onConflict) {
488
798
  const imports = {};
489
799
  const selectedVersions = {
490
800
  ...currentSharedDependencyVersions,
491
801
  };
492
802
  const lockedVersions = new Set(Object.keys(currentSharedDependencyVersions));
803
+ const metadataByPackage = new Map();
804
+ const unpublished = new Map();
493
805
  for (const [, pkg] of Object.entries(elements)) {
494
- const packageName = extractPackageName(pkg);
806
+ const metadata = await packageMetadataLoader(pkg, cdnProvider.packageJsonUrl(pkg));
807
+ const missingExport = missingBrowserEsmExport(metadata, pkg, viewConfig, loadControllers, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
808
+ if (missingExport) {
809
+ unpublished.set(pkg, missingExport);
810
+ continue;
811
+ }
812
+ metadataByPackage.set(pkg, metadata);
813
+ const packageName = parsePackageName(pkg).name;
495
814
  if (includeElementImports) {
496
815
  imports[packageName] = resolveBrowserViewUrl(pkg, BUILT_IN_VIEWS.delivery, cdnProvider, "delivery");
497
816
  if (viewConfig.subpath) {
@@ -510,8 +829,6 @@ async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControl
510
829
  }
511
830
  }
512
831
  }
513
- const metadata = await packageMetadataLoader(pkg, cdnProvider.packageJsonUrl(pkg));
514
- assertBrowserEsmExports(metadata, pkg, viewConfig, loadControllers, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
515
832
  for (const dependencyName of SHARED_BROWSER_DEPENDENCIES) {
516
833
  if (!packageUsesSharedDependency(metadata, dependencyName)) {
517
834
  continue;
@@ -519,25 +836,223 @@ async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControl
519
836
  addSharedDependencyImports(imports, selectedVersions, lockedVersions, dependencyName, declaredSharedDependencyVersion(metadata, dependencyName, pkg), cdnProvider, pkg, onConflict);
520
837
  }
521
838
  }
522
- if (Object.keys(imports).length === 0) {
523
- return { json: null, sharedDependencyVersions: selectedVersions };
524
- }
525
839
  return {
526
- json: JSON.stringify({ imports }, null, 2),
840
+ imports,
527
841
  sharedDependencyVersions: selectedVersions,
842
+ metadata: metadataByPackage,
843
+ unpublished,
528
844
  };
529
845
  }
530
- function injectImportMap(json, doc) {
846
+ // ─── Shared editor runtime ───────────────────────────────────────────────────
847
+ /**
848
+ * Names the editor runtime an injected import map maps, as `<name>@<version>`.
849
+ * The document holds it because every loader on the page, whichever backend
850
+ * or players-shared copy runs it, shares the one import map.
851
+ */
852
+ const EDITOR_RUNTIME_ATTRIBUTE = "data-pie-editor-runtime";
853
+ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
854
+ /** Slash-separated segments, none starting with a dot. */
855
+ const BROWSER_VIEW_PATH = /^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
856
+ const BARE_SPECIFIER = /^(?:@[A-Za-z0-9_-][A-Za-z0-9._-]*\/)?[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
857
+ function isRecord(value) {
858
+ return typeof value === "object" && value !== null && !Array.isArray(value);
859
+ }
860
+ /**
861
+ * A package's `pie.browserEditorRuntime`: `undefined` when it declares none,
862
+ * `null` when the declaration is malformed.
863
+ */
864
+ function readEditorRuntimeDeclaration(metadata) {
865
+ const declared = metadata?.pie?.browserEditorRuntime;
866
+ if (declared === undefined)
867
+ return undefined;
868
+ if (!isRecord(declared) || !isRecord(declared.views))
869
+ return null;
870
+ const { name, version } = declared;
871
+ if (typeof name !== "string" || !PACKAGE_NAME.test(name))
872
+ return null;
873
+ if (typeof version !== "string" || !isExactSemver(version))
874
+ return null;
875
+ const views = Object.entries(declared.views);
876
+ if (views.length === 0 ||
877
+ !views.every(([, path]) => typeof path === "string" && BROWSER_VIEW_PATH.test(path))) {
878
+ return null;
879
+ }
880
+ return {
881
+ name,
882
+ version,
883
+ views: Object.fromEntries(views),
884
+ };
885
+ }
886
+ /**
887
+ * The editor runtime's `pie.browserModules`, or `null` when it is missing or
888
+ * malformed. A runtime has no React import, so it maps no React specifier.
889
+ */
890
+ function readBrowserModules(metadata) {
891
+ const declared = metadata.pie?.browserModules;
892
+ if (!isRecord(declared))
893
+ return null;
894
+ const modules = Object.entries(declared);
895
+ const valid = modules.length > 0 &&
896
+ modules.every(([specifier, view]) => BARE_SPECIFIER.test(specifier) &&
897
+ !SHARED_BROWSER_DEPENDENCIES.some((dependency) => specifier === dependency || specifier.startsWith(`${dependency}/`)) &&
898
+ typeof view === "string" &&
899
+ BROWSER_VIEW_PATH.test(view));
900
+ return valid ? Object.fromEntries(modules) : null;
901
+ }
902
+ /** The highest runtime version the candidates declare, by semver precedence. */
903
+ function highestDeclaredEditorRuntime(candidates) {
904
+ const { name } = candidates[0].declaration;
905
+ let { version } = candidates[0].declaration;
906
+ for (const { declaration } of candidates) {
907
+ if (declaration.name === name &&
908
+ compareSemver(declaration.version, version) > 0) {
909
+ version = declaration.version;
910
+ }
911
+ }
912
+ return { name, version };
913
+ }
914
+ /**
915
+ * A runtime serves a variant built against a version in its caret range (the
916
+ * same major, and below 1.0.0 the same minor) at or below its own.
917
+ */
918
+ function canServeEditorRuntime(runtime, declaration) {
919
+ if (runtime.name !== declaration.name)
920
+ return false;
921
+ if (semverMajor(runtime.version) !== semverMajor(declaration.version)) {
922
+ return false;
923
+ }
924
+ if (semverMajor(runtime.version) === 0 &&
925
+ semverMinor(runtime.version) !== semverMinor(declaration.version)) {
926
+ return false;
927
+ }
928
+ return compareSemver(runtime.version, declaration.version) >= 0;
929
+ }
930
+ function importMapScripts(doc) {
931
+ return typeof doc.querySelectorAll === "function"
932
+ ? Array.from(doc.querySelectorAll(`script[type="importmap"], script[type="${SHIM_IMPORT_MAP_TYPE}"]`))
933
+ : [];
934
+ }
935
+ /** The editor runtime an import map in the document maps, first map first. */
936
+ function readMappedEditorRuntime(doc) {
937
+ for (const script of importMapScripts(doc)) {
938
+ const mapped = script.getAttribute(EDITOR_RUNTIME_ATTRIBUTE);
939
+ const at = mapped?.lastIndexOf("@") ?? -1;
940
+ if (!mapped || at <= 0)
941
+ continue;
942
+ const name = mapped.slice(0, at);
943
+ const version = mapped.slice(at + 1);
944
+ if (PACKAGE_NAME.test(name) && isExactSemver(version)) {
945
+ return { name, version };
946
+ }
947
+ }
948
+ return null;
949
+ }
950
+ function errorMessage(err) {
951
+ return err instanceof Error ? err.message : String(err);
952
+ }
953
+ /**
954
+ * Drop the specifiers an import map already in the document maps. The browser
955
+ * keeps the first rule for a specifier and discards a later one with a console
956
+ * warning, so injecting them changes no resolution.
957
+ */
958
+ function withoutMappedSpecifiers(imports, doc) {
959
+ const mapped = documentImports(doc);
960
+ return Object.fromEntries(Object.entries(imports).filter(([specifier]) => !(specifier in mapped)));
961
+ }
962
+ /**
963
+ * Whether the browser applied an import map this backend added, recorded on
964
+ * the map so every player and every copy of this module on the page reads the
965
+ * same answer: `pending` until the backend knows, then `applied` or
966
+ * `rejected`. A document with a rejected map loads elements through
967
+ * es-module-shims.
968
+ */
969
+ const IMPORT_MAP_STATE_ATTRIBUTE = "data-pie-import-map";
970
+ const IMPORT_MAP_SETTLED_EVENT = "pie-import-map-settled";
971
+ const SHIM_IMPORT_MAP_TYPE = "importmap-shim";
972
+ function injectImportMap(json, doc, editorRuntime) {
531
973
  const script = doc.createElement("script");
532
- script.type = "importmap";
974
+ script.type = documentUsesImportShim(doc)
975
+ ? SHIM_IMPORT_MAP_TYPE
976
+ : "importmap";
977
+ script.setAttribute(IMPORT_MAP_STATE_ATTRIBUTE, "pending");
978
+ if (editorRuntime)
979
+ script.setAttribute(EDITOR_RUNTIME_ATTRIBUTE, editorRuntime);
533
980
  script.textContent = json;
534
981
  doc.head.appendChild(script);
982
+ return script;
535
983
  }
536
- function assertImportMapSupported() {
984
+ function settleImportMap(script, state) {
985
+ script.setAttribute(IMPORT_MAP_STATE_ATTRIBUTE, state);
986
+ script.dispatchEvent(new Event(IMPORT_MAP_SETTLED_EVENT));
987
+ }
988
+ function documentUsesImportShim(doc) {
989
+ return importMapScripts(doc).some((script) => script.getAttribute(IMPORT_MAP_STATE_ATTRIBUTE) === "rejected");
990
+ }
991
+ /** Resolves once no import map a PIE backend added to `doc` is pending. */
992
+ async function importMapsSettled(doc) {
993
+ const pending = importMapScripts(doc).filter((script) => script.getAttribute(IMPORT_MAP_STATE_ATTRIBUTE) === "pending");
994
+ await Promise.all(pending.map((script) => new Promise((resolve) => script.addEventListener(IMPORT_MAP_SETTLED_EVENT, () => resolve(), {
995
+ once: true,
996
+ }))));
997
+ }
998
+ /**
999
+ * Every mapping the import maps in `doc` define, the first map's rule winning
1000
+ * for a specifier as it does in the browser.
1001
+ */
1002
+ function documentImports(doc) {
1003
+ const imports = Object.create(null);
1004
+ for (const script of importMapScripts(doc)) {
1005
+ try {
1006
+ const mapped = JSON.parse(script.textContent || "{}")?.imports;
1007
+ if (!mapped || typeof mapped !== "object")
1008
+ continue;
1009
+ for (const [specifier, url] of Object.entries(mapped)) {
1010
+ if (typeof url === "string" && !(specifier in imports)) {
1011
+ imports[specifier] = url;
1012
+ }
1013
+ }
1014
+ }
1015
+ catch {
1016
+ // The browser rejects a map it cannot parse, so it maps nothing.
1017
+ }
1018
+ }
1019
+ return imports;
1020
+ }
1021
+ /**
1022
+ * es-module-shims in shim mode: it resolves through the import maps this
1023
+ * backend hands it and leaves the page's own scripts and load events alone.
1024
+ * A page that already runs es-module-shims keeps its instance, which has to be
1025
+ * in shim mode to take maps from this backend.
1026
+ */
1027
+ async function defaultImportShimLoader() {
1028
+ const scope = globalThis;
1029
+ if (!scope.importShim) {
1030
+ scope.esmsInitOptions = {
1031
+ ...scope.esmsInitOptions,
1032
+ shimMode: true,
1033
+ noLoadEventRetriggers: true,
1034
+ };
1035
+ await import("./module-shim.js");
1036
+ }
1037
+ const shim = scope.importShim;
1038
+ if (typeof shim !== "function" || typeof shim.addImportMap !== "function") {
1039
+ throw new Error("es-module-shims did not initialize, so this browser cannot load browser ESM elements after the page's first module load. Use the iife or preloaded strategy.");
1040
+ }
1041
+ try {
1042
+ shim.addImportMap({ imports: {} });
1043
+ }
1044
+ catch {
1045
+ throw new Error("This page runs es-module-shims in polyfill mode, and this browser rejects import maps added after the page's first module load. Load es-module-shims in shim mode, or use the iife or preloaded strategy.");
1046
+ }
1047
+ return shim;
1048
+ }
1049
+ function importMapsSupported() {
537
1050
  const htmlScriptElement = (typeof HTMLScriptElement !== "undefined" ? HTMLScriptElement : undefined);
538
- const supports = typeof htmlScriptElement?.supports === "function" &&
539
- htmlScriptElement.supports("importmap");
540
- if (!supports) {
1051
+ return (typeof htmlScriptElement?.supports === "function" &&
1052
+ htmlScriptElement.supports("importmap"));
1053
+ }
1054
+ function assertImportMapSupported() {
1055
+ if (!importMapsSupported()) {
541
1056
  throw new Error('This browser does not support import maps. Use moduleResolution="url" or switch to iife/preloaded strategy.');
542
1057
  }
543
1058
  }