@wordpress/interactivity-router
Version:
Package that exposes state and actions from the `core/router` store, part of the Interactivity API.
213 lines (208 loc) • 7.29 kB
JavaScript
/* wp:polyfill */
/**
* This code is derived from the following projects:
*
* 1. dynamic-importmap (https://github.com/keller-mark/dynamic-importmap)
* 2. es-module-shims (https://github.com/guybedford/es-module-shims)
*
* The original implementation was created by Guy Bedford in es-module-shims,
* then adapted by Mark Keller in dynamic-importmap, and further modified
* for use in this project.
*
* Both projects are licensed under the MIT license.
*
* MIT License: https://opensource.org/licenses/MIT
*/
const backslashRegEx = /\\/g;
function isURL(url) {
if (url.indexOf(':') === -1) {
return false;
}
try {
new URL(url);
return true;
} catch (_) {
return false;
}
}
function resolveIfNotPlainOrUrl(relUrl, parentUrl) {
const hIdx = parentUrl.indexOf('#'),
qIdx = parentUrl.indexOf('?');
if (hIdx + qIdx > -2) {
parentUrl = parentUrl.slice(0,
// eslint-disable-next-line no-nested-ternary
hIdx === -1 ? qIdx : qIdx === -1 || qIdx > hIdx ? hIdx : qIdx);
}
if (relUrl.indexOf('\\') !== -1) {
relUrl = relUrl.replace(backslashRegEx, '/');
}
// protocol-relative
if (relUrl[0] === '/' && relUrl[1] === '/') {
return parentUrl.slice(0, parentUrl.indexOf(':') + 1) + relUrl;
}
// relative-url
else if (relUrl[0] === '.' && (relUrl[1] === '/' || relUrl[1] === '.' && (relUrl[2] === '/' || relUrl.length === 2 && (relUrl += '/')) || relUrl.length === 1 && (relUrl += '/')) || relUrl[0] === '/') {
const parentProtocol = parentUrl.slice(0, parentUrl.indexOf(':') + 1);
// Disabled, but these cases will give inconsistent results for deep backtracking
//if (parentUrl[parentProtocol.length] !== '/')
// throw new Error('Cannot resolve');
// read pathname from parent URL
// pathname taken to be part after leading "/"
let pathname;
if (parentUrl[parentProtocol.length + 1] === '/') {
// resolving to a :// so we need to read out the auth and host
if (parentProtocol !== 'file:') {
pathname = parentUrl.slice(parentProtocol.length + 2);
pathname = pathname.slice(pathname.indexOf('/') + 1);
} else {
pathname = parentUrl.slice(8);
}
} else {
// resolving to :/ so pathname is the /... part
pathname = parentUrl.slice(parentProtocol.length + (parentUrl[parentProtocol.length] === '/'));
}
if (relUrl[0] === '/') {
return parentUrl.slice(0, parentUrl.length - pathname.length - 1) + relUrl;
}
// join together and split for removal of .. and . segments
// looping the string instead of anything fancy for perf reasons
// '../../../../../z' resolved to 'x/y' is just 'z'
const segmented = pathname.slice(0, pathname.lastIndexOf('/') + 1) + relUrl;
const output = [];
let segmentIndex = -1;
for (let i = 0; i < segmented.length; i++) {
// busy reading a segment - only terminate on '/'
if (segmentIndex !== -1) {
if (segmented[i] === '/') {
output.push(segmented.slice(segmentIndex, i + 1));
segmentIndex = -1;
}
continue;
}
// new segment - check if it is relative
else if (segmented[i] === '.') {
// ../ segment
if (segmented[i + 1] === '.' && (segmented[i + 2] === '/' || i + 2 === segmented.length)) {
output.pop();
i += 2;
continue;
}
// ./ segment
else if (segmented[i + 1] === '/' || i + 1 === segmented.length) {
i += 1;
continue;
}
}
// it is the start of a new segment
while (segmented[i] === '/') {
i++;
}
segmentIndex = i;
}
// finish reading out the last segment
if (segmentIndex !== -1) {
output.push(segmented.slice(segmentIndex));
}
return parentUrl.slice(0, parentUrl.length - pathname.length) + output.join('');
}
}
function resolveUrl(relUrl, parentUrl) {
return resolveIfNotPlainOrUrl(relUrl, parentUrl) || (isURL(relUrl) ? relUrl : resolveIfNotPlainOrUrl('./' + relUrl, parentUrl));
}
function getMatch(path, matchObj) {
if (matchObj[path]) {
return path;
}
let sepIndex = path.length;
do {
const segment = path.slice(0, sepIndex + 1);
if (segment in matchObj) {
return segment;
}
} while ((sepIndex = path.lastIndexOf('/', sepIndex - 1)) !== -1);
}
function applyPackages(id, packages) {
const pkgName = getMatch(id, packages);
if (pkgName) {
const pkg = packages[pkgName];
if (pkg === null) {
return;
}
return pkg + id.slice(pkgName.length);
}
}
function resolveImportMap(importMap, resolvedOrPlain, parentUrl) {
let scopeUrl = parentUrl && getMatch(parentUrl, importMap.scopes);
while (scopeUrl) {
const packageResolution = applyPackages(resolvedOrPlain, importMap.scopes[scopeUrl]);
if (packageResolution) {
return packageResolution;
}
scopeUrl = getMatch(scopeUrl.slice(0, scopeUrl.lastIndexOf('/')), importMap.scopes);
}
return applyPackages(resolvedOrPlain, importMap.imports) || resolvedOrPlain.indexOf(':') !== -1 && resolvedOrPlain;
}
function resolveAndComposePackages(packages, outPackages, baseUrl, parentMap) {
for (const p in packages) {
const resolvedLhs = resolveIfNotPlainOrUrl(p, baseUrl) || p;
const target = packages[p];
if (typeof target !== 'string') {
continue;
}
const mapped = resolveImportMap(parentMap, resolveIfNotPlainOrUrl(target, baseUrl) || target, baseUrl);
if (mapped) {
outPackages[resolvedLhs] = mapped;
continue;
}
// console.warn(
// `Mapping "${ p }" -> "${ packages[ p ] }" does not resolve`
// );
}
}
function resolveAndComposeImportMap(json, baseUrl, parentMap) {
const outMap = {
imports: Object.assign({}, parentMap.imports),
scopes: Object.assign({}, parentMap.scopes)
};
if (json.imports) {
resolveAndComposePackages(json.imports, outMap.imports, baseUrl, parentMap);
}
if (json.scopes) {
for (const s in json.scopes) {
const resolvedScope = resolveUrl(s, baseUrl);
resolveAndComposePackages(json.scopes[s], outMap.scopes[resolvedScope] || (outMap.scopes[resolvedScope] = {}), baseUrl, parentMap);
}
}
return outMap;
}
let importMap = {
imports: {},
scopes: {}
};
// TODO: check if this baseURI should change per document, and so
// it need to be passed as a parameter to methods like `resolve`.
const baseUrl = document.baseURI;
const pageBaseUrl = baseUrl;
/**
* Extends the internal dynamic import map with the passed one.
*
* @param importMapIn Import map.
* @param importMapIn.imports Imports declaration.
* @param importMapIn.scopes Scopes declaration.
*/
export function addImportMap(importMapIn) {
importMap = resolveAndComposeImportMap(importMapIn, pageBaseUrl, importMap);
}
/**
* Resolves the URL of the passed module ID against the current internal
* dynamic import map.
*
* @param id Module ID.
* @param parentUrl Parent URL, in case the module ID is relative.
* @return Resolved module URL.
*/
export function resolve(id, parentUrl) {
const urlResolved = resolveIfNotPlainOrUrl(id, parentUrl);
return resolveImportMap(importMap, urlResolved || id, parentUrl) || id;
}
//# sourceMappingURL=resolver.js.map