Plugins: Selectively load plugins using script tags (#85750)

* feat(plugins): introduce logic to selectively load fe plugins via script tags

* feat(plugins): extend cache to store isAngular flag. use isAngular in shouldFetch

* revert(plugins): remove unused prepareImport from SystemJSWithLoaderHooks type

* fix(plugins): cache[path] maybe undefined if not registered or invalidated

* Update public/app/features/plugins/plugin_loader.ts

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

---------

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
This commit is contained in:
Jack Westbrook 2024-04-24 12:27:28 +02:00 committed by GitHub
parent 60ed6bfc33
commit fd1bf66d86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 28 additions and 12 deletions

View File

@ -1,17 +1,22 @@
import { clearPluginSettingsCache } from '../pluginSettings';
const cache: Record<string, string> = {};
const cache: Record<string, CacheablePlugin> = {};
const initializedAt: number = Date.now();
type CacheablePlugin = {
path: string;
version: string;
isAngular?: boolean;
};
export function registerPluginInCache({ path, version }: CacheablePlugin): void {
export function registerPluginInCache({ path, version, isAngular }: CacheablePlugin): void {
const key = extractPath(path);
if (key && !cache[key]) {
cache[key] = encodeURI(version);
cache[key] = {
version: encodeURI(version),
isAngular,
path,
};
}
}
@ -28,12 +33,19 @@ export function resolveWithCache(url: string, defaultBust = initializedAt): stri
if (!path) {
return `${url}?_cache=${defaultBust}`;
}
const version = cache[path];
const version = cache[path]?.version;
const bust = version || defaultBust;
return `${url}?_cache=${bust}`;
}
export function getPluginFromCache(path: string): CacheablePlugin | undefined {
const key = extractPath(path);
if (!key) {
return;
}
return cache[key];
}
function extractPath(address: string): string | undefined {
const match = /\/?.+\/(plugins\/.+\/module)\.js/i.exec(address);
if (!match) {

View File

@ -1,7 +1,7 @@
// Extend the System type with the loader hooks we use
// to provide backwards compatibility with older version of Systemjs
export type SystemJSWithLoaderHooks = typeof System & {
shouldFetch: () => Boolean;
shouldFetch: (url: string) => Boolean;
fetch: (url: string, options?: Record<string, unknown>) => Promise<Response>;
onload: (err: unknown, id: string) => void;
};

View File

@ -11,14 +11,14 @@ import { DataQuery } from '@grafana/schema';
import { GenericDataSourcePlugin } from '../datasources/types';
import builtInPlugins from './built_in_plugins';
import { registerPluginInCache } from './loader/cache';
import { getPluginFromCache, registerPluginInCache } from './loader/cache';
// SystemJS has to be imported before the sharedDependenciesMap
import { SystemJS } from './loader/systemjs';
// eslint-disable-next-line import/order
import { sharedDependenciesMap } from './loader/sharedDependencies';
import { decorateSystemJSFetch, decorateSystemJSResolve, decorateSystemJsOnload } from './loader/systemjsHooks';
import { SystemJSWithLoaderHooks } from './loader/types';
import { buildImportMap, resolveModulePath } from './loader/utils';
import { buildImportMap, isHostedOnCDN, resolveModulePath } from './loader/utils';
import { importPluginModuleInSandbox } from './sandbox/sandbox_plugin_loader';
import { isFrontendSandboxSupported } from './sandbox/utils';
@ -28,9 +28,13 @@ SystemJS.addImportMap({ imports });
const systemJSPrototype: SystemJSWithLoaderHooks = SystemJS.constructor.prototype;
// Monaco Editors reliance on RequireJS means we need to transform
// the content of the plugin code at runtime which can only be done with fetch/eval.
systemJSPrototype.shouldFetch = () => true;
// This instructs SystemJS to load a plugin using fetch and eval if it returns a truthy value, otherwise it will load the plugin using a script tag.
// We only want to fetch and eval plugins that are hosted on a CDN or are Angular plugins.
systemJSPrototype.shouldFetch = function (url) {
const pluginInfo = getPluginFromCache(url);
return isHostedOnCDN(url) || Boolean(pluginInfo?.isAngular);
};
const originalImport = systemJSPrototype.import;
// Hook Systemjs import to support plugins that only have a default export.
@ -68,7 +72,7 @@ export async function importPluginModule({
isAngular?: boolean;
}): Promise<System.Module> {
if (version) {
registerPluginInCache({ path, version });
registerPluginInCache({ path, version, isAngular });
}
const builtIn = builtInPlugins[path];