@jsenv/import-map
Version:
Helpers to implement importmaps.
125 lines (114 loc) • 3.46 kB
JavaScript
import { createDetailedMessage } from "@jsenv/logger"
import { assertImportMap } from "./internal/assertImportMap.js"
import { hasScheme } from "./internal/hasScheme.js"
import { tryUrlResolution } from "./internal/tryUrlResolution.js"
import { resolveSpecifier } from "./resolveSpecifier.js"
export const applyImportMap = ({
importMap,
specifier,
importer,
createBareSpecifierError = ({ specifier, importer }) => {
return new Error(createDetailedMessage(`Unmapped bare specifier.`, { specifier, importer }))
},
onImportMapping = () => {},
}) => {
assertImportMap(importMap)
if (typeof specifier !== "string") {
throw new TypeError(
createDetailedMessage("specifier must be a string.", {
specifier,
importer,
}),
)
}
if (importer) {
if (typeof importer !== "string") {
throw new TypeError(
createDetailedMessage("importer must be a string.", {
importer,
specifier,
}),
)
}
if (!hasScheme(importer)) {
throw new Error(
createDetailedMessage(`importer must be an absolute url.`, {
importer,
specifier,
}),
)
}
}
const specifierUrl = resolveSpecifier(specifier, importer)
const specifierNormalized = specifierUrl || specifier
const { scopes } = importMap
if (scopes && importer) {
const scopeSpecifierMatching = Object.keys(scopes).find((scopeSpecifier) => {
return scopeSpecifier === importer || specifierIsPrefixOf(scopeSpecifier, importer)
})
if (scopeSpecifierMatching) {
const scopeMappings = scopes[scopeSpecifierMatching]
const mappingFromScopes = applyMappings(
scopeMappings,
specifierNormalized,
scopeSpecifierMatching,
onImportMapping,
)
if (mappingFromScopes !== null) {
return mappingFromScopes
}
}
}
const { imports } = importMap
if (imports) {
const mappingFromImports = applyMappings(
imports,
specifierNormalized,
undefined,
onImportMapping,
)
if (mappingFromImports !== null) {
return mappingFromImports
}
}
if (specifierUrl) {
return specifierUrl
}
throw createBareSpecifierError({ specifier, importer })
}
const applyMappings = (mappings, specifierNormalized, scope, onImportMapping) => {
const specifierCandidates = Object.keys(mappings)
let i = 0
while (i < specifierCandidates.length) {
const specifierCandidate = specifierCandidates[i]
i++
if (specifierCandidate === specifierNormalized) {
const address = mappings[specifierCandidate]
onImportMapping({
scope,
from: specifierCandidate,
to: address,
before: specifierNormalized,
after: address,
})
return address
}
if (specifierIsPrefixOf(specifierCandidate, specifierNormalized)) {
const address = mappings[specifierCandidate]
const afterSpecifier = specifierNormalized.slice(specifierCandidate.length)
const addressFinal = tryUrlResolution(afterSpecifier, address)
onImportMapping({
scope,
from: specifierCandidate,
to: address,
before: specifierNormalized,
after: addressFinal,
})
return addressFinal
}
}
return null
}
const specifierIsPrefixOf = (specifierHref, href) => {
return specifierHref[specifierHref.length - 1] === "/" && href.startsWith(specifierHref)
}