fix(markdown): compile the parser wasm asynchronously (#2900)

This commit is contained in:
Hampus
2026-09-22 19:58:38 +02:00
committed by GitHub
parent 045dd5d027
commit 91340c5c84
2 changed files with 51 additions and 11 deletions
@@ -47,11 +47,19 @@ const lenientTextDecoder = new TextDecoder('utf-8', {ignoreBOM: true, fatal: fal
let textDecoder = new TextDecoder('utf-8', {ignoreBOM: true, fatal: true}); let textDecoder = new TextDecoder('utf-8', {ignoreBOM: true, fatal: true});
let wasm: WasmExports | null = null; let wasm: WasmExports | null = null;
let wasmModule: unknown = null;
let cachedMemory: Uint8Array | null = null; let cachedMemory: Uint8Array | null = null;
let replacingOversizedInstance = false;
interface WasmInstance {
exports: Record<string, unknown>;
}
declare const WebAssembly: { declare const WebAssembly: {
Module: new (bytes: Uint8Array) => unknown; Module: new (bytes: Uint8Array) => unknown;
Instance: new (module: unknown, imports?: Record<string, unknown>) => {exports: Record<string, unknown>}; Instance: new (module: unknown, imports?: Record<string, unknown>) => WasmInstance;
compile(bytes: Uint8Array): Promise<unknown>;
instantiate(module: unknown, imports?: Record<string, unknown>): Promise<WasmInstance>;
}; };
function getWasmMemory(exports: Record<string, unknown>): WasmExports['memory'] { function getWasmMemory(exports: Record<string, unknown>): WasmExports['memory'] {
@@ -94,22 +102,39 @@ function decodeBase64(value: string): Uint8Array {
return bytes; return bytes;
} }
function getWasm(): WasmExports { function setWasmInstance(instance: WasmInstance): WasmExports {
if (!wasm) { wasm = createWasmExports(instance.exports);
const module = new WebAssembly.Module(decodeBase64(MARKDOWN_PARSER_WASM_BASE64)); cachedMemory = null;
const instance = new WebAssembly.Instance(module, {});
wasm = createWasmExports(instance.exports);
cachedMemory = null;
}
return wasm; return wasm;
} }
export async function preloadMarkdownParserWasm(): Promise<void> {
if (wasm) return;
wasmModule ??= await WebAssembly.compile(decodeBase64(MARKDOWN_PARSER_WASM_BASE64));
const instance = await WebAssembly.instantiate(wasmModule, {});
if (!wasm) setWasmInstance(instance);
}
function getWasm(): WasmExports {
if (wasm) return wasm;
wasmModule ??= new WebAssembly.Module(decodeBase64(MARKDOWN_PARSER_WASM_BASE64));
return setWasmInstance(new WebAssembly.Instance(wasmModule, {}));
}
function releaseOversizedWasmMemory(): void { function releaseOversizedWasmMemory(): void {
if ((wasm?.memory.buffer.byteLength ?? 0) <= MAX_RETAINED_WASM_MEMORY_BYTES) { if (replacingOversizedInstance || (wasm?.memory.buffer.byteLength ?? 0) <= MAX_RETAINED_WASM_MEMORY_BYTES) {
return; return;
} }
wasm = null; const oversized = wasm;
cachedMemory = null; replacingOversizedInstance = true;
WebAssembly.instantiate(wasmModule, {})
.then((instance) => {
if (wasm === oversized) setWasmInstance(instance);
})
.catch(() => {})
.finally(() => {
replacingOversizedInstance = false;
});
} }
function memoryU8(): Uint8Array { function memoryU8(): Uint8Array {
+15
View File
@@ -86,7 +86,19 @@ async function logClientInfo(): Promise<void> {
} }
} }
async function preloadMarkdownParser(): Promise<void> {
try {
const {preloadMarkdownParserWasm} = await loadLazyModule(
() => import('@app/features/messaging/utils/markdown/parser/MarkdownParserWasm'),
);
await preloadMarkdownParserWasm();
} catch (error) {
logger.warn('Failed to preload markdown parser:', error);
}
}
async function bootstrapThemeStudio(): Promise<void> { async function bootstrapThemeStudio(): Promise<void> {
const markdownParserReady = preloadMarkdownParser();
const [{ThemeStudioStandaloneApp}, {setupHttp}, {default: AccountManager}] = await Promise.all([ const [{ThemeStudioStandaloneApp}, {setupHttp}, {default: AccountManager}] = await Promise.all([
loadLazyModule(() => import('@app/features/theme_studio/ThemeStudioStandaloneApp')), loadLazyModule(() => import('@app/features/theme_studio/ThemeStudioStandaloneApp')),
loadLazyModule(() => import('@app/app/SetupHttp')), loadLazyModule(() => import('@app/app/SetupHttp')),
@@ -94,6 +106,7 @@ async function bootstrapThemeStudio(): Promise<void> {
]); ]);
await AccountManager.bootstrap(); await AccountManager.bootstrap();
setupHttp(); setupHttp();
await markdownParserReady;
mountRoot( mountRoot(
<AppI18nProvider i18n={i18n}> <AppI18nProvider i18n={i18n}>
<ThemeStudioStandaloneApp data-flx="index.render-theme-studio.theme-studio-standalone-app" /> <ThemeStudioStandaloneApp data-flx="index.render-theme-studio.theme-studio-standalone-app" />
@@ -103,6 +116,7 @@ async function bootstrapThemeStudio(): Promise<void> {
} }
async function bootstrapApp(): Promise<void> { async function bootstrapApp(): Promise<void> {
const markdownParserReady = preloadMarkdownParser();
const [ const [
{App}, {App},
{setupHttp}, {setupHttp},
@@ -148,6 +162,7 @@ async function bootstrapApp(): Promise<void> {
await AccountManager.bootstrap(); await AccountManager.bootstrap();
setupHttp(); setupHttp();
initializeEmojiParser(); initializeEmojiParser();
await markdownParserReady;
mountRoot(<App data-flx="index.bootstrap.app" />, 'index.bootstrap'); mountRoot(<App data-flx="index.bootstrap.app" />, 'index.bootstrap');
QuickSwitcher.preloadModal(); QuickSwitcher.preloadModal();
registerServiceWorker(); registerServiceWorker();