UNPKG

@wordpress/interactivity-router

Version:

Package that exposes state and actions from the `core/router` store, part of the Interactivity API.

279 lines (268 loc) 8.84 kB
/* 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 */ /** * External dependencies */ import * as lexer from 'es-module-lexer'; /** * Internal dependencies */ import { fetchModule } from './fetch'; import { resolve } from './resolver'; export const initPromise = lexer.init; /** * Script element containing the initial page's import map. */ const initialImportMapElement = window.document.querySelector('script#wp-importmap[type=importmap]'); /** * Data from the initial page's import map. * * Pages containing any of the imports present on the original page * in their import maps should ignore them, as those imports would * be handled natively. */ export const initialImportMap = initialImportMapElement ? JSON.parse(initialImportMapElement.text) : { imports: {}, scopes: {} }; const skip = id => Object.keys(initialImportMap.imports).includes(id); const fetchCache = {}; export const registry = {}; // Init registry with importamp content. Object.keys(initialImportMap.imports).forEach(id => { registry[id] = { blobUrl: id }; }); async function loadAll(load, seen) { if (load.blobUrl || seen[load.url]) { return; } seen[load.url] = 1; await load.linkPromise; await Promise.all(load.deps.map(dep => loadAll(dep, seen))); } function urlJsString(url) { return `'${url.replace(/'/g, "\\'")}'`; } const createBlob = (source, type = 'text/javascript') => URL.createObjectURL(new Blob([source], { type })); function resolveDeps(load, seen) { if (load.blobUrl || !seen[load.url]) { return; } seen[load.url] = 0; for (const dep of load.deps) { resolveDeps(dep, seen); } const [imports, exports] = load.analysis; const source = load.source; let resolvedSource = ''; if (!imports.length) { resolvedSource += source; } else { let lastIndex = 0; let depIndex = 0; const dynamicImportEndStack = []; function pushStringTo(originalIndex) { while (dynamicImportEndStack.length && dynamicImportEndStack[dynamicImportEndStack.length - 1] < originalIndex) { const dynamicImportEnd = dynamicImportEndStack.pop(); resolvedSource += `${source.slice(lastIndex, dynamicImportEnd)}, ${urlJsString(load.responseUrl)}`; lastIndex = dynamicImportEnd; } resolvedSource += source.slice(lastIndex, originalIndex); lastIndex = originalIndex; } for (const { s: start, ss: statementStart, se: statementEnd, d: dynamicImportIndex } of imports) { // static import if (dynamicImportIndex === -1) { const depLoad = load.deps[depIndex++]; let blobUrl = depLoad.blobUrl; const cycleShell = !blobUrl; if (cycleShell) { // Circular shell creation if (!(blobUrl = depLoad.shellUrl)) { blobUrl = depLoad.shellUrl = createBlob(`export function u$_(m){${depLoad.analysis[1].map(({ s, e }, i) => { const q = depLoad.source[s] === '"' || depLoad.source[s] === "'"; return `e$_${i}=m${q ? `[` : '.'}${depLoad.source.slice(s, e)}${q ? `]` : ''}`; }).join(',')}}${depLoad.analysis[1].length ? `let ${depLoad.analysis[1].map((_, i) => `e$_${i}`).join(',')};` : ''}export {${depLoad.analysis[1].map(({ s, e }, i) => `e$_${i} as ${depLoad.source.slice(s, e)}`).join(',')}}\n//# sourceURL=${depLoad.responseUrl}?cycle`); } } pushStringTo(start - 1); resolvedSource += `/*${source.slice(start - 1, statementEnd)}*/${urlJsString(blobUrl)}`; // circular shell execution if (!cycleShell && depLoad.shellUrl) { resolvedSource += `;import*as m$_${depIndex} from'${depLoad.blobUrl}';import{u$_ as u$_${depIndex}}from'${depLoad.shellUrl}';u$_${depIndex}(m$_${depIndex})`; depLoad.shellUrl = undefined; } lastIndex = statementEnd; } // import.meta else if (dynamicImportIndex === -2) { throw Error('The import.meta property is not supported.'); } // dynamic import else { pushStringTo(statementStart); resolvedSource += `wpInteractivityRouterImport(`; dynamicImportEndStack.push(statementEnd - 1); lastIndex = start; } } // progressive cycle binding updates if (load.shellUrl) { resolvedSource += `\n;import{u$_}from'${load.shellUrl}';try{u$_({${exports.filter(e => e.ln).map(({ s, e, ln }) => `${source.slice(s, e)}:${ln}`).join(',')}})}catch(_){};\n`; } pushStringTo(source.length); } // ensure we have a proper sourceURL let hasSourceURL = false; resolvedSource = resolvedSource.replace(sourceMapURLRegEx, (match, isMapping, url) => { hasSourceURL = !isMapping; return match.replace(url, () => new URL(url, load.responseUrl).toString()); }); if (!hasSourceURL) { resolvedSource += '\n//# sourceURL=' + load.responseUrl; } load.blobUrl = createBlob(resolvedSource); load.source = undefined; // free memory } const sourceMapURLRegEx = /\n\/\/# source(Mapping)?URL=([^\n]+)\s*((;|\/\/[^#][^\n]*)\s*)*$/; function getOrCreateLoad(url, fetchOpts, parent) { let load = registry[url]; if (load) { return load; } load = { url }; if (registry[url]) { // If there's a naming conflict, keep incrementing until unique let i = 0; while (registry[load.url + ++i]) { /* no-op */ } load.url += i; } registry[load.url] = load; load.fetchPromise = (async () => { let source; ({ responseUrl: load.responseUrl, source: source } = await (fetchCache[url] || fetchModule(url, fetchOpts, parent))); try { load.analysis = lexer.parse(source, load.url); } catch (e) { // eslint-disable-next-line no-console console.error(e); load.analysis = [[], [], false, false]; } load.source = source; return load; })(); load.linkPromise = load.fetchPromise.then(async () => { let childFetchOpts = fetchOpts; load.deps = (await Promise.all(load.analysis[0].map(async ({ n, d }) => { if (d !== -1 || !n) { return undefined; } const responseUrl = resolve(n, load.responseUrl || load.url); if (skip && skip(responseUrl)) { return { blobUrl: responseUrl }; } // remove integrity for child fetches if (childFetchOpts.integrity) { childFetchOpts = { ...childFetchOpts, integrity: undefined }; } return getOrCreateLoad(responseUrl, childFetchOpts, load.responseUrl).fetchPromise; }))).filter(l => l); }); return load; } const dynamicImport = u => import(/* webpackIgnore: true */u); /** * Resolves the passed module URL and fetches the corresponding module * and their dependencies, returning a `ModuleLoad` object once all * of them have been fetched. * * @param url Module URL. * @param fetchOpts Fetch options. * @return A promise with a `ModuleLoad` instance. */ export async function preloadModule(url, fetchOpts) { await initPromise; const load = getOrCreateLoad(url, fetchOpts, null); const seen = {}; await loadAll(load, seen); resolveDeps(load, seen); // microtask scheduling – can help ensure Blob is fully ready await Promise.resolve(); return load; } /** * Imports the module represented by the passed `ModuleLoad` instance. * * @param load The `ModuleLoad` instance representing the module. * @return A promise with the imported module. */ export async function importPreloadedModule(load) { const module = await dynamicImport(load.blobUrl); // if the preloaded module ended up with a shell (circular refs), finalize it if (load.shellUrl) { (await dynamicImport(load.shellUrl)).u$_(module); } return module; } /** * Imports the module represented by the passed module URL. * * The module URL and all its dependencies are resolved using the * current status of the internal dynamic import map. * * @param url Module URL. * @param fetchOpts Fetch options. * @return A promise with the imported module. */ export async function topLevelLoad(url, fetchOpts) { const load = await preloadModule(url, fetchOpts); return importPreloadedModule(load); } //# sourceMappingURL=loader.js.map