102 lines
2.9 KiB
TypeScript
102 lines
2.9 KiB
TypeScript
"use client";
|
||
|
||
const MANIFEST_URL = "/api/leptos-tiptap-runtime/manifest.json";
|
||
|
||
type RuntimeManifest = {
|
||
bridgeRuntimePath: string | null;
|
||
extensionModulePaths: string[];
|
||
generatedRootPath: string | null;
|
||
entryScriptPath: string | null;
|
||
wasmPath: string | null;
|
||
};
|
||
|
||
type BridgeSuccess<T> = {
|
||
ok: true;
|
||
value: T;
|
||
};
|
||
|
||
type BridgeFailure = {
|
||
ok: false;
|
||
error: {
|
||
kind: string;
|
||
message: string;
|
||
operation?: string;
|
||
};
|
||
};
|
||
|
||
type BridgeResult<T> = BridgeSuccess<T> | BridgeFailure;
|
||
|
||
type BridgeRuntimeModule = {
|
||
init_bridge_runtime: () => void;
|
||
create: (
|
||
request: unknown,
|
||
onReady: (payload: { generation: number }) => void,
|
||
onChange: () => void,
|
||
onSelectionChange: (selection: Record<string, unknown>) => void,
|
||
onError: (error: { kind: string; message: string; operation?: string }) => void,
|
||
) => void;
|
||
destroy: (id: string) => void;
|
||
command: (request: unknown) => BridgeResult<{ kind: "empty" }>;
|
||
document: (request: unknown) => BridgeResult<{ kind: "content"; content: { format: "json" | "html"; value: unknown } }>;
|
||
};
|
||
|
||
export type LoadedLeptosTiptapRuntime = {
|
||
manifest: RuntimeManifest;
|
||
bridge: BridgeRuntimeModule;
|
||
};
|
||
|
||
let runtimePromise: Promise<LoadedLeptosTiptapRuntime> | null = null;
|
||
|
||
function toRuntimeAssetUrl(relativePath: string): string {
|
||
return `/api/leptos-tiptap-runtime/${relativePath}`;
|
||
}
|
||
|
||
async function importRuntimeModule<T>(relativePath: string): Promise<T> {
|
||
return import(/* webpackIgnore: true */ toRuntimeAssetUrl(relativePath)) as Promise<T>;
|
||
}
|
||
|
||
function extractRegisterFunction(module: Record<string, unknown>): (() => void) | null {
|
||
for (const value of Object.values(module)) {
|
||
if (typeof value === "function") {
|
||
return value as () => void;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function fetchRuntimeManifest(): Promise<RuntimeManifest> {
|
||
const response = await fetch(MANIFEST_URL, { cache: "no-store" });
|
||
if (!response.ok) {
|
||
throw new Error(`读取 leptos-tiptap runtime manifest 失败(${response.status})`);
|
||
}
|
||
const manifest = (await response.json()) as RuntimeManifest;
|
||
if (!manifest.bridgeRuntimePath) {
|
||
throw new Error("runtime manifest 缺少 bridgeRuntimePath");
|
||
}
|
||
return manifest;
|
||
}
|
||
|
||
async function loadRuntimeModules(): Promise<LoadedLeptosTiptapRuntime> {
|
||
const manifest = await fetchRuntimeManifest();
|
||
const bridge = await importRuntimeModule<BridgeRuntimeModule>(manifest.bridgeRuntimePath!);
|
||
bridge.init_bridge_runtime();
|
||
|
||
for (const modulePath of manifest.extensionModulePaths) {
|
||
const extensionModule = await importRuntimeModule<Record<string, unknown>>(modulePath);
|
||
const register = extractRegisterFunction(extensionModule);
|
||
register?.();
|
||
}
|
||
|
||
return { manifest, bridge };
|
||
}
|
||
|
||
export async function loadLeptosTiptapRuntime(): Promise<LoadedLeptosTiptapRuntime> {
|
||
if (!runtimePromise) {
|
||
runtimePromise = loadRuntimeModules().catch((error) => {
|
||
runtimePromise = null;
|
||
throw error;
|
||
});
|
||
}
|
||
return runtimePromise;
|
||
}
|