UNPKG

@jspm/generator

Version:

Package Import Map Generation Tool

317 lines (315 loc) 16.7 kB
function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import { importedFrom, isFetchProtocol, isPlain, isURL, resolveUrl } from "../common/url.js"; import { Installer } from "../install/installer.js"; import { JspmError, throwInternalError } from "../common/err.js"; import { parsePkg } from "../install/package.js"; // @ts-ignore import { ImportMap, getMapMatch, getScopeMatches } from "@jspm/import-map"; import { isBuiltinScheme, isMappableScheme } from "./resolver.js"; import { mergeConstraints, mergeLocks, extractLockConstraintsAndMap } from "../install/lock.js"; function combineSubpaths(installSubpath, traceSubpath) { if (traceSubpath.endsWith("/")) throw new Error("Trailing slash subpaths unsupported"); return installSubpath === null || installSubpath === "." || traceSubpath === "." ? installSubpath || traceSubpath : `${installSubpath}${traceSubpath.slice(1)}`; } class TraceMap { async addInputMap(map, mapUrl = this.mapUrl, rootUrl = this.rootUrl, preloads) { // Note: integrity is currently ignored for inputMaps, instead integrity // is always trusted at generation time. return this.processInputMap = this.processInputMap.then(async ()=>{ const inMap = new ImportMap({ map, mapUrl, rootUrl }).rebase(this.mapUrl, this.rootUrl); const pins = Object.keys(inMap.imports || []); for (const pin of pins){ if (!this.pins.includes(pin)) this.pins.push(pin); } const { maps, locks, constraints } = await extractLockConstraintsAndMap(inMap, preloads, mapUrl, rootUrl, this.installer.defaultRegistry, this.resolver, this.installer.defaultProvider); this.inputMap.extend(maps); mergeLocks(this.installer.installs, locks); mergeConstraints(this.installer.constraints, constraints); }); } /** * Resolves, analyses and recursively visits the given module specifier and all of its dependencies. * * @param {string} specifier Module specifier to visit. * @param {VisitOpts} opts Visitor configuration. * @param {} parentUrl URL of the parent context for the specifier. * @param {} seen Cache for optimisation. */ async visit(specifier, opts, parentUrl = this.baseUrl.href, seen = new Set()) { var _this_opts_ignore; if (!parentUrl) throw new Error("Internal error: expected parentUrl"); if ((_this_opts_ignore = this.opts.ignore) === null || _this_opts_ignore === void 0 ? void 0 : _this_opts_ignore.includes(specifier)) return; if (seen.has(`${specifier}##${parentUrl}`)) return; seen.add(`${specifier}##${parentUrl}`); this.log("tracemap/visit", `Attempting to resolve ${specifier} to a module from ${parentUrl}, toplevel=${opts.toplevel}, mode=${opts.installMode}`); const resolved = await this.resolve(specifier, parentUrl, opts.installMode, opts.toplevel); // We support analysis of CommonJS modules for local workflows, where it's // very likely that the user has some CommonJS dependencies, but this is // something that the user has to explicitly enable: if (isBuiltinScheme(resolved)) return null; if (resolved.endsWith("/")) throw new JspmError(`Trailing "/" installs not supported installing ${resolved} for ${parentUrl}`); try { var entry = await this.resolver.analyze(resolved); } catch (e) { if (e instanceof JspmError) throw new Error(`Unable to analyze ${resolved} imported from ${parentUrl}: ${e.message}`); else throw new Error(`Unable to analyze ${resolved} imported from ${parentUrl}`, { cause: e }); } if (entry === null) { throw new Error(`Module not found ${resolved} imported from ${parentUrl}`); } if ((entry === null || entry === void 0 ? void 0 : entry.format) === "commonjs" && entry.usesCjs && !this.opts.commonJS) { throw new JspmError(`Unable to trace ${resolved}, as it is a CommonJS module. Either enable CommonJS tracing explicitly by setting "GeneratorOptions.commonJS" to true, or use a provider that performs ESM transpiling like jspm.io via defaultProvider: 'jspm.io'.`); } if (opts.visitor) { const stop = await opts.visitor(specifier, parentUrl, resolved, opts.toplevel, entry); if (stop) return; } if (!entry) return; let allDeps = [ ...entry.deps ]; if (entry.dynamicDeps.length && !opts.static) { for (const dep of entry.dynamicDeps){ if (!allDeps.includes(dep)) allDeps.push(dep); } } if (entry.cjsLazyDeps && !opts.static) { for (const dep of entry.cjsLazyDeps){ if (!allDeps.includes(dep)) allDeps.push(dep); } } if (opts.toplevel && (isMappableScheme(specifier) || isPlain(specifier))) { opts = { ...opts, toplevel: false }; } await Promise.all(allDeps.map(async (dep)=>{ // Special wildcard tracing syntax for dynamic imports, todo if (dep.indexOf("\x10") !== -1) { this.log("todo", "Handle wildcard trace " + dep + " in " + resolved); return; } await this.visit(dep, opts, resolved, seen); })); } async extractMap(modules, integrity) { const map = new ImportMap({ mapUrl: this.mapUrl, rootUrl: this.rootUrl }); // note this plucks custom top-level custom imports // we may want better control over this map.extend(this.inputMap); // visit to build the mappings const staticList = new Set(); const dynamicList = new Set(); const dynamics = []; let list = staticList; const visitor = async (specifier, parentUrl, resolved, toplevel, entry)=>{ if (!list.has(resolved)) list.add(resolved); // no entry applies to builtins if (entry) { if (integrity) map.setIntegrity(resolved, entry.integrity); for (const dep of entry.dynamicDeps){ dynamics.push([ dep, resolved ]); } } if (toplevel) { if (isPlain(specifier) || isMappableScheme(specifier)) { var _this_resolver_getAnalysis; const existing = map.imports[specifier]; if (!existing || existing !== resolved && ((_this_resolver_getAnalysis = this.resolver.getAnalysis(parentUrl)) === null || _this_resolver_getAnalysis === void 0 ? void 0 : _this_resolver_getAnalysis.wasCjs)) { map.set(specifier, resolved); } } } else { if (isPlain(specifier) || isMappableScheme(specifier)) { var _map_scopes_scopeUrl, _map_scopes_parentUrl; const scopeUrl = await this.resolver.getPackageBase(parentUrl); const existing = (_map_scopes_scopeUrl = map.scopes[scopeUrl]) === null || _map_scopes_scopeUrl === void 0 ? void 0 : _map_scopes_scopeUrl[specifier]; if (!existing) { map.set(specifier, resolved, scopeUrl); } else if (existing !== resolved && ((_map_scopes_parentUrl = map.scopes[parentUrl]) === null || _map_scopes_parentUrl === void 0 ? void 0 : _map_scopes_parentUrl[specifier]) !== resolved) { map.set(specifier, resolved, parentUrl); } } } }; const seen = new Set(); await Promise.all(modules.map(async (module)=>{ await this.visit(module, { static: true, visitor, installMode: "freeze", toplevel: true }, this.baseUrl.href, seen); })); list = dynamicList; await Promise.all(dynamics.map(async ([specifier, parent])=>{ await this.visit(specifier, { visitor, installMode: "freeze", toplevel: false }, parent, seen); })); if (this.installer.newInstalls) { // Disabled as it obscures valid errors // console.warn('Unexpected resolution divergence.'); } return { map, staticDeps: [ ...staticList ], dynamicDeps: [ ...dynamicList ] }; } async add(name, target, opts) { await this.installer.installTarget(name, target, null, opts, null, this.mapUrl.href); } /** * @returns `resolved` - either a URL `string` pointing to the module or `null` if the specifier should be ignored. */ async resolve(specifier, parentUrl, installOpts, toplevel) { const parentAnalysis = this.resolver.getAnalysis(parentUrl); const cjsEnv = parentAnalysis === null || parentAnalysis === void 0 ? void 0 : parentAnalysis.wasCjs; const parentPkgUrl = await this.resolver.getPackageBase(parentUrl); if (!parentPkgUrl) throwInternalError(); const parentIsCjs = (parentAnalysis === null || parentAnalysis === void 0 ? void 0 : parentAnalysis.format) === "commonjs"; if ((!isPlain(specifier) || specifier === "..") && !isMappableScheme(specifier)) { let resolvedUrl = new URL(specifier, parentUrl); if (!isFetchProtocol(resolvedUrl.protocol)) throw new JspmError(`Found unexpected protocol ${resolvedUrl.protocol}${importedFrom(parentUrl)}`); const resolvedHref = resolvedUrl.href; let finalized = await this.resolver.realPath(await this.resolver.finalizeResolve(resolvedHref, parentIsCjs, false, parentPkgUrl)); // handle URL mappings const urlResolved = this.inputMap.resolve(finalized, parentUrl); // TODO: avoid this hack - perhaps solved by conditional maps if (urlResolved !== finalized && !urlResolved.startsWith("node:") && !urlResolved.startsWith("deno:")) { finalized = urlResolved; } if (finalized !== resolvedHref) { this.inputMap.set(resolvedHref.endsWith("/") ? resolvedHref.slice(0, -1) : resolvedHref, finalized); resolvedUrl = new URL(finalized); } this.log("tracemap/resolve", `${specifier} ${parentUrl} -> ${resolvedUrl} (URL resolution)`); return resolvedUrl.href; } // Subscope override const scopeMatches = getScopeMatches(parentUrl, this.inputMap.scopes, this.inputMap.mapUrl); const pkgSubscopes = scopeMatches.filter(([, url])=>url.startsWith(parentPkgUrl)); if (pkgSubscopes.length) { for (const [scope] of pkgSubscopes){ const mapMatch = getMapMatch(specifier, this.inputMap.scopes[scope]); if (mapMatch) { const resolved = await this.resolver.realPath(resolveUrl(this.inputMap.scopes[scope][mapMatch] + specifier.slice(mapMatch.length), this.inputMap.mapUrl, this.inputMap.rootUrl)); this.log("tracemap/resolve", `${specifier} ${parentUrl} -> ${resolved} (subscope resolution)`); return resolved; } } } // Scope override // TODO: isn't this subsumed by previous check? const userScopeMatch = scopeMatches.find(([, url])=>url === parentPkgUrl); if (userScopeMatch) { const imports = this.inputMap.scopes[userScopeMatch[0]]; const userImportsMatch = getMapMatch(specifier, imports); const userImportsResolved = userImportsMatch ? await this.resolver.realPath(resolveUrl(imports[userImportsMatch] + specifier.slice(userImportsMatch.length), this.inputMap.mapUrl, this.inputMap.rootUrl)) : null; if (userImportsResolved) { this.log("tracemap/resolve", `${specifier} ${parentUrl} -> ${userImportsResolved} (scope resolution)`); return userImportsResolved; } } // User import overrides const userImportsMatch = getMapMatch(specifier, this.inputMap.imports); const userImportsResolved = userImportsMatch ? await this.resolver.realPath(resolveUrl(this.inputMap.imports[userImportsMatch] + specifier.slice(userImportsMatch.length), this.inputMap.mapUrl, this.inputMap.rootUrl)) : null; if (userImportsResolved) { this.log("tracemap/resolve", `${specifier} ${parentUrl} -> ${userImportsResolved} (imports resolution)`); return userImportsResolved; } const parsed = parsePkg(specifier); if (!parsed) throw new JspmError(`Invalid package name ${specifier}`); const { pkgName, subpath } = parsed; // Own name import const pcfg = await this.resolver.getPackageConfig(parentPkgUrl) || {}; if (pcfg.exports && pcfg.name === pkgName) { const resolved = await this.resolver.realPath(await this.resolver.resolveExport(parentPkgUrl, subpath, cjsEnv, parentIsCjs, specifier, parentUrl)); this.log("tracemap/resolve", `${specifier} ${parentUrl} -> ${resolved} (package own-name resolution)`); return resolved; } // Imports if (pcfg.imports && pkgName[0] === "#") { const match = getMapMatch(specifier, pcfg.imports); if (!match) throw new JspmError(`No '${specifier}' import defined in ${parentPkgUrl}${importedFrom(parentUrl)}.`); const target = this.resolver.resolvePackageTarget(pcfg.imports[match], parentPkgUrl, cjsEnv, specifier.slice(match.length), true); if (!isURL(target)) { return this.resolve(target, parentUrl, installOpts, toplevel); } const resolved = await this.resolver.realPath(target); this.log("tracemap/resolve", `${specifier} ${parentUrl} -> ${resolved} (package imports resolution)`); return resolved; } // @ts-ignore const installed = await this.installer.install(pkgName, installOpts, toplevel ? null : parentPkgUrl, subpath, parentUrl); if (typeof installed === "string") { return installed; } else if (installed) { const { installUrl, installSubpath } = installed; const resolved = await this.resolver.realPath(await this.resolver.resolveExport(installUrl, combineSubpaths(installSubpath, parentIsCjs && subpath.endsWith("/") ? subpath.slice(0, -1) : subpath), cjsEnv, parentIsCjs, specifier, parentUrl)); this.log("tracemap/resolve", `${specifier} ${parentUrl} -> ${resolved} (installation resolution)`); return resolved; } throw new JspmError(`No resolution in map for ${specifier}${importedFrom(parentUrl)}`); } constructor(opts, log, resolver){ _define_property(this, "installer", void 0); _define_property(this, "opts", void 0); _define_property(this, "inputMap", void 0 // custom imports ); _define_property(this, "mapUrl", void 0); _define_property(this, "baseUrl", void 0); _define_property(this, "rootUrl", void 0); _define_property(this, "pins", []); _define_property(this, "log", void 0); _define_property(this, "resolver", void 0); /** * Lock to ensure no races against input map processing. * @type {Promise<void>} */ _define_property(this, "processInputMap", Promise.resolve()); this.log = log; this.resolver = resolver; this.mapUrl = opts.mapUrl; this.baseUrl = opts.baseUrl; this.rootUrl = opts.rootUrl || null; this.opts = opts; this.inputMap = new ImportMap({ mapUrl: this.mapUrl, rootUrl: this.rootUrl }); this.installer = new Installer(this.mapUrl.pathname.endsWith("/") ? this.mapUrl.href : `${this.mapUrl.href}/`, this.opts, this.log, this.resolver); } } // The tracemap fully drives the installer export { TraceMap as default }; //# sourceMappingURL=tracemap.js.map