@pie-players/pie-players-shared 0.3.73 → 0.3.75
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/README.md +30 -2
- package/dist/components/vendor/nds/README.md +7 -5
- package/dist/config/profile.d.ts +0 -2
- package/dist/config/profile.js +2 -8
- package/dist/i18n/index.d.ts +2 -3
- package/dist/i18n/index.js +2 -3
- package/dist/i18n/messages/en-US.d.ts +11 -24
- package/dist/i18n/messages/en-US.js +13 -26
- package/dist/i18n/messages/nl-NL.d.ts +7 -24
- package/dist/i18n/messages/nl-NL.js +9 -26
- package/dist/i18n/provider.d.ts +4 -5
- package/dist/i18n/provider.js +4 -5
- package/dist/i18n/types.d.ts +4 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/instrumentation/index.d.ts +5 -4
- package/dist/instrumentation/index.js +5 -4
- package/dist/instrumentation/new-relic-agent.d.ts +20 -0
- package/dist/instrumentation/new-relic-agent.js +24 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
- package/dist/instrumentation/types.d.ts +7 -3
- package/dist/loader-config.d.ts +16 -6
- package/dist/loaders/controller-loading.d.ts +14 -0
- package/dist/loaders/controller-loading.js +12 -0
- package/dist/loaders/defaults.d.ts +10 -0
- package/dist/loaders/defaults.js +10 -0
- package/dist/loaders/element-loader.d.ts +20 -8
- package/dist/loaders/element-loader.js +102 -36
- package/dist/loaders/element-package-policy.d.ts +2 -0
- package/dist/loaders/element-package-policy.js +2 -1
- package/dist/loaders/esm-adapter.d.ts +29 -0
- package/dist/loaders/esm-adapter.js +635 -102
- package/dist/loaders/iife-adapter.d.ts +0 -5
- package/dist/loaders/iife-adapter.js +3 -9
- package/dist/loaders/index.d.ts +12 -3
- package/dist/loaders/index.js +9 -3
- package/dist/loaders/mathjax-events.d.ts +22 -0
- package/dist/loaders/mathjax-events.js +65 -0
- package/dist/loaders/module-shim.d.ts +12 -0
- package/dist/loaders/module-shim.js +12 -0
- package/dist/loaders/preloaded-alignment.d.ts +24 -0
- package/dist/loaders/preloaded-alignment.js +57 -0
- package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
- package/dist/loaders/preloaded-authored-tags.js +69 -0
- package/dist/loaders/preloaded-registration.d.ts +72 -0
- package/dist/loaders/preloaded-registration.js +141 -0
- package/dist/media/index.d.ts +69 -0
- package/dist/media/index.js +194 -0
- package/dist/pie/authoring.d.ts +1 -1
- package/dist/pie/authoring.js +25 -18
- package/dist/pie/element-announcements.d.ts +39 -0
- package/dist/pie/element-announcements.js +67 -0
- package/dist/pie/element-observer.d.ts +3 -1
- package/dist/pie/element-observer.js +1 -0
- package/dist/pie/elements-rendered.d.ts +25 -0
- package/dist/pie/elements-rendered.js +96 -0
- package/dist/pie/index.d.ts +8 -10
- package/dist/pie/index.js +9 -10
- package/dist/pie/initialization.js +13 -22
- package/dist/pie/initialize-element.d.ts +3 -0
- package/dist/pie/initialize-element.js +3 -4
- package/dist/pie/instrumentation-event-bridge.js +23 -1
- package/dist/pie/instrumentation-event-map.js +11 -25
- package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
- package/dist/pie/instrumentation-provider-resolution.js +0 -1
- package/dist/pie/item-controller.js +4 -0
- package/dist/pie/item-session-contract.d.ts +8 -0
- package/dist/pie/item-session-contract.js +30 -9
- package/dist/pie/logger.d.ts +6 -0
- package/dist/pie/logger.js +10 -0
- package/dist/pie/markup-math.d.ts +20 -0
- package/dist/pie/markup-math.js +118 -0
- package/dist/pie/math-assets.d.ts +48 -0
- package/dist/pie/math-assets.js +81 -0
- package/dist/pie/math-rendering.d.ts +10 -21
- package/dist/pie/math-rendering.js +50 -35
- package/dist/pie/registry.d.ts +13 -1
- package/dist/pie/registry.js +39 -1
- package/dist/pie/resource-monitor.d.ts +6 -33
- package/dist/pie/resource-monitor.js +14 -213
- package/dist/pie/scoring.d.ts +12 -8
- package/dist/pie/scoring.js +21 -24
- package/dist/pie/session-commit.d.ts +16 -0
- package/dist/pie/session-commit.js +34 -6
- package/dist/pie/session-snapshot.d.ts +80 -0
- package/dist/pie/session-snapshot.js +166 -0
- package/dist/pie/stages.d.ts +10 -47
- package/dist/pie/stages.js +10 -47
- package/dist/pie/tag-names.d.ts +1 -0
- package/dist/pie/tag-names.js +1 -0
- package/dist/pie/types.d.ts +4 -5
- package/dist/pie/types.js +1 -0
- package/dist/pie/updates.d.ts +2 -2
- package/dist/pie/updates.js +33 -15
- package/dist/pie/versioned-tag.d.ts +4 -0
- package/dist/pie/versioned-tag.js +4 -0
- package/dist/security/index.d.ts +2 -3
- package/dist/security/index.js +2 -3
- package/dist/security/sanitize-item-markup.d.ts +4 -2
- package/dist/security/sanitize-item-markup.js +48 -6
- package/dist/security/sanitize-svg-icon.d.ts +0 -2
- package/dist/security/sanitize-svg-icon.js +0 -5
- package/dist/security/wrap-overwide-images.js +19 -0
- package/dist/security/wrap-overwide.d.ts +13 -0
- package/dist/security/wrap-overwide.js +63 -3
- package/dist/timed-media/policy.js +5 -16
- package/dist/types/index.d.ts +67 -423
- package/dist/types/index.js +0 -68
- package/dist/ui/attribute-coercion.d.ts +12 -0
- package/dist/ui/attribute-coercion.js +33 -0
- package/dist/ui/content-styles.d.ts +18 -16
- package/dist/ui/content-styles.js +67 -36
- package/dist/ui/overlay-containment.d.ts +31 -0
- package/dist/ui/overlay-containment.js +63 -0
- package/dist/ui/overlay-placement.d.ts +110 -0
- package/dist/ui/overlay-placement.js +222 -0
- package/dist/ui/pointer-gesture.d.ts +32 -0
- package/dist/ui/pointer-gesture.js +56 -0
- package/dist/ui/pointer-rotate.d.ts +29 -0
- package/dist/ui/pointer-rotate.js +39 -0
- package/package.json +23 -18
- package/dist/pie/component-context.d.ts +0 -21
- package/dist/pie/component-context.js +0 -29
- package/dist/pie/player-initializer.d.ts +0 -54
- package/dist/pie/player-initializer.js +0 -122
- package/dist/pie/stage-tracker.d.ts +0 -50
- package/dist/pie/stage-tracker.js +0 -105
- package/dist/security/wrap-model-rich-content.d.ts +0 -1
- package/dist/security/wrap-model-rich-content.js +0 -40
- package/dist/server/npm-auth-env.d.ts +0 -13
- package/dist/server/npm-auth-env.js +0 -64
- package/dist/server/npm-registry.d.ts +0 -7
- package/dist/server/npm-registry.js +0 -59
- package/dist/ui/debug-panel-persistence.d.ts +0 -17
- 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 {
|
|
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 { forwardMathjaxEvents } from "./mathjax-events.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 events this backend forwards. */
|
|
45
|
+
const mathjaxEventViews = 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
|
+
forwardMathjaxEventsOf(context.doc);
|
|
64
86
|
const newEntries = {};
|
|
65
87
|
for (const [tag, pkg] of Object.entries(elements)) {
|
|
66
|
-
const packageName =
|
|
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
|
|
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
|
|
87
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
199
|
+
else {
|
|
129
200
|
reasons.set(tag, {
|
|
130
201
|
kind: "module-load-failed",
|
|
131
202
|
tag,
|
|
132
|
-
specifier
|
|
133
|
-
cause:
|
|
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
|
-
|
|
209
|
+
ElementClass = pickElementClass(module, view);
|
|
210
|
+
if (!ElementClass) {
|
|
141
211
|
reasons.set(tag, {
|
|
142
|
-
kind: "
|
|
212
|
+
kind: "no-element-class",
|
|
143
213
|
tag,
|
|
144
|
-
|
|
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
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
484
|
+
function forwardMathjaxEventsOf(doc) {
|
|
485
|
+
const view = doc.defaultView;
|
|
486
|
+
if (!view || mathjaxEventViews.has(view))
|
|
487
|
+
return;
|
|
488
|
+
mathjaxEventViews.add(view);
|
|
489
|
+
forwardMathjaxEvents(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(
|
|
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();
|
|
563
|
+
}
|
|
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;
|
|
265
580
|
}
|
|
266
|
-
async function
|
|
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,40 @@ 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
|
+
}
|
|
611
|
+
/** A package the probe below names, so a provider URL ends in its `<package>@<version>` path. */
|
|
612
|
+
const ROOT_PROBE_PACKAGE = "@pie-element/shared-math-rendering-mathjax@0.0.0";
|
|
613
|
+
/**
|
|
614
|
+
* The npm root the ESM CDN the players load elements from serves raw package
|
|
615
|
+
* files under, `<root>/<package>@<version>/<path>`: the base URL for jsDelivr,
|
|
616
|
+
* raw.esm.sh for esm.sh, and for a custom provider whatever its
|
|
617
|
+
* `packageJsonUrl` puts before the package. `undefined` when that URL names
|
|
618
|
+
* no such root.
|
|
619
|
+
*/
|
|
620
|
+
export function resolveEsmAssetRoot(options) {
|
|
621
|
+
const cdnBaseUrl = options.cdnBaseUrl.replace(/\/+$/, "");
|
|
622
|
+
const provider = resolveCdnProvider(options.cdnProvider, cdnBaseUrl);
|
|
623
|
+
const suffix = `/${ROOT_PROBE_PACKAGE}/package.json`;
|
|
624
|
+
const packageJsonUrl = provider.packageJsonUrl(ROOT_PROBE_PACKAGE);
|
|
625
|
+
return packageJsonUrl.endsWith(suffix)
|
|
626
|
+
? packageJsonUrl.slice(0, -suffix.length) || undefined
|
|
627
|
+
: undefined;
|
|
628
|
+
}
|
|
280
629
|
function resolveCdnProvider(provider, cdnBaseUrl) {
|
|
281
630
|
if (isEsmCdnProvider(provider))
|
|
282
631
|
return provider;
|
|
@@ -302,6 +651,7 @@ function createJsDelivrProvider(cdnBaseUrl, name = "jsdelivr") {
|
|
|
302
651
|
packageJsonUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/package.json`,
|
|
303
652
|
browserViewUrl: (packageVersion, view) => `${cdnBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
|
|
304
653
|
browserControllerUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
|
|
654
|
+
runtimeSupportUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/${RUNTIME_SUPPORT_PATH}`,
|
|
305
655
|
sharedDependencyUrl: (dependencyName, version, subpath) => {
|
|
306
656
|
const suffix = subpath ? `/${subpath}/+esm` : "/+esm";
|
|
307
657
|
return `${cdnBaseUrl}/${dependencyName}@${version}${suffix}`;
|
|
@@ -316,6 +666,7 @@ function createEsmShProvider(cdnBaseUrl) {
|
|
|
316
666
|
packageJsonUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/package.json`,
|
|
317
667
|
browserViewUrl: (packageVersion, view) => `${rawBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
|
|
318
668
|
browserControllerUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
|
|
669
|
+
runtimeSupportUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/${RUNTIME_SUPPORT_PATH}`,
|
|
319
670
|
sharedDependencyUrl: (dependencyName, version, subpath) => {
|
|
320
671
|
const suffix = subpath ? `/${subpath}` : "";
|
|
321
672
|
return `${esmBaseUrl}/${dependencyName}@${version}${suffix}`;
|
|
@@ -340,9 +691,13 @@ function toRawEsmShBaseUrl(cdnBaseUrl) {
|
|
|
340
691
|
function resolveEsmViewConfig(view, viewConfig) {
|
|
341
692
|
return viewConfig ?? BUILT_IN_VIEWS[view] ?? BUILT_IN_VIEWS.delivery;
|
|
342
693
|
}
|
|
343
|
-
|
|
694
|
+
/**
|
|
695
|
+
* Why a package cannot load this view as browser ESM, or `undefined` when its
|
|
696
|
+
* metadata publishes every export the view needs or names no exports.
|
|
697
|
+
*/
|
|
698
|
+
function missingBrowserEsmExport(metadata, packageVersion, viewConfig, loadControllers, viewName) {
|
|
344
699
|
if (!metadata?.exports)
|
|
345
|
-
return;
|
|
700
|
+
return undefined;
|
|
346
701
|
const requiredExports = new Set([
|
|
347
702
|
`./browser/${browserViewName(viewConfig, viewName)}`,
|
|
348
703
|
]);
|
|
@@ -357,13 +712,10 @@ function assertBrowserEsmExports(metadata, packageVersion, viewConfig, loadContr
|
|
|
357
712
|
}
|
|
358
713
|
for (const exportKey of requiredExports) {
|
|
359
714
|
if (!(exportKey in metadata.exports)) {
|
|
360
|
-
|
|
715
|
+
return `${packageVersion} does not publish browser ESM export ${exportKey}; use IIFE/preloaded mode or publish browser ESM artifacts first`;
|
|
361
716
|
}
|
|
362
717
|
}
|
|
363
|
-
|
|
364
|
-
function extractPackageName(packageVersion) {
|
|
365
|
-
const parts = packageVersion.split("@");
|
|
366
|
-
return parts.length >= 3 ? `@${parts[1]}` : parts[0];
|
|
718
|
+
return undefined;
|
|
367
719
|
}
|
|
368
720
|
function cleanViewSubpath(subpath) {
|
|
369
721
|
const cleanSubpath = subpath.replace(/^\/+|\/+$/g, "");
|
|
@@ -394,7 +746,7 @@ function resolveControllerSpecifier(packageName, packageVersion, moduleResolutio
|
|
|
394
746
|
}
|
|
395
747
|
return resolveBrowserControllerUrl(packageVersion, cdnProvider);
|
|
396
748
|
}
|
|
397
|
-
function pickElementClass(module, view) {
|
|
749
|
+
export function pickElementClass(module, view) {
|
|
398
750
|
if (!module || typeof module !== "object")
|
|
399
751
|
return undefined;
|
|
400
752
|
if (view === "author") {
|
|
@@ -411,7 +763,7 @@ function declaredSharedDependencyVersion(metadata, dependencyName, packageVersio
|
|
|
411
763
|
if (!version) {
|
|
412
764
|
throw new Error(`${packageVersion} is missing required pie.browserSharedDependencies.${dependencyName}`);
|
|
413
765
|
}
|
|
414
|
-
if (!
|
|
766
|
+
if (!isExactSemver(version)) {
|
|
415
767
|
throw new Error(`${packageVersion} pie.browserSharedDependencies.${dependencyName} must be an exact version; received "${version}"`);
|
|
416
768
|
}
|
|
417
769
|
return version;
|
|
@@ -448,50 +800,35 @@ function addSharedDependencyImports(imports, selectedVersions, lockedVersions, d
|
|
|
448
800
|
imports["react-dom/client"] = cdnProvider.sharedDependencyUrl("react-dom", resolvedVersion, "client");
|
|
449
801
|
}
|
|
450
802
|
}
|
|
451
|
-
|
|
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
|
-
}
|
|
803
|
+
/** Versions are exact semver, validated before they are compared. */
|
|
474
804
|
function resolveSharedDependencyVersion(dependencyName, currentVersion, requestedVersion, packageVersion, isLocked) {
|
|
475
|
-
|
|
476
|
-
const requested = parseVersion(requestedVersion);
|
|
477
|
-
if (current.major !== requested.major) {
|
|
805
|
+
if (semverMajor(currentVersion) !== semverMajor(requestedVersion)) {
|
|
478
806
|
throw new Error(`Conflicting shared browser dependency ${dependencyName}: ${currentVersion} vs ${requestedVersion} from ${packageVersion}; different major versions cannot share one browser singleton`);
|
|
479
807
|
}
|
|
480
|
-
if (isLocked &&
|
|
808
|
+
if (isLocked && compareSemver(requestedVersion, currentVersion) > 0) {
|
|
481
809
|
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
810
|
}
|
|
483
|
-
return
|
|
811
|
+
return compareSemver(currentVersion, requestedVersion) >= 0
|
|
484
812
|
? currentVersion
|
|
485
813
|
: requestedVersion;
|
|
486
814
|
}
|
|
487
|
-
async function
|
|
815
|
+
async function buildImportMap(elements, viewConfig, cdnProvider, loadControllers, includeElementImports, packageMetadataLoader, currentSharedDependencyVersions, onConflict) {
|
|
488
816
|
const imports = {};
|
|
489
817
|
const selectedVersions = {
|
|
490
818
|
...currentSharedDependencyVersions,
|
|
491
819
|
};
|
|
492
820
|
const lockedVersions = new Set(Object.keys(currentSharedDependencyVersions));
|
|
821
|
+
const metadataByPackage = new Map();
|
|
822
|
+
const unpublished = new Map();
|
|
493
823
|
for (const [, pkg] of Object.entries(elements)) {
|
|
494
|
-
const
|
|
824
|
+
const metadata = await packageMetadataLoader(pkg, cdnProvider.packageJsonUrl(pkg));
|
|
825
|
+
const missingExport = missingBrowserEsmExport(metadata, pkg, viewConfig, loadControllers, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
|
|
826
|
+
if (missingExport) {
|
|
827
|
+
unpublished.set(pkg, missingExport);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
metadataByPackage.set(pkg, metadata);
|
|
831
|
+
const packageName = parsePackageName(pkg).name;
|
|
495
832
|
if (includeElementImports) {
|
|
496
833
|
imports[packageName] = resolveBrowserViewUrl(pkg, BUILT_IN_VIEWS.delivery, cdnProvider, "delivery");
|
|
497
834
|
if (viewConfig.subpath) {
|
|
@@ -510,8 +847,6 @@ async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControl
|
|
|
510
847
|
}
|
|
511
848
|
}
|
|
512
849
|
}
|
|
513
|
-
const metadata = await packageMetadataLoader(pkg, cdnProvider.packageJsonUrl(pkg));
|
|
514
|
-
assertBrowserEsmExports(metadata, pkg, viewConfig, loadControllers, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
|
|
515
850
|
for (const dependencyName of SHARED_BROWSER_DEPENDENCIES) {
|
|
516
851
|
if (!packageUsesSharedDependency(metadata, dependencyName)) {
|
|
517
852
|
continue;
|
|
@@ -519,25 +854,223 @@ async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControl
|
|
|
519
854
|
addSharedDependencyImports(imports, selectedVersions, lockedVersions, dependencyName, declaredSharedDependencyVersion(metadata, dependencyName, pkg), cdnProvider, pkg, onConflict);
|
|
520
855
|
}
|
|
521
856
|
}
|
|
522
|
-
if (Object.keys(imports).length === 0) {
|
|
523
|
-
return { json: null, sharedDependencyVersions: selectedVersions };
|
|
524
|
-
}
|
|
525
857
|
return {
|
|
526
|
-
|
|
858
|
+
imports,
|
|
527
859
|
sharedDependencyVersions: selectedVersions,
|
|
860
|
+
metadata: metadataByPackage,
|
|
861
|
+
unpublished,
|
|
528
862
|
};
|
|
529
863
|
}
|
|
530
|
-
|
|
864
|
+
// ─── Shared editor runtime ───────────────────────────────────────────────────
|
|
865
|
+
/**
|
|
866
|
+
* Names the editor runtime an injected import map maps, as `<name>@<version>`.
|
|
867
|
+
* The document holds it because every loader on the page, whichever backend
|
|
868
|
+
* or players-shared copy runs it, shares the one import map.
|
|
869
|
+
*/
|
|
870
|
+
const EDITOR_RUNTIME_ATTRIBUTE = "data-pie-editor-runtime";
|
|
871
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
872
|
+
/** Slash-separated segments, none starting with a dot. */
|
|
873
|
+
const BROWSER_VIEW_PATH = /^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
|
|
874
|
+
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._-]*)*$/;
|
|
875
|
+
function isRecord(value) {
|
|
876
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* A package's `pie.browserEditorRuntime`: `undefined` when it declares none,
|
|
880
|
+
* `null` when the declaration is malformed.
|
|
881
|
+
*/
|
|
882
|
+
function readEditorRuntimeDeclaration(metadata) {
|
|
883
|
+
const declared = metadata?.pie?.browserEditorRuntime;
|
|
884
|
+
if (declared === undefined)
|
|
885
|
+
return undefined;
|
|
886
|
+
if (!isRecord(declared) || !isRecord(declared.views))
|
|
887
|
+
return null;
|
|
888
|
+
const { name, version } = declared;
|
|
889
|
+
if (typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
890
|
+
return null;
|
|
891
|
+
if (typeof version !== "string" || !isExactSemver(version))
|
|
892
|
+
return null;
|
|
893
|
+
const views = Object.entries(declared.views);
|
|
894
|
+
if (views.length === 0 ||
|
|
895
|
+
!views.every(([, path]) => typeof path === "string" && BROWSER_VIEW_PATH.test(path))) {
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
name,
|
|
900
|
+
version,
|
|
901
|
+
views: Object.fromEntries(views),
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* The editor runtime's `pie.browserModules`, or `null` when it is missing or
|
|
906
|
+
* malformed. A runtime has no React import, so it maps no React specifier.
|
|
907
|
+
*/
|
|
908
|
+
function readBrowserModules(metadata) {
|
|
909
|
+
const declared = metadata.pie?.browserModules;
|
|
910
|
+
if (!isRecord(declared))
|
|
911
|
+
return null;
|
|
912
|
+
const modules = Object.entries(declared);
|
|
913
|
+
const valid = modules.length > 0 &&
|
|
914
|
+
modules.every(([specifier, view]) => BARE_SPECIFIER.test(specifier) &&
|
|
915
|
+
!SHARED_BROWSER_DEPENDENCIES.some((dependency) => specifier === dependency || specifier.startsWith(`${dependency}/`)) &&
|
|
916
|
+
typeof view === "string" &&
|
|
917
|
+
BROWSER_VIEW_PATH.test(view));
|
|
918
|
+
return valid ? Object.fromEntries(modules) : null;
|
|
919
|
+
}
|
|
920
|
+
/** The highest runtime version the candidates declare, by semver precedence. */
|
|
921
|
+
function highestDeclaredEditorRuntime(candidates) {
|
|
922
|
+
const { name } = candidates[0].declaration;
|
|
923
|
+
let { version } = candidates[0].declaration;
|
|
924
|
+
for (const { declaration } of candidates) {
|
|
925
|
+
if (declaration.name === name &&
|
|
926
|
+
compareSemver(declaration.version, version) > 0) {
|
|
927
|
+
version = declaration.version;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return { name, version };
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* A runtime serves a variant built against a version in its caret range (the
|
|
934
|
+
* same major, and below 1.0.0 the same minor) at or below its own.
|
|
935
|
+
*/
|
|
936
|
+
function canServeEditorRuntime(runtime, declaration) {
|
|
937
|
+
if (runtime.name !== declaration.name)
|
|
938
|
+
return false;
|
|
939
|
+
if (semverMajor(runtime.version) !== semverMajor(declaration.version)) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
if (semverMajor(runtime.version) === 0 &&
|
|
943
|
+
semverMinor(runtime.version) !== semverMinor(declaration.version)) {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
return compareSemver(runtime.version, declaration.version) >= 0;
|
|
947
|
+
}
|
|
948
|
+
function importMapScripts(doc) {
|
|
949
|
+
return typeof doc.querySelectorAll === "function"
|
|
950
|
+
? Array.from(doc.querySelectorAll(`script[type="importmap"], script[type="${SHIM_IMPORT_MAP_TYPE}"]`))
|
|
951
|
+
: [];
|
|
952
|
+
}
|
|
953
|
+
/** The editor runtime an import map in the document maps, first map first. */
|
|
954
|
+
function readMappedEditorRuntime(doc) {
|
|
955
|
+
for (const script of importMapScripts(doc)) {
|
|
956
|
+
const mapped = script.getAttribute(EDITOR_RUNTIME_ATTRIBUTE);
|
|
957
|
+
const at = mapped?.lastIndexOf("@") ?? -1;
|
|
958
|
+
if (!mapped || at <= 0)
|
|
959
|
+
continue;
|
|
960
|
+
const name = mapped.slice(0, at);
|
|
961
|
+
const version = mapped.slice(at + 1);
|
|
962
|
+
if (PACKAGE_NAME.test(name) && isExactSemver(version)) {
|
|
963
|
+
return { name, version };
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
function errorMessage(err) {
|
|
969
|
+
return err instanceof Error ? err.message : String(err);
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Drop the specifiers an import map already in the document maps. The browser
|
|
973
|
+
* keeps the first rule for a specifier and discards a later one with a console
|
|
974
|
+
* warning, so injecting them changes no resolution.
|
|
975
|
+
*/
|
|
976
|
+
function withoutMappedSpecifiers(imports, doc) {
|
|
977
|
+
const mapped = documentImports(doc);
|
|
978
|
+
return Object.fromEntries(Object.entries(imports).filter(([specifier]) => !(specifier in mapped)));
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Whether the browser applied an import map this backend added, recorded on
|
|
982
|
+
* the map so every player and every copy of this module on the page reads the
|
|
983
|
+
* same answer: `pending` until the backend knows, then `applied` or
|
|
984
|
+
* `rejected`. A document with a rejected map loads elements through
|
|
985
|
+
* es-module-shims.
|
|
986
|
+
*/
|
|
987
|
+
const IMPORT_MAP_STATE_ATTRIBUTE = "data-pie-import-map";
|
|
988
|
+
const IMPORT_MAP_SETTLED_EVENT = "pie-import-map-settled";
|
|
989
|
+
const SHIM_IMPORT_MAP_TYPE = "importmap-shim";
|
|
990
|
+
function injectImportMap(json, doc, editorRuntime) {
|
|
531
991
|
const script = doc.createElement("script");
|
|
532
|
-
script.type =
|
|
992
|
+
script.type = documentUsesImportShim(doc)
|
|
993
|
+
? SHIM_IMPORT_MAP_TYPE
|
|
994
|
+
: "importmap";
|
|
995
|
+
script.setAttribute(IMPORT_MAP_STATE_ATTRIBUTE, "pending");
|
|
996
|
+
if (editorRuntime)
|
|
997
|
+
script.setAttribute(EDITOR_RUNTIME_ATTRIBUTE, editorRuntime);
|
|
533
998
|
script.textContent = json;
|
|
534
999
|
doc.head.appendChild(script);
|
|
1000
|
+
return script;
|
|
535
1001
|
}
|
|
536
|
-
function
|
|
1002
|
+
function settleImportMap(script, state) {
|
|
1003
|
+
script.setAttribute(IMPORT_MAP_STATE_ATTRIBUTE, state);
|
|
1004
|
+
script.dispatchEvent(new Event(IMPORT_MAP_SETTLED_EVENT));
|
|
1005
|
+
}
|
|
1006
|
+
function documentUsesImportShim(doc) {
|
|
1007
|
+
return importMapScripts(doc).some((script) => script.getAttribute(IMPORT_MAP_STATE_ATTRIBUTE) === "rejected");
|
|
1008
|
+
}
|
|
1009
|
+
/** Resolves once no import map a PIE backend added to `doc` is pending. */
|
|
1010
|
+
async function importMapsSettled(doc) {
|
|
1011
|
+
const pending = importMapScripts(doc).filter((script) => script.getAttribute(IMPORT_MAP_STATE_ATTRIBUTE) === "pending");
|
|
1012
|
+
await Promise.all(pending.map((script) => new Promise((resolve) => script.addEventListener(IMPORT_MAP_SETTLED_EVENT, () => resolve(), {
|
|
1013
|
+
once: true,
|
|
1014
|
+
}))));
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Every mapping the import maps in `doc` define, the first map's rule winning
|
|
1018
|
+
* for a specifier as it does in the browser.
|
|
1019
|
+
*/
|
|
1020
|
+
function documentImports(doc) {
|
|
1021
|
+
const imports = Object.create(null);
|
|
1022
|
+
for (const script of importMapScripts(doc)) {
|
|
1023
|
+
try {
|
|
1024
|
+
const mapped = JSON.parse(script.textContent || "{}")?.imports;
|
|
1025
|
+
if (!mapped || typeof mapped !== "object")
|
|
1026
|
+
continue;
|
|
1027
|
+
for (const [specifier, url] of Object.entries(mapped)) {
|
|
1028
|
+
if (typeof url === "string" && !(specifier in imports)) {
|
|
1029
|
+
imports[specifier] = url;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
catch {
|
|
1034
|
+
// The browser rejects a map it cannot parse, so it maps nothing.
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return imports;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* es-module-shims in shim mode: it resolves through the import maps this
|
|
1041
|
+
* backend hands it and leaves the page's own scripts and load events alone.
|
|
1042
|
+
* A page that already runs es-module-shims keeps its instance, which has to be
|
|
1043
|
+
* in shim mode to take maps from this backend.
|
|
1044
|
+
*/
|
|
1045
|
+
async function defaultImportShimLoader() {
|
|
1046
|
+
const scope = globalThis;
|
|
1047
|
+
if (!scope.importShim) {
|
|
1048
|
+
scope.esmsInitOptions = {
|
|
1049
|
+
...scope.esmsInitOptions,
|
|
1050
|
+
shimMode: true,
|
|
1051
|
+
noLoadEventRetriggers: true,
|
|
1052
|
+
};
|
|
1053
|
+
await import("./module-shim.js");
|
|
1054
|
+
}
|
|
1055
|
+
const shim = scope.importShim;
|
|
1056
|
+
if (typeof shim !== "function" || typeof shim.addImportMap !== "function") {
|
|
1057
|
+
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.");
|
|
1058
|
+
}
|
|
1059
|
+
try {
|
|
1060
|
+
shim.addImportMap({ imports: {} });
|
|
1061
|
+
}
|
|
1062
|
+
catch {
|
|
1063
|
+
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.");
|
|
1064
|
+
}
|
|
1065
|
+
return shim;
|
|
1066
|
+
}
|
|
1067
|
+
function importMapsSupported() {
|
|
537
1068
|
const htmlScriptElement = (typeof HTMLScriptElement !== "undefined" ? HTMLScriptElement : undefined);
|
|
538
|
-
|
|
539
|
-
htmlScriptElement.supports("importmap");
|
|
540
|
-
|
|
1069
|
+
return (typeof htmlScriptElement?.supports === "function" &&
|
|
1070
|
+
htmlScriptElement.supports("importmap"));
|
|
1071
|
+
}
|
|
1072
|
+
function assertImportMapSupported() {
|
|
1073
|
+
if (!importMapsSupported()) {
|
|
541
1074
|
throw new Error('This browser does not support import maps. Use moduleResolution="url" or switch to iife/preloaded strategy.');
|
|
542
1075
|
}
|
|
543
1076
|
}
|