UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

175 lines (174 loc) 5.99 kB
import Plugin from "./Plugin.js"; import ReExports from "./ReExports/index.js"; import { isElectron } from "./util/index.js"; export function isUMDPluginDefinition(def) { return ((def.umdUrl !== undefined || def.url !== undefined || def.umdLoc !== undefined) && def.name !== undefined); } export function isESMPluginDefinition(def) { return (def.esmUrl !== undefined || def.esmLoc !== undefined); } function promisifiedLoadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.type = 'text/javascript'; script.async = true; script.src = src; script.onload = () => { resolve(script.src); }; script.onerror = () => { reject(new Error(`Failed to load script: ${src}`)); }; document.head.append(script); }); } async function loadScript(scriptUrl) { if (!isInWebWorker()) { return promisifiedLoadScript(scriptUrl); } if (globalThis.importScripts) { await globalThis.importScripts(scriptUrl); return; } throw new Error('cannot figure out how to load external JS scripts in this environment'); } export function isCJSPluginDefinition(def) { return def.cjsUrl !== undefined; } export function pluginDescriptionString(d) { if (isUMDPluginDefinition(d)) { return `UMD plugin ${d.name}`; } else if (isESMPluginDefinition(d)) { return `ESM plugin ${d.esmUrl || d.esmLoc.uri}`; } else if (isCJSPluginDefinition(d)) { return `CJS plugin ${d.cjsUrl}`; } else { return 'unknown plugin'; } } export function pluginUrl(d) { if (isUMDPluginDefinition(d)) { return d.url ?? d.umdLoc?.uri ?? d.umdUrl; } else if (isESMPluginDefinition(d)) { return d.esmUrl ?? d.esmUri ?? d.esmLoc?.uri; } else if (isCJSPluginDefinition(d)) { return d.cjsUrl || d.cjsLoc.uri; } else { return 'unknown url'; } } function isInWebWorker() { return 'WorkerGlobalScope' in globalThis; } function addCacheBuster(url) { if (!globalThis.__jbrowseCacheBuster) { return url; } const u = new URL(url); u.searchParams.set('_cb', Date.now().toString()); return u.href; } export default class PluginLoader { definitions = []; fetchESM; fetchCJS; constructor(defs = [], args) { this.fetchESM = args?.fetchESM; this.fetchCJS = args?.fetchCJS; this.definitions = JSON.parse(JSON.stringify(defs)); } async loadCJSPlugin(def, baseUri) { const parsedUrl = new URL(def.cjsUrl, baseUri); if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { throw new Error(`Cannot load plugins using protocol "${parsedUrl.protocol}"`); } if (!this.fetchCJS) { throw new Error('No fetchCJS callback provided'); } return this.fetchCJS(parsedUrl.href); } async loadESMPlugin(def, baseUri) { const parsedUrl = 'esmUrl' in def ? new URL(def.esmUrl, baseUri) : new URL(def.esmLoc.uri, def.esmLoc.baseUri); if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { throw new Error(`cannot load plugins using protocol "${parsedUrl.protocol}"`); } if (!this.fetchESM) { throw new Error('No ESM fetcher installed'); } const plugin = await this.fetchESM(addCacheBuster(parsedUrl.href)); if (!plugin) { throw new Error(`Could not load ESM plugin: ${parsedUrl}`); } return plugin; } async loadUMDPlugin(def, baseUri) { const parsedUrl = 'url' in def ? new URL(def.url, baseUri) : 'umdUrl' in def ? new URL(def.umdUrl, baseUri) : new URL(def.umdLoc.uri, def.umdLoc.baseUri); if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { throw new Error(`cannot load plugins using protocol "${parsedUrl.protocol}"`); } const moduleName = def.name; const umdName = `JBrowsePlugin${moduleName}`; if (typeof jest === 'undefined') { await loadScript(addCacheBuster(parsedUrl.href)); } else { globalThis[umdName] = { default: Plugin }; } const plugin = globalThis[umdName]; if (!plugin) { throw new Error(`Failed to load UMD bundle for ${moduleName}, ${umdName} is undefined`); } return plugin; } async loadPlugin(def, baseUri) { let plugin; if (isElectron && isCJSPluginDefinition(def)) { plugin = await this.loadCJSPlugin(def, baseUri); } else if (isESMPluginDefinition(def)) { plugin = await this.loadESMPlugin(def, baseUri); } else if (isUMDPluginDefinition(def)) { plugin = await this.loadUMDPlugin(def, baseUri); } else if (!isElectron && isCJSPluginDefinition(def)) { throw new Error(`CommonJS plugin found, but not in a NodeJS environment: ${JSON.stringify(def)}`); } else { throw new Error(`Could not determine plugin type: ${JSON.stringify(def)}`); } if (!plugin.default) { throw new Error(`${pluginDescriptionString(def)} does not have a default export, cannot load`); } return plugin.default; } installGlobalReExports(target) { target.JBrowseExports = Object.fromEntries(Object.entries(ReExports).map(([moduleName, module]) => { return [moduleName, module]; })); return this; } async load(baseUri) { return Promise.all(this.definitions.map(async (definition) => ({ plugin: await this.loadPlugin(definition, baseUri), definition, }))); } }