From dc2802d4c076554c472adad8b2e24cacad94c9e7 Mon Sep 17 00:00:00 2001 From: stroblme Date: Thu, 15 Oct 2026 21:37:31 +0200 Subject: [PATCH] ELK runs in a worker: the flow layout loads elkjs's worker build as a Vite ?worker behind elk-api, so a phone's eight layout passes no longer block the page (Demo house at 390px with 4x CPU throttling, long tasks 15.0 to 0.8 s, longest 2.1 to 0.3 s); positions and routes unchanged --- frontend/src/components/Flow/layout.check.ts | 14 ++++++++++++++ frontend/src/components/Flow/layout.ts | 12 +++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Flow/layout.check.ts b/frontend/src/components/Flow/layout.check.ts index 8876f68..2ca64d3 100644 --- a/frontend/src/components/Flow/layout.check.ts +++ b/frontend/src/components/Flow/layout.check.ts @@ -14,6 +14,20 @@ import assert from "node:assert/strict" // @ts-expect-error the runtime has it, the DOM types say it is not removable. globalThis.self = undefined +// A page runs ELK in a worker Vite builds (`?worker`). Here the same build +// runs in-thread, as the fake worker it exports outside a worker. +// @ts-expect-error the worker build ships without types. +const { Worker: InThread } = await import("elkjs/lib/elk-worker.min.js") +// @ts-expect-error bun's global, which the DOM types do not know. +Bun.plugin({ + name: "elk in-thread", + setup: (build: { module: (id: string, load: () => object) => void }) => + build.module("elkjs/lib/elk-worker.min.js?worker", () => ({ + exports: { default: InThread }, + loader: "object", + })), +}) + const { layoutGraph, nodeHeight, portFraction, PORT_SPAN } = await import( "./layout" ) diff --git a/frontend/src/components/Flow/layout.ts b/frontend/src/components/Flow/layout.ts index 61ca7cb..e523bd3 100644 --- a/frontend/src/components/Flow/layout.ts +++ b/frontend/src/components/Flow/layout.ts @@ -384,14 +384,20 @@ function wrapWideRanks(placed: Map, ids: string[]): Wrap[] { * The layout engine, fetched the first time a flow needs laying out. * * Its own chunk: ELK is a compiled Java library and weighs more than the rest - * of the canvas put together, so nothing but this route ever loads it. + * of the canvas put together, so nothing but this route ever loads it. And + * its own thread: a phone lays the Demo house out eight times over, about + * half a second each, which on the page would freeze it for seconds. */ let engine: Promise<{ layout: (graph: ElkNode) => Promise }> | null = null function elk() { - engine ??= import("elkjs/lib/elk.bundled.js").then( - (module) => new module.default(), + engine ??= Promise.all([ + import("elkjs/lib/elk-api.js"), + import("elkjs/lib/elk-worker.min.js?worker"), + ]).then( + ([api, worker]) => + new api.default({ workerFactory: () => new worker.default() }), ) return engine }