136 lines
4.5 KiB
TypeScript
136 lines
4.5 KiB
TypeScript
import type { Component, DefineComponent } from 'vue';
|
|
|
|
/**
|
|
* The registration surface handed to every plugin bundle.
|
|
*/
|
|
export interface PluginRegistrar {
|
|
/** Register an Inertia page, e.g. `page('announcements::Index', Index)`. */
|
|
page(name: string, component: DefineComponent): void;
|
|
/**
|
|
* Register a slot component by name, e.g.
|
|
* `slot('announcements::NavbarBell', Bell)`.
|
|
*
|
|
* Which position a component fills is decided server side by
|
|
* Vanguard::slot('navbar:items', NavbarItemsSlot::class), keeping placement
|
|
* next to the permission and data logic that drives it.
|
|
*/
|
|
slot(name: string, component: Component): void;
|
|
/** Register a dashboard widget component by name. */
|
|
widget(name: string, component: Component): void;
|
|
}
|
|
|
|
export type PluginCallback = (registrar: PluginRegistrar) => void;
|
|
|
|
/**
|
|
* The queue shim installed inline in the root template. Plugin bundles call
|
|
* `Vanguard.plugin(...)` which pushes onto `_q` until the real runtime boots,
|
|
* so plugin scripts and the app bundle may load in any order.
|
|
*/
|
|
interface VanguardShim {
|
|
_q?: Array<[string, PluginCallback]>;
|
|
plugin(slug: string, callback: PluginCallback): void;
|
|
}
|
|
|
|
const pages = new Map<string, DefineComponent>();
|
|
const slots = new Map<string, Component>();
|
|
const widgets = new Map<string, Component>();
|
|
const registered = new Set<string>();
|
|
|
|
const registrar: PluginRegistrar = {
|
|
page(name, component) {
|
|
pages.set(name, component);
|
|
},
|
|
slot(name, component) {
|
|
slots.set(name, component);
|
|
},
|
|
widget(name, component) {
|
|
widgets.set(name, component);
|
|
},
|
|
};
|
|
|
|
function registerPlugin(slug: string, callback: PluginCallback): void {
|
|
if (registered.has(slug)) {
|
|
return;
|
|
}
|
|
|
|
registered.add(slug);
|
|
callback(registrar);
|
|
}
|
|
|
|
/**
|
|
* Replace the inline shim with the real runtime and drain anything that was
|
|
* queued while the app bundle was still loading.
|
|
*
|
|
* `libs` are the shared dependencies plugin bundles externalise rather than
|
|
* bundling themselves, which keeps a plugin bundle to just its own components.
|
|
*/
|
|
export function installVanguardRuntime(libs: Record<string, unknown>): void {
|
|
const shim = (window as unknown as { Vanguard?: VanguardShim }).Vanguard;
|
|
const queued = shim?._q ?? [];
|
|
|
|
(window as unknown as { Vanguard: unknown }).Vanguard = {
|
|
...libs,
|
|
plugin: registerPlugin,
|
|
resolvePage,
|
|
resolveSlot,
|
|
resolveWidget,
|
|
};
|
|
|
|
for (const [slug, callback] of queued) {
|
|
registerPlugin(slug, callback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves once every deferred script in the document has run.
|
|
*
|
|
* Plugin bundles are module scripts at the end of the body, so they execute
|
|
* after the app bundle that calls createInertiaApp. Waiting for them is what
|
|
* lets the first page of a visit be a plugin page.
|
|
*
|
|
* Built at module load, which happens while the app bundle is still executing,
|
|
* because `readyState` is already "interactive" by then: the spec sets it
|
|
* before running deferred scripts, so checking it later would short circuit and
|
|
* report ready while plugin bundles were still queued. The load listener is a
|
|
* backstop for the window between DOMContentLoaded and readyState "complete".
|
|
*/
|
|
const pluginsLoaded: Promise<void> =
|
|
document.readyState === 'complete'
|
|
? Promise.resolve()
|
|
: new Promise((resolve) => {
|
|
document.addEventListener('DOMContentLoaded', () => resolve(), { once: true });
|
|
window.addEventListener('load', () => resolve(), { once: true });
|
|
});
|
|
|
|
/** Resolve a namespaced plugin page such as `announcements::Index`. */
|
|
export function resolvePage(name: string): DefineComponent | Promise<DefineComponent> {
|
|
const registered = pages.get(name);
|
|
|
|
if (registered) {
|
|
return registered;
|
|
}
|
|
|
|
return pluginsLoaded.then(() => {
|
|
const component = pages.get(name);
|
|
|
|
if (!component) {
|
|
throw new Error(
|
|
`[Vanguard] Inertia page "${name}" was not registered by any plugin. ` +
|
|
`Did you publish the plugin assets (php artisan vendor:publish --tag=public)?`,
|
|
);
|
|
}
|
|
|
|
return component;
|
|
});
|
|
}
|
|
|
|
/** Resolve a slot component by name, e.g. `announcements::NavbarBell`. */
|
|
export function resolveSlot(name: string): Component | undefined {
|
|
return slots.get(name);
|
|
}
|
|
|
|
/** Resolve a dashboard widget component by name. */
|
|
export function resolveWidget(name: string): Component | undefined {
|
|
return widgets.get(name);
|
|
}
|