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;
|
|||
|
|
}
|