@jsenv/import-map
Advanced tools
Comparing version 5.5.0 to 6.2.0
@@ -5,413 +5,374 @@ 'use strict'; | ||
var nativeTypeOf = function nativeTypeOf(obj) { | ||
return typeof obj; | ||
}; | ||
var customTypeOf = function customTypeOf(obj) { | ||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; | ||
}; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? nativeTypeOf : customTypeOf; | ||
var assertImportMap = function assertImportMap(value) { | ||
const assertImportMap = value => { | ||
if (value === null) { | ||
throw new TypeError("an importMap must be an object, got null"); | ||
throw new TypeError(`an importMap must be an object, got null`); | ||
} | ||
var type = _typeof(value); | ||
const type = typeof value; | ||
if (type !== "object") { | ||
throw new TypeError("an importMap must be an object, received ".concat(value)); | ||
throw new TypeError(`an importMap must be an object, received ${value}`); | ||
} | ||
if (Array.isArray(value)) { | ||
throw new TypeError("an importMap must be an object, received array ".concat(value)); | ||
throw new TypeError(`an importMap must be an object, received array ${value}`); | ||
} | ||
}; | ||
// "https://domain.com/folder/file.js" | ||
// "file:///folder/file.js" | ||
// "chrome://folder/file.js" | ||
var isAbsoluteSpecifier = function isAbsoluteSpecifier(specifier) { | ||
if (isWindowsDriveSpecifier(specifier)) { | ||
// window drive letter could are not protocol yep | ||
// something like `C:/folder/file.js` | ||
// will be considered as a bare import | ||
return false; | ||
} | ||
const hasScheme = string => { | ||
return /^[a-zA-Z]{2,}:/.test(string); | ||
}; | ||
return /^[a-zA-Z]+:/.test(specifier); | ||
}; // https://url.spec.whatwg.org/#example-start-with-a-widows-drive-letter | ||
var isWindowsDriveSpecifier = function isWindowsDriveSpecifier(specifier) { | ||
var firstChar = specifier[0]; | ||
if (!/[a-zA-Z]/.test(firstChar)) return false; | ||
var secondChar = specifier[1]; | ||
if (secondChar !== ":") return false; | ||
var thirdChar = specifier[2]; | ||
return thirdChar === "/"; | ||
const hrefToScheme = href => { | ||
const colonIndex = href.indexOf(":"); | ||
if (colonIndex === -1) return ""; | ||
return href.slice(0, colonIndex); | ||
}; | ||
var resolveAbsoluteSpecifier = function resolveAbsoluteSpecifier(specifier) { | ||
return specifier; | ||
const hrefToPathname = href => { | ||
return ressourceToPathname(hrefToRessource(href)); | ||
}; | ||
var assertUrlLike = function assertUrlLike(value) { | ||
var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "specifier"; | ||
const hrefToRessource = href => { | ||
const scheme = hrefToScheme(href); | ||
if (typeof value !== "string") { | ||
throw new TypeError("".concat(name, " must be a string, got ").concat(value)); | ||
if (scheme === "file") { | ||
return href.slice("file://".length); | ||
} | ||
if (!isAbsoluteSpecifier(value)) { | ||
throw new Error("".concat(name, " must be a url and no scheme found, got ").concat(value)); | ||
if (scheme === "https" || scheme === "http") { | ||
// remove origin | ||
const afterProtocol = href.slice(scheme.length + "://".length); | ||
const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length); | ||
return afterProtocol.slice(pathnameSlashIndex); | ||
} | ||
return href.slice(scheme.length + 1); | ||
}; | ||
var applyImportMap = function applyImportMap(_ref) { | ||
var importMap = _ref.importMap, | ||
href = _ref.href, | ||
importerHref = _ref.importerHref; | ||
assertImportMap(importMap); | ||
assertUrlLike(href, "href"); | ||
const ressourceToPathname = ressource => { | ||
const searchSeparatorIndex = ressource.indexOf("?"); | ||
return searchSeparatorIndex === -1 ? ressource : ressource.slice(0, searchSeparatorIndex); | ||
}; | ||
if (importerHref) { | ||
assertUrlLike(importerHref, "importerHref"); | ||
} | ||
const hrefToOrigin = href => { | ||
const scheme = hrefToScheme(href); | ||
var scopes = importMap.scopes; | ||
if (scopes && importerHref) { | ||
var scopeKeyMatching = Object.keys(scopes).find(function (scopeKey) { | ||
return scopeKey === importerHref || specifierIsPrefixOf(scopeKey, importerHref); | ||
}); | ||
if (scopeKeyMatching) { | ||
var scopeValue = scopes[scopeKeyMatching]; | ||
var remappingFromScopeImports = applyImports(href, scopeValue); | ||
if (remappingFromScopeImports !== null) { | ||
return remappingFromScopeImports; | ||
} | ||
} | ||
if (scheme === "file") { | ||
return "file://"; | ||
} | ||
var imports = importMap.imports; | ||
if (imports) { | ||
var remappingFromImports = applyImports(href, imports); | ||
if (remappingFromImports !== null) { | ||
return remappingFromImports; | ||
} | ||
if (scheme === "http" || scheme === "https") { | ||
const secondProtocolSlashIndex = scheme.length + "://".length; | ||
const pathnameSlashIndex = href.indexOf("/", secondProtocolSlashIndex); | ||
if (pathnameSlashIndex === -1) return href; | ||
return href.slice(0, pathnameSlashIndex); | ||
} | ||
return href; | ||
return href.slice(0, scheme.length + 1); | ||
}; | ||
var applyImports = function applyImports(href, imports) { | ||
var importKeyArray = Object.keys(imports); | ||
var i = 0; | ||
const pathnameToDirname = pathname => { | ||
const slashLastIndex = pathname.lastIndexOf("/"); | ||
if (slashLastIndex === -1) return ""; | ||
return pathname.slice(0, slashLastIndex); | ||
}; | ||
while (i < importKeyArray.length) { | ||
var importKey = importKeyArray[i]; | ||
i++; | ||
if (importKey === href) { | ||
var importValue = imports[importKey]; | ||
return importValue; | ||
// could be useful: https://url.spec.whatwg.org/#url-miscellaneous | ||
const resolveUrl = (specifier, baseUrl) => { | ||
if (baseUrl) { | ||
if (typeof baseUrl !== "string") { | ||
throw new TypeError(writeBaseUrlMustBeAString({ | ||
baseUrl, | ||
specifier | ||
})); | ||
} | ||
if (specifierIsPrefixOf(importKey, href)) { | ||
var _importValue = imports[importKey]; | ||
var afterImportKey = href.slice(importKey.length); | ||
return _importValue + afterImportKey; | ||
if (!hasScheme(baseUrl)) { | ||
throw new Error(writeBaseUrlMustBeAbsolute({ | ||
baseUrl, | ||
specifier | ||
})); | ||
} | ||
} | ||
return null; | ||
}; | ||
var specifierIsPrefixOf = function specifierIsPrefixOf(specifierHref, href) { | ||
return specifierHref[specifierHref.length - 1] === "/" && href.startsWith(specifierHref); | ||
}; | ||
var defineProperty = (function (obj, key, value) { | ||
// Shortcircuit the slow defineProperty path when possible. | ||
// We are trying to avoid issues where setters defined on the | ||
// prototype cause side effects under the fast path of simple | ||
// assignment. By checking for existence of the property with | ||
// the in operator, we can optimize most of this overhead away. | ||
if (key in obj) { | ||
Object.defineProperty(obj, key, { | ||
value: value, | ||
enumerable: true, | ||
configurable: true, | ||
writable: true | ||
}); | ||
} else { | ||
obj[key] = value; | ||
if (hasScheme(specifier)) { | ||
return specifier; | ||
} | ||
return obj; | ||
}); | ||
if (!baseUrl) { | ||
throw new Error(writeBaseUrlRequired({ | ||
baseUrl, | ||
specifier | ||
})); | ||
} // scheme relative | ||
function _objectSpread (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
// eslint-disable-next-line prefer-rest-params | ||
var source = arguments[i] === null ? {} : arguments[i]; | ||
if (i % 2) { | ||
// eslint-disable-next-line no-loop-func | ||
ownKeys(source, true).forEach(function (key) { | ||
defineProperty(target, key, source[key]); | ||
}); | ||
} else if (Object.getOwnPropertyDescriptors) { | ||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); | ||
} else { | ||
// eslint-disable-next-line no-loop-func | ||
ownKeys(source).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
} | ||
if (specifier.slice(0, 2) === "//") { | ||
return `${hrefToScheme(baseUrl)}:${specifier}`; | ||
} // origin relative | ||
return target; | ||
} // This function is different to "Reflect.ownKeys". The enumerableOnly | ||
// filters on symbol properties only. Returned string properties are always | ||
// enumerable. It is good to use in objectSpread. | ||
function ownKeys(object, enumerableOnly) { | ||
var keys = Object.keys(object); | ||
if (Object.getOwnPropertySymbols) { | ||
var symbols = Object.getOwnPropertySymbols(object); | ||
if (enumerableOnly) symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
}); // eslint-disable-next-line prefer-spread | ||
keys.push.apply(keys, symbols); | ||
if (specifier[0] === "/") { | ||
return `${hrefToOrigin(baseUrl)}${specifier}`; | ||
} | ||
return keys; | ||
} | ||
const baseOrigin = hrefToOrigin(baseUrl); | ||
const basePathname = hrefToPathname(baseUrl); // pathname relative inside | ||
var composeTwoImportMaps = function composeTwoImportMaps(leftImportMap, rightImportMap) { | ||
assertImportMap(leftImportMap); | ||
assertImportMap(rightImportMap); | ||
return { | ||
imports: composeTwoImports(leftImportMap.imports, rightImportMap.imports), | ||
scopes: composeTwoScopes(leftImportMap.scopes, rightImportMap.scopes) | ||
}; | ||
}; | ||
if (specifier.slice(0, 2) === "./") { | ||
const baseDirname = pathnameToDirname(basePathname); | ||
return `${baseOrigin}${baseDirname}/${specifier.slice(2)}`; | ||
} // pathname relative outside | ||
var composeTwoImports = function composeTwoImports() { | ||
var leftImports = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var rightImports = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return _objectSpread({}, leftImports, {}, rightImports); | ||
}; | ||
var composeTwoScopes = function composeTwoScopes() { | ||
var leftScopes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var rightScopes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
if (specifier.slice(0, 3) === "../") { | ||
let unresolvedPathname = specifier; | ||
const importerFolders = basePathname.split("/"); | ||
importerFolders.pop(); | ||
var scopes = _objectSpread({}, leftScopes); | ||
while (unresolvedPathname.slice(0, 3) === "../") { | ||
// when there is no folder left to resolved | ||
// we just ignore '../' | ||
if (importerFolders.length) { | ||
importerFolders.pop(); | ||
} | ||
Object.keys(rightScopes).forEach(function (scopeKey) { | ||
if (scopes.hasOwnProperty(scopeKey)) { | ||
scopes[scopeKey] = _objectSpread({}, scopes[scopeKey], {}, rightScopes[scopeKey]); | ||
} else { | ||
scopes[scopeKey] = _objectSpread({}, rightScopes[scopeKey]); | ||
unresolvedPathname = unresolvedPathname.slice(3); | ||
} | ||
}); | ||
return scopes; | ||
}; | ||
var hrefToScheme = function hrefToScheme(href) { | ||
var colonIndex = href.indexOf(":"); | ||
if (colonIndex === -1) return ""; | ||
return href.slice(0, colonIndex); | ||
}; | ||
const resolvedPathname = `${importerFolders.join("/")}/${unresolvedPathname}`; | ||
return `${baseOrigin}${resolvedPathname}`; | ||
} // bare | ||
var hrefToOrigin = function hrefToOrigin(href) { | ||
var scheme = hrefToScheme(href); | ||
if (scheme === "file") { | ||
return "file://"; | ||
if (basePathname === "") { | ||
return `${baseOrigin}/${specifier}`; | ||
} | ||
if (scheme === "http" || scheme === "https") { | ||
var secondProtocolSlashIndex = scheme.length + "://".length; | ||
var pathnameSlashIndex = href.indexOf("/", secondProtocolSlashIndex); | ||
if (pathnameSlashIndex === -1) return href; | ||
return href.slice(0, pathnameSlashIndex); | ||
if (basePathname[basePathname.length] === "/") { | ||
return `${baseOrigin}${basePathname}${specifier}`; | ||
} | ||
return href.slice(0, scheme.length + 1); | ||
return `${baseOrigin}${pathnameToDirname(basePathname)}/${specifier}`; | ||
}; | ||
var hrefToPathname = function hrefToPathname(href) { | ||
return ressourceToPathname(hrefToRessource(href)); | ||
const writeBaseUrlMustBeAString = ({ | ||
baseUrl, | ||
specifier | ||
}) => `baseUrl must be a string. | ||
--- base url --- | ||
${baseUrl} | ||
--- specifier --- | ||
${specifier}`; | ||
const writeBaseUrlMustBeAbsolute = ({ | ||
baseUrl, | ||
specifier | ||
}) => `baseUrl must be absolute. | ||
--- base url --- | ||
${baseUrl} | ||
--- specifier --- | ||
${specifier}`; | ||
const writeBaseUrlRequired = ({ | ||
baseUrl, | ||
specifier | ||
}) => `baseUrl required to resolve relative specifier. | ||
--- base url --- | ||
${baseUrl} | ||
--- specifier --- | ||
${specifier}`; | ||
const tryUrlResolution = (string, url) => { | ||
const result = resolveUrl(string, url); | ||
return hasScheme(result) ? result : null; | ||
}; | ||
var hrefToRessource = function hrefToRessource(href) { | ||
var scheme = hrefToScheme(href); | ||
if (scheme === "file") { | ||
return href.slice("file://".length); | ||
const resolveSpecifier = (specifier, importer) => { | ||
if (specifier[0] === "/" || specifier.startsWith("./") || specifier.startsWith("../")) { | ||
return resolveUrl(specifier, importer); | ||
} | ||
if (scheme === "https" || scheme === "http") { | ||
// remove origin | ||
var afterProtocol = href.slice(scheme.length + "://".length); | ||
var pathnameSlashIndex = afterProtocol.indexOf("/", "://".length); | ||
return afterProtocol.slice(pathnameSlashIndex); | ||
if (hasScheme(specifier)) { | ||
return specifier; | ||
} | ||
return href.slice(scheme.length + 1); | ||
return null; | ||
}; | ||
var ressourceToPathname = function ressourceToPathname(ressource) { | ||
var searchSeparatorIndex = ressource.indexOf("?"); | ||
return searchSeparatorIndex === -1 ? ressource : ressource.slice(0, searchSeparatorIndex); | ||
}; | ||
const applyImportMap = ({ | ||
importMap, | ||
specifier, | ||
importer | ||
}) => { | ||
assertImportMap(importMap); | ||
var pathnameToDirname = function pathnameToDirname(pathname) { | ||
var slashLastIndex = pathname.lastIndexOf("/"); | ||
if (slashLastIndex === -1) return ""; | ||
return pathname.slice(0, slashLastIndex); | ||
}; | ||
if (typeof specifier !== "string") { | ||
throw new TypeError(writeSpecifierMustBeAString({ | ||
specifier, | ||
importer | ||
})); | ||
} | ||
var pathnameToExtension = function pathnameToExtension(pathname) { | ||
var slashLastIndex = pathname.lastIndexOf("/"); | ||
if (importer) { | ||
if (typeof importer !== "string") { | ||
throw new TypeError(writeImporterMustBeAString({ | ||
importer, | ||
specifier | ||
})); | ||
} | ||
if (slashLastIndex !== -1) { | ||
pathname = pathname.slice(slashLastIndex + 1); | ||
if (!hasScheme(importer)) { | ||
throw new Error(writeImporterMustBeAbsolute({ | ||
importer, | ||
specifier | ||
})); | ||
} | ||
} | ||
var dotLastIndex = pathname.lastIndexOf("."); | ||
if (dotLastIndex === -1) return ""; // if (dotLastIndex === pathname.length - 1) return "" | ||
const specifierUrl = resolveSpecifier(specifier, importer); | ||
const specifierNormalized = specifierUrl || specifier; | ||
const { | ||
scopes | ||
} = importMap; | ||
return pathname.slice(dotLastIndex); | ||
}; | ||
if (scopes && importer) { | ||
const scopeKeyMatching = Object.keys(scopes).find(scopeKey => { | ||
return scopeKey === importer || specifierIsPrefixOf(scopeKey, importer); | ||
}); | ||
var pathnameToRelativePath = function pathnameToRelativePath(pathname, otherPathname) { | ||
return pathname.slice(otherPathname.length); | ||
}; | ||
if (scopeKeyMatching) { | ||
const scopeValue = scopes[scopeKeyMatching]; | ||
const remappingFromScopeImports = applyImports(specifierNormalized, scopeValue); | ||
var isSchemeRelativeSpecifier = function isSchemeRelativeSpecifier(specifier) { | ||
return specifier.slice(0, 2) === "//"; | ||
}; | ||
var resolveSchemeRelativeSpecifier = function resolveSchemeRelativeSpecifier(specifier, importer) { | ||
return "".concat(hrefToScheme(importer), ":").concat(specifier); | ||
}; | ||
if (remappingFromScopeImports !== null) { | ||
return remappingFromScopeImports; | ||
} | ||
} | ||
} | ||
var isOriginRelativeSpecifier = function isOriginRelativeSpecifier(specifier) { | ||
var firstChar = specifier[0]; | ||
if (firstChar !== "/") return false; | ||
var secondChar = specifier[1]; | ||
if (secondChar === "/") return false; | ||
return true; | ||
}; | ||
var resolveOriginRelativeSpecifier = function resolveOriginRelativeSpecifier(specifier, importer) { | ||
var importerOrigin = hrefToOrigin(importer); | ||
return "".concat(importerOrigin, "/").concat(specifier.slice(1)); | ||
}; | ||
const { | ||
imports | ||
} = importMap; | ||
// https://github.com/systemjs/systemjs/blob/master/src/common.js | ||
// "../folder/file.js" | ||
if (imports) { | ||
const remappingFromImports = applyImports(specifierNormalized, imports); | ||
var isPathnameRelativeSpecifier = function isPathnameRelativeSpecifier(specifier) { | ||
if (specifier.slice(0, 2) === "./") return true; | ||
if (specifier.slice(0, 3) === "../") return true; | ||
return false; | ||
}; | ||
var resolvePathnameRelativeSpecifier = function resolvePathnameRelativeSpecifier(specifier, importer) { | ||
var importerPathname = hrefToPathname(importer); // ./foo.js on /folder/file.js -> /folder/foo.js | ||
// ./foo/bar.js on /folder/file.js -> /folder/foo/bar.js | ||
// ./foo.js on /folder/subfolder/file.js -> /folder/subfolder/foo.js | ||
if (remappingFromImports !== null) { | ||
return remappingFromImports; | ||
} | ||
} | ||
if (specifier.slice(0, 2) === "./") { | ||
var _importerOrigin = hrefToOrigin(importer); | ||
if (specifierUrl) { | ||
return specifierUrl; | ||
} | ||
var importerDirname = pathnameToDirname(importerPathname); | ||
return "".concat(_importerOrigin).concat(importerDirname, "/").concat(specifier.slice(2)); | ||
} // ../foo/bar.js on /folder/file.js -> /foo/bar.js | ||
// ../foo/bar.js on /folder/subfolder/file.js -> /folder/foo/bar.js | ||
// ../../foo/bar.js on /folder/file.js -> /foo/bar.js | ||
// ../bar.js on / -> /bar.js | ||
throw new Error(writeBareSpecifierMustBeRemapped({ | ||
specifier, | ||
importer | ||
})); | ||
}; | ||
const applyImports = (specifier, imports) => { | ||
const importKeyArray = Object.keys(imports); | ||
let i = 0; | ||
var unresolvedPathname = specifier; | ||
var importerFolders = importerPathname.split("/"); | ||
importerFolders.pop(); // remove file, it is not a folder | ||
while (i < importKeyArray.length) { | ||
const importKey = importKeyArray[i]; | ||
i++; | ||
while (unresolvedPathname.slice(0, 3) === "../") { | ||
// when there is no folder left to resolved | ||
// we just ignore '../' | ||
if (importerFolders.length) { | ||
importerFolders.pop(); | ||
if (importKey === specifier) { | ||
const importValue = imports[importKey]; | ||
return importValue; | ||
} | ||
unresolvedPathname = unresolvedPathname.slice(3); | ||
if (specifierIsPrefixOf(importKey, specifier)) { | ||
const importValue = imports[importKey]; | ||
const afterImportKey = specifier.slice(importKey.length); | ||
return tryUrlResolution(afterImportKey, importValue); | ||
} | ||
} | ||
var importerOrigin = hrefToOrigin(importer); | ||
var resolvedPathname = "".concat(importerFolders.join("/"), "/").concat(unresolvedPathname); | ||
return "".concat(importerOrigin).concat(resolvedPathname); | ||
return null; | ||
}; | ||
var isBareSpecifier = function isBareSpecifier(specifier) { | ||
if (isAbsoluteSpecifier(specifier)) return false; | ||
if (isSchemeRelativeSpecifier(specifier)) return false; | ||
if (isOriginRelativeSpecifier(specifier)) return false; | ||
if (isPathnameRelativeSpecifier(specifier)) return false; | ||
return true; | ||
const specifierIsPrefixOf = (specifierHref, href) => { | ||
return specifierHref[specifierHref.length - 1] === "/" && href.startsWith(specifierHref); | ||
}; | ||
var resolveBareSpecifier = function resolveBareSpecifier(specifier, importer) { | ||
var importerOrigin = hrefToOrigin(importer); | ||
return "".concat(importerOrigin, "/").concat(specifier); | ||
}; | ||
// could be useful: https://url.spec.whatwg.org/#url-miscellaneous | ||
var resolveSpecifier = function resolveSpecifier(specifier, importer) { | ||
if (isAbsoluteSpecifier(specifier)) { | ||
return resolveAbsoluteSpecifier(specifier); | ||
} | ||
const writeSpecifierMustBeAString = ({ | ||
specifier, | ||
importer | ||
}) => `specifier must be a string. | ||
--- specifier --- | ||
${specifier} | ||
--- importer --- | ||
${importer}`; | ||
if (!importer) { | ||
throw new Error(createMissingImporterMessage(specifier, importer)); | ||
} | ||
const writeImporterMustBeAString = ({ | ||
importer, | ||
specifier | ||
}) => `importer must be a string. | ||
--- importer --- | ||
${importer} | ||
--- specifier --- | ||
${specifier}`; | ||
if (!isAbsoluteSpecifier(importer)) { | ||
throw new Error(createAbsoluteImporterRequiredMessage(importer)); | ||
} | ||
const writeImporterMustBeAbsolute = ({ | ||
importer, | ||
specifier | ||
}) => `importer must be an absolute url. | ||
--- importer --- | ||
${importer} | ||
--- specifier --- | ||
${specifier}`; | ||
if (isSchemeRelativeSpecifier(specifier)) { | ||
return resolveSchemeRelativeSpecifier(specifier, importer); | ||
} | ||
const writeBareSpecifierMustBeRemapped = ({ | ||
specifier, | ||
importer | ||
}) => `Unmapped bare specifier. | ||
--- specifier --- | ||
${specifier} | ||
--- importer --- | ||
${importer}`; | ||
if (isOriginRelativeSpecifier(specifier)) { | ||
return resolveOriginRelativeSpecifier(specifier, importer); | ||
} | ||
if (isPathnameRelativeSpecifier(specifier)) { | ||
return resolvePathnameRelativeSpecifier(specifier, importer); | ||
} | ||
return resolveBareSpecifier(specifier, importer); | ||
// https://github.com/systemjs/systemjs/blob/89391f92dfeac33919b0223bbf834a1f4eea5750/src/common.js#L136 | ||
const composeTwoImportMaps = (leftImportMap, rightImportMap) => { | ||
assertImportMap(leftImportMap); | ||
assertImportMap(rightImportMap); | ||
return { | ||
imports: composeTwoImports(leftImportMap.imports, rightImportMap.imports), | ||
scopes: composeTwoScopes(leftImportMap.scopes, rightImportMap.scopes) | ||
}; | ||
}; | ||
var createMissingImporterMessage = function createMissingImporterMessage(specifier, importer) { | ||
return "missing importer to resolve relative specifier.\n--- specifier ---\n".concat(specifier, "\n--- importer ---\n").concat(importer); | ||
const composeTwoImports = (leftImports = {}, rightImports = {}) => { | ||
return { ...leftImports, | ||
...rightImports | ||
}; | ||
}; | ||
var createAbsoluteImporterRequiredMessage = function createAbsoluteImporterRequiredMessage(importer) { | ||
return "importer must be absolute.\n--- importer ---\n".concat(importer); | ||
const composeTwoScopes = (leftScopes = {}, rightScopes = {}) => { | ||
const scopes = { ...leftScopes | ||
}; | ||
Object.keys(rightScopes).forEach(scopeKey => { | ||
if (scopes.hasOwnProperty(scopeKey)) { | ||
scopes[scopeKey] = { ...scopes[scopeKey], | ||
...rightScopes[scopeKey] | ||
}; | ||
} else { | ||
scopes[scopeKey] = { ...rightScopes[scopeKey] | ||
}; | ||
} | ||
}); | ||
return scopes; | ||
}; | ||
var sortImportMap = function sortImportMap(importMap) { | ||
const sortImportMap = importMap => { | ||
assertImportMap(importMap); | ||
var imports = importMap.imports, | ||
scopes = importMap.scopes; | ||
const { | ||
imports, | ||
scopes | ||
} = importMap; | ||
return { | ||
@@ -422,5 +383,5 @@ imports: imports ? sortImports(imports) : undefined, | ||
}; | ||
var sortImports = function sortImports(imports) { | ||
var importsSorted = {}; | ||
Object.keys(imports).sort(compareLengthOrLocaleCompare).forEach(function (name) { | ||
const sortImports = imports => { | ||
const importsSorted = {}; | ||
Object.keys(imports).sort(compareLengthOrLocaleCompare).forEach(name => { | ||
importsSorted[name] = imports[name]; | ||
@@ -430,5 +391,5 @@ }); | ||
}; | ||
var sortScopes = function sortScopes(scopes) { | ||
var scopesSorted = {}; | ||
Object.keys(scopes).sort(compareLengthOrLocaleCompare).forEach(function (scopeName) { | ||
const sortScopes = scopes => { | ||
const scopesSorted = {}; | ||
Object.keys(scopes).sort(compareLengthOrLocaleCompare).forEach(scopeName => { | ||
scopesSorted[scopeName] = sortImports(scopes[scopeName]); | ||
@@ -439,28 +400,60 @@ }); | ||
var compareLengthOrLocaleCompare = function compareLengthOrLocaleCompare(a, b) { | ||
const compareLengthOrLocaleCompare = (a, b) => { | ||
return b.length - a.length || a.localeCompare(b); | ||
}; | ||
var normalizeImportMap = function normalizeImportMap(importMap, href) { | ||
const normalizeImportMap = (importMap, baseUrl) => { | ||
assertImportMap(importMap); | ||
if (typeof href !== "string") { | ||
throw new TypeError("href must be a string, got ".concat(href)); | ||
if (typeof baseUrl !== "string") { | ||
throw new TypeError(formulateBaseUrlMustBeAString({ | ||
baseUrl | ||
})); | ||
} | ||
var imports = importMap.imports, | ||
scopes = importMap.scopes; | ||
const { | ||
imports, | ||
scopes | ||
} = importMap; | ||
return { | ||
imports: imports ? normalizeImports(imports, href) : undefined, | ||
scopes: scopes ? normalizeScopes(scopes, href) : undefined | ||
imports: imports ? normalizeImports(imports, baseUrl) : undefined, | ||
scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined | ||
}; | ||
}; | ||
var normalizeImports = function normalizeImports(imports, href) { | ||
var importsNormalized = {}; | ||
Object.keys(imports).forEach(function (importKey) { | ||
var importValue = imports[importKey]; | ||
var importKeyNormalized = resolveSpecifier(importKey, href); | ||
var importValueNormalized = resolveSpecifier(importValue, href); | ||
importsNormalized[importKeyNormalized] = importValueNormalized; | ||
const normalizeImports = (imports, baseUrl) => { | ||
const importsNormalized = {}; | ||
Object.keys(imports).forEach(specifier => { | ||
const address = imports[specifier]; | ||
if (typeof address !== "string") { | ||
console.warn(formulateAddressMustBeAString({ | ||
address, | ||
specifier | ||
})); | ||
return; | ||
} | ||
const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier; | ||
const addressUrl = tryUrlResolution(address, baseUrl); | ||
if (addressUrl === null) { | ||
console.warn(formulateAdressResolutionFailed({ | ||
address, | ||
baseUrl, | ||
specifier | ||
})); | ||
return; | ||
} | ||
if (specifier.endsWith("/") && !addressUrl.endsWith("/")) { | ||
console.warn(formulateAddressUrlRequiresTrailingSlash({ | ||
addressUrl, | ||
address, | ||
specifier | ||
})); | ||
return; | ||
} | ||
importsNormalized[specifierResolved] = addressUrl; | ||
}); | ||
@@ -470,9 +463,18 @@ return sortImports(importsNormalized); | ||
var normalizeScopes = function normalizeScopes(scopes, href) { | ||
var scopesNormalized = {}; | ||
Object.keys(scopes).forEach(function (scopeKey) { | ||
var scopeValue = scopes[scopeKey]; | ||
var scopeKeyNormalized = resolveSpecifier(scopeKey, href); | ||
var scopeValueNormalized = normalizeImports(scopeValue, href); | ||
scopesNormalized[scopeKeyNormalized] = scopeValueNormalized; | ||
const normalizeScopes = (scopes, baseUrl) => { | ||
const scopesNormalized = {}; | ||
Object.keys(scopes).forEach(scope => { | ||
const scopeValue = scopes[scope]; | ||
const scopeUrl = tryUrlResolution(scope, baseUrl); | ||
if (scopeUrl === null) { | ||
console.warn(formulateScopeResolutionFailed({ | ||
scope, | ||
baseUrl | ||
})); | ||
return; | ||
} | ||
const scopeValueNormalized = normalizeImports(scopeValue, baseUrl); | ||
scopesNormalized[scopeUrl] = scopeValueNormalized; | ||
}); | ||
@@ -482,48 +484,117 @@ return sortScopes(scopesNormalized); | ||
var normalizeImportMapForProject = function normalizeImportMapForProject(importMap) { | ||
var httpResolutionOrigin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "http://fake_origin_unlikely_to_collide.ext"; | ||
const formulateBaseUrlMustBeAString = ({ | ||
baseUrl | ||
}) => `baseUrl must be a string. | ||
--- base url --- | ||
${baseUrl}`; | ||
const formulateAddressMustBeAString = ({ | ||
specifier, | ||
address | ||
}) => `Address must be a string. | ||
--- address --- | ||
${address} | ||
--- specifier --- | ||
${specifier}`; | ||
const formulateAdressResolutionFailed = ({ | ||
address, | ||
baseUrl, | ||
specifier | ||
}) => `Address url resolution failed. | ||
--- address --- | ||
${address} | ||
--- base url --- | ||
${baseUrl} | ||
--- specifier --- | ||
${specifier}`; | ||
const formulateAddressUrlRequiresTrailingSlash = ({ | ||
addressURL, | ||
address, | ||
specifier | ||
}) => `Address must end with /. | ||
--- address url --- | ||
${addressURL} | ||
--- address --- | ||
${address} | ||
--- specifier --- | ||
${specifier}`; | ||
const formulateScopeResolutionFailed = ({ | ||
scope, | ||
baseUrl | ||
}) => `Scope url resolution failed. | ||
--- scope --- | ||
${scope} | ||
--- base url --- | ||
${baseUrl}`; | ||
const normalizeImportMapForProject = (importMap, httpResolutionOrigin = "http://fake_origin_unlikely_to_collide.ext") => { | ||
return normalizeImportMap(importMap, httpResolutionOrigin); | ||
}; | ||
const pathnameToExtension = pathname => { | ||
const slashLastIndex = pathname.lastIndexOf("/"); | ||
if (slashLastIndex !== -1) { | ||
pathname = pathname.slice(slashLastIndex + 1); | ||
} | ||
const dotLastIndex = pathname.lastIndexOf("."); | ||
if (dotLastIndex === -1) return ""; // if (dotLastIndex === pathname.length - 1) return "" | ||
return pathname.slice(dotLastIndex); | ||
}; | ||
// directly target the files because this code | ||
var resolveImport = function resolveImport(_ref) { | ||
var specifier = _ref.specifier, | ||
importer = _ref.importer, | ||
importMap = _ref.importMap, | ||
_ref$defaultExtension = _ref.defaultExtension, | ||
defaultExtension = _ref$defaultExtension === void 0 ? true : _ref$defaultExtension; | ||
var specifierResolved = resolveSpecifier(specifier, importer); | ||
var specifierRemapped = importMap ? applyImportMap({ | ||
importMap: importMap, | ||
href: specifierResolved, | ||
importerHref: importer | ||
}) : specifierResolved; | ||
const resolveImport = ({ | ||
specifier, | ||
importer, | ||
importMap, | ||
defaultExtension = true | ||
}) => { | ||
return applyDefaultExtension({ | ||
url: importMap ? applyImportMap({ | ||
importMap, | ||
specifier, | ||
importer | ||
}) : resolveUrl(specifier, importer), | ||
importer, | ||
defaultExtension | ||
}); | ||
}; | ||
const applyDefaultExtension = ({ | ||
url, | ||
importer, | ||
defaultExtension | ||
}) => { | ||
if (typeof defaultExtension === "string") { | ||
var extension = pathnameToExtension(specifierRemapped); | ||
const extension = pathnameToExtension(url); | ||
if (extension === "") { | ||
return "".concat(specifierRemapped).concat(defaultExtension); | ||
return `${url}${defaultExtension}`; | ||
} | ||
return url; | ||
} | ||
if (defaultExtension === true) { | ||
var _extension = pathnameToExtension(specifierRemapped); | ||
const extension = pathnameToExtension(url); | ||
if (_extension === "") { | ||
if (importer) { | ||
var importerPathname = hrefToPathname(importer); | ||
var importerExtension = pathnameToExtension(importerPathname); | ||
return "".concat(specifierRemapped).concat(importerExtension); | ||
} | ||
if (extension === "" && importer) { | ||
const importerPathname = hrefToPathname(importer); | ||
const importerExtension = pathnameToExtension(importerPathname); | ||
return `${url}${importerExtension}`; | ||
} | ||
} | ||
return specifierRemapped; | ||
return url; | ||
}; | ||
var startsWithWindowsDriveLetter = function startsWithWindowsDriveLetter(string) { | ||
var firstChar = string[0]; | ||
const startsWithWindowsDriveLetter = string => { | ||
const firstChar = string[0]; | ||
if (!/[a-zA-Z]/.test(firstChar)) return false; | ||
var secondChar = string[1]; | ||
const secondChar = string[1]; | ||
if (secondChar !== ":") return false; | ||
@@ -533,9 +604,7 @@ return true; | ||
var replaceSlashesWithBackSlashes = function replaceSlashesWithBackSlashes(string) { | ||
return string.replace(/\//g, "\\"); | ||
}; | ||
const replaceSlashesWithBackSlashes = string => string.replace(/\//g, "\\"); | ||
var pathnameToOperatingSystemPath = function pathnameToOperatingSystemPath(pathname) { | ||
if (pathname[0] !== "/") throw new Error("pathname must start with /, got ".concat(pathname)); | ||
var pathnameWithoutLeadingSlash = pathname.slice(1); | ||
const pathnameToOperatingSystemPath = pathname => { | ||
if (pathname[0] !== "/") throw new Error(`pathname must start with /, got ${pathname}`); | ||
const pathnameWithoutLeadingSlash = pathname.slice(1); | ||
@@ -550,42 +619,40 @@ if (startsWithWindowsDriveLetter(pathnameWithoutLeadingSlash) && pathnameWithoutLeadingSlash[2] === "/") { | ||
var resolveImportForProject = function resolveImportForProject(_ref) { | ||
var projectPathname = _ref.projectPathname, | ||
specifier = _ref.specifier, | ||
_ref$importer = _ref.importer, | ||
importer = _ref$importer === void 0 ? projectPathname : _ref$importer, | ||
importMap = _ref.importMap, | ||
importDefaultExtension = _ref.importDefaultExtension, | ||
_ref$httpResolutionFo = _ref.httpResolutionForcing, | ||
httpResolutionForcing = _ref$httpResolutionFo === void 0 ? true : _ref$httpResolutionFo, | ||
_ref$httpResolutionOr = _ref.httpResolutionOrigin, | ||
httpResolutionOrigin = _ref$httpResolutionOr === void 0 ? "http://fake_origin_unlikely_to_collide.ext" : _ref$httpResolutionOr, | ||
_ref$insideProjectFor = _ref.insideProjectForcing, | ||
insideProjectForcing = _ref$insideProjectFor === void 0 ? true : _ref$insideProjectFor; | ||
const pathnameToRelativePath = (pathname, otherPathname) => pathname.slice(otherPathname.length); | ||
const resolveImportForProject = ({ | ||
projectPathname, | ||
specifier, | ||
importer = projectPathname, | ||
importMap, | ||
importDefaultExtension, | ||
httpResolutionForcing = true, | ||
httpResolutionOrigin = "http://fake_origin_unlikely_to_collide.ext", | ||
insideProjectForcing = true | ||
}) => { | ||
if (typeof projectPathname !== "string") { | ||
throw new TypeError("projectPathname must be a string, got ".concat(projectPathname)); | ||
throw new TypeError(`projectPathname must be a string, got ${projectPathname}`); | ||
} | ||
var projectHref = "file://".concat(projectPathname); | ||
var importerHref = importer || projectHref; | ||
const projectHref = `file://${projectPathname}`; | ||
const importerHref = importer || projectHref; | ||
if (insideProjectForcing && importer !== projectHref && hrefUseFileProtocol(importerHref) && !importerHref.startsWith("".concat(projectHref, "/"))) { | ||
throw new Error(createImporterMustBeInsideProjectMessage({ | ||
projectPathname: projectPathname, | ||
importer: importer | ||
if (insideProjectForcing && importer !== projectHref && hrefUseFileProtocol(importerHref) && !importerHref.startsWith(`${projectHref}/`)) { | ||
throw new Error(formulateImporterMustBeInsideProject({ | ||
projectPathname, | ||
importer | ||
})); | ||
} | ||
var importerForProject; | ||
let importerForProject; | ||
if (httpResolutionForcing) { | ||
if (importerHref === projectHref) { | ||
importerForProject = "".concat(httpResolutionOrigin); | ||
} else if (importerHref.startsWith("".concat(projectHref, "/"))) { | ||
var importerPathname = hrefToPathname(importerHref); | ||
var importerRelativePath = pathnameToRelativePath(importerPathname, projectPathname); // 99% of the time importer is an operating system path | ||
importerForProject = `${httpResolutionOrigin}`; | ||
} else if (importerHref.startsWith(`${projectHref}/`)) { | ||
const importerPathname = hrefToPathname(importerHref); | ||
const importerRelativePath = pathnameToRelativePath(importerPathname, projectPathname); // 99% of the time importer is an operating system path | ||
// here we ensure / is resolved against project by forcing an url resolution | ||
// prefixing with origin | ||
importerForProject = "".concat(httpResolutionOrigin).concat(importerRelativePath); | ||
importerForProject = `${httpResolutionOrigin}${importerRelativePath}`; | ||
} else { | ||
@@ -600,6 +667,6 @@ // there is already a scheme (http, https, file), keep it | ||
var importResolved = resolveImport({ | ||
specifier: specifier, | ||
const importResolved = resolveImport({ | ||
specifier, | ||
importer: importerForProject, | ||
importMap: importMap, | ||
importMap, | ||
defaultExtension: importDefaultExtension | ||
@@ -610,8 +677,8 @@ }); | ||
// it's ok to have an external import like "https://cdn.com/jquery.js" | ||
hrefUseFileProtocol(importResolved) && importResolved !== projectHref && !importResolved.startsWith("".concat(projectHref, "/"))) { | ||
throw new Error(createImportMustBeInsideProjectMessage({ | ||
projectPathname: projectPathname, | ||
specifier: specifier, | ||
importer: importer, | ||
importResolved: importResolved | ||
hrefUseFileProtocol(importResolved) && importResolved !== projectHref && !importResolved.startsWith(`${projectHref}/`)) { | ||
throw new Error(formulateImportMustBeInsideProject({ | ||
projectPathname, | ||
specifier, | ||
importer, | ||
importResolved | ||
})); | ||
@@ -621,4 +688,4 @@ } | ||
if (httpResolutionForcing && hrefToOrigin(importResolved) === httpResolutionOrigin) { | ||
var importRelativePath = hrefToPathname(importResolved); | ||
return "".concat(projectHref).concat(importRelativePath); | ||
const importRelativePath = hrefToPathname(importResolved); | ||
return `${projectHref}${importRelativePath}`; | ||
} | ||
@@ -629,30 +696,34 @@ | ||
var hrefUseFileProtocol = function hrefUseFileProtocol(specifier) { | ||
return specifier.startsWith("file://"); | ||
}; | ||
const hrefUseFileProtocol = specifier => specifier.startsWith("file://"); | ||
var createImporterMustBeInsideProjectMessage = function createImporterMustBeInsideProjectMessage(_ref2) { | ||
var projectPathname = _ref2.projectPathname, | ||
importer = _ref2.importer; | ||
return "importer must be inside project.\n --- importer ---\n ".concat(importer, "\n --- project ---\n ").concat(pathnameToOperatingSystemPath(projectPathname)); | ||
}; | ||
const formulateImporterMustBeInsideProject = ({ | ||
projectPathname, | ||
importer | ||
}) => `importer must be inside project. | ||
--- importer --- | ||
${importer} | ||
--- project --- | ||
${pathnameToOperatingSystemPath(projectPathname)}`; | ||
var createImportMustBeInsideProjectMessage = function createImportMustBeInsideProjectMessage(_ref3) { | ||
var projectPathname = _ref3.projectPathname, | ||
specifier = _ref3.specifier, | ||
importer = _ref3.importer, | ||
importResolved = _ref3.importResolved; | ||
return "import must be inside project.\n--- specifier ---\n".concat(specifier, "\n--- importer ---\n").concat(importer, "\n--- resolved import ---\n").concat(importResolved, "\n--- project path ---\n").concat(pathnameToOperatingSystemPath(projectPathname)); | ||
}; | ||
const formulateImportMustBeInsideProject = ({ | ||
projectPathname, | ||
specifier, | ||
importer, | ||
importResolved | ||
}) => `import must be inside project. | ||
--- specifier --- | ||
${specifier} | ||
--- importer --- | ||
${importer} | ||
--- resolved import --- | ||
${importResolved} | ||
--- project path --- | ||
${pathnameToOperatingSystemPath(projectPathname)}`; | ||
var resolveSpecifierForProject = function resolveSpecifierForProject(_ref) { | ||
var projectPathname = _ref.projectPathname, | ||
specifier = _ref.specifier, | ||
_ref$httpResolutionOr = _ref.httpResolutionOrigin, | ||
httpResolutionOrigin = _ref$httpResolutionOr === void 0 ? "http://fake_origin_unlikely_to_collide.ext" : _ref$httpResolutionOr; | ||
var specifierHttpResolved = resolveSpecifier(specifier, httpResolutionOrigin); | ||
const resolveSpecifierForProject = (specifier, projectPathname, httpResolutionOrigin = "http://fake_origin_unlikely_to_collide.ext") => { | ||
const specifierHttpResolved = resolveSpecifier(specifier, httpResolutionOrigin); | ||
if (hrefToOrigin(specifierHttpResolved) === httpResolutionOrigin) { | ||
var specifierRelativePath = hrefToPathname(specifierHttpResolved); | ||
return "file://".concat(projectPathname).concat(specifierRelativePath); | ||
const specifierRelativePath = hrefToPathname(specifierHttpResolved); | ||
return `file://${projectPathname}${specifierRelativePath}`; | ||
} | ||
@@ -663,15 +734,42 @@ | ||
var wrapImportMap = function wrapImportMap(importMap, folderRelativeName) { | ||
/** | ||
* wrapImportMap can be used to remap all your imports under a folder. | ||
* | ||
* It is used by jsenv to import from a compiled folder instead of source folder. | ||
* | ||
* wrapImportMap must preserve the import map order so that | ||
* applyImportMap can still match the most specific pattern first. | ||
* | ||
* Because order is directly connected to the pattern length | ||
* prefixing every pattern with something does not means we have to sort | ||
* the wrappedImportMap. | ||
* | ||
* However we must absolutely ensure if an import like '/' exists in imports | ||
* or scoped imports. It must remain the last when being prefixed. | ||
* | ||
*/ | ||
const wrapImportMap = (importMap, folderRelativeName, ensureInto = true) => { | ||
assertImportMap(importMap); | ||
if (typeof folderRelativeName !== "string") { | ||
throw new TypeError("folderRelativeName must be a string, got ".concat(folderRelativeName)); | ||
throw new TypeError(formulateFolderRelativeNameMustBeAString({ | ||
folderRelativeName | ||
})); | ||
} | ||
var into = "/".concat(folderRelativeName, "/"); | ||
var imports = importMap.imports, | ||
scopes = importMap.scopes; | ||
var importsForWrapping; | ||
var scopesForWrapping; | ||
const into = `/${folderRelativeName}/`; | ||
const { | ||
imports, | ||
scopes | ||
} = importMap; | ||
let importsForWrapping; | ||
if (imports) { | ||
importsForWrapping = wrapTopLevelImports(imports, into); | ||
} else { | ||
importsForWrapping = {}; | ||
} | ||
let scopesForWrapping; | ||
if (scopes) { | ||
@@ -683,16 +781,12 @@ scopesForWrapping = wrapScopes(scopes, into); | ||
if (imports) { | ||
importsForWrapping = wrapTopLevelImports(imports, into); | ||
scopesForWrapping[into] = wrapTopLevelImports(imports, into); | ||
} else { | ||
importsForWrapping = {}; | ||
scopesForWrapping[into] = {}; | ||
} // ensure anything not directly remapped is remapped inside into | ||
if (ensureInto) { | ||
// ensure anything not directly remapped is remapped inside into | ||
importsForWrapping[into] = into; | ||
importsForWrapping["/"] = into; // and when already into, you stay inside | ||
scopesForWrapping[into] = { | ||
[into]: into | ||
}; | ||
} | ||
importsForWrapping[into] = into; | ||
importsForWrapping["/"] = into; // and when already into, you stay inside | ||
scopesForWrapping[into][into] = into; | ||
scopesForWrapping[into]["/"] = into; | ||
return { | ||
@@ -704,99 +798,135 @@ imports: importsForWrapping, | ||
var wrapScopes = function wrapScopes(scopes, into) { | ||
var scopesKeyWrapped = {}; | ||
var scopesRemaining = {}; | ||
Object.keys(scopes).forEach(function (scopeKey) { | ||
var scopeValue = scopes[scopeKey]; | ||
var scopeKeyWrapped = wrapSpecifier(scopeKey, into); | ||
const wrapScopes = (scopes, into) => { | ||
const scopesWrapped = {}; | ||
Object.keys(scopes).forEach(scopeKey => { | ||
const scopeValue = scopes[scopeKey]; | ||
const scopeKeyWrapped = wrapAddress(scopeKey, into); | ||
const { | ||
importsWrapped, | ||
importsRemaining | ||
} = wrapImports(scopeValue, into); | ||
let scopeValueWrapped; | ||
if (scopeKeyWrapped === scopeKey) { | ||
scopesRemaining[scopeKey] = scopeValue; | ||
} else { | ||
var _wrapImports = wrapImports(scopeValue, into), | ||
importsWithKeyWrapped = _wrapImports.importsWithKeyWrapped, | ||
importsRemaining = _wrapImports.importsRemaining; | ||
if (scopeHasLeadingSlashScopedRemapping(scopeValue, scopeKey)) { | ||
const leadingSlashSpecifier = `${into}${scopeKey.slice(1)}`; | ||
scopeValueWrapped = {}; // put everything except the leading slash remapping | ||
var scopeValueWrapped; | ||
Object.keys(importsWrapped).forEach(importKeyWrapped => { | ||
if (importKeyWrapped === leadingSlashSpecifier || importKeyWrapped === into) { | ||
return; | ||
} | ||
if (scopeHasLeadingSlashScopedRemapping(scopeValue, scopeKey)) { | ||
var leadingSlashSpecifier = "".concat(into).concat(scopeKey.slice(1)); | ||
scopeValueWrapped = {}; // put everything except the leading slash remapping | ||
scopeValueWrapped[importKeyWrapped] = importsWrapped[importKeyWrapped]; | ||
}); | ||
Object.keys(importsRemaining).forEach(importKey => { | ||
if (importKey === scopeKey || importKey === "/") { | ||
return; | ||
} | ||
Object.keys(importsWithKeyWrapped).forEach(function (importKeyWrapped) { | ||
if (importKeyWrapped === leadingSlashSpecifier || importKeyWrapped === into) { | ||
return; | ||
} | ||
scopeValueWrapped[importKey] = importsRemaining[importKey]; | ||
}); // now put leading slash remapping to ensure it comes last | ||
scopeValueWrapped[importKeyWrapped] = importsWithKeyWrapped[importKeyWrapped]; | ||
}); | ||
Object.keys(importsRemaining).forEach(function (importKey) { | ||
if (importKey === scopeKey || importKey === "/") { | ||
return; | ||
} | ||
scopeValueWrapped[leadingSlashSpecifier] = leadingSlashSpecifier; | ||
scopeValueWrapped[scopeKey] = leadingSlashSpecifier; | ||
scopeValueWrapped[into] = leadingSlashSpecifier; | ||
scopeValueWrapped["/"] = leadingSlashSpecifier; | ||
} else { | ||
scopeValueWrapped = { ...importsWrapped, | ||
...importsRemaining | ||
}; | ||
} | ||
scopeValueWrapped[importKey] = importsRemaining[importKey]; | ||
}); // now put leading slash remapping to ensure it comes last | ||
scopesWrapped[scopeKeyWrapped] = scopeValueWrapped; | ||
scopeValueWrapped[leadingSlashSpecifier] = leadingSlashSpecifier; | ||
scopeValueWrapped[scopeKey] = leadingSlashSpecifier; | ||
scopeValueWrapped[into] = leadingSlashSpecifier; | ||
scopeValueWrapped["/"] = leadingSlashSpecifier; | ||
} else { | ||
scopeValueWrapped = _objectSpread({}, importsWithKeyWrapped, {}, importsRemaining); | ||
} | ||
scopesKeyWrapped[scopeKeyWrapped] = scopeValueWrapped; | ||
scopesRemaining[scopeKey] = importsRemaining; | ||
if (scopeKeyWrapped !== scopeKey) { | ||
scopesWrapped[scopeKey] = { ...scopeValueWrapped | ||
}; | ||
} | ||
}); | ||
return _objectSpread({}, scopesKeyWrapped, {}, scopesRemaining); | ||
return scopesWrapped; | ||
}; | ||
var scopeHasLeadingSlashScopedRemapping = function scopeHasLeadingSlashScopedRemapping(scopeImports, scopeKey) { | ||
const scopeHasLeadingSlashScopedRemapping = (scopeImports, scopeKey) => { | ||
return scopeKey in scopeImports && scopeImports[scopeKey] === scopeKey && "/" in scopeImports && scopeImports["/"] === scopeKey; | ||
}; | ||
var wrapImports = function wrapImports(imports, into) { | ||
var importsWithKeyWrapped = {}; | ||
var importsRemaining = {}; | ||
Object.keys(imports).forEach(function (importKey) { | ||
var importValue = imports[importKey]; | ||
var importKeyWrapped = wrapSpecifier(importKey, into); | ||
var importValueWrapped = wrapSpecifier(importValue, into); | ||
const wrapImports = (imports, into) => { | ||
const importsWrapped = {}; | ||
const importsRemaining = {}; | ||
Object.keys(imports).forEach(importKey => { | ||
const importValue = imports[importKey]; | ||
const importKeyWrapped = wrapSpecifier(importKey, into); | ||
const importValueWrapped = wrapAddress(importValue, into); | ||
const keyChanged = importKeyWrapped !== importKey; | ||
const valueChanged = importValueWrapped !== importValue; | ||
if (importKeyWrapped === importKey) { | ||
if (keyChanged || valueChanged) { | ||
importsWrapped[importKeyWrapped] = importValueWrapped; | ||
} else { | ||
importsRemaining[importKey] = importValue; | ||
} else if (importValueWrapped === importValue) { | ||
importsWithKeyWrapped[importKeyWrapped] = importValue; | ||
} else { | ||
importsWithKeyWrapped[importKeyWrapped] = importValueWrapped; | ||
importsRemaining[importKey] = importValueWrapped; | ||
} | ||
}); | ||
return { | ||
importsWithKeyWrapped: importsWithKeyWrapped, | ||
importsRemaining: importsRemaining | ||
importsWrapped, | ||
importsRemaining | ||
}; | ||
}; | ||
var wrapTopLevelImports = function wrapTopLevelImports(imports, into) { | ||
var _wrapImports2 = wrapImports(imports, into), | ||
importsWithKeyWrapped = _wrapImports2.importsWithKeyWrapped, | ||
importsRemaining = _wrapImports2.importsRemaining; | ||
return _objectSpread({}, importsWithKeyWrapped, {}, importsRemaining); | ||
const wrapTopLevelImports = (imports, into) => { | ||
const { | ||
importsWrapped, | ||
importsRemaining | ||
} = wrapImports(imports, into); | ||
return { ...importsWrapped, | ||
...importsRemaining | ||
}; | ||
}; | ||
var wrapSpecifier = function wrapSpecifier(specifier, into) { | ||
if (isOriginRelativeSpecifier(specifier)) { | ||
return "".concat(into).concat(specifier.slice(1)); | ||
const wrapSpecifier = (specifier, into) => { | ||
if (specifier.startsWith("//")) { | ||
return specifier; | ||
} | ||
if (isBareSpecifier(specifier)) { | ||
return "".concat(into).concat(specifier); | ||
if (specifier[0] === "/") { | ||
return `${into}${specifier.slice(1)}`; | ||
} | ||
if (specifier.startsWith("./")) { | ||
return `./${into}${specifier.slice(2)}`; | ||
} | ||
return specifier; | ||
}; | ||
const wrapAddress = (string, into) => { | ||
if (string.startsWith("//")) { | ||
return string; | ||
} | ||
if (string[0] === "/") { | ||
return `${into}${string.slice(1)}`; | ||
} | ||
if (string.startsWith("./")) { | ||
return `./${into}${string.slice(2)}`; | ||
} | ||
if (string.startsWith("../")) { | ||
return string; | ||
} | ||
if (hasScheme(string)) { | ||
return string; | ||
} // bare | ||
return `${into}${string}`; | ||
}; | ||
const formulateFolderRelativeNameMustBeAString = ({ | ||
folderRelativeName | ||
}) => `folderRelativeName must be a string. | ||
--- folder relative name --- | ||
${folderRelativeName}`; | ||
exports.applyImportMap = applyImportMap; | ||
@@ -810,4 +940,5 @@ exports.composeTwoImportMaps = composeTwoImportMaps; | ||
exports.resolveSpecifierForProject = resolveSpecifierForProject; | ||
exports.resolveUrl = resolveUrl; | ||
exports.sortImportMap = sortImportMap; | ||
exports.wrapImportMap = wrapImportMap; | ||
//# sourceMappingURL=main.js.map |
@@ -13,3 +13,4 @@ export { applyImportMap } from "./src/applyImportMap/applyImportMap.js" | ||
} from "./src/resolveSpecifierForProject/resolveSpecifierForProject.js" | ||
export { resolveUrl } from "./src/resolveUrl/resolveUrl.js" | ||
export { sortImportMap } from "./src/sortImportMap/sortImportMap.js" | ||
export { wrapImportMap } from "./src/wrapImportMap/wrapImportMap.js" |
{ | ||
"name": "@jsenv/import-map", | ||
"version": "5.5.0", | ||
"description": "Helpers to implement importMap", | ||
"version": "6.2.0", | ||
"license": "MIT", | ||
@@ -9,4 +10,8 @@ "repository": { | ||
}, | ||
"engines": { | ||
"node": ">=12.0.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
"access": "public", | ||
"registry": "https://registry.npmjs.org" | ||
}, | ||
@@ -21,3 +26,3 @@ "main": "dist/commonjs/main.js", | ||
"exports": { | ||
"/": "/" | ||
"./": "./" | ||
}, | ||
@@ -39,19 +44,19 @@ "scripts": { | ||
"dependencies": { | ||
"@jsenv/href": "1.0.0" | ||
"@jsenv/href": "1.2.0", | ||
"@jsenv/operating-system-path": "2.7.0" | ||
}, | ||
"devDependencies": { | ||
"@dmail/assert": "3.14.0", | ||
"@jsenv/bundling": "5.15.0", | ||
"@jsenv/codecov-upload": "1.8.0", | ||
"@jsenv/eslint-config": "10.0.0", | ||
"@jsenv/execution": "5.11.0", | ||
"@jsenv/node-launcher": "4.15.0", | ||
"@jsenv/node-module-import-map": "7.0.0", | ||
"@jsenv/operating-system-path": "2.4.0", | ||
"@jsenv/prettier-config": "1.0.0", | ||
"@jsenv/prettier-check-project": "3.3.0", | ||
"@jsenv/url-meta": "2.0.0", | ||
"@jsenv/testing": "3.2.0", | ||
"@jsenv/auto-publish": "1.9.0", | ||
"@jsenv/bundling": "7.6.2", | ||
"@jsenv/codecov-upload": "2.0.0", | ||
"@jsenv/eslint-config": "10.2.0", | ||
"@jsenv/execution": "6.11.0", | ||
"@jsenv/node-launcher": "4.26.0", | ||
"@jsenv/node-module-import-map": "8.2.1", | ||
"@jsenv/prettier-config": "1.0.1", | ||
"@jsenv/prettier-check-project": "3.5.0", | ||
"@jsenv/testing": "3.9.0", | ||
"babel-eslint": "11.0.0-beta.0", | ||
"eslint": "6.4.0", | ||
"eslint": "6.5.1", | ||
"prettier": "1.18.2", | ||
@@ -58,0 +63,0 @@ "rimraf": "3.0.0" |
207
readme.md
# jsenv-import-map | ||
> importMap programmatic implementation | ||
[![github package](https://img.shields.io/github/package-json/v/jsenv/jsenv-import-map.svg?logo=github&label=package)](https://github.com/jsenv/jsenv-import-map/packages) | ||
[![npm package](https://img.shields.io/npm/v/@jsenv/import-map.svg?logo=npm&label=package)](https://www.npmjs.com/package/@jsenv/import-map) | ||
[![github ci](https://github.com/jsenv/jsenv-import-map/workflows/ci/badge.svg)](https://github.com/jsenv/jsenv-import-map/actions?workflow=ci) | ||
[![codecov coverage](https://codecov.io/gh/jsenv/jsenv-import-map/branch/master/graph/badge.svg)](https://codecov.io/gh/jsenv/jsenv-import-map) | ||
[![github package](https://img.shields.io/github/package-json/v/jsenv/jsenv-import-map.svg?label=package&logo=github)](https://github.com/jsenv/jsenv-import-map/packages) | ||
[![ci status](https://github.com/jsenv/jsenv-import-map/workflows/ci/badge.svg)](https://github.com/jsenv/jsenv-import-map/actions) | ||
[![codecov](https://codecov.io/gh/jsenv/jsenv-import-map/branch/master/graph/badge.svg)](https://codecov.io/gh/jsenv/jsenv-import-map) | ||
Helpers to implement importMap | ||
## Introduction | ||
## Table of contents | ||
`jsenv-import-map` is a javasScript implementation of the importMap specification. It is written using es modules and is compatible with Node.js.<br /> | ||
- [Presentation](#Presentation) | ||
- [Usage](#Usage) | ||
- [composeTwoImportMaps](#composetwoimportmaps) | ||
- [normalizeImportMap](#normalizeimportmap) | ||
- [resolveImport](#resolveimport) | ||
- [Installation](#installation) | ||
I made this project because jsenv uses importMap but they are not yet available in browsers.<br /> | ||
## Presentation | ||
`@jsenv/import-map` can be used to implement the behaviour of importMap as described in the specification. It is written using es modules and is compatible with Node.js.<br /> | ||
— see [importMap spec](https://github.com/WICG/import-maps) | ||
`@jsenv/import-map` has the following exports: | ||
## Usage | ||
- [applyImportMap](#applyimportmap) | ||
- [composeTwoImportMaps](#composetwoimportmaps) | ||
- [normalizeImportMap](#normalizeimportmap) | ||
- [resolveImport](#resolveimport) | ||
- [resolveSpecifier](#resolvespecifier) | ||
- [wrapImportMap](#wrapimportmap) | ||
`@jsenv/import-map` exports are documented in this section. | ||
## applyImportMap | ||
### composeTwoImportMaps | ||
> takes { `importMap`, `href`, `importerHref` } and returns either the `href` remapped by `importMap` or the original `href`. | ||
> `composeTwoImportMaps` takes two `importMap` and return a single `importMap` being the composition of the two.<br /> | ||
> Implemented by [src/composeTwoImportMaps/composeTwoImportMaps.js](src/composeTwoImportMaps/composeTwoImportMaps.js) | ||
```js | ||
import { applyImportMap } from "@jsenv/import-map" | ||
const href = "http://domain.com/foo" | ||
const importMap = { | ||
imports: { | ||
"http://domain.com/foo": "http://domain.com/bar", | ||
}, | ||
} | ||
const hrefRemapped = applyImportMap({ | ||
href, | ||
importMap, | ||
}) | ||
console.log(hrefRemapped) | ||
``` | ||
The code above logs `"http://domain.com/bar"`.<br /> | ||
The provided `importMap` specifiers must be absolute and sorted to work as expected.<br /> | ||
You can use [normalizeImportMap](#normalizeimportmap) to do that.<br /> | ||
— see [applyImportMap source code](./src/applyImportMap/applyImportMap.js) | ||
## composeTwoImportMaps | ||
> takes (`leftImportMap`, `rightImportMap`) and returns an importMap being the composition of the two. | ||
```js | ||
import { composeTwoImportMaps } from "@jsenv/import-map" | ||
const leftImportMap = { | ||
imports: { | ||
foo: "bar", | ||
const importMap = composeTwoImportMaps( | ||
{ | ||
imports: { | ||
foo: "bar", | ||
}, | ||
}, | ||
} | ||
const rightImportMap = { | ||
imports: { | ||
foo: "whatever", | ||
{ | ||
imports: { | ||
foo: "whatever", | ||
}, | ||
}, | ||
} | ||
const importMap = composeTwoImportMaps(leftImportMap, rightImportMap) | ||
) | ||
console.log(importMap.imports.foo) | ||
console.log(JSON.stringify(importMap, null, " ")) | ||
``` | ||
The code above logs `"whatever"`. | ||
```console | ||
{ | ||
"imports": { | ||
"foo": "whatever" | ||
} | ||
} | ||
``` | ||
— see [composeTwoImportMaps source code](./src/composeTwoImportMaps/composeTwoImportMaps.js) | ||
### normalizeImportMap | ||
## normalizeImportMap | ||
> `normalizeImportMap` returns an `importMap` resolved against an `url` and sorted.<br /> | ||
> Implemented by [src/normalizeImportMap/normalizeImportMap.js](src/normalizeImportMap/normalizeImportMap.js) | ||
> takes (`importMap`, `href`) and returns an importMap where relative specifier are resolved against `href` and sorted. | ||
```js | ||
import { normalizeImportMap } from "@jsenv/import-map" | ||
const importMap = { | ||
imports: { | ||
foo: "http://cdndomain.com/bar", | ||
const importMap = normalizeImportMap( | ||
{ | ||
imports: { | ||
foo: "./bar", | ||
"./ding.js": "./dong.js" | ||
}, | ||
}, | ||
} | ||
const href = "http://mydomain.com" | ||
const importMapNormalized = normalizeImportMap(importMap, href) | ||
"http://your-domain.com", | ||
) | ||
console.log(importMapNormalized.imports["http://mydomain.com/foo"]) | ||
console.log(JSON.stringify(importMap, null, ' ') | ||
``` | ||
The code above logs `"http://cdndomain.com/bar"`. | ||
```console | ||
{ | ||
"imports": { | ||
"foo": "http://your-domain.com/bar", | ||
"http://your-domain.com/ding.js": "http://your-domain.com/dong.js" | ||
} | ||
} | ||
``` | ||
— see [normalizeImportMap source code](./src/normalizeImportMap/normalizeImportMap.js) | ||
### resolveImport | ||
## resolveImport | ||
> `resolveImport` returns an import `url` applying an `importMap` to `specifier` and `importer`.<br /> | ||
> Implemented by [src/resolveImport/resolveImport.js](src/resolveImport/resolveImport.js) | ||
> takes { `specifier`, `importer`, `importMap`, `defaultExtension` } and returns a url. | ||
```js | ||
@@ -120,83 +112,20 @@ import { resolveImport } from "@jsenv/import-map" | ||
The code above logs `"http://domain.com/main.js"`. | ||
— see [resolveImport source code](./src/resolveImport/resolveImport.js) | ||
## resolveSpecifier | ||
> takes (`specifier`, `importer`) and returns `specifier` resolved against `importer`. | ||
```js | ||
import { resolveSpecifier } from "@jsenv/import-map" | ||
const specifier = "../file.js" | ||
const importer = "http://mydomain.com/folder/index.js" | ||
const specifierResolved = resolveSpecifier(specifier, importer) | ||
console.log(specifierResolved) | ||
``` | ||
The code above logs `"http://mydomain.com/file.js"`. | ||
— see [resolveSpecifier source code](./src/resolveSpecifier/resolveSpecifier.js) | ||
## wrapImportMap | ||
> takes (`importMap`, `folderRelativeName`) and returns an importMap wrapped inside `folderRelativeName`. | ||
```js | ||
import { wrapImportMap } from "@jsenv/import-map" | ||
const importMap = { | ||
imports: { | ||
foo: "bar", | ||
}, | ||
} | ||
const folderRelativeName = "/dist" | ||
const importMapWrapped = wrapImportMap(specifier, importer) | ||
console.log(importMapWrapped.imports.foo) | ||
``` | ||
The code above logs `"/dist/bar"`.<br /> | ||
This feature is not part of the spec but is usefull to redirect your imports inside a given folder.<br /> | ||
— see [wrapImportMap source code](./src/wrapImportMap/wrapImportMap.js) | ||
## Installation using npm | ||
To install `@jsenv/import-map` you need to configure npm to use github registry for this package. | ||
1. Configure npm authentification | ||
Github registry requires an authentification token. If you haven't configured it already, read how in the documentation below.<br /> | ||
— see [Authenticating to GitHub Package Registry documentation on GitHub](https://help.github.com/en/articles/configuring-npm-for-use-with-github-package-registry#authenticating-to-github-package-registry) | ||
For the record, you can save your token with this command | ||
```console | ||
npm config set '//npm.pkg.github.com/:_authToken' 'personal-access-token' | ||
http://domain.com/main.js | ||
``` | ||
2. Configure npm registry | ||
The provided `importMap` must be resolved and sorted to work as expected. You can use [normalizeImportMap](#normalizeimportmap) to do that.<br /> | ||
Run the command below to use github registry for `@jsenv/import-map` | ||
## Installation | ||
```console | ||
npm config set @jsenv:registry https://npm.pkg.github.com | ||
``` | ||
If you never installed a jsenv package, read [Installing a jsenv package](https://github.com/jsenv/jsenv-core/blob/master/docs/installing-jsenv-package.md#installing-a-jsenv-package) before going further. | ||
3. Run install command | ||
This documentation is up-to-date with a specific version so prefer any of the following commands | ||
```console | ||
npm install @jsenv/import-map@5.2.0 | ||
npm install --save-dev @jsenv/import-map@6.2.0 | ||
``` | ||
### Installation using yarn | ||
Same steps as [Installation using npm](#installation-using-npm) replacing step 3 by | ||
```console | ||
yarn add @jsenv/import-map@5.2.0 | ||
yarn add --dev @jsenv/import-map@6.2.0 | ||
``` |
import { assertImportMap } from "../assertImportMap.js" | ||
import { assertUrlLike } from "../assertUrlLike.js" | ||
import { hasScheme } from "../hasScheme.js" | ||
import { tryUrlResolution } from "../tryUrlResolution.js" | ||
import { resolveSpecifier } from "../resolveSpecifier/resolveSpecifier.js" | ||
export const applyImportMap = ({ importMap, href, importerHref }) => { | ||
export const applyImportMap = ({ importMap, specifier, importer }) => { | ||
assertImportMap(importMap) | ||
assertUrlLike(href, "href") | ||
if (importerHref) { | ||
assertUrlLike(importerHref, "importerHref") | ||
if (typeof specifier !== "string") { | ||
throw new TypeError(writeSpecifierMustBeAString({ specifier, importer })) | ||
} | ||
if (importer) { | ||
if (typeof importer !== "string") { | ||
throw new TypeError(writeImporterMustBeAString({ importer, specifier })) | ||
} | ||
if (!hasScheme(importer)) { | ||
throw new Error(writeImporterMustBeAbsolute({ importer, specifier })) | ||
} | ||
} | ||
const specifierUrl = resolveSpecifier(specifier, importer) | ||
const specifierNormalized = specifierUrl || specifier | ||
const { scopes } = importMap | ||
if (scopes && importerHref) { | ||
if (scopes && importer) { | ||
const scopeKeyMatching = Object.keys(scopes).find((scopeKey) => { | ||
return scopeKey === importerHref || specifierIsPrefixOf(scopeKey, importerHref) | ||
return scopeKey === importer || specifierIsPrefixOf(scopeKey, importer) | ||
}) | ||
if (scopeKeyMatching) { | ||
const scopeValue = scopes[scopeKeyMatching] | ||
const remappingFromScopeImports = applyImports(href, scopeValue) | ||
const remappingFromScopeImports = applyImports(specifierNormalized, scopeValue) | ||
if (remappingFromScopeImports !== null) { | ||
@@ -27,3 +39,3 @@ return remappingFromScopeImports | ||
if (imports) { | ||
const remappingFromImports = applyImports(href, imports) | ||
const remappingFromImports = applyImports(specifierNormalized, imports) | ||
if (remappingFromImports !== null) { | ||
@@ -34,6 +46,10 @@ return remappingFromImports | ||
return href | ||
if (specifierUrl) { | ||
return specifierUrl | ||
} | ||
throw new Error(writeBareSpecifierMustBeRemapped({ specifier, importer })) | ||
} | ||
const applyImports = (href, imports) => { | ||
const applyImports = (specifier, imports) => { | ||
const importKeyArray = Object.keys(imports) | ||
@@ -45,10 +61,11 @@ | ||
i++ | ||
if (importKey === href) { | ||
if (importKey === specifier) { | ||
const importValue = imports[importKey] | ||
return importValue | ||
} | ||
if (specifierIsPrefixOf(importKey, href)) { | ||
if (specifierIsPrefixOf(importKey, specifier)) { | ||
const importValue = imports[importKey] | ||
const afterImportKey = href.slice(importKey.length) | ||
return importValue + afterImportKey | ||
const afterImportKey = specifier.slice(importKey.length) | ||
return tryUrlResolution(afterImportKey, importValue) | ||
} | ||
@@ -63,1 +80,25 @@ } | ||
} | ||
const writeSpecifierMustBeAString = ({ specifier, importer }) => `specifier must be a string. | ||
--- specifier --- | ||
${specifier} | ||
--- importer --- | ||
${importer}` | ||
const writeImporterMustBeAString = ({ importer, specifier }) => `importer must be a string. | ||
--- importer --- | ||
${importer} | ||
--- specifier --- | ||
${specifier}` | ||
const writeImporterMustBeAbsolute = ({ importer, specifier }) => `importer must be an absolute url. | ||
--- importer --- | ||
${importer} | ||
--- specifier --- | ||
${specifier}` | ||
const writeBareSpecifierMustBeRemapped = ({ specifier, importer }) => `Unmapped bare specifier. | ||
--- specifier --- | ||
${specifier} | ||
--- importer --- | ||
${importer}` |
import { assertImportMap } from "../assertImportMap.js" | ||
import { tryUrlResolution } from "../tryUrlResolution.js" | ||
import { resolveSpecifier } from "../resolveSpecifier/resolveSpecifier.js" | ||
import { sortImports, sortScopes } from "../sortImportMap/sortImportMap.js" | ||
export const normalizeImportMap = (importMap, href) => { | ||
export const normalizeImportMap = (importMap, baseUrl) => { | ||
assertImportMap(importMap) | ||
if (typeof href !== "string") { | ||
throw new TypeError(`href must be a string, got ${href}`) | ||
if (typeof baseUrl !== "string") { | ||
throw new TypeError(formulateBaseUrlMustBeAString({ baseUrl })) | ||
} | ||
@@ -14,14 +16,47 @@ | ||
return { | ||
imports: imports ? normalizeImports(imports, href) : undefined, | ||
scopes: scopes ? normalizeScopes(scopes, href) : undefined, | ||
imports: imports ? normalizeImports(imports, baseUrl) : undefined, | ||
scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined, | ||
} | ||
} | ||
const normalizeImports = (imports, href) => { | ||
const normalizeImports = (imports, baseUrl) => { | ||
const importsNormalized = {} | ||
Object.keys(imports).forEach((importKey) => { | ||
const importValue = imports[importKey] | ||
const importKeyNormalized = resolveSpecifier(importKey, href) | ||
const importValueNormalized = resolveSpecifier(importValue, href) | ||
importsNormalized[importKeyNormalized] = importValueNormalized | ||
Object.keys(imports).forEach((specifier) => { | ||
const address = imports[specifier] | ||
if (typeof address !== "string") { | ||
console.warn( | ||
formulateAddressMustBeAString({ | ||
address, | ||
specifier, | ||
}), | ||
) | ||
return | ||
} | ||
const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier | ||
const addressUrl = tryUrlResolution(address, baseUrl) | ||
if (addressUrl === null) { | ||
console.warn( | ||
formulateAdressResolutionFailed({ | ||
address, | ||
baseUrl, | ||
specifier, | ||
}), | ||
) | ||
return | ||
} | ||
if (specifier.endsWith("/") && !addressUrl.endsWith("/")) { | ||
console.warn( | ||
formulateAddressUrlRequiresTrailingSlash({ | ||
addressUrl, | ||
address, | ||
specifier, | ||
}), | ||
) | ||
return | ||
} | ||
importsNormalized[specifierResolved] = addressUrl | ||
}) | ||
@@ -31,11 +66,60 @@ return sortImports(importsNormalized) | ||
const normalizeScopes = (scopes, href) => { | ||
const normalizeScopes = (scopes, baseUrl) => { | ||
const scopesNormalized = {} | ||
Object.keys(scopes).forEach((scopeKey) => { | ||
const scopeValue = scopes[scopeKey] | ||
const scopeKeyNormalized = resolveSpecifier(scopeKey, href) | ||
const scopeValueNormalized = normalizeImports(scopeValue, href) | ||
scopesNormalized[scopeKeyNormalized] = scopeValueNormalized | ||
Object.keys(scopes).forEach((scope) => { | ||
const scopeValue = scopes[scope] | ||
const scopeUrl = tryUrlResolution(scope, baseUrl) | ||
if (scopeUrl === null) { | ||
console.warn( | ||
formulateScopeResolutionFailed({ | ||
scope, | ||
baseUrl, | ||
}), | ||
) | ||
return | ||
} | ||
const scopeValueNormalized = normalizeImports(scopeValue, baseUrl) | ||
scopesNormalized[scopeUrl] = scopeValueNormalized | ||
}) | ||
return sortScopes(scopesNormalized) | ||
} | ||
const formulateBaseUrlMustBeAString = ({ baseUrl }) => `baseUrl must be a string. | ||
--- base url --- | ||
${baseUrl}` | ||
const formulateAddressMustBeAString = ({ specifier, address }) => `Address must be a string. | ||
--- address --- | ||
${address} | ||
--- specifier --- | ||
${specifier}` | ||
const formulateAdressResolutionFailed = ({ | ||
address, | ||
baseUrl, | ||
specifier, | ||
}) => `Address url resolution failed. | ||
--- address --- | ||
${address} | ||
--- base url --- | ||
${baseUrl} | ||
--- specifier --- | ||
${specifier}` | ||
const formulateAddressUrlRequiresTrailingSlash = ({ | ||
addressURL, | ||
address, | ||
specifier, | ||
}) => `Address must end with /. | ||
--- address url --- | ||
${addressURL} | ||
--- address --- | ||
${address} | ||
--- specifier --- | ||
${specifier}` | ||
const formulateScopeResolutionFailed = ({ scope, baseUrl }) => `Scope url resolution failed. | ||
--- scope --- | ||
${scope} | ||
--- base url --- | ||
${baseUrl}` |
@@ -7,27 +7,33 @@ // directly target the files because this code | ||
import { pathnameToExtension } from "@jsenv/href/src/pathnameToExtension/pathnameToExtension.js" | ||
import { resolveSpecifier } from "../resolveSpecifier/resolveSpecifier.js" | ||
import { resolveUrl } from "../resolveUrl/resolveUrl.js" | ||
import { applyImportMap } from "../applyImportMap/applyImportMap.js" | ||
export const resolveImport = ({ specifier, importer, importMap, defaultExtension = true }) => { | ||
const specifierResolved = resolveSpecifier(specifier, importer) | ||
const specifierRemapped = importMap | ||
? applyImportMap({ importMap, href: specifierResolved, importerHref: importer }) | ||
: specifierResolved | ||
return applyDefaultExtension({ | ||
url: importMap | ||
? applyImportMap({ importMap, specifier, importer }) | ||
: resolveUrl(specifier, importer), | ||
importer, | ||
defaultExtension, | ||
}) | ||
} | ||
const applyDefaultExtension = ({ url, importer, defaultExtension }) => { | ||
if (typeof defaultExtension === "string") { | ||
const extension = pathnameToExtension(specifierRemapped) | ||
const extension = pathnameToExtension(url) | ||
if (extension === "") { | ||
return `${specifierRemapped}${defaultExtension}` | ||
return `${url}${defaultExtension}` | ||
} | ||
return url | ||
} | ||
if (defaultExtension === true) { | ||
const extension = pathnameToExtension(specifierRemapped) | ||
if (extension === "") { | ||
if (importer) { | ||
const importerPathname = hrefToPathname(importer) | ||
const importerExtension = pathnameToExtension(importerPathname) | ||
return `${specifierRemapped}${importerExtension}` | ||
} | ||
const extension = pathnameToExtension(url) | ||
if (extension === "" && importer) { | ||
const importerPathname = hrefToPathname(importer) | ||
const importerExtension = pathnameToExtension(importerPathname) | ||
return `${url}${importerExtension}` | ||
} | ||
} | ||
return specifierRemapped | ||
return url | ||
} |
@@ -29,3 +29,3 @@ import { pathnameToOperatingSystemPath } from "@jsenv/operating-system-path" | ||
throw new Error( | ||
createImporterMustBeInsideProjectMessage({ | ||
formulateImporterMustBeInsideProject({ | ||
projectPathname, | ||
@@ -73,3 +73,3 @@ importer, | ||
throw new Error( | ||
createImportMustBeInsideProjectMessage({ | ||
formulateImportMustBeInsideProject({ | ||
projectPathname, | ||
@@ -93,12 +93,12 @@ specifier, | ||
const createImporterMustBeInsideProjectMessage = ({ | ||
const formulateImporterMustBeInsideProject = ({ | ||
projectPathname, | ||
importer, | ||
}) => `importer must be inside project. | ||
--- importer --- | ||
${importer} | ||
--- project --- | ||
${pathnameToOperatingSystemPath(projectPathname)}` | ||
--- importer --- | ||
${importer} | ||
--- project --- | ||
${pathnameToOperatingSystemPath(projectPathname)}` | ||
const createImportMustBeInsideProjectMessage = ({ | ||
const formulateImportMustBeInsideProject = ({ | ||
projectPathname, | ||
@@ -105,0 +105,0 @@ specifier, |
@@ -1,56 +0,14 @@ | ||
// could be useful: https://url.spec.whatwg.org/#url-miscellaneous | ||
import { hasScheme } from "../hasScheme.js" | ||
import { resolveUrl } from "../resolveUrl/resolveUrl.js" | ||
import { isAbsoluteSpecifier, resolveAbsoluteSpecifier } from "./absoluteSpecifier.js" | ||
import { | ||
isSchemeRelativeSpecifier, | ||
resolveSchemeRelativeSpecifier, | ||
} from "./schemeRelativeSpecifier.js" | ||
import { | ||
isOriginRelativeSpecifier, | ||
resolveOriginRelativeSpecifier, | ||
} from "./originRelativeSpecifier.js" | ||
import { | ||
isPathnameRelativeSpecifier, | ||
resolvePathnameRelativeSpecifier, | ||
} from "./pathnameRelativeSpecifier.js" | ||
import { resolveBareSpecifier } from "./bareSpecifier.js" | ||
export const resolveSpecifier = (specifier, importer) => { | ||
if (isAbsoluteSpecifier(specifier)) { | ||
return resolveAbsoluteSpecifier(specifier, importer) | ||
if (specifier[0] === "/" || specifier.startsWith("./") || specifier.startsWith("../")) { | ||
return resolveUrl(specifier, importer) | ||
} | ||
if (!importer) { | ||
throw new Error(createMissingImporterMessage(specifier, importer)) | ||
if (hasScheme(specifier)) { | ||
return specifier | ||
} | ||
if (!isAbsoluteSpecifier(importer)) { | ||
throw new Error(createAbsoluteImporterRequiredMessage(importer)) | ||
} | ||
if (isSchemeRelativeSpecifier(specifier)) { | ||
return resolveSchemeRelativeSpecifier(specifier, importer) | ||
} | ||
if (isOriginRelativeSpecifier(specifier)) { | ||
return resolveOriginRelativeSpecifier(specifier, importer) | ||
} | ||
if (isPathnameRelativeSpecifier(specifier)) { | ||
return resolvePathnameRelativeSpecifier(specifier, importer) | ||
} | ||
return resolveBareSpecifier(specifier, importer) | ||
return null | ||
} | ||
const createMissingImporterMessage = ( | ||
specifier, | ||
importer, | ||
) => `missing importer to resolve relative specifier. | ||
--- specifier --- | ||
${specifier} | ||
--- importer --- | ||
${importer}` | ||
const createAbsoluteImporterRequiredMessage = (importer) => `importer must be absolute. | ||
--- importer --- | ||
${importer}` |
import { hrefToOrigin, hrefToPathname } from "@jsenv/href" | ||
import { resolveSpecifier } from "../resolveSpecifier/resolveSpecifier.js" | ||
export const resolveSpecifierForProject = ({ | ||
export const resolveSpecifierForProject = ( | ||
specifier, | ||
projectPathname, | ||
specifier, | ||
httpResolutionOrigin = "http://fake_origin_unlikely_to_collide.ext", | ||
}) => { | ||
) => { | ||
const specifierHttpResolved = resolveSpecifier(specifier, httpResolutionOrigin) | ||
@@ -10,0 +10,0 @@ if (hrefToOrigin(specifierHttpResolved) === httpResolutionOrigin) { |
@@ -18,10 +18,9 @@ /** | ||
import { isOriginRelativeSpecifier } from "../resolveSpecifier/originRelativeSpecifier.js" | ||
import { isBareSpecifier } from "../resolveSpecifier/bareSpecifier.js" | ||
import { assertImportMap } from "../assertImportMap.js" | ||
import { hasScheme } from "../hasScheme.js" | ||
export const wrapImportMap = (importMap, folderRelativeName) => { | ||
export const wrapImportMap = (importMap, folderRelativeName, ensureInto = true) => { | ||
assertImportMap(importMap) | ||
if (typeof folderRelativeName !== "string") { | ||
throw new TypeError(`folderRelativeName must be a string, got ${folderRelativeName}`) | ||
throw new TypeError(formulateFolderRelativeNameMustBeAString({ folderRelativeName })) | ||
} | ||
@@ -33,4 +32,9 @@ | ||
let importsForWrapping | ||
if (imports) { | ||
importsForWrapping = wrapTopLevelImports(imports, into) | ||
} else { | ||
importsForWrapping = {} | ||
} | ||
let scopesForWrapping | ||
if (scopes) { | ||
@@ -42,17 +46,10 @@ scopesForWrapping = wrapScopes(scopes, into) | ||
if (imports) { | ||
importsForWrapping = wrapTopLevelImports(imports, into) | ||
scopesForWrapping[into] = wrapTopLevelImports(imports, into) | ||
} else { | ||
importsForWrapping = {} | ||
scopesForWrapping[into] = {} | ||
if (ensureInto) { | ||
// ensure anything not directly remapped is remapped inside into | ||
importsForWrapping[into] = into | ||
importsForWrapping["/"] = into | ||
// and when already into, you stay inside | ||
scopesForWrapping[into] = { [into]: into } | ||
} | ||
// ensure anything not directly remapped is remapped inside into | ||
importsForWrapping[into] = into | ||
importsForWrapping["/"] = into | ||
// and when already into, you stay inside | ||
scopesForWrapping[into][into] = into | ||
scopesForWrapping[into]["/"] = into | ||
return { | ||
@@ -65,51 +62,46 @@ imports: importsForWrapping, | ||
const wrapScopes = (scopes, into) => { | ||
const scopesKeyWrapped = {} | ||
const scopesRemaining = {} | ||
const scopesWrapped = {} | ||
Object.keys(scopes).forEach((scopeKey) => { | ||
const scopeValue = scopes[scopeKey] | ||
const scopeKeyWrapped = wrapSpecifier(scopeKey, into) | ||
const scopeKeyWrapped = wrapAddress(scopeKey, into) | ||
if (scopeKeyWrapped === scopeKey) { | ||
scopesRemaining[scopeKey] = scopeValue | ||
} else { | ||
const { importsWithKeyWrapped, importsRemaining } = wrapImports(scopeValue, into) | ||
const { importsWrapped, importsRemaining } = wrapImports(scopeValue, into) | ||
let scopeValueWrapped | ||
if (scopeHasLeadingSlashScopedRemapping(scopeValue, scopeKey)) { | ||
const leadingSlashSpecifier = `${into}${scopeKey.slice(1)}` | ||
scopeValueWrapped = {} | ||
// put everything except the leading slash remapping | ||
Object.keys(importsWithKeyWrapped).forEach((importKeyWrapped) => { | ||
if (importKeyWrapped === leadingSlashSpecifier || importKeyWrapped === into) { | ||
return | ||
} | ||
scopeValueWrapped[importKeyWrapped] = importsWithKeyWrapped[importKeyWrapped] | ||
}) | ||
Object.keys(importsRemaining).forEach((importKey) => { | ||
if (importKey === scopeKey || importKey === "/") { | ||
return | ||
} | ||
scopeValueWrapped[importKey] = importsRemaining[importKey] | ||
}) | ||
// now put leading slash remapping to ensure it comes last | ||
scopeValueWrapped[leadingSlashSpecifier] = leadingSlashSpecifier | ||
scopeValueWrapped[scopeKey] = leadingSlashSpecifier | ||
scopeValueWrapped[into] = leadingSlashSpecifier | ||
scopeValueWrapped["/"] = leadingSlashSpecifier | ||
} else { | ||
scopeValueWrapped = { | ||
...importsWithKeyWrapped, | ||
...importsRemaining, | ||
let scopeValueWrapped | ||
if (scopeHasLeadingSlashScopedRemapping(scopeValue, scopeKey)) { | ||
const leadingSlashSpecifier = `${into}${scopeKey.slice(1)}` | ||
scopeValueWrapped = {} | ||
// put everything except the leading slash remapping | ||
Object.keys(importsWrapped).forEach((importKeyWrapped) => { | ||
if (importKeyWrapped === leadingSlashSpecifier || importKeyWrapped === into) { | ||
return | ||
} | ||
scopeValueWrapped[importKeyWrapped] = importsWrapped[importKeyWrapped] | ||
}) | ||
Object.keys(importsRemaining).forEach((importKey) => { | ||
if (importKey === scopeKey || importKey === "/") { | ||
return | ||
} | ||
scopeValueWrapped[importKey] = importsRemaining[importKey] | ||
}) | ||
// now put leading slash remapping to ensure it comes last | ||
scopeValueWrapped[leadingSlashSpecifier] = leadingSlashSpecifier | ||
scopeValueWrapped[scopeKey] = leadingSlashSpecifier | ||
scopeValueWrapped[into] = leadingSlashSpecifier | ||
scopeValueWrapped["/"] = leadingSlashSpecifier | ||
} else { | ||
scopeValueWrapped = { | ||
...importsWrapped, | ||
...importsRemaining, | ||
} | ||
} | ||
scopesKeyWrapped[scopeKeyWrapped] = scopeValueWrapped | ||
scopesRemaining[scopeKey] = importsRemaining | ||
scopesWrapped[scopeKeyWrapped] = scopeValueWrapped | ||
if (scopeKeyWrapped !== scopeKey) { | ||
scopesWrapped[scopeKey] = { ...scopeValueWrapped } | ||
} | ||
}) | ||
return { | ||
...scopesKeyWrapped, | ||
...scopesRemaining, | ||
} | ||
return scopesWrapped | ||
} | ||
@@ -127,3 +119,3 @@ | ||
const wrapImports = (imports, into) => { | ||
const importsWithKeyWrapped = {} | ||
const importsWrapped = {} | ||
const importsRemaining = {} | ||
@@ -134,11 +126,10 @@ | ||
const importKeyWrapped = wrapSpecifier(importKey, into) | ||
const importValueWrapped = wrapSpecifier(importValue, into) | ||
const importValueWrapped = wrapAddress(importValue, into) | ||
if (importKeyWrapped === importKey) { | ||
const keyChanged = importKeyWrapped !== importKey | ||
const valueChanged = importValueWrapped !== importValue | ||
if (keyChanged || valueChanged) { | ||
importsWrapped[importKeyWrapped] = importValueWrapped | ||
} else { | ||
importsRemaining[importKey] = importValue | ||
} else if (importValueWrapped === importValue) { | ||
importsWithKeyWrapped[importKeyWrapped] = importValue | ||
} else { | ||
importsWithKeyWrapped[importKeyWrapped] = importValueWrapped | ||
importsRemaining[importKey] = importValueWrapped | ||
} | ||
@@ -148,3 +139,3 @@ }) | ||
return { | ||
importsWithKeyWrapped, | ||
importsWrapped, | ||
importsRemaining, | ||
@@ -155,5 +146,5 @@ } | ||
const wrapTopLevelImports = (imports, into) => { | ||
const { importsWithKeyWrapped, importsRemaining } = wrapImports(imports, into) | ||
const { importsWrapped, importsRemaining } = wrapImports(imports, into) | ||
return { | ||
...importsWithKeyWrapped, | ||
...importsWrapped, | ||
...importsRemaining, | ||
@@ -164,9 +155,46 @@ } | ||
const wrapSpecifier = (specifier, into) => { | ||
if (isOriginRelativeSpecifier(specifier)) { | ||
if (specifier.startsWith("//")) { | ||
return specifier | ||
} | ||
if (specifier[0] === "/") { | ||
return `${into}${specifier.slice(1)}` | ||
} | ||
if (isBareSpecifier(specifier)) { | ||
return `${into}${specifier}` | ||
if (specifier.startsWith("./")) { | ||
return `./${into}${specifier.slice(2)}` | ||
} | ||
return specifier | ||
} | ||
const wrapAddress = (string, into) => { | ||
if (string.startsWith("//")) { | ||
return string | ||
} | ||
if (string[0] === "/") { | ||
return `${into}${string.slice(1)}` | ||
} | ||
if (string.startsWith("./")) { | ||
return `./${into}${string.slice(2)}` | ||
} | ||
if (string.startsWith("../")) { | ||
return string | ||
} | ||
if (hasScheme(string)) { | ||
return string | ||
} | ||
// bare | ||
return `${into}${string}` | ||
} | ||
const formulateFolderRelativeNameMustBeAString = ({ | ||
folderRelativeName, | ||
}) => `folderRelativeName must be a string. | ||
--- folder relative name --- | ||
${folderRelativeName}` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15
1462
103793
2
20
131
1
+ Added@jsenv/href@1.2.0(transitive)
+ Added@jsenv/operating-system-path@2.7.0(transitive)
- Removed@jsenv/href@1.0.0(transitive)
Updated@jsenv/href@1.2.0