Fix Monaco workers failing in cross-origin deployments #4963
Conversation
In deployed environments the host bundle is served from a different origin than the realm-server page that boots it, so the worker URLs emitted by Vite's `?worker` import are cross-origin. `new Worker(url)` rejects cross-origin URLs, which knocks out Monaco's JSON, TS, CSS, and HTML language services and surfaces a `FileAccessImpl.toUri` TypeError from the same-thread fallback. Switch the 5 Monaco worker imports to `?worker&url` and route them through a `makeMonacoWorker` helper. Same-origin (dev/CI) constructs the Worker directly; cross-origin wraps the URL in a same-origin Blob that `importScripts` the real worker code — workers spawned from a Blob URL adopt the page's origin, and `importScripts` is allowed to fetch cross-origin without CORS. No vite-config or realm-server changes required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79b57991c4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }); | ||
| return new Worker(URL.createObjectURL(blob)); | ||
| } | ||
| return new Worker(absolute.href); |
There was a problem hiding this comment.
Instantiate same-origin Monaco workers as module workers
This branch now creates workers with new Worker(absolute.href) (classic by default), but these Monaco worker entries are imported via Vite’s worker pipeline and are expected to run with module semantics in dev/same-origin environments. Losing the worker type: 'module' can cause the worker script to fail parsing/booting (for example when ESM syntax is present), which disables Monaco language services locally and in CI while this code path is taken.
Useful? React with 👍 / 👎.
| let blob = new Blob([`importScripts(${JSON.stringify(absolute.href)});`], { | ||
| type: 'text/javascript', | ||
| }); | ||
| return new Worker(URL.createObjectURL(blob)); |
There was a problem hiding this comment.
Revoke Blob URL after creating cross-origin worker
Each cross-origin worker creation allocates a new blob URL via URL.createObjectURL(blob) but never revokes it. If Monaco workers are recreated repeatedly (editor reloads, route transitions, hot reload), these unreleased object URLs accumulate for the lifetime of the page and increase memory usage. Capture the blob URL, construct the worker, then call URL.revokeObjectURL(...) once the worker is created.
Useful? React with 👍 / 👎.
Preview deploymentsHost Test Results 1 files 1 suites 1h 37m 19s ⏱️ Results for commit 4907fde. Realm Server Test Results 1 files ±0 1 suites ±0 10m 22s ⏱️ +34s Results for commit 4907fde. ± Comparison against earlier commit 2bc7eb8. |
There was a problem hiding this comment.
Pull request overview
This PR addresses Monaco language-service workers failing to start when the host bundle is served from a different origin than the realm-server page (cross-origin deployments), which breaks JSON/TS/CSS/HTML diagnostics and IntelliSense in code mode.
Changes:
- Switched Monaco worker imports from Vite’s
?worker(constructor) to?worker&url(URL string). - Added a
makeMonacoWorker(url)helper that creates workers directly for same-origin URLs and uses a same-origin Blob shim (withimportScripts) for cross-origin worker URLs. - Updated
MonacoEnvironment.getWorkerto usemakeMonacoWorkerfor all Monaco worker labels.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…hese object URLs can accumulate and leak memory for the lifetime of the page Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…due-to-cross-origin-host
|
After deploying this branch to staging- I confirm this is fixed and the error is gone. |
Summary
In deployed environments the host bundle (e.g.
boxel-host.stack.cards) is served from a different origin than the realm-server page that boots it (realms.stack.cards). Vite's?workerimport emits a Worker constructor pointed at an absolute cross-origin URL, andnew Worker(crossOriginUrl)is forbidden by the browser. As a result Monaco's JSON, TS, CSS, and HTML language services fail to spin up — silently degrading code-mode (no JSON diagnostics on.jsoncard-instance files, no TS IntelliSense on.gts, no formatting) and surfacing aTypeError: Cannot read properties of undefined (reading 'toUrl')from Monaco's same-thread fallback path. Dev/CI is onlocalhostfor both origins so the bug only manifests on staging/prod.Fix
packages/host/app/services/monaco-service.ts:?worker(Worker constructor) to?worker&url(URL string).makeMonacoWorker(url)helper:new Worker(url)as before — keeps dev/CI behavior identical.Blobthat immediatelyimportScripts(...)the real worker code. Workers spawned from a Blob URL adopt the page's origin, andimportScriptsis allowed to fetch cross-origin without CORS, so the bundled worker code still loads.No vite-config or realm-server changes required — the existing
experimental.renderBuiltUrlstill resolves the worker URL against__boxelAssetsURL, and the helper handles the cross-origin case from that.Linear: https://linear.app/cardstack/issue/CS-11095/monaco-workers-fail-in-deployments-due-to-cross-origin-host-bundle
Test plan
pnpm run lint:typesclean (verified locally)..jsoncard-instance file in code mode.Failed to construct 'Worker'warnings, noFileAccessImpl.toUriTypeErrors..jsoncard instance, TS IntelliSense fires on a.gtsfile, formatting works.🤖 Generated with Claude Code