mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -1,17 +1,22 @@
|
|||||||
import { clearPluginSettingsCache } from '../pluginSettings';
|
import { clearPluginSettingsCache } from '../pluginSettings';
|
||||||
|
|
||||||
const cache: Record<string, string> = {};
|
const cache: Record<string, CacheablePlugin> = {};
|
||||||
const initializedAt: number = Date.now();
|
const initializedAt: number = Date.now();
|
||||||
|
|
||||||
type CacheablePlugin = {
|
type CacheablePlugin = {
|
||||||
path: string;
|
path: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
isAngular?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function registerPluginInCache({ path, version }: CacheablePlugin): void {
|
export function registerPluginInCache({ path, version, isAngular }: CacheablePlugin): void {
|
||||||
const key = extractPath(path);
|
const key = extractPath(path);
|
||||||
if (key && !cache[key]) {
|
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) {
|
if (!path) {
|
||||||
return `${url}?_cache=${defaultBust}`;
|
return `${url}?_cache=${defaultBust}`;
|
||||||
}
|
}
|
||||||
|
const version = cache[path]?.version;
|
||||||
const version = cache[path];
|
|
||||||
const bust = version || defaultBust;
|
const bust = version || defaultBust;
|
||||||
return `${url}?_cache=${bust}`;
|
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 {
|
function extractPath(address: string): string | undefined {
|
||||||
const match = /\/?.+\/(plugins\/.+\/module)\.js/i.exec(address);
|
const match = /\/?.+\/(plugins\/.+\/module)\.js/i.exec(address);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Extend the System type with the loader hooks we use
|
// Extend the System type with the loader hooks we use
|
||||||
// to provide backwards compatibility with older version of Systemjs
|
// to provide backwards compatibility with older version of Systemjs
|
||||||
export type SystemJSWithLoaderHooks = typeof System & {
|
export type SystemJSWithLoaderHooks = typeof System & {
|
||||||
shouldFetch: () => Boolean;
|
shouldFetch: (url: string) => Boolean;
|
||||||
fetch: (url: string, options?: Record<string, unknown>) => Promise<Response>;
|
fetch: (url: string, options?: Record<string, unknown>) => Promise<Response>;
|
||||||
onload: (err: unknown, id: string) => void;
|
onload: (err: unknown, id: string) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import { DataQuery } from '@grafana/schema';
|
|||||||
import { GenericDataSourcePlugin } from '../datasources/types';
|
import { GenericDataSourcePlugin } from '../datasources/types';
|
||||||
|
|
||||||
import builtInPlugins from './built_in_plugins';
|
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
|
// SystemJS has to be imported before the sharedDependenciesMap
|
||||||
import { SystemJS } from './loader/systemjs';
|
import { SystemJS } from './loader/systemjs';
|
||||||
// eslint-disable-next-line import/order
|
// eslint-disable-next-line import/order
|
||||||
import { sharedDependenciesMap } from './loader/sharedDependencies';
|
import { sharedDependenciesMap } from './loader/sharedDependencies';
|
||||||
import { decorateSystemJSFetch, decorateSystemJSResolve, decorateSystemJsOnload } from './loader/systemjsHooks';
|
import { decorateSystemJSFetch, decorateSystemJSResolve, decorateSystemJsOnload } from './loader/systemjsHooks';
|
||||||
import { SystemJSWithLoaderHooks } from './loader/types';
|
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 { importPluginModuleInSandbox } from './sandbox/sandbox_plugin_loader';
|
||||||
import { isFrontendSandboxSupported } from './sandbox/utils';
|
import { isFrontendSandboxSupported } from './sandbox/utils';
|
||||||
|
|
||||||
@@ -28,9 +28,13 @@ SystemJS.addImportMap({ imports });
|
|||||||
|
|
||||||
const systemJSPrototype: SystemJSWithLoaderHooks = SystemJS.constructor.prototype;
|
const systemJSPrototype: SystemJSWithLoaderHooks = SystemJS.constructor.prototype;
|
||||||
|
|
||||||
// Monaco Editors reliance on RequireJS means we need to transform
|
// 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.
|
||||||
// the content of the plugin code at runtime which can only be done with fetch/eval.
|
// We only want to fetch and eval plugins that are hosted on a CDN or are Angular plugins.
|
||||||
systemJSPrototype.shouldFetch = () => true;
|
systemJSPrototype.shouldFetch = function (url) {
|
||||||
|
const pluginInfo = getPluginFromCache(url);
|
||||||
|
|
||||||
|
return isHostedOnCDN(url) || Boolean(pluginInfo?.isAngular);
|
||||||
|
};
|
||||||
|
|
||||||
const originalImport = systemJSPrototype.import;
|
const originalImport = systemJSPrototype.import;
|
||||||
// Hook Systemjs import to support plugins that only have a default export.
|
// Hook Systemjs import to support plugins that only have a default export.
|
||||||
@@ -68,7 +72,7 @@ export async function importPluginModule({
|
|||||||
isAngular?: boolean;
|
isAngular?: boolean;
|
||||||
}): Promise<System.Module> {
|
}): Promise<System.Module> {
|
||||||
if (version) {
|
if (version) {
|
||||||
registerPluginInCache({ path, version });
|
registerPluginInCache({ path, version, isAngular });
|
||||||
}
|
}
|
||||||
|
|
||||||
const builtIn = builtInPlugins[path];
|
const builtIn = builtInPlugins[path];
|
||||||
|
|||||||
Reference in New Issue
Block a user