@esm.sh/import-map 0.3.2 → 0.4.0
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 +15 -2
- package/dist/index.mjs +51 -36
- package/package.json +1 -1
- package/types/index.d.ts +4 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm i @esm.sh/import-map
|
|
|
15
15
|
|
|
16
16
|
## API
|
|
17
17
|
|
|
18
|
-
### `new ImportMap(
|
|
18
|
+
### `new ImportMap(init?: ImportMapRaw, baseURL?: string)`
|
|
19
19
|
|
|
20
20
|
Create an import map instance:
|
|
21
21
|
|
|
@@ -28,7 +28,7 @@ const im = new ImportMap();
|
|
|
28
28
|
You can also initialize from a raw object:
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
|
-
const im = new ImportMap(
|
|
31
|
+
const im = new ImportMap({
|
|
32
32
|
config: { cdn: "https://esm.sh", target: "es2022" },
|
|
33
33
|
imports: { react: "https://esm.sh/react@19.2.4/es2022/react.mjs" },
|
|
34
34
|
scopes: {
|
|
@@ -99,6 +99,19 @@ const im = new ImportMap();
|
|
|
99
99
|
await im.addImport("react-dom@19/client");
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
### `setFetcher(fetcher: (url: string | URL) => Promise<Response>)`
|
|
103
|
+
|
|
104
|
+
Override the default `fetch` used internally by `addImport`.
|
|
105
|
+
This is useful for caching metadata responses, or to use a custom fetch implementation.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// Use a custom fetch with cache.
|
|
109
|
+
setFetcher(cacheFetch);
|
|
110
|
+
|
|
111
|
+
// Restore default behavior.
|
|
112
|
+
setFetcher(globalThis.fetch);
|
|
113
|
+
```
|
|
114
|
+
|
|
102
115
|
### `ImportMap.resolve(specifier: string, containingFile: string)`
|
|
103
116
|
|
|
104
117
|
The `resolve` method resolves a specifier using import-map matching rules:
|
package/dist/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ async function addImport(importMap, specifier, noSRI) {
|
|
|
35
35
|
const imp = parseImportSpecifier(specifier);
|
|
36
36
|
const config = importMap.config ?? {};
|
|
37
37
|
const target = normalizeTarget(config.target);
|
|
38
|
-
const cdnOrigin =
|
|
38
|
+
const cdnOrigin = normalizeCdnOrigin(config.cdn);
|
|
39
39
|
const meta = await fetchImportMeta(cdnOrigin, imp, target);
|
|
40
40
|
const mark = /* @__PURE__ */ new Set();
|
|
41
41
|
await addImportImpl(importMap, mark, meta, false, void 0, cdnOrigin, target, noSRI ?? false);
|
|
@@ -43,12 +43,12 @@ async function addImport(importMap, specifier, noSRI) {
|
|
|
43
43
|
pruneEmptyScopes(importMap);
|
|
44
44
|
}
|
|
45
45
|
async function addImportImpl(importMap, mark, imp, indirect, targetImports, cdnOrigin, target, noSRI) {
|
|
46
|
-
const markedSpecifier =
|
|
46
|
+
const markedSpecifier = specifierOf(imp) + SPECIFIER_MARK_SEPARATOR + imp.version;
|
|
47
47
|
if (mark.has(markedSpecifier)) {
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
mark.add(markedSpecifier);
|
|
51
|
-
const cdnScopeKey =
|
|
51
|
+
const cdnScopeKey = cdnOrigin + "/";
|
|
52
52
|
const cdnScopeImports = importMap.scopes?.[cdnScopeKey];
|
|
53
53
|
const imports = indirect ? targetImports ?? ensureScope(importMap, cdnScopeKey) : importMap.imports;
|
|
54
54
|
const moduleUrl = moduleUrlOf(cdnOrigin, target, imp);
|
|
@@ -77,7 +77,7 @@ async function addImportImpl(importMap, mark, imp, indirect, targetImports, cdnO
|
|
|
77
77
|
const depSpecifier = specifierOf(depImport);
|
|
78
78
|
const existingUrl = importMap.imports[depSpecifier] ?? importMap.scopes?.[cdnScopeKey]?.[depSpecifier];
|
|
79
79
|
let scopedTargetImports = targetImports;
|
|
80
|
-
if (existingUrl?.startsWith(
|
|
80
|
+
if (existingUrl?.startsWith(cdnOrigin + "/")) {
|
|
81
81
|
const existingImport = parseEsmPath(existingUrl);
|
|
82
82
|
const existingVersion = valid(existingImport.version);
|
|
83
83
|
if (existingVersion && depImport.version === existingImport.version) {
|
|
@@ -89,11 +89,11 @@ async function addImportImpl(importMap, mark, imp, indirect, targetImports, cdnO
|
|
|
89
89
|
}
|
|
90
90
|
if (isPeer) {
|
|
91
91
|
console.warn(
|
|
92
|
-
|
|
92
|
+
"incorrect peer dependency(unmeet " + depImport.version + "): " + depImport.name + "@" + existingVersion
|
|
93
93
|
);
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
const scope =
|
|
96
|
+
const scope = cdnOrigin + "/" + esmSpecifierOf(imp) + "/";
|
|
97
97
|
scopedTargetImports = ensureScope(importMap, scope);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -161,10 +161,10 @@ function parseImportSpecifier(specifier) {
|
|
|
161
161
|
[packageAndVersion, imp.subPath] = splitByFirst(source, "/");
|
|
162
162
|
[imp.name, imp.version] = splitByFirst(packageAndVersion, "@");
|
|
163
163
|
if (scopeName) {
|
|
164
|
-
imp.name =
|
|
164
|
+
imp.name = scopeName + "/" + imp.name;
|
|
165
165
|
}
|
|
166
166
|
if (!imp.name) {
|
|
167
|
-
throw new Error(
|
|
167
|
+
throw new Error("invalid package name or version: " + specifier);
|
|
168
168
|
}
|
|
169
169
|
return imp;
|
|
170
170
|
}
|
|
@@ -174,20 +174,24 @@ function normalizeTarget(target) {
|
|
|
174
174
|
}
|
|
175
175
|
return "es2022";
|
|
176
176
|
}
|
|
177
|
-
function
|
|
177
|
+
function normalizeCdnOrigin(cdn) {
|
|
178
178
|
if (cdn && (cdn.startsWith("https://") || cdn.startsWith("http://"))) {
|
|
179
|
-
|
|
179
|
+
try {
|
|
180
|
+
return new URL(cdn).origin;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn("invalid cdn: " + cdn);
|
|
183
|
+
}
|
|
180
184
|
}
|
|
181
185
|
return "https://esm.sh";
|
|
182
186
|
}
|
|
183
187
|
function specifierOf(imp) {
|
|
184
188
|
const prefix = imp.github ? "gh:" : imp.jsr ? "jsr:" : "";
|
|
185
|
-
return
|
|
189
|
+
return prefix + imp.name + (imp.subPath ? "/" + imp.subPath : "");
|
|
186
190
|
}
|
|
187
191
|
function esmSpecifierOf(imp) {
|
|
188
192
|
const prefix = imp.github ? "gh/" : imp.jsr ? "jsr/" : "";
|
|
189
193
|
const external = hasExternalImports(imp) ? "*" : "";
|
|
190
|
-
return
|
|
194
|
+
return prefix + external + imp.name + "@" + imp.version;
|
|
191
195
|
}
|
|
192
196
|
function registryPrefix(imp) {
|
|
193
197
|
if (imp.github) {
|
|
@@ -203,49 +207,53 @@ function hasExternalImports(meta) {
|
|
|
203
207
|
return true;
|
|
204
208
|
}
|
|
205
209
|
for (const dep of meta.imports) {
|
|
206
|
-
if (!dep.startsWith("/node/") && !dep.startsWith(
|
|
210
|
+
if (!dep.startsWith("/node/") && !dep.startsWith("/" + meta.name + "@")) {
|
|
207
211
|
return true;
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
return false;
|
|
211
215
|
}
|
|
212
216
|
function moduleUrlOf(cdnOrigin, target, imp) {
|
|
213
|
-
let url =
|
|
217
|
+
let url = cdnOrigin + "/" + esmSpecifierOf(imp) + "/" + target + "/";
|
|
214
218
|
if (imp.subPath) {
|
|
215
219
|
if (imp.dev || imp.subPath === "jsx-dev-runtime") {
|
|
216
|
-
url +=
|
|
220
|
+
url += imp.subPath + ".development.mjs";
|
|
217
221
|
} else {
|
|
218
|
-
url +=
|
|
222
|
+
url += imp.subPath + ".mjs";
|
|
219
223
|
}
|
|
220
224
|
return url;
|
|
221
225
|
}
|
|
222
226
|
const fileName = imp.name.includes("/") ? imp.name.split("/").at(-1) : imp.name;
|
|
223
|
-
return
|
|
227
|
+
return url + fileName + ".mjs";
|
|
228
|
+
}
|
|
229
|
+
var fetcher = globalThis.fetch;
|
|
230
|
+
function setFetcher(f) {
|
|
231
|
+
fetcher = f;
|
|
224
232
|
}
|
|
225
233
|
async function fetchImportMeta(cdnOrigin, imp, target) {
|
|
226
234
|
const star = imp.external ? "*" : "";
|
|
227
|
-
const version = imp.version ?
|
|
228
|
-
const subPath = imp.subPath ?
|
|
229
|
-
const targetQuery = target !== "es2022" ?
|
|
230
|
-
const url =
|
|
235
|
+
const version = imp.version ? "@" + imp.version : "";
|
|
236
|
+
const subPath = imp.subPath ? "/" + imp.subPath : "";
|
|
237
|
+
const targetQuery = target !== "es2022" ? "&target=" + encodeURIComponent(target) : "";
|
|
238
|
+
const url = cdnOrigin + "/" + star + registryPrefix(imp) + imp.name + version + subPath + "?meta" + targetQuery;
|
|
231
239
|
const cached = META_CACHE.get(url);
|
|
232
240
|
if (cached) {
|
|
233
241
|
return cached;
|
|
234
242
|
}
|
|
235
243
|
const pending = (async () => {
|
|
236
|
-
const res = await
|
|
244
|
+
const res = await fetcher(url);
|
|
237
245
|
if (res.status === 404) {
|
|
238
|
-
throw new Error(
|
|
246
|
+
throw new Error("package not found: " + imp.name + version + subPath);
|
|
239
247
|
}
|
|
240
248
|
if (!res.ok) {
|
|
241
|
-
throw new Error(
|
|
249
|
+
throw new Error("unexpected http status " + res.status + ": " + await res.text());
|
|
242
250
|
}
|
|
243
251
|
const bodyText = await res.text();
|
|
244
252
|
let data;
|
|
245
253
|
try {
|
|
246
254
|
data = JSON.parse(bodyText);
|
|
247
|
-
} catch {
|
|
248
|
-
throw new Error(
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw new Error("invalid meta response from " + url + ": " + String(error));
|
|
249
257
|
}
|
|
250
258
|
return {
|
|
251
259
|
name: data.name ?? imp.name,
|
|
@@ -306,7 +314,7 @@ function parseEsmPath(pathnameOrUrl) {
|
|
|
306
314
|
} else if (pathnameOrUrl.startsWith("/")) {
|
|
307
315
|
pathname = splitByFirst(splitByFirst(pathnameOrUrl, "#")[0], "?")[0];
|
|
308
316
|
} else {
|
|
309
|
-
throw new Error(
|
|
317
|
+
throw new Error("invalid pathname or url: " + pathnameOrUrl);
|
|
310
318
|
}
|
|
311
319
|
const imp = {
|
|
312
320
|
name: "",
|
|
@@ -326,19 +334,19 @@ function parseEsmPath(pathnameOrUrl) {
|
|
|
326
334
|
}
|
|
327
335
|
const segs = pathname.split("/").filter(Boolean);
|
|
328
336
|
if (segs.length === 0) {
|
|
329
|
-
throw new Error(
|
|
337
|
+
throw new Error("invalid pathname: " + pathnameOrUrl);
|
|
330
338
|
}
|
|
331
339
|
if (segs[0].startsWith("@")) {
|
|
332
340
|
if (!segs[1]) {
|
|
333
|
-
throw new Error(
|
|
341
|
+
throw new Error("invalid pathname: " + pathnameOrUrl);
|
|
334
342
|
}
|
|
335
343
|
const [name, version] = splitByLast(segs[1], "@");
|
|
336
|
-
imp.name =
|
|
344
|
+
imp.name = trimLeadingStar(segs[0] + "/" + name);
|
|
337
345
|
imp.version = version;
|
|
338
346
|
segs.splice(0, 2);
|
|
339
347
|
} else {
|
|
340
348
|
const [name, version] = splitByLast(segs[0], "@");
|
|
341
|
-
imp.name = name
|
|
349
|
+
imp.name = trimLeadingStar(name);
|
|
342
350
|
imp.version = version;
|
|
343
351
|
segs.splice(0, 1);
|
|
344
352
|
}
|
|
@@ -357,7 +365,7 @@ function parseEsmPath(pathnameOrUrl) {
|
|
|
357
365
|
subPath = subPath.slice(0, -12);
|
|
358
366
|
imp.dev = true;
|
|
359
367
|
}
|
|
360
|
-
if (subPath.includes("/") || subPath !== imp.name && !imp.name.endsWith(
|
|
368
|
+
if (subPath.includes("/") || subPath !== imp.name && !imp.name.endsWith("/" + subPath)) {
|
|
361
369
|
imp.subPath = subPath;
|
|
362
370
|
}
|
|
363
371
|
} else {
|
|
@@ -366,6 +374,12 @@ function parseEsmPath(pathnameOrUrl) {
|
|
|
366
374
|
}
|
|
367
375
|
return imp;
|
|
368
376
|
}
|
|
377
|
+
function trimLeadingStar(value) {
|
|
378
|
+
if (value.startsWith("*")) {
|
|
379
|
+
return value.slice(1);
|
|
380
|
+
}
|
|
381
|
+
return value;
|
|
382
|
+
}
|
|
369
383
|
function splitByFirst(value, separator) {
|
|
370
384
|
const idx = value.indexOf(separator);
|
|
371
385
|
if (idx < 0) {
|
|
@@ -480,7 +494,7 @@ var ImportMap = class {
|
|
|
480
494
|
imports = {};
|
|
481
495
|
scopes = {};
|
|
482
496
|
integrity = {};
|
|
483
|
-
constructor(
|
|
497
|
+
constructor(init, baseURL) {
|
|
484
498
|
this.#baseURL = new URL(baseURL ?? globalThis.location?.href ?? "file:///");
|
|
485
499
|
if (init) {
|
|
486
500
|
this.config = sanitizeStringMap(init.config);
|
|
@@ -543,7 +557,7 @@ function sortScopes(scopes) {
|
|
|
543
557
|
|
|
544
558
|
// src/parse.ts
|
|
545
559
|
function parseFromJson(json, baseURL) {
|
|
546
|
-
const im = new ImportMap(baseURL);
|
|
560
|
+
const im = new ImportMap({}, baseURL);
|
|
547
561
|
const v = JSON.parse(json);
|
|
548
562
|
if (isObject(v)) {
|
|
549
563
|
const { config, imports, scopes, integrity } = v;
|
|
@@ -561,7 +575,7 @@ function parseFromHtml(html, baseURL) {
|
|
|
561
575
|
if (scriptEl) {
|
|
562
576
|
return parseFromJson(scriptEl.textContent, baseURL);
|
|
563
577
|
}
|
|
564
|
-
return new ImportMap(baseURL);
|
|
578
|
+
return new ImportMap({}, baseURL);
|
|
565
579
|
}
|
|
566
580
|
|
|
567
581
|
// src/support.ts
|
|
@@ -572,5 +586,6 @@ export {
|
|
|
572
586
|
ImportMap,
|
|
573
587
|
isSupportImportMap,
|
|
574
588
|
parseFromHtml,
|
|
575
|
-
parseFromJson
|
|
589
|
+
parseFromJson,
|
|
590
|
+
setFetcher
|
|
576
591
|
};
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface ImportMapRaw {
|
|
|
25
25
|
|
|
26
26
|
/** The import map class. */
|
|
27
27
|
export class ImportMap {
|
|
28
|
-
constructor(
|
|
28
|
+
constructor(init?: Record<string, any>, baseURL?: string | URL);
|
|
29
29
|
|
|
30
30
|
/** The config for generating the new import url from CDN. */
|
|
31
31
|
config: ImportMapConfig;
|
|
@@ -66,3 +66,6 @@ export function parseFromHtml(html: string, baseURL?: string): ImportMap;
|
|
|
66
66
|
|
|
67
67
|
/** Check if current browser supports import maps. */
|
|
68
68
|
export function isSupportImportMap(): boolean;
|
|
69
|
+
|
|
70
|
+
/** Set the fetcher to use for fetching import meta. */
|
|
71
|
+
export function setFetcher(fetcher: (url: string | URL) => Promise<Response>): void;
|