@esm.sh/import-map 0.2.0 → 0.2.1
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 +5 -6
- package/dist/add.mjs +105 -105
- package/dist/blank.mjs +1 -1
- package/dist/resolve.mjs +50 -32
- package/package.json +1 -1
- package/types/index.d.ts +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# @esm.sh/import-map
|
|
2
2
|
|
|
3
|
-
An [Import Maps](https://wicg.github.io/import-maps/) manager
|
|
3
|
+
An [Import Maps](https://wicg.github.io/import-maps/) manager, with features:
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- maintain optional `integrity` metadata
|
|
5
|
+
- Parse import maps from JSON/HTML
|
|
6
|
+
- Resolve specifiers to URLs using import map matching rules
|
|
7
|
+
- Add npm/jsr/github modules from [esm.sh](https://esm.sh) CDN
|
|
8
|
+
- Generate `integrity` entries for added modules
|
|
10
9
|
|
|
11
10
|
## Installation
|
|
12
11
|
|
package/dist/add.mjs
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import { satisfies, valid } from "semver";
|
|
2
|
-
async function addImport(importMap, specifier, noSRI) {
|
|
3
|
-
const imp = parseImportSpecifier(specifier);
|
|
4
|
-
const config = importMap.config ?? {};
|
|
5
|
-
const target = normalizeTarget(config.target);
|
|
6
|
-
const cdnOrigin = getCdnOrigin(config.cdn);
|
|
7
|
-
const meta = await fetchImportMeta(cdnOrigin, imp, target);
|
|
8
|
-
const mark = /* @__PURE__ */ new Set();
|
|
9
|
-
await addImportMeta(importMap, mark, meta, false, void 0, cdnOrigin, target, noSRI ?? false);
|
|
10
|
-
}
|
|
11
2
|
const KNOWN_TARGETS = /* @__PURE__ */ new Set([
|
|
12
3
|
"es2015",
|
|
13
4
|
"es2016",
|
|
@@ -39,6 +30,111 @@ const ESM_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
39
30
|
]);
|
|
40
31
|
const SPECIFIER_MARK_SEPARATOR = "\0";
|
|
41
32
|
const META_CACHE = /* @__PURE__ */ new Map();
|
|
33
|
+
async function addImport(importMap, specifier, noSRI) {
|
|
34
|
+
const imp = parseImportSpecifier(specifier);
|
|
35
|
+
const config = importMap.config ?? {};
|
|
36
|
+
const target = normalizeTarget(config.target);
|
|
37
|
+
const cdnOrigin = getCdnOrigin(config.cdn);
|
|
38
|
+
const meta = await fetchImportMeta(cdnOrigin, imp, target);
|
|
39
|
+
const mark = /* @__PURE__ */ new Set();
|
|
40
|
+
await addImportImpl(importMap, mark, meta, false, void 0, cdnOrigin, target, noSRI ?? false);
|
|
41
|
+
}
|
|
42
|
+
async function addImportImpl(importMap, mark, imp, indirect, targetImports, cdnOrigin, target, noSRI) {
|
|
43
|
+
const markedSpecifier = `${specifierOf(imp)}${SPECIFIER_MARK_SEPARATOR}${imp.version}`;
|
|
44
|
+
if (mark.has(markedSpecifier)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
mark.add(markedSpecifier);
|
|
48
|
+
const cdnScopeKey = `${cdnOrigin}/`;
|
|
49
|
+
const cdnScopeImports = importMap.scopes?.[cdnScopeKey];
|
|
50
|
+
const imports = indirect ? targetImports ?? ensureScope(importMap, cdnScopeKey) : importMap.imports;
|
|
51
|
+
const moduleUrl = moduleUrlOf(cdnOrigin, target, imp);
|
|
52
|
+
const currentSpecifier = specifierOf(imp);
|
|
53
|
+
imports[currentSpecifier] = moduleUrl;
|
|
54
|
+
await updateIntegrity(importMap, imp, moduleUrl, cdnOrigin, target, noSRI);
|
|
55
|
+
if (!indirect) {
|
|
56
|
+
if (cdnScopeImports) {
|
|
57
|
+
delete cdnScopeImports[currentSpecifier];
|
|
58
|
+
}
|
|
59
|
+
pruneEmptyScopes(importMap);
|
|
60
|
+
}
|
|
61
|
+
const allDeps = [
|
|
62
|
+
...imp.peerImports.map((pathname) => ({ pathname, isPeer: true })),
|
|
63
|
+
...imp.imports.map((pathname) => ({ pathname, isPeer: false }))
|
|
64
|
+
];
|
|
65
|
+
await Promise.all(
|
|
66
|
+
allDeps.map(async ({ pathname, isPeer }) => {
|
|
67
|
+
if (pathname.startsWith("/node/")) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const depImport = parseEsmPath(pathname);
|
|
71
|
+
if (depImport.name === imp.name) {
|
|
72
|
+
depImport.version = imp.version;
|
|
73
|
+
}
|
|
74
|
+
const depSpecifier = specifierOf(depImport);
|
|
75
|
+
const existingUrl = importMap.imports[depSpecifier] ?? importMap.scopes?.[cdnScopeKey]?.[depSpecifier];
|
|
76
|
+
let scopedTargetImports = targetImports;
|
|
77
|
+
if (existingUrl?.startsWith(`${cdnOrigin}/`)) {
|
|
78
|
+
const existingImport = parseEsmPath(existingUrl);
|
|
79
|
+
const existingVersion = valid(existingImport.version);
|
|
80
|
+
if (existingVersion && depImport.version === existingImport.version) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (existingVersion && depImport.version && !valid(depImport.version)) {
|
|
84
|
+
if (satisfies(existingVersion, depImport.version, { includePrerelease: true })) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (isPeer) {
|
|
88
|
+
console.warn(
|
|
89
|
+
`incorrect peer dependency(unmeet ${depImport.version}): ${depImport.name}@${existingVersion}`
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const scope = `${cdnOrigin}/${esmSpecifierOf(imp)}/`;
|
|
94
|
+
scopedTargetImports = ensureScope(importMap, scope);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const depMeta = await fetchImportMeta(cdnOrigin, depImport, target);
|
|
98
|
+
await addImportImpl(importMap, mark, depMeta, !isPeer, scopedTargetImports, cdnOrigin, target, noSRI);
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
pruneEmptyScopes(importMap);
|
|
102
|
+
}
|
|
103
|
+
async function updateIntegrity(importMap, imp, moduleUrl, cdnOrigin, target, noSRI) {
|
|
104
|
+
if (noSRI) {
|
|
105
|
+
if (importMap.integrity) {
|
|
106
|
+
delete importMap.integrity[moduleUrl];
|
|
107
|
+
if (Object.keys(importMap.integrity).length === 0) {
|
|
108
|
+
delete importMap.integrity;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (!hasExternalImports(imp)) {
|
|
114
|
+
if (imp.integrity) {
|
|
115
|
+
importMap.integrity ??= {};
|
|
116
|
+
importMap.integrity[moduleUrl] = imp.integrity;
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const integrityMeta = await fetchImportMeta(
|
|
121
|
+
cdnOrigin,
|
|
122
|
+
{
|
|
123
|
+
name: imp.name,
|
|
124
|
+
version: imp.version,
|
|
125
|
+
subPath: imp.subPath,
|
|
126
|
+
github: imp.github,
|
|
127
|
+
jsr: imp.jsr,
|
|
128
|
+
external: true,
|
|
129
|
+
dev: imp.dev
|
|
130
|
+
},
|
|
131
|
+
target
|
|
132
|
+
);
|
|
133
|
+
if (integrityMeta.integrity) {
|
|
134
|
+
importMap.integrity ??= {};
|
|
135
|
+
importMap.integrity[moduleUrl] = integrityMeta.integrity;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
42
138
|
function parseImportSpecifier(specifier) {
|
|
43
139
|
let source = specifier.trim();
|
|
44
140
|
const imp = {
|
|
@@ -174,102 +270,6 @@ async function fetchImportMeta(cdnOrigin, imp, target) {
|
|
|
174
270
|
throw error;
|
|
175
271
|
}
|
|
176
272
|
}
|
|
177
|
-
async function addImportMeta(importMap, mark, imp, indirect, targetImports, cdnOrigin, target, noSRI) {
|
|
178
|
-
const markedSpecifier = `${specifierOf(imp)}${SPECIFIER_MARK_SEPARATOR}${imp.version}`;
|
|
179
|
-
if (mark.has(markedSpecifier)) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
mark.add(markedSpecifier);
|
|
183
|
-
const cdnScopeKey = `${cdnOrigin}/`;
|
|
184
|
-
const cdnScopeImports = importMap.scopes?.[cdnScopeKey];
|
|
185
|
-
const imports = indirect ? targetImports ?? ensureScope(importMap, cdnScopeKey) : importMap.imports;
|
|
186
|
-
const moduleUrl = moduleUrlOf(cdnOrigin, target, imp);
|
|
187
|
-
const currentSpecifier = specifierOf(imp);
|
|
188
|
-
imports[currentSpecifier] = moduleUrl;
|
|
189
|
-
await updateIntegrity(importMap, imp, moduleUrl, cdnOrigin, target, noSRI);
|
|
190
|
-
if (!indirect) {
|
|
191
|
-
if (cdnScopeImports) {
|
|
192
|
-
delete cdnScopeImports[currentSpecifier];
|
|
193
|
-
}
|
|
194
|
-
pruneEmptyScopes(importMap);
|
|
195
|
-
}
|
|
196
|
-
const allDeps = [
|
|
197
|
-
...imp.peerImports.map((pathname) => ({ pathname, isPeer: true })),
|
|
198
|
-
...imp.imports.map((pathname) => ({ pathname, isPeer: false }))
|
|
199
|
-
];
|
|
200
|
-
await Promise.all(
|
|
201
|
-
allDeps.map(async ({ pathname, isPeer }) => {
|
|
202
|
-
if (pathname.startsWith("/node/")) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const depImport = parseEsmPath(pathname);
|
|
206
|
-
if (depImport.name === imp.name) {
|
|
207
|
-
depImport.version = imp.version;
|
|
208
|
-
}
|
|
209
|
-
const depSpecifier = specifierOf(depImport);
|
|
210
|
-
const existingUrl = importMap.imports[depSpecifier] ?? importMap.scopes?.[cdnScopeKey]?.[depSpecifier];
|
|
211
|
-
let scopedTargetImports = targetImports;
|
|
212
|
-
if (existingUrl?.startsWith(`${cdnOrigin}/`)) {
|
|
213
|
-
const existingImport = parseEsmPath(existingUrl);
|
|
214
|
-
const existingVersion = valid(existingImport.version);
|
|
215
|
-
if (existingVersion && depImport.version === existingImport.version) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
if (existingVersion && depImport.version && !valid(depImport.version)) {
|
|
219
|
-
if (satisfies(existingVersion, depImport.version, { includePrerelease: true })) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
if (isPeer) {
|
|
223
|
-
console.warn(
|
|
224
|
-
`incorrect peer dependency(unmeet ${depImport.version}): ${depImport.name}@${existingVersion}`
|
|
225
|
-
);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
const scope = `${cdnOrigin}/${esmSpecifierOf(imp)}/`;
|
|
229
|
-
scopedTargetImports = ensureScope(importMap, scope);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const depMeta = await fetchImportMeta(cdnOrigin, depImport, target);
|
|
233
|
-
await addImportMeta(importMap, mark, depMeta, !isPeer, scopedTargetImports, cdnOrigin, target, noSRI);
|
|
234
|
-
})
|
|
235
|
-
);
|
|
236
|
-
pruneEmptyScopes(importMap);
|
|
237
|
-
}
|
|
238
|
-
async function updateIntegrity(importMap, imp, moduleUrl, cdnOrigin, target, noSRI) {
|
|
239
|
-
if (noSRI) {
|
|
240
|
-
if (importMap.integrity) {
|
|
241
|
-
delete importMap.integrity[moduleUrl];
|
|
242
|
-
if (Object.keys(importMap.integrity).length === 0) {
|
|
243
|
-
delete importMap.integrity;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (!hasExternalImports(imp)) {
|
|
249
|
-
if (imp.integrity) {
|
|
250
|
-
importMap.integrity ??= {};
|
|
251
|
-
importMap.integrity[moduleUrl] = imp.integrity;
|
|
252
|
-
}
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const integrityMeta = await fetchImportMeta(
|
|
256
|
-
cdnOrigin,
|
|
257
|
-
{
|
|
258
|
-
name: imp.name,
|
|
259
|
-
version: imp.version,
|
|
260
|
-
subPath: imp.subPath,
|
|
261
|
-
github: imp.github,
|
|
262
|
-
jsr: imp.jsr,
|
|
263
|
-
external: true,
|
|
264
|
-
dev: imp.dev
|
|
265
|
-
},
|
|
266
|
-
target
|
|
267
|
-
);
|
|
268
|
-
if (integrityMeta.integrity) {
|
|
269
|
-
importMap.integrity ??= {};
|
|
270
|
-
importMap.integrity[moduleUrl] = integrityMeta.integrity;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
273
|
function ensureScope(importMap, scopeKey) {
|
|
274
274
|
importMap.scopes ??= {};
|
|
275
275
|
importMap.scopes[scopeKey] ??= {};
|
package/dist/blank.mjs
CHANGED
package/dist/resolve.mjs
CHANGED
|
@@ -1,45 +1,63 @@
|
|
|
1
1
|
function resolve(importMap, specifier, containingFile) {
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
const baseURL = importMap.baseURL ?? new URL(globalThis.location?.href ?? "file:///");
|
|
3
|
+
const referrer = new URL(containingFile, baseURL);
|
|
4
|
+
const [specifierWithoutHash, hashPart = ""] = specifier.split("#", 2);
|
|
5
|
+
const [specifierWithoutQuery, queryPart = ""] = specifierWithoutHash.split("?", 2);
|
|
6
|
+
const hash = hashPart ? `#${hashPart}` : "";
|
|
7
|
+
const query = queryPart ? `?${queryPart}` : "";
|
|
8
|
+
const cleanSpecifier = specifierWithoutQuery;
|
|
9
|
+
const scopes = importMap.scopes ?? {};
|
|
10
|
+
const scopeEntries = Object.entries(scopes).map(([scopeKey, scopeImports]) => {
|
|
11
|
+
try {
|
|
12
|
+
return [new URL(scopeKey, baseURL).toString(), scopeImports];
|
|
13
|
+
} catch {
|
|
14
|
+
return [scopeKey, scopeImports];
|
|
9
15
|
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (pathname.startsWith(scopePathname)) {
|
|
15
|
-
const url = matchImport(specifier, scopeImports);
|
|
16
|
-
if (url) {
|
|
17
|
-
return [url, true];
|
|
18
|
-
}
|
|
19
|
-
}
|
|
16
|
+
}).sort((a, b) => compareScopeKeys(a[0], b[0]));
|
|
17
|
+
for (const [scopeKey, scopeImports] of scopeEntries) {
|
|
18
|
+
if (!referrer.toString().startsWith(scopeKey)) {
|
|
19
|
+
continue;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (url) {
|
|
25
|
-
return [url, true];
|
|
21
|
+
const mapped2 = resolveWith(cleanSpecifier, scopeImports ?? {});
|
|
22
|
+
if (mapped2) {
|
|
23
|
+
return [normalizeUrl(baseURL, mapped2) + query + hash, true];
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
|
-
|
|
26
|
+
const mapped = resolveWith(cleanSpecifier, importMap.imports);
|
|
27
|
+
if (mapped) {
|
|
28
|
+
return [normalizeUrl(baseURL, mapped) + query + hash, true];
|
|
29
|
+
}
|
|
30
|
+
return [cleanSpecifier + query + hash, false];
|
|
29
31
|
}
|
|
30
|
-
function
|
|
31
|
-
if (specifier
|
|
32
|
+
function resolveWith(specifier, imports) {
|
|
33
|
+
if (imports[specifier]) {
|
|
32
34
|
return imports[specifier];
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
if (!specifier.includes("/")) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const prefixKeys = Object.keys(imports).filter((k) => k.endsWith("/") && specifier.startsWith(k)).sort((a, b) => b.length - a.length || (a < b ? 1 : -1));
|
|
40
|
+
for (const key of prefixKeys) {
|
|
41
|
+
const value = imports[key];
|
|
42
|
+
if (value && value.endsWith("/")) {
|
|
43
|
+
return value + specifier.slice(key.length);
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
|
-
return
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function compareScopeKeys(a, b) {
|
|
49
|
+
const aSlashCount = a.split("/").length;
|
|
50
|
+
const bSlashCount = b.split("/").length;
|
|
51
|
+
if (aSlashCount !== bSlashCount) {
|
|
52
|
+
return bSlashCount - aSlashCount;
|
|
53
|
+
}
|
|
54
|
+
return a < b ? 1 : -1;
|
|
55
|
+
}
|
|
56
|
+
function normalizeUrl(baseURL, path) {
|
|
57
|
+
if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) {
|
|
58
|
+
return new URL(path, baseURL).toString();
|
|
59
|
+
}
|
|
60
|
+
return path;
|
|
43
61
|
}
|
|
44
62
|
export {
|
|
45
63
|
resolve
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED