UNPKG

@jspm/generator

Version:

Package Import Map Generation Tool

842 lines (840 loc) 37.4 kB
/** * Copyright 2020-2023 Guy Bedford * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * The main entry point into the @jspm/generator package. * @module generator.ts */ 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 { baseUrl as _baseUrl, relativeUrl, resolveUrl } from "./common/url.js"; import { parseTarget, validatePkgName } from "./install/package.js"; import TraceMap from "./trace/tracemap.js"; // @ts-ignore import { clearCache as clearFetchCache, fetch as _fetch, setFetch } from "./common/fetch.js"; import { ImportMap } from "@jspm/import-map"; import process from "process"; import { SemverRange } from "sver"; import { JspmError } from "./common/err.js"; import { getIntegrity } from "./common/integrity.js"; import { createLogger } from "./common/log.js"; import { Replacer } from "./common/str.js"; import { analyzeHtml } from "./html/analyze.js"; import { configureProviders, getDefaultProviderStrings } from "./providers/index.js"; import * as nodemodules from "./providers/nodemodules.js"; import { Resolver } from "./trace/resolver.js"; import { getMaybeWrapperUrl } from "./common/wrapper.js"; import { setRetryCount } from "./common/fetch-common.js"; export { // utility export analyzeHtml, // hook export setFetch }; /** * Supports clearing the global fetch cache in Node.js. * * Example: * * ```js * import { clearCache } from '@jspm/generator'; * clearCache(); * ``` */ export function clearCache() { clearFetchCache(); } /** * Generator. */ export class Generator { /** * Add new custom mappings and lock resolutions to the input map * of the generator, which are then applied in subsequent installs. * * @param jsonOrHtml The mappings are parsed as a JSON data object or string, falling back to reading an inline import map from an HTML file. * @param mapUrl An optional URL for the map to handle relative resolutions, defaults to generator mapUrl. * @param rootUrl An optional root URL for the map to handle root resolutions, defaults to generator rootUrl. * @returns The list of modules pinned by this import map or HTML. */ async addMappings(jsonOrHtml, mapUrl = this.mapUrl, rootUrl = this.rootUrl, preloads) { if (typeof mapUrl === "string") mapUrl = new URL(mapUrl, this.baseUrl); if (typeof rootUrl === "string") rootUrl = new URL(rootUrl, this.baseUrl); let htmlModules; if (typeof jsonOrHtml === "string") { try { jsonOrHtml = JSON.parse(jsonOrHtml); } catch { const analysis = analyzeHtml(jsonOrHtml, mapUrl); jsonOrHtml = analysis.map.json || {}; preloads = (preloads || []).concat(analysis.preloads.map((preload)=>{ var _preload_attrs_href; return (_preload_attrs_href = preload.attrs.href) === null || _preload_attrs_href === void 0 ? void 0 : _preload_attrs_href.value; }).filter((x)=>x)); htmlModules = [ ...new Set([ ...analysis.staticImports, ...analysis.dynamicImports ]) ]; } } await this.traceMap.addInputMap(jsonOrHtml, mapUrl, rootUrl, preloads); return htmlModules || [ ...this.traceMap.pins ]; } /** * Retrieve the lockfile data from the installer */ getLock() { return JSON.parse(JSON.stringify(this.traceMap.installer.installs)); } /** * Link a module, installing all dependencies necessary into the map * to support its execution including static and dynamic module imports. * * @param specifier Module or list of modules to link * @param parentUrl Optional parent URL */ async link(specifier, parentUrl) { if (typeof specifier === "string") specifier = [ specifier ]; let error = false; await this.traceMap.processInputMap; specifier = specifier.map((specifier)=>specifier.replace(/\\/g, "/")); try { await Promise.all(specifier.map((specifier)=>this.traceMap.visit(specifier, { installMode: "freeze", toplevel: true }, parentUrl || this.baseUrl.href))); for (const s of specifier){ if (!this.traceMap.pins.includes(s)) this.traceMap.pins.push(s); } } catch (e) { error = true; throw e; } finally{ const { map, staticDeps, dynamicDeps } = await this.traceMap.extractMap(this.traceMap.pins, this.integrity); this.map = map; if (!error) return { staticDeps, dynamicDeps }; } } /** * Links every imported module in the given HTML file, installing all * dependencies necessary to support its execution. * * @param html HTML to link * @param htmlUrl URL of the given HTML */ async linkHtml(html, htmlUrl) { if (Array.isArray(html)) { const impts = await Promise.all(html.map((h)=>this.linkHtml(h, htmlUrl))); return [ ...new Set(impts) ].reduce((a, b)=>a.concat(b), []); } let resolvedUrl; if (htmlUrl) { if (typeof htmlUrl === "string") { resolvedUrl = new URL(resolveUrl(htmlUrl, this.mapUrl, this.rootUrl)); } else { resolvedUrl = htmlUrl; } } const analysis = analyzeHtml(html, resolvedUrl); const impts = [ ...new Set([ ...analysis.staticImports, ...analysis.dynamicImports ]) ]; await Promise.all(impts.map((impt)=>this.link(impt, resolvedUrl === null || resolvedUrl === void 0 ? void 0 : resolvedUrl.href))); return impts; } /** * Inject the import map into the provided HTML source * * @param html HTML source to inject into * @param opts Injection options * @returns HTML source with import map injection */ async htmlInject(html, { trace = false, pins = !trace, htmlUrl = this.mapUrl, rootUrl = this.rootUrl, preload = false, integrity = false, whitespace = true, esModuleShims = true, comment = true } = {}) { if (comment === true) comment = " Generated by @jspm/generator - https://github.com/jspm/generator "; if (typeof htmlUrl === "string") htmlUrl = new URL(htmlUrl); const analysis = analyzeHtml(html, htmlUrl); let modules = pins === true ? this.traceMap.pins : Array.isArray(pins) ? pins : []; if (trace) { const impts = await this.linkHtml(html, htmlUrl); modules = [ ...new Set([ ...modules, ...impts ]) ]; } try { var { map, staticDeps, dynamicDeps } = await this.extractMap(modules, htmlUrl, rootUrl, integrity); } catch (err) { // Most likely cause of a generation failure: err.message += "\n\nIf you are linking locally against your node_modules folder, make sure that you have all the necessary dependencies installed."; } const preloadDeps = preload === "all" ? [ ...new Set([ ...staticDeps, ...dynamicDeps ]) ] : staticDeps; const newlineTab = !whitespace ? analysis.newlineTab : analysis.newlineTab.includes("\n") ? analysis.newlineTab : "\n" + analysis.newlineTab; const replacer = new Replacer(html); let esms = ""; if (esModuleShims) { let esmsPkg; try { esmsPkg = await this.traceMap.resolver.resolveLatestTarget({ name: "es-module-shims", registry: "npm", ranges: [ new SemverRange("*") ], unstable: false }, this.traceMap.installer.defaultProvider, this.baseUrl.href); } catch (err) { // This usually happens because the user is trying to use their // node_modules as the provider but has not installed the shim: let errMsg = `Unable to resolve "es-module-shims@*" under current provider "${this.traceMap.installer.defaultProvider.provider}".`; if (this.traceMap.installer.defaultProvider.provider === "nodemodules") { errMsg += `\n\nJspm automatically injects a shim so that the import map in your HTML file will be usable by older browsers.\nYou may need to run "npm install es-module-shims" to install the shim if you want to link against your local node_modules folder.`; } errMsg += `\nTo disable the import maps polyfill injection, set esModuleShims: false.`; throw new JspmError(errMsg); } let esmsUrl = await this.traceMap.resolver.pkgToUrl(esmsPkg, this.traceMap.installer.defaultProvider) + "dist/es-module-shims.js"; // detect esmsUrl as a wrapper URL esmsUrl = await getMaybeWrapperUrl(esmsUrl, this.traceMap.resolver.fetchOpts); if (htmlUrl || rootUrl) esmsUrl = relativeUrl(new URL(esmsUrl), new URL(rootUrl !== null && rootUrl !== void 0 ? rootUrl : htmlUrl), !!rootUrl); esms = `<script async src="${esmsUrl}" crossorigin="anonymous"${integrity ? ` integrity="${await getIntegrity(new Uint8Array(await (await fetch(esmsUrl, this.traceMap.resolver.fetchOpts)).arrayBuffer()))}"` : ""}></script>${newlineTab}`; if (analysis.esModuleShims) replacer.remove(analysis.esModuleShims.start, analysis.esModuleShims.end, true); } for (const preload of analysis.preloads){ replacer.remove(preload.start, preload.end, true); } let preloads = ""; if (preload && preloadDeps.length) { let first = true; for (let dep of preloadDeps.sort()){ if (first || whitespace) preloads += newlineTab; if (first) first = false; const url = rootUrl || htmlUrl ? relativeUrl(new URL(dep), new URL(rootUrl || htmlUrl), !!rootUrl) : dep; preloads += `<link rel="modulepreload" href="${url}"${integrity ? ` integrity="${await getIntegrity(new Uint8Array(await (await fetch(dep, this.traceMap.resolver.fetchOpts)).arrayBuffer()))}"` : ""} />`; } } if (comment) { const existingComment = analysis.comments.find((c)=>replacer.source.slice(replacer.idx(c.start), replacer.idx(c.end)).includes(comment)); if (existingComment) { replacer.remove(existingComment.start, existingComment.end, true); } } replacer.replace(analysis.map.start, analysis.map.end, (comment ? "<!--" + comment + "-->" + newlineTab : "") + esms + '<script type="importmap">' + (whitespace ? newlineTab : "") + JSON.stringify(map, null, whitespace ? 2 : 0).replace(/\n/g, newlineTab) + (whitespace ? newlineTab : "") + "</script>" + preloads + (analysis.map.newScript ? newlineTab : "")); return replacer.source; } /** * Install a package target into the import map, including all its dependency resolutions via tracing. * * @param install Package or list of packages to install into the import map. * * @example * ```js * // Install a new package into the import map * await generator.install('react-dom'); * * // Install a package version and subpath into the import map (installs lit/decorators.js) * await generator.install('lit@2/decorators.js'); * * // Install a package version to a custom alias * await generator.install({ alias: 'react16', target: 'react@16' }); * * // Install a specific subpath of a package * await generator.install({ target: 'lit@2', subpath: './html.js' }); * * // Install an export from a locally located package folder into the map * // The package.json is used to determine the exports and dependencies. * await generator.install({ alias: 'mypkg', target: './packages/local-pkg', subpath: './feature' }); * ``` */ async install(install) { return this._install(install); } async _install(install, mode) { // If there are no arguments, then we reinstall all the top-level locks: if (install === null || install === undefined) { await this.traceMap.processInputMap; // To match the behaviour of an argumentless `npm install`, we use // existing resolutions for everything unless it's out-of-range: mode !== null && mode !== void 0 ? mode : mode = "default"; return this._install(Object.entries(this.traceMap.installer.installs.primary).map(([alias, target])=>{ const pkgTarget = this.traceMap.installer.constraints.primary[alias]; // Try to reinstall lock against constraints if possible, otherwise // reinstall it as a URL directly (which has the downside that it // won't have NPM versioning semantics): let newTarget = target.installUrl; if (pkgTarget) { if (pkgTarget instanceof URL) { newTarget = pkgTarget.href; } else { newTarget = `${pkgTarget.registry}:${pkgTarget.name}`; } } var _target_installSubpath; return { alias, target: newTarget, subpath: (_target_installSubpath = target.installSubpath) !== null && _target_installSubpath !== void 0 ? _target_installSubpath : undefined }; }), mode); } // Split the case of multiple install subpaths into multiple installs // TODO: flatten all subpath installs here if (!Array.isArray(install) && typeof install !== "string" && install.subpaths !== undefined) { install.subpaths.every((subpath)=>{ if (typeof subpath !== "string" || subpath !== "." && !subpath.startsWith("./")) throw new Error(`Install subpath "${subpath}" must be equal to "." or start with "./".`); }); return this._install(install.subpaths.map((subpath)=>({ target: install.target, alias: install.alias, subpath }))); } if (!Array.isArray(install)) install = [ install ]; // Handle case of multiple install targets with at most one subpath: await this.traceMap.processInputMap; // don't race input processing const imports = await Promise.all(install.map(async (install)=>{ // Resolve input information to a target package: let alias, target, subpath; if (typeof install === "string" || typeof install.target === "string") { ({ alias, target, subpath } = await installToTarget.call(this, install, this.traceMap.installer.defaultRegistry)); } else { ({ alias, target, subpath } = install); validatePkgName(alias); } this.log("generator/install", `Adding primary constraint for ${alias}: ${JSON.stringify(target)}`); // By default, an install takes the latest compatible version for primary // dependencies, and existing in-range versions for secondaries: mode !== null && mode !== void 0 ? mode : mode = "latest-primaries"; await this.traceMap.add(alias, target, mode); return alias + (subpath ? subpath.slice(1) : ""); })); await Promise.all(imports.map(async (impt)=>{ await this.traceMap.visit(impt, { installMode: mode, toplevel: true }, this.mapUrl.href); // Add the target import as a top-level pin // we do this after the trace, so failed installs don't pollute the map if (!this.traceMap.pins.includes(impt)) this.traceMap.pins.push(impt); })); const { map, staticDeps, dynamicDeps } = await this.traceMap.extractMap(this.traceMap.pins, this.integrity); this.map = map; return { staticDeps, dynamicDeps }; } /** * Locking install, retraces all top-level pins but does not change the * versions of anything (similar to "npm ci"). */ async reinstall() { await this.traceMap.processInputMap; const { map, staticDeps, dynamicDeps } = await this.traceMap.extractMap(this.traceMap.pins, this.integrity); this.map = map; return { staticDeps, dynamicDeps }; } /** * Updates the versions of the given packages to the latest versions * compatible with their parent's package.json ranges. If no packages are * given then all the top-level packages in the "imports" field of the * initial import map are updated. * * @param {string | string[]} pkgNames Package name or list of package names to update. */ async update(pkgNames) { if (typeof pkgNames === "string") pkgNames = [ pkgNames ]; await this.traceMap.processInputMap; const primaryResolutions = this.traceMap.installer.installs.primary; const primaryConstraints = this.traceMap.installer.constraints.primary; // Matching the behaviour of "npm update": let mode = "latest-primaries"; if (!pkgNames) { pkgNames = Object.keys(primaryResolutions); mode = "latest-all"; } const installs = []; for (const name of pkgNames){ const resolution = primaryResolutions[name]; if (!resolution) { throw new JspmError(`No "imports" package entry for "${name}" to update. Note update takes package names not package specifiers.`); } const { installUrl, installSubpath } = resolution; const subpaths = this.traceMap.pins.filter((pin)=>pin === name || pin.startsWith(name) && pin[name.length] === "/").map((pin)=>`.${pin.slice(name.length)}`); // use package.json range if present if (primaryConstraints[name]) { installs.push({ alias: name, subpaths, target: { pkgTarget: primaryConstraints[name], installSubpath } }); } else { const pkg = await this.traceMap.resolver.parseUrlPkg(installUrl); if (!pkg) throw new Error(`Unable to determine a package version lookup for ${name}. Make sure it is supported as a provider package.`); const target = { pkgTarget: { registry: pkg.pkg.registry, name: pkg.pkg.name, ranges: [ new SemverRange("^" + pkg.pkg.version) ], unstable: false }, installSubpath }; installs.push({ alias: name, subpaths, target }); } } await this._install(installs, mode); const { map, staticDeps, dynamicDeps } = await this.traceMap.extractMap(this.traceMap.pins, this.integrity); this.map = map; return { staticDeps, dynamicDeps }; } async uninstall(names) { if (typeof names === "string") names = [ names ]; await this.traceMap.processInputMap; let pins = this.traceMap.pins; const unusedNames = new Set([ ...names ]); for(let i = 0; i < pins.length; i++){ const pin = pins[i]; const pinNames = names.filter((name)=>name === pin || name.endsWith("/") && pin.startsWith(name)); if (pinNames.length) { pins.splice(i--, 1); for (const name of pinNames)unusedNames.delete(name); } } if (unusedNames.size) { throw new JspmError(`No "imports" entry for "${[ ...unusedNames ][0]}" to uninstall.`); } this.traceMap.pins = pins; const { staticDeps, dynamicDeps, map } = await this.traceMap.extractMap(this.traceMap.pins, this.integrity); this.map = map; return { staticDeps, dynamicDeps }; } async extractMap(pins, mapUrl, rootUrl, integrity) { if (typeof mapUrl === "string") mapUrl = new URL(mapUrl, this.baseUrl); if (typeof rootUrl === "string") rootUrl = new URL(rootUrl, this.baseUrl); if (!Array.isArray(pins)) pins = [ pins ]; if (typeof integrity !== "boolean") integrity = this.integrity; await this.traceMap.processInputMap; const { map, staticDeps, dynamicDeps } = await this.traceMap.extractMap(pins, integrity); map.rebase(mapUrl, rootUrl); map.flatten(); map.sort(); map.combineSubpaths(); return { map: map.toJSON(), staticDeps, dynamicDeps }; } /** * Resolve a specifier using the import map. * * @param specifier Module to resolve * @param parentUrl ParentURL of module to resolve * @returns Resolved URL string */ resolve(specifier, parentUrl = this.baseUrl) { if (typeof parentUrl === "string") parentUrl = new URL(parentUrl, this.baseUrl); const resolved = this.map.resolve(specifier, parentUrl); if (resolved === null) throw new JspmError(`Unable to resolve "${specifier}" from ${parentUrl.href}`, "MODULE_NOT_FOUND"); return resolved; } get importMap() { return this.map; } getAnalysis(url) { if (typeof url !== "string") url = url.href; const trace = this.traceMap.resolver.getAnalysis(url); if (!trace) throw new Error(`The URL ${url} has not been traced by this generator instance.`); return { format: trace.format, staticDeps: trace.deps, dynamicDeps: trace.dynamicDeps, cjsLazyDeps: trace.cjsLazyDeps || [] }; } getMap(mapUrl, rootUrl) { const map = this.map.clone(); map.rebase(mapUrl, rootUrl); map.flatten(); map.sort(); map.combineSubpaths(); return map.toJSON(); } /** * Constructs a new Generator instance. * * For example: * * ```js * const generator = new Generator({ * mapUrl: import.meta.url, * inputMap: { * "imports": { * "react": "https://cdn.skypack.dev/react" * } * }, * defaultProvider: 'jspm', * defaultRegistry: 'npm', * providers: { * '@orgscope': 'nodemodules' * }, * customProviders: {}, * env: ['production', 'browser'], * cache: false, * }); * ``` * @param {GeneratorOptions} opts Configuration for the new generator instance. */ constructor({ baseUrl, mapUrl, rootUrl = undefined, inputMap = undefined, env = [ "browser", "development", "module", "import" ], defaultProvider, defaultRegistry = "npm", customProviders = undefined, providers, resolutions = {}, cache = true, fetchOptions = {}, ignore = [], commonJS = false, typeScript = false, system = false, integrity = false, fetchRetries, providerConfig = {}, preserveSymlinks } = {}){ var _process_env, _process_versions; _define_property(this, "traceMap", void 0); _define_property(this, "baseUrl", void 0); _define_property(this, "mapUrl", void 0); _define_property(this, "rootUrl", void 0); _define_property(this, "map", void 0); _define_property(this, "logStream", void 0); _define_property(this, "log", void 0); _define_property(this, "integrity", void 0); // Initialise the debug logger: const { log, logStream } = createLogger(); this.log = log; this.logStream = logStream; if (process === null || process === void 0 ? void 0 : (_process_env = process.env) === null || _process_env === void 0 ? void 0 : _process_env.JSPM_GENERATOR_LOG) { (async ()=>{ for await (const { type, message } of this.logStream()){ console.log(`\x1b[1m${type}:\x1b[0m ${message}`); } })(); } if (typeof preserveSymlinks !== "boolean") preserveSymlinks = typeof (process === null || process === void 0 ? void 0 : (_process_versions = process.versions) === null || _process_versions === void 0 ? void 0 : _process_versions.node) === "string"; // Initialise the resource fetcher: let fetchOpts = { retry: 1, timeout: 10000, ...fetchOptions, headers: { "Accept-Encoding": "gzip, br" } }; if (cache === "offline") fetchOpts.cache = "force-cache"; else if (!cache) fetchOpts.cache = "no-store"; // Default logic for the mapUrl, baseUrl and rootUrl: if (mapUrl && !baseUrl) { mapUrl = typeof mapUrl === "string" ? new URL(mapUrl, _baseUrl) : mapUrl; try { baseUrl = new URL("./", mapUrl); } catch { baseUrl = new URL(mapUrl + "/"); } } else if (baseUrl && !mapUrl) { mapUrl = baseUrl; } else if (!mapUrl && !baseUrl) { baseUrl = mapUrl = _baseUrl; } this.baseUrl = typeof baseUrl === "string" ? new URL(baseUrl, _baseUrl) : baseUrl; if (!this.baseUrl.pathname.endsWith("/")) { this.baseUrl = new URL(this.baseUrl.href); this.baseUrl.pathname += "/"; } this.mapUrl = typeof mapUrl === "string" ? new URL(mapUrl, this.baseUrl) : mapUrl; this.rootUrl = typeof rootUrl === "string" ? new URL(rootUrl, this.baseUrl) : rootUrl || null; if (this.rootUrl && !this.rootUrl.pathname.endsWith("/")) this.rootUrl.pathname += "/"; if (!this.mapUrl.pathname.endsWith("/")) { try { this.mapUrl = new URL("./", this.mapUrl); } catch { this.mapUrl = new URL(this.mapUrl.href + "/"); } } this.integrity = integrity; // Initialise the resolver: const resolver = new Resolver({ env, log, fetchOpts, preserveSymlinks, traceCjs: commonJS, traceTs: typeScript, traceSystem: system }); if (customProviders) { for (const provider of Object.keys(customProviders)){ resolver.addCustomProvider(provider, customProviders[provider]); } } // The node_modules provider is special, because it needs to be rooted to // perform resolutions against the local node_modules directory: const nmProvider = nodemodules.createProvider(this.baseUrl.href, defaultProvider === "nodemodules"); resolver.addCustomProvider("nodemodules", nmProvider); // We make an attempt to auto-detect the default provider from the input // map, by picking the provider with the most owned URLs: defaultProvider = detectDefaultProvider(defaultProvider, inputMap, resolver); // Initialise the tracer: this.traceMap = new TraceMap({ mapUrl: this.mapUrl, rootUrl: this.rootUrl, baseUrl: this.baseUrl, defaultProvider, defaultRegistry, providers, ignore, resolutions, commonJS }, log, resolver); // Reconstruct constraints and locks from the input map: this.map = new ImportMap({ mapUrl: this.mapUrl, rootUrl: this.rootUrl }); if (!integrity) this.map.integrity = {}; if (inputMap) this.addMappings(inputMap); // Set the fetch retry count if (typeof fetchRetries === "number") setRetryCount(fetchRetries); configureProviders(providerConfig, resolver.providers); } } /** * _Use the internal fetch implementation, useful for hooking into the same shared local fetch cache._ * * ```js * import { fetch } from '@jspm/generator'; * * const res = await fetch(url); * console.log(await res.text()); * ``` * * Use the `{ cache: 'no-store' }` option to disable the cache, and the `{ cache: 'force-cache' }` option to enforce the offline cache. */ export async function fetch(url, opts = {}) { // @ts-ignore return _fetch(url, opts); } /** * Get the lookup resolution information for a specific install. * * @param install The install object * @param lookupOptions Provider and cache defaults for lookup * @returns The resolved install and exact package \{ install, resolved \} */ export async function lookup(install, { provider, cache } = {}) { const generator = new Generator({ cache: !cache, defaultProvider: provider }); const { target, subpath, alias } = await installToTarget.call(generator, install, generator.traceMap.installer.defaultRegistry); if (typeof target === "string") throw new Error(`Resolved install "${install}" to package specifier ${target}, but expected a fully qualified install target.`); const { pkgTarget, installSubpath } = target; if (pkgTarget instanceof URL) throw new Error("URL lookups not supported"); const resolved = await generator.traceMap.resolver.resolveLatestTarget(pkgTarget, generator.traceMap.installer.getProvider(pkgTarget), generator.baseUrl.href); return { install: { target: { registry: pkgTarget.registry, name: pkgTarget.name, range: pkgTarget.ranges.map((range)=>range.toString()).join(" || ") }, installSubpath, subpath, alias }, resolved: resolved }; } /** * Get the package.json configuration for a specific URL or package. * * @param pkg Package to lookup configuration for * @param lookupOptions Optional provider and cache defaults for lookup * @returns Package JSON configuration * * Example: * ```js * import { getPackageConfig } from '@jspm/generator'; * * // Supports a resolved package * { * const packageJson = await getPackageConfig({ registry: 'npm', name: 'lit-element', version: '2.5.1' }); * } * * // Or alternatively provide any URL * { * const packageJson = await getPackageConfig('https://ga.jspm.io/npm:lit-element@2.5.1/lit-element.js'); * } * ``` */ export async function getPackageConfig(pkg, { provider, cache } = {}) { const generator = new Generator({ cache: !cache, defaultProvider: provider }); if (typeof pkg === "object" && "name" in pkg) pkg = await generator.traceMap.resolver.pkgToUrl(pkg, generator.traceMap.installer.defaultProvider); else if (typeof pkg === "string") pkg = new URL(pkg).href; else pkg = pkg.href; return generator.traceMap.resolver.getPackageConfig(pkg); } /** * Get the package base URL for the given module URL. * * @param url module URL * @param lookupOptions Optional provider and cache defaults for lookup * @returns Base package URL * * Modules can be remote CDN URLs or local file:/// URLs. * * All modules in JSPM are resolved as within a package boundary, which is the * parent path of the package containing a package.json file. * * For JSPM CDN this will always be the base of the package as defined by the * JSPM CDN provider. For non-provider-defined origins it is always determined * by trying to fetch the package.json in each parent path until the root is reached * or one is found. On file:/// URLs this exactly matches the Node.js resolution * algorithm boundary lookup. * * This package.json file controls the package name, imports resolution, dependency * resolutions and other package information. * * getPackageBase will return the folder containing the package.json, * with a trailing '/'. * * This URL will either be the root URL of the origin, or it will be a * path "pkgBase" such that fetch(`${pkgBase}package.json`) is an existing * package.json file. * * @example * ```js * import { getPackageBase } from '@jspm/generator'; * const pkgUrl = await getPackageBase('https://ga.jspm.io/npm:lit-element@2.5.1/lit-element.js'); * // Returns: https://ga.jspm.io/npm:lit-element@2.5.1/ * ``` */ export async function getPackageBase(url, { provider, cache } = {}) { const generator = new Generator({ cache: !cache, defaultProvider: provider }); return generator.traceMap.resolver.getPackageBase(typeof url === "string" ? url : url.href); } /** * Get the package metadata for the given module or package URL. * * @param url URL of a module or package for a configured provider. * @param lookupOptions Optional provider and cache defaults for lookup. * @returns Package metadata for the given URL if one of the configured * providers owns it, else null. * * The returned metadata will always contain the package name, version and * registry, along with the provider name and layer that handles resolution * for the given URL. */ export async function parseUrlPkg(url, { provider, cache } = {}) { const generator = new Generator({ cache: !cache, defaultProvider: provider }); return generator.traceMap.resolver.parseUrlPkg(typeof url === "string" ? url : url.href); } /** * Returns a list of providers that are supported by default. * * @returns List of valid provider strings supported by default. * * To use one of these providers, pass the string to either the "defaultProvider" * option or the "providers" mapping when constructing a Generator. */ export function getDefaultProviders() { return getDefaultProviderStrings(); } async function installToTarget(install, defaultRegistry) { if (typeof install === "string") install = { target: install }; if (typeof install.target !== "string") throw new Error('All installs require a "target" string.'); if (install.subpath !== undefined && (typeof install.subpath !== "string" || install.subpath !== "." && !install.subpath.startsWith("./"))) throw new Error(`Install subpath "${install.subpath}" must be a string equal to "." or starting with "./".${typeof install.subpath === "string" ? `\nTry setting the subpath to "./${install.subpath}"` : ""}`); const { alias, target, subpath } = await parseTarget(this.traceMap.resolver, install.target, this.baseUrl, defaultRegistry); return { target, alias: install.alias || alias, subpath: install.subpath || subpath }; } function detectDefaultProvider(defaultProvider, inputMap, resolver) { // We only use top-level install information to detect the provider: const counts = {}; for (const url of Object.values((inputMap === null || inputMap === void 0 ? void 0 : inputMap.imports) || {})){ const name = resolver.providerNameForUrl(url); if (name) { counts[name] = (counts[name] || 0) + 1; } } let winner; let winnerCount = 0; for (const [name, count] of Object.entries(counts)){ if (count > winnerCount) { winner = name; winnerCount = count; } } // TODO: this should be the behaviour once we support full 'providers' opt // The leading provider in the input map takes precedence as the provider of // the root package. Failing that, the user-provided default is used. The // 'providers' field can be used for hard-overriding this: // return winner || defaultProvider || "jspm.io"; return defaultProvider || winner || "jspm.io"; } //# sourceMappingURL=generator.js.map