diff --git a/lib/plugins/src/_internal/decorators.ts b/lib/plugins/src/_internal/decorators.ts index e944a78..c0a1317 100644 --- a/lib/plugins/src/_internal/decorators.ts +++ b/lib/plugins/src/_internal/decorators.ts @@ -1,36 +1,77 @@ +import { getPluginDependencies, pMetadata } from '.' import type { InitPluginApi, Plugin, PluginApi, PreInitPluginApi, } from '../types' -import type { InternalPluginMeta } from '.' +import type { AnyPlugin, InternalPluginMeta } from '.' export type PluginApiDecoratorStore< A extends PreInitPluginApi | InitPluginApi | PluginApi, -> = [decorator: Parameters[0], handleError: (e: unknown) => void] +> = WeakMap[0][]> -export const pDecorators: { - preInit: PluginApiDecoratorStore[] - init: PluginApiDecoratorStore[] - start: PluginApiDecoratorStore[] -} = { - preInit: [], - init: [], - start: [], +// Set of plugins that will always decorate the API of every other plugin. +export const pImplicitDeps = new Set() + +export const pDecoratorsPreInit: PluginApiDecoratorStore = + new WeakMap() + +export const pDecoratorsInit: PluginApiDecoratorStore = + new WeakMap() + +export const pDecoratorsStart: PluginApiDecoratorStore = + new WeakMap() + +export function addPluginApiDecorator( + store: PluginApiDecoratorStore, + plugin: AnyPlugin, + decorator: Parameters['decorate']>[0], +) { + let list = store.get(plugin) + if (!list) { + list = [] + store.set(plugin, list) + } + + list.push(decorator) } export function decoratePluginApi( store: PluginApiDecoratorStore< PreInitPluginApi | InitPluginApi | PluginApi - >[], - plugin: Plugin, + >, + plugin: Plugin, meta: InternalPluginMeta, ) { - for (const [decorator, handleError] of store) - try { - decorator(plugin as Plugin, meta.options) - } catch (e) { - handleError(e) + // Don't decorate implicit dependencies + if (pImplicitDeps.has(plugin)) return + + // Decorate the plugin API with implicit dependencies + // Implicit dependencies are internal plugin APIs, so we can run this without try-catch + // If anything fails, everything else would fail anyway + for (const dep of pImplicitDeps) { + const decorators = store.get(dep) + if (decorators) + for (const decorator of decorators) decorator(plugin, meta.options) + } + + const deps = getPluginDependencies(plugin) + const { handleError: handleDependentError } = meta + + for (const dep of deps) { + const decorators = store.get(dep) + + if (decorators) { + const { handleError } = pMetadata.get(dep)! + + try { + for (const decorator of decorators) + decorator(plugin, meta.options) + } catch (e) { + handleError(e) + handleDependentError(e) + } } + } } diff --git a/lib/plugins/src/_internal/index.ts b/lib/plugins/src/_internal/index.ts index 661e5fd..827e20d 100644 --- a/lib/plugins/src/_internal/index.ts +++ b/lib/plugins/src/_internal/index.ts @@ -7,7 +7,14 @@ import { PluginFlags, PluginStatus as Status, } from '../constants' -import { decoratePluginApi, pDecorators } from './decorators' +import { + addPluginApiDecorator, + decoratePluginApi, + pDecoratorsInit, + pDecoratorsPreInit, + pDecoratorsStart, + pImplicitDeps, +} from './decorators' import { pLeafOrSingleNodes, pPending } from './dependency-graph' import type { InitPluginApi, @@ -88,8 +95,10 @@ export function registerPlugin( pMetadata.set(plugin, meta) pList.set(manifest.id, plugin) - if (iflags & InternalPluginFlags.ImplicitDependency) + if (iflags & InternalPluginFlags.ImplicitDependency) { pLeafOrSingleNodes.add(plugin) + pImplicitDeps.add(plugin) + } // Only add to pending if the plugin is enabled else if (flags & PluginFlags.Enabled) pPending.add(plugin) @@ -159,11 +168,11 @@ function preparePluginPreInit(plugin: AnyPlugin) { plugin, unscoped: pUnscopedApi, decorate: decorator => { - pDecorators.preInit.push([decorator, meta.handleError]) + addPluginApiDecorator(pDecoratorsPreInit, plugin, decorator) }, } satisfies PreInitPluginApi - decoratePluginApi(pDecorators.preInit, plugin, meta) + decoratePluginApi(pDecoratorsPreInit, plugin, meta) meta.apiLevel = PluginApiLevel.PreInit } @@ -172,10 +181,10 @@ function preparePluginInit(plugin: AnyPlugin) { const meta = pMetadata.get(plugin)! const api = plugin.api as InitPluginApi api.decorate = decorator => { - pDecorators.init.push([decorator, meta.handleError]) + addPluginApiDecorator(pDecoratorsInit, plugin, decorator) } - decoratePluginApi(pDecorators.init, plugin, meta) + decoratePluginApi(pDecoratorsInit, plugin, meta) meta.apiLevel = PluginApiLevel.Init } @@ -184,10 +193,10 @@ function preparePluginStart(plugin: AnyPlugin) { const meta = pMetadata.get(plugin)! const api = plugin.api as PluginApi api.decorate = decorator => { - pDecorators.start.push([decorator, meta.handleError]) + addPluginApiDecorator(pDecoratorsStart, plugin, decorator) } - decoratePluginApi(pDecorators.start, plugin, meta) + decoratePluginApi(pDecoratorsStart, plugin, meta) meta.apiLevel = PluginApiLevel.Start }