module-tsx 0.0.1 → 0.0.2
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 +67 -11
- package/dist/index.d.ts +37 -6
- package/dist/index.js +298 -76
- package/dist/index.mjs +454 -434
- package/dist/index.umd.js +117 -113
- package/package.json +6 -4
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
203
|
+
warn("Failed to parse importmap script:", error);
|
|
46
204
|
}
|
|
47
205
|
return result;
|
|
48
206
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
319
|
-
if (
|
|
320
|
-
|
|
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
|
-
|
|
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))
|
|
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
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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 };
|