From e82ab7440d030c4bc530e999457f13ab422e07a1 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Fri, 2 Oct 2026 18:09:38 -0700 Subject: [PATCH] Run the worker prelude ahead of Cesium's own workers script Cesium's script sets CESIUM_WORKERS itself when it loads, so the panel's combined workers script was never used, and neither was the embedded terrain-heights file: the worker still requested it from the panel page's site. The panel build now writes only the prelude, and the panel puts it ahead of whatever Cesium assigns to CESIUM_WORKERS. The separate esbuild bundle of Cesium's workers is gone. --- build/panel.js | 23 +++++------------------ docs/TOOLS.md | 6 ++++-- scripts/build-panel.mjs | 28 ++++++---------------------- src/app/globePanelRuntime.js | 19 +++++++++++++++---- src/tooling/panelBuild.test.mjs | 21 +++++---------------- src/tools/globePanel.js | 6 +++--- 6 files changed, 38 insertions(+), 65 deletions(-) diff --git a/build/panel.js b/build/panel.js index af5b3e709..e10d0bbca 100644 --- a/build/panel.js +++ b/build/panel.js @@ -1,16 +1,16 @@ /** * The MCP Apps panel build. A panel loads the app through its MCP server, * one tool call per file (see src/tools/globePanel.js), so the app must be - * few files: one app script, one stylesheet, Cesium's own script, and - * Cesium's workers combined into one script that Cesium runs from memory. - * The build is served at PANEL_BASE next to the app. + * few files: one app script, one stylesheet, and Cesium's own script, which + * carries its workers and starts them from memory. The build is served at + * PANEL_BASE next to the app. */ // The panel loads these paths (src/tools/globePanel.js); a test keeps the // two in step. export const PANEL_BASE = '/panel/'; -/** Cesium's workers as one script, relative to the panel build. */ -export const PANEL_WORKERS_PATH = 'cesium/workers.js'; +/** The script the panel runs ahead of Cesium's workers (workerFilesPrelude). */ +export const PANEL_WORKER_PRELUDE_PATH = 'cesium/worker-prelude.js'; export const PANEL_OUT_DIR = 'dist/panel'; /** A browser Vite config changed to produce the panel build. */ @@ -32,19 +32,6 @@ export function panelBuildConfig(config) { }; } -/** - * Source for Cesium's `CESIUM_WORKERS` script: every worker module, each - * evaluated only when Cesium starts that worker. `workerNames` are the - * module names in Cesium's Workers directory, without `.js`. - */ -export function workersEntrySource(workerNames, workersDir) { - const entries = workerNames.map( - (name) => - ` ${JSON.stringify(name)}: () => import(${JSON.stringify(`${workersDir}/${name}.js`)}),`, - ); - return `self.CesiumWorkers = {\n${entries.join('\n')}\n};\n`; -} - const CONTENT_TYPES = { '.css': 'text/css', '.geojsonl': 'application/geo+json-seq', diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 8930ca373..c216d365f 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -133,8 +133,10 @@ provider keys. The panel loads the app's panel build, which `npm run build:panel` writes to `dist/panel` and the servers serve at `/panel/`: one app script, one -stylesheet, Cesium's script, and Cesium's workers combined into one script -that Cesium runs from memory (`CESIUM_WORKERS`). Rebuild it after changing +stylesheet, and Cesium's script, which carries its workers and starts them +from memory (`CESIUM_WORKERS`). Files those workers load themselves are +embedded in a prelude the panel runs ahead of them, since a worker's +requests reach the panel page's own site. Rebuild it after changing the app. Hosts differ in ways the panel works around, all inside the panel only: diff --git a/scripts/build-panel.mjs b/scripts/build-panel.mjs index 82d4c9043..ce98bfb3b 100644 --- a/scripts/build-panel.mjs +++ b/scripts/build-panel.mjs @@ -4,25 +4,22 @@ * See build/panel.js. */ -import { readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'; +import { readFile, rename, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { build as esbuild } from 'esbuild'; import { build } from 'vite'; import standaloneConfig from '../server/standalone/vite.config.js'; import { PANEL_BASE, PANEL_OUT_DIR, PANEL_WORKER_FILES, - PANEL_WORKERS_PATH, + PANEL_WORKER_PRELUDE_PATH, panelBuildConfig, workerFilesPrelude, - workersEntrySource, } from '../build/panel.js'; const root = fileURLToPath(new URL('../', import.meta.url)); const outDir = join(root, PANEL_OUT_DIR); -const cesiumWorkers = join(root, 'node_modules/cesium/Build/Cesium/Workers'); const config = standaloneConfig({ command: 'build', mode: 'production' }); await build({ @@ -37,26 +34,13 @@ await rm(join(outDir, 'cesium'), { recursive: true, force: true }); await rename(nested, join(outDir, 'cesium')); await rm(join(outDir, PANEL_BASE.split('/')[1]), { recursive: true }); -const names = (await readdir(cesiumWorkers)) - .filter((file) => file.endsWith('.js') && !file.startsWith('chunk-')) - .map((file) => file.slice(0, -3)); -const entry = join(outDir, 'cesium', 'workers-entry.js'); -await writeFile(entry, workersEntrySource(names, cesiumWorkers)); -await esbuild({ - entryPoints: [entry], - bundle: true, - format: 'iife', - minify: true, - outfile: join(outDir, PANEL_WORKERS_PATH), - logLevel: 'warning', -}); -await rm(entry); +// Cesium's workers load these files themselves; embed them in a prelude the +// panel runs ahead of Cesium's workers script. const files = {}; for (const name of PANEL_WORKER_FILES) files[name] = await readFile(join(outDir, 'cesium', name), 'utf8'); -const workers = join(outDir, PANEL_WORKERS_PATH); await writeFile( - workers, - workerFilesPrelude(files) + (await readFile(workers, 'utf8')), + join(outDir, PANEL_WORKER_PRELUDE_PATH), + workerFilesPrelude(files), ); console.log(`Panel build written to ${PANEL_OUT_DIR}`); diff --git a/src/app/globePanelRuntime.js b/src/app/globePanelRuntime.js index 8b33dbcce..33f807af3 100644 --- a/src/app/globePanelRuntime.js +++ b/src/app/globePanelRuntime.js @@ -489,14 +489,25 @@ export function panelRuntime(config) { 'text/html', ); window.CESIUM_BASE_URL = `${config.panelBase}cesium/`; - const [workers] = await Promise.all([ - appFetch(config.panelBase + config.workersPath).then((r) => r.text()), + const [prelude] = await Promise.all([ + appFetch(config.panelBase + config.workerPreludePath).then((r) => + r.text(), + ), ...[...built.head.querySelectorAll('link[rel="stylesheet"]')].map( (link) => addStylesheet(link.getAttribute('href')), ), ]); - // Cesium starts its workers from this script instead of from files. - window.CESIUM_WORKERS = workers; + // Cesium's script sets CESIUM_WORKERS, its workers as one script it + // starts from memory. Workers request files themselves, which this page + // cannot answer, so the prelude, holding those files, runs ahead of it. + let workers; + Object.defineProperty(window, 'CESIUM_WORKERS', { + configurable: true, + get: () => workers, + set: (script) => { + workers = prelude + script; + }, + }); // Images in the markup load their files once added; give them the // files instead of letting them request this page's site. const images = []; diff --git a/src/tooling/panelBuild.test.mjs b/src/tooling/panelBuild.test.mjs index 65bf35bb9..0b130167e 100644 --- a/src/tooling/panelBuild.test.mjs +++ b/src/tooling/panelBuild.test.mjs @@ -6,16 +6,18 @@ import test from 'node:test'; import * as PANEL_PATHS from '../tools/globePanel.js'; import { PANEL_BASE, - PANEL_WORKERS_PATH, + PANEL_WORKER_PRELUDE_PATH, panelBuildConfig, panelBuildPlugin, workerFilesPrelude, - workersEntrySource, } from '../../build/panel.js'; test('the panel loads the paths the panel build writes', () => { assert.equal(PANEL_BASE, PANEL_PATHS.PANEL_BASE); - assert.equal(PANEL_WORKERS_PATH, PANEL_PATHS.PANEL_WORKERS_PATH); + assert.equal( + PANEL_WORKER_PRELUDE_PATH, + PANEL_PATHS.PANEL_WORKER_PRELUDE_PATH, + ); }); test('the panel build is one script and one stylesheet under /panel/', () => { @@ -30,19 +32,6 @@ test('the panel build is one script and one stylesheet under /panel/', () => { assert.equal(config.build.chunkSizeWarningLimit, 1500); }); -test("Cesium's workers load lazily from one script", () => { - const source = workersEntrySource(['createGeometry', 'decodeDraco'], '/w'); - assert.match(source, /^self\.CesiumWorkers = \{/); - assert.match( - source, - /"createGeometry": \(\) => import\("\/w\/createGeometry\.js"\)/, - ); - assert.match( - source, - /"decodeDraco": \(\) => import\("\/w\/decodeDraco\.js"\)/, - ); -}); - test('the dev server serves the panel build and nothing outside it', async () => { const root = await mkdtemp(join(tmpdir(), 'panel-build-')); try { diff --git a/src/tools/globePanel.js b/src/tools/globePanel.js index cee6bc976..abc8213bc 100644 --- a/src/tools/globePanel.js +++ b/src/tools/globePanel.js @@ -9,8 +9,8 @@ export const GLOBE_PANEL_URI = 'ui://gods-eye-view/globe'; /** Where the app's server serves its panel build (see build/panel.js). */ export const PANEL_BASE = '/panel/'; -/** Cesium's workers as one script, relative to the panel build. */ -export const PANEL_WORKERS_PATH = 'cesium/workers.js'; +/** The script the panel runs ahead of Cesium's workers, in the build. */ +export const PANEL_WORKER_PRELUDE_PATH = 'cesium/worker-prelude.js'; /** * Sites the app's browser code loads from directly: map imagery, 3D tiles, @@ -63,7 +63,7 @@ function panelHtml(runtime) { panelHeight: PANEL_HEIGHT_PX, protocolVersion: MCP_APPS_PROTOCOL_VERSION, toolName: PANEL_REQUEST_TOOL, - workersPath: PANEL_WORKERS_PATH, + workerPreludePath: PANEL_WORKER_PRELUDE_PATH, }; const script = `(${runtime})(${JSON.stringify(config)});`.replace( /<\/script/gi,