module-tsx 0.0.1 → 0.0.3

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/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import ts from "typescript";
2
-
3
2
  //#region src/error.ts
4
3
  /** Custom error class for module-tsx */
5
4
  var ModuleTSXError = class ModuleTSXError extends Error {
@@ -13,53 +12,255 @@ var ModuleTSXError = class ModuleTSXError extends Error {
13
12
  function warn(message, ...args) {
14
13
  console.warn(`[module-tsx] ${message}`, ...args);
15
14
  }
16
-
17
15
  //#endregion
18
16
  //#region src/importmap.ts
19
- function parseImportMaps() {
20
- const result = {
21
- imports: {},
22
- scopes: {}
23
- };
24
- const mappedSpecifiers = /* @__PURE__ */ new Set();
25
- const scripts = document.querySelectorAll("script[type=\"importmap\"]");
26
- for (const script of scripts) try {
27
- const data = JSON.parse(script.textContent || "{}");
28
- if (data.imports) {
29
- for (const [specifier, url] of Object.entries(data.imports)) if (!mappedSpecifiers.has(specifier)) {
30
- result.imports[specifier] = url;
31
- mappedSpecifiers.add(specifier);
32
- }
17
+ var ImportMap = class {
18
+ imports;
19
+ scopes;
20
+ integrity;
21
+ constructor(imports = /* @__PURE__ */ new Map(), scopes = /* @__PURE__ */ new Map(), integrity = /* @__PURE__ */ new Map()) {
22
+ this.imports = imports;
23
+ this.scopes = scopes;
24
+ this.integrity = integrity;
25
+ }
26
+ /** Parse a JSON import map string against a base URL. */
27
+ static parse(input, baseURL) {
28
+ return parseImportMapString(input, typeof baseURL === "string" ? new URL(baseURL) : baseURL);
29
+ }
30
+ /** Build an ImportMap from a plain object (same shape as the JSON format). */
31
+ static of(json, baseURL) {
32
+ return parseImportMapString(JSON.stringify(json), typeof baseURL === "string" ? new URL(baseURL) : baseURL);
33
+ }
34
+ /** Merge newImportMap into oldImportMap in place (spec § "merge existing and new import maps"). */
35
+ static merge(oldImportMap, newImportMap, resolvedModuleSet = []) {
36
+ mergeExistingAndNewImportMaps(oldImportMap, newImportMap, resolvedModuleSet);
37
+ }
38
+ /** Resolve a specifier against an import map (spec § "resolve a module specifier"). */
39
+ static resolve(specifier, importMap, baseURL) {
40
+ return resolveFromImportMap(specifier, importMap, baseURL);
41
+ }
42
+ /** Parse all <script type="importmap"> elements from the DOM. */
43
+ static fromDOM() {
44
+ return parseImportMaps();
45
+ }
46
+ };
47
+ function resolveURLLikeModuleSpecifier(specifier, baseURL) {
48
+ if (specifier.startsWith("/") || specifier.startsWith("./") || specifier.startsWith("../")) try {
49
+ return new URL(specifier, baseURL);
50
+ } catch {
51
+ return null;
52
+ }
53
+ try {
54
+ return new URL(specifier);
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ function normalizeSpecifierKey(specifierKey, baseURL) {
60
+ if (specifierKey === "") {
61
+ warn("Specifier keys may not be the empty string.");
62
+ return null;
63
+ }
64
+ const url = resolveURLLikeModuleSpecifier(specifierKey, baseURL);
65
+ if (url !== null) return url.href;
66
+ return specifierKey;
67
+ }
68
+ function sortAndNormalizeSpecifierMap(originalMap, baseURL) {
69
+ const normalized = /* @__PURE__ */ new Map();
70
+ for (const [specifierKey, value] of Object.entries(originalMap)) {
71
+ const normalizedKey = normalizeSpecifierKey(specifierKey, baseURL);
72
+ if (normalizedKey === null) continue;
73
+ if (typeof value !== "string") {
74
+ warn(`Import map addresses must be strings; ignoring key "${specifierKey}".`);
75
+ normalized.set(normalizedKey, null);
76
+ continue;
33
77
  }
34
- if (data.scopes) for (const [scope, imports] of Object.entries(data.scopes)) {
35
- if (!result.scopes[scope]) result.scopes[scope] = {};
36
- for (const [specifier, url] of Object.entries(imports)) {
37
- const scopedKey = `${scope}::${specifier}`;
38
- if (!mappedSpecifiers.has(scopedKey)) {
39
- result.scopes[scope][specifier] = url;
40
- mappedSpecifiers.add(scopedKey);
41
- }
78
+ const addressURL = resolveURLLikeModuleSpecifier(value, baseURL);
79
+ if (addressURL === null) {
80
+ warn(`Invalid address "${value}" for key "${specifierKey}".`);
81
+ normalized.set(normalizedKey, null);
82
+ continue;
83
+ }
84
+ if (specifierKey.endsWith("/") && !addressURL.href.endsWith("/")) {
85
+ warn(`Invalid address "${value}" for specifier key "${specifierKey}": since the specifier key ends with "/", the address must as well.`);
86
+ normalized.set(normalizedKey, null);
87
+ continue;
88
+ }
89
+ normalized.set(normalizedKey, addressURL);
90
+ }
91
+ return sortedDescending(normalized);
92
+ }
93
+ function sortAndNormalizeScopes(originalMap, baseURL) {
94
+ const normalized = /* @__PURE__ */ new Map();
95
+ for (const [scopePrefix, potentialSpecifierMap] of Object.entries(originalMap)) {
96
+ if (typeof potentialSpecifierMap !== "object" || potentialSpecifierMap === null || Array.isArray(potentialSpecifierMap)) throw new TypeError(`The value of the scope with prefix "${scopePrefix}" must be a JSON object.`);
97
+ let scopePrefixURL;
98
+ try {
99
+ scopePrefixURL = new URL(scopePrefix, baseURL);
100
+ } catch {
101
+ warn(`Scope prefix URL "${scopePrefix}" is not parseable.`);
102
+ continue;
103
+ }
104
+ normalized.set(scopePrefixURL.href, sortAndNormalizeSpecifierMap(potentialSpecifierMap, baseURL));
105
+ }
106
+ return sortedDescending(normalized);
107
+ }
108
+ function normalizeModuleIntegrityMap(originalMap, baseURL) {
109
+ const normalized = /* @__PURE__ */ new Map();
110
+ for (const [key, value] of Object.entries(originalMap)) {
111
+ const url = resolveURLLikeModuleSpecifier(key, baseURL);
112
+ if (url === null) {
113
+ warn(`Invalid URL key "${key}" in integrity map.`);
114
+ continue;
115
+ }
116
+ if (typeof value !== "string") {
117
+ warn(`Integrity values must be strings; ignoring key "${key}".`);
118
+ continue;
119
+ }
120
+ normalized.set(url.href, value);
121
+ }
122
+ return normalized;
123
+ }
124
+ function sortedDescending(map) {
125
+ return new Map([...map.entries()].sort((a, b) => a[0] < b[0] ? 1 : a[0] > b[0] ? -1 : 0));
126
+ }
127
+ function parseImportMapString(input, baseURL) {
128
+ let parsed;
129
+ try {
130
+ parsed = JSON.parse(input);
131
+ } catch {
132
+ throw new TypeError("Failed to parse import map: not valid JSON.");
133
+ }
134
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new TypeError("Import map: top-level value must be a JSON object.");
135
+ const obj = parsed;
136
+ let imports = /* @__PURE__ */ new Map();
137
+ if ("imports" in obj) {
138
+ if (typeof obj.imports !== "object" || obj.imports === null || Array.isArray(obj.imports)) throw new TypeError("Import map: \"imports\" must be a JSON object.");
139
+ imports = sortAndNormalizeSpecifierMap(obj.imports, baseURL);
140
+ }
141
+ let scopes = /* @__PURE__ */ new Map();
142
+ if ("scopes" in obj) {
143
+ if (typeof obj.scopes !== "object" || obj.scopes === null || Array.isArray(obj.scopes)) throw new TypeError("Import map: \"scopes\" must be a JSON object.");
144
+ scopes = sortAndNormalizeScopes(obj.scopes, baseURL);
145
+ }
146
+ let integrity = /* @__PURE__ */ new Map();
147
+ if ("integrity" in obj) {
148
+ if (typeof obj.integrity !== "object" || obj.integrity === null || Array.isArray(obj.integrity)) throw new TypeError("Import map: \"integrity\" must be a JSON object.");
149
+ integrity = normalizeModuleIntegrityMap(obj.integrity, baseURL);
150
+ }
151
+ for (const key of Object.keys(obj)) if (key !== "imports" && key !== "scopes" && key !== "integrity") warn(`Invalid top-level key "${key}" in import map.`);
152
+ return new ImportMap(imports, scopes, integrity);
153
+ }
154
+ function mergeSpecifierMaps(newMap, oldMap) {
155
+ const merged = new Map(oldMap);
156
+ for (const [specifier, url] of newMap) {
157
+ if (merged.has(specifier)) {
158
+ warn(`Import map merge: ignoring duplicate specifier key "${specifier}".`);
159
+ continue;
160
+ }
161
+ merged.set(specifier, url);
162
+ }
163
+ return sortedDescending(merged);
164
+ }
165
+ function mergeExistingAndNewImportMaps(oldImportMap, newImportMap, resolvedModuleSet) {
166
+ const newImportMapScopes = new Map([...newImportMap.scopes.entries()].map(([k, v]) => [k, new Map(v)]));
167
+ const newImportMapImports = new Map(newImportMap.imports);
168
+ for (const [scopePrefix, scopeImports] of newImportMapScopes) {
169
+ for (const record of resolvedModuleSet) {
170
+ if (!(scopePrefix === record.serializedBaseURL || scopePrefix.endsWith("/") && record.serializedBaseURL.startsWith(scopePrefix))) continue;
171
+ for (const specifierKey of [...scopeImports.keys()]) if (specifierKey === record.specifier || specifierKey.endsWith("/") && record.specifier.startsWith(specifierKey) && (record.specifierAsURL === null || isSpecialURL(record.specifierAsURL))) {
172
+ warn(`Import map merge: ignoring scope rule "${specifierKey}" under "${scopePrefix}" — already resolved.`);
173
+ scopeImports.delete(specifierKey);
42
174
  }
43
175
  }
176
+ if (oldImportMap.scopes.has(scopePrefix)) oldImportMap.scopes.set(scopePrefix, mergeSpecifierMaps(scopeImports, oldImportMap.scopes.get(scopePrefix)));
177
+ else oldImportMap.scopes.set(scopePrefix, scopeImports);
178
+ }
179
+ const resortedScopes = sortedDescending(oldImportMap.scopes);
180
+ oldImportMap.scopes.clear();
181
+ for (const [k, v] of resortedScopes) oldImportMap.scopes.set(k, v);
182
+ for (const [url, integrity] of newImportMap.integrity) {
183
+ if (oldImportMap.integrity.has(url)) {
184
+ warn(`Import map merge: ignoring duplicate integrity entry for "${url}".`);
185
+ continue;
186
+ }
187
+ oldImportMap.integrity.set(url, integrity);
188
+ }
189
+ for (const record of resolvedModuleSet) for (const specifierKey of [...newImportMapImports.keys()]) if (specifierKey.startsWith(record.specifier)) {
190
+ warn(`Import map merge: ignoring global rule "${specifierKey}" — already resolved.`);
191
+ newImportMapImports.delete(specifierKey);
192
+ }
193
+ const merged = mergeSpecifierMaps(newImportMapImports, oldImportMap.imports);
194
+ oldImportMap.imports.clear();
195
+ for (const [k, v] of merged) oldImportMap.imports.set(k, v);
196
+ }
197
+ function parseImportMaps() {
198
+ const base = new URL(document.baseURI || location.href);
199
+ const result = new ImportMap();
200
+ for (const script of document.querySelectorAll("script[type=\"importmap\"]")) try {
201
+ mergeExistingAndNewImportMaps(result, parseImportMapString(script.textContent || "{}", base), []);
44
202
  } catch (error) {
45
- warn(`Failed to parse importmap script:`, error);
203
+ warn("Failed to parse importmap script:", error);
46
204
  }
47
205
  return result;
48
206
  }
49
- /**
50
- * Resolve specifier from import maps, checking scopes by specificity then global imports
51
- */
52
- function resolveFromImportMap(specifier, importMaps, sourceUrl) {
53
- if (sourceUrl && importMaps.scopes) {
54
- const matchingScopes = Object.keys(importMaps.scopes).filter((scope) => sourceUrl.startsWith(scope)).sort((a, b) => b.length - a.length);
55
- for (const scope of matchingScopes) {
56
- const scopedImports = importMaps.scopes[scope];
57
- if (scopedImports && scopedImports[specifier]) return scopedImports[specifier];
207
+ function resolveImportsMatch(normalizedSpecifier, asURL, specifierMap) {
208
+ for (const [specifierKey, resolutionResult] of specifierMap) {
209
+ if (specifierKey === normalizedSpecifier) {
210
+ if (resolutionResult === null) throw new TypeError(`Resolution of "${specifierKey}" was blocked by a null entry in the import map.`);
211
+ return resolutionResult;
212
+ }
213
+ if (specifierKey.endsWith("/") && normalizedSpecifier.startsWith(specifierKey) && (asURL === null || isSpecialURL(asURL))) {
214
+ if (resolutionResult === null) throw new TypeError(`Resolution of "${specifierKey}" was blocked by a null entry in the import map.`);
215
+ const afterPrefix = normalizedSpecifier.slice(specifierKey.length);
216
+ let url;
217
+ try {
218
+ url = new URL(afterPrefix, resolutionResult);
219
+ } catch {
220
+ throw new TypeError(`Resolution of "${normalizedSpecifier}" was blocked: "${afterPrefix}" could not be URL-parsed relative to "${resolutionResult.href}".`);
221
+ }
222
+ if (!url.href.startsWith(resolutionResult.href)) throw new TypeError(`Resolution of "${normalizedSpecifier}" was blocked due to backtracking above its prefix "${specifierKey}".`);
223
+ return url;
58
224
  }
59
225
  }
60
- if (importMaps.imports && importMaps.imports[specifier]) return importMaps.imports[specifier];
226
+ return null;
227
+ }
228
+ function isSpecialURL(url) {
229
+ return [
230
+ "ftp:",
231
+ "file:",
232
+ "http:",
233
+ "https:",
234
+ "ws:",
235
+ "wss:"
236
+ ].includes(url.protocol);
237
+ }
238
+ function resolveFromImportMap(specifier, importMap, baseURL) {
239
+ const base = typeof baseURL === "string" ? new URL(baseURL) : baseURL;
240
+ const asURL = resolveURLLikeModuleSpecifier(specifier, base);
241
+ const normalizedSpecifier = asURL !== null ? asURL.href : specifier;
242
+ const serializedBaseURL = base.href;
243
+ for (const [scopePrefix, scopeImports] of importMap.scopes) if (scopePrefix === serializedBaseURL || scopePrefix.endsWith("/") && serializedBaseURL.startsWith(scopePrefix)) {
244
+ const match = resolveImportsMatch(normalizedSpecifier, asURL, scopeImports);
245
+ if (match !== null) return {
246
+ url: match.href,
247
+ record: {
248
+ serializedBaseURL,
249
+ specifier: normalizedSpecifier,
250
+ specifierAsURL: asURL
251
+ }
252
+ };
253
+ }
254
+ const match = resolveImportsMatch(normalizedSpecifier, asURL, importMap.imports);
255
+ if (match !== null) return {
256
+ url: match.href,
257
+ record: {
258
+ serializedBaseURL,
259
+ specifier: normalizedSpecifier,
260
+ specifierAsURL: asURL
261
+ }
262
+ };
61
263
  }
62
-
63
264
  //#endregion
64
265
  //#region src/loader.ts
65
266
  const cssLoader = (sourceUrl, sourceCode) => {
@@ -103,7 +304,6 @@ function cssToModule(cssString, prefix) {
103
304
  map: jsonMap
104
305
  };
105
306
  }
106
-
107
307
  //#endregion
108
308
  //#region src/network.ts
109
309
  async function fetchResponse(input, init) {
@@ -111,7 +311,6 @@ async function fetchResponse(input, init) {
111
311
  if (!res.ok) throw new ModuleTSXError(`Failed to fetch resource ${res.url}: ${res.status}`);
112
312
  return res;
113
313
  }
114
-
115
314
  //#endregion
116
315
  //#region src/specifier.ts
117
316
  function isBareSpecifier(specifier) {
@@ -170,7 +369,6 @@ function createRewriteImportTransformer(specifierMap) {
170
369
  };
171
370
  return transformer;
172
371
  }
173
-
174
372
  //#endregion
175
373
  //#region src/react.ts
176
374
  /** Check if code uses JSX without React import */
@@ -212,7 +410,6 @@ function addReactImport(sourceFile) {
212
410
  const statements = [ts.factory.createImportDeclaration(void 0, ts.factory.createImportClause(false, ts.factory.createIdentifier("React"), void 0), ts.factory.createStringLiteral(reactSpecifier), void 0), ...sourceFile.statements];
213
411
  return ts.factory.updateSourceFile(sourceFile, statements, sourceFile.isDeclarationFile, sourceFile.referencedFiles, sourceFile.typeReferenceDirectives, sourceFile.hasNoDefaultLib, sourceFile.libReferenceDirectives);
214
412
  }
215
-
216
413
  //#endregion
217
414
  //#region src/source-tracker.ts
218
415
  var SourceTransformTracker = class {
@@ -248,7 +445,6 @@ var SourceTransformTracker = class {
248
445
  return `${sourceType}:${sourceUrl}`;
249
446
  }
250
447
  };
251
-
252
448
  //#endregion
253
449
  //#region src/ts.ts
254
450
  function createSourceFile(code, fileName) {
@@ -285,7 +481,6 @@ function transform(sourceFile, transformers) {
285
481
  throw new ModuleTSXError(`Failed to transform typescript source file ${sourceFile.fileName}`, { cause });
286
482
  }
287
483
  }
288
-
289
484
  //#endregion
290
485
  //#region src/module-tsx.ts
291
486
  var ModuleTSX = class extends EventTarget {
@@ -293,6 +488,7 @@ var ModuleTSX = class extends EventTarget {
293
488
  importMap;
294
489
  fetch;
295
490
  resolveBareSpecifier;
491
+ resolvedModuleSet = [];
296
492
  sourceTracker = new SourceTransformTracker();
297
493
  fetchText = async (url) => {
298
494
  return this.fetch(url).then((res) => res.text());
@@ -300,10 +496,15 @@ var ModuleTSX = class extends EventTarget {
300
496
  constructor(config) {
301
497
  super();
302
498
  this.baseUrl = config?.baseUrl ?? location.href;
303
- this.importMap = config?.importMap ?? parseImportMaps();
499
+ this.importMap = config?.importMap ?? new ImportMap();
304
500
  this.fetch = config?.fetch ?? fetchResponse;
305
501
  this.resolveBareSpecifier = typeof config?.resolveBareSpecifier === "function" ? config?.resolveBareSpecifier : (specifier) => (config?.resolveBareSpecifier ?? "https://esm.sh/") + specifier;
306
502
  }
503
+ /** Add a new import map, merging it into the existing one per the spec.
504
+ * Rules that conflict with already-resolved modules are silently dropped. */
505
+ addImportMap(newImportMap) {
506
+ ImportMap.merge(this.importMap, newImportMap, this.resolvedModuleSet);
507
+ }
307
508
  emit(type, detail) {
308
509
  this.dispatchEvent(new CustomEvent(type, { detail }));
309
510
  this.dispatchEvent(new CustomEvent("*", { detail: {
@@ -315,9 +516,11 @@ var ModuleTSX = class extends EventTarget {
315
516
  this.emit("import", { id });
316
517
  try {
317
518
  if (isBareSpecifier(id)) {
318
- const mappedSpecifier = resolveFromImportMap(id, this.importMap, this.baseUrl);
319
- if (mappedSpecifier) id = mappedSpecifier;
320
- else id = this.resolveBareSpecifier(id);
519
+ const resolved = ImportMap.resolve(id, this.importMap, this.baseUrl);
520
+ if (resolved) {
521
+ this.resolvedModuleSet.push(resolved.record);
522
+ id = resolved.url;
523
+ } else id = this.resolveBareSpecifier(id);
321
524
  }
322
525
  const url = isRelativeSpecifier(id) ? new URL(id, this.baseUrl).href : id;
323
526
  const code = await this.fetchText(url);
@@ -331,7 +534,15 @@ var ModuleTSX = class extends EventTarget {
331
534
  }
332
535
  }
333
536
  async importCode(sourceUrl, code, options) {
334
- return await import(await this.transformSourceModule("esm", sourceUrl, code), options);
537
+ try {
538
+ return await import(await this.transformSourceModule("esm", sourceUrl, code), options);
539
+ } catch (error) {
540
+ this.emit("import:error", {
541
+ id: sourceUrl,
542
+ error
543
+ });
544
+ throw error;
545
+ }
335
546
  }
336
547
  /** Transform module source code and return a blob URL with the transformed content */
337
548
  async transformSourceModule(sourceType, sourceUrl, sourceCode) {
@@ -361,7 +572,13 @@ var ModuleTSX = class extends EventTarget {
361
572
  const specifiers = collectSpecifiers(sourceFile);
362
573
  const rewrittenSpecifiers = await this.resolveSpecifiers(specifiers, sourceUrl);
363
574
  let workingSourceFile = sourceFile;
364
- if (needsReactImport(workingSourceFile)) workingSourceFile = addReactImport(workingSourceFile);
575
+ if (needsReactImport(workingSourceFile)) {
576
+ workingSourceFile = addReactImport(workingSourceFile);
577
+ if (!rewrittenSpecifiers.has("react")) {
578
+ const reactUrl = await this.resolveSpecifier("react", sourceUrl);
579
+ if (reactUrl !== "react") rewrittenSpecifiers.set("react", reactUrl);
580
+ }
581
+ }
365
582
  const transformers = [createRewriteImportTransformer(rewrittenSpecifiers)];
366
583
  return printSourceFile(transform(workingSourceFile, transformers));
367
584
  } catch (error) {
@@ -372,32 +589,34 @@ var ModuleTSX = class extends EventTarget {
372
589
  throw error;
373
590
  }
374
591
  }
592
+ async resolveLocalUrl(fullUrl) {
593
+ const { pathname } = new URL(fullUrl);
594
+ if (pathname.endsWith(".module.css")) return this.transformSourceModule("css-module", fullUrl, await this.fetchText(fullUrl));
595
+ if (pathname.endsWith(".css")) return this.transformSourceModule("css", fullUrl, await this.fetchText(fullUrl));
596
+ if (pathname.endsWith(".wasm")) return fullUrl;
597
+ if (this.sourceTracker.isInFlight("esm", fullUrl)) return fullUrl;
598
+ //! ^ transformSourceModule is recursive ^
599
+ return this.transformSourceModule("esm", fullUrl, await this.fetchText(fullUrl));
600
+ }
375
601
  async resolveSpecifier(specifier, sourceUrl) {
376
- const mappedSpecifier = resolveFromImportMap(specifier, this.importMap, sourceUrl);
377
- if (mappedSpecifier) return mappedSpecifier;
378
- const getCssUrl = async (fullURL) => {
379
- return await this.transformSourceModule("css", fullURL, await this.fetchText(fullURL));
380
- };
381
- const toCDNUrl = (specifier) => {
382
- const subpath = specifier.startsWith("@") ? specifier.split("/").slice(2).join("/") : specifier.split("/").slice(1).join("/");
383
- const url = this.resolveBareSpecifier(specifier);
384
- if (subpath.endsWith(".css")) return getCssUrl(url);
602
+ const resolved = ImportMap.resolve(specifier, this.importMap, sourceUrl);
603
+ if (resolved) {
604
+ this.resolvedModuleSet.push(resolved.record);
605
+ const { pathname } = new URL(resolved.url);
606
+ if (pathname.endsWith(".module.css")) return this.transformSourceModule("css-module", resolved.url, await this.fetchText(resolved.url));
607
+ if (pathname.endsWith(".css")) return this.transformSourceModule("css", resolved.url, await this.fetchText(resolved.url));
608
+ return resolved.url;
609
+ }
610
+ if (isRelativeSpecifier(specifier)) return this.resolveLocalUrl(new URL(specifier, sourceUrl).href);
611
+ if (specifier.startsWith("node:")) return `https://raw.esm.sh/@jspm/core/nodelibs/browser/${specifier.slice(5)}.js`;
612
+ const bareSpecifier = specifier.startsWith("npm:") ? specifier.slice(4) : specifier;
613
+ if (specifier.startsWith("npm:") || isBareSpecifier(specifier)) {
614
+ const subpath = bareSpecifier.startsWith("@") ? bareSpecifier.split("/").slice(2).join("/") : bareSpecifier.split("/").slice(1).join("/");
615
+ const url = this.resolveBareSpecifier(bareSpecifier);
616
+ if (subpath.endsWith(".css")) return this.transformSourceModule("css", url, await this.fetchText(url));
385
617
  return url;
386
- };
387
- if (isRelativeSpecifier(specifier)) {
388
- const targetUrl = new URL(specifier, sourceUrl);
389
- if (targetUrl.pathname.endsWith(".module.css")) return await this.transformSourceModule("css-module", targetUrl.href, await this.fetchText(targetUrl.href));
390
- else if (targetUrl.pathname.endsWith(".css")) return getCssUrl(targetUrl.href);
391
- else if (targetUrl.pathname.endsWith(".wasm")) return targetUrl.href;
392
- else {
393
- if (this.sourceTracker.isInFlight("esm", targetUrl.href)) return targetUrl.href;
394
- //! ^ transformSourceModule is recursive ^
395
- return await this.transformSourceModule("esm", targetUrl.href, await this.fetchText(targetUrl.href));
396
- }
397
- } else if (specifier.startsWith("node:")) return `https://raw.esm.sh/@jspm/core/nodelibs/browser/${specifier.slice(5)}.js`;
398
- else if (specifier.startsWith("npm:")) return toCDNUrl(specifier.slice(4));
399
- else if (isBareSpecifier(specifier)) return toCDNUrl(specifier);
400
- else return specifier;
618
+ }
619
+ return specifier;
401
620
  }
402
621
  async resolveSpecifiers(specifiers, sourceUrl) {
403
622
  const resolved = /* @__PURE__ */ new Map();
@@ -416,13 +635,12 @@ function getFileName(sourceUrl) {
416
635
  return "temp.tsx";
417
636
  }
418
637
  }
419
-
420
638
  //#endregion
421
639
  //#region src/index.ts
422
640
  /**
423
641
  * The singleton global instance of ModuleTSX.
424
642
  */
425
- const instance = new ModuleTSX();
643
+ const instance = new ModuleTSX({ importMap: ImportMap.fromDOM() });
426
644
  const TYPE_ATTRIBUTE_VALUE = "module-tsx";
427
645
  async function sideEffect() {
428
646
  const importScript = async (script) => {
@@ -441,7 +659,11 @@ async function sideEffect() {
441
659
  else await importScript(script);
442
660
  }
443
661
  }
662
+ /**
663
+ * Since this module can be loaded as both ESM and UMD, we listen for DOMContentLoaded to ensure all type="module-tsx" tags are present.
664
+ * ESM scripts are always deferred, so the document is already fully parsed when this module executes and the listener fires immediately.
665
+ * Classic scripts run inline as the parser encounters them, so they must wait for DOMContentLoaded.
666
+ */
444
667
  document.addEventListener("DOMContentLoaded", sideEffect);
445
-
446
668
  //#endregion
447
- export { ModuleTSX, ModuleTSXError, instance };
669
+ export { ImportMap, ModuleTSX, ModuleTSXError, instance };