From eb9bc4fd46ae9b2c876fd7e79a16e24c81c96f3e Mon Sep 17 00:00:00 2001 From: Alaister Young <10985857+alaister@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:01:26 +0800 Subject: [PATCH] fix(studio): spawn GraphiQL's Monaco workers off the main thread in the TanStack build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GraphiQLTab imports graphiql/setup-workers/webpack, whose new Worker(new URL('monaco-editor/...', import.meta.url)) registrations only work when the bundler rewrites the URLs. Vite doesn't rewrite bare specifiers inside new URL(..., import.meta.url), so on /project/[ref]/integrations/graphiql/graphiql the json / editorWorkerService / graphql workers 404'd and Monaco fell back to the main thread ('Could not create web worker(s)...' console warning). Add a graphiqlViteWorkers plugin that resolves the import to graphiql's own setup-workers/vite variant (same workers via ?worker imports) in client builds, leaving SSR resolution and the Next build untouched — app source keeps the .../webpack specifier. The setup-workers chain is also excluded from optimizeDeps: the Rolldown dep optimizer can't load ?worker ids (UNLOADABLE_DEPENDENCY), so the modules must go through the normal transform pipeline where Vite's built-in worker plugin handles them. Verified: vite dev serves the vite setup-workers module for GraphiQLTab with each ?worker import transformed into a WorkerWrapper constructor; vite build emits json.worker/editor.worker/graphql.worker chunks; next dev (turbopack) still compiles the graphiql route; tsc --noEmit clean. Claude-Session: https://claude.ai/code/session_01MLH8WVg8zBJnmZTYTV3ooo --- apps/studio/TANSTACK_MIGRATION.md | 23 ++++++++++++++++ apps/studio/vite.config.ts | 44 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/apps/studio/TANSTACK_MIGRATION.md b/apps/studio/TANSTACK_MIGRATION.md index eb62d8d1bc7..cf3def20a5a 100644 --- a/apps/studio/TANSTACK_MIGRATION.md +++ b/apps/studio/TANSTACK_MIGRATION.md @@ -519,6 +519,29 @@ App source keeps importing `@sentry/nextjs` so the Next build Next build when the migration is done (switch imports to `@sentry/react` directly). +### GraphiQL Monaco workers: `setup-workers/webpack` → `setup-workers/vite` + +App source (`GraphiQLTab.tsx`) imports `graphiql/setup-workers/webpack`, +which registers `MonacoEnvironment.getWorker` using +`new Worker(new URL('monaco-editor/...', import.meta.url))` — the URL form +webpack/turbopack rewrites at build time. Vite doesn't rewrite bare module +specifiers inside `new URL(..., import.meta.url)`, so under the TanStack +build the worker URLs 404'd and Monaco fell back to running the `json`, +`editorWorkerService` and `graphql` workers on the main thread ("Could not +create web worker(s). Falling back to loading web worker code in main +thread" in the console). + +The `graphiqlViteWorkers` plugin in `vite.config.ts` resolves that import +to graphiql's own `setup-workers/vite` variant (same three workers via +Vite `?worker` imports) in client builds; SSR resolution is untouched. The +import specifier stays `.../webpack` in app source so the Next build keeps +working. The whole setup-workers chain is also in `optimizeDeps.exclude` — +the Rolldown dep optimizer can't load `?worker` ids +(`UNLOADABLE_DEPENDENCY`), so the modules go through the normal transform +pipeline where Vite's built-in worker plugin handles them. Drop the plugin +and the exclude, and switch the import to `graphiql/setup-workers/vite`, +when the Next build goes away. + ### Raw-text imports: `*.md` + `public/deno/*.d.ts` (`rawTextLoader`) Next's raw-loader rules (next.config.ts `turbopack.rules`) serve `*.md` diff --git a/apps/studio/vite.config.ts b/apps/studio/vite.config.ts index 59134312bd8..7cdf8515a72 100644 --- a/apps/studio/vite.config.ts +++ b/apps/studio/vite.config.ts @@ -108,6 +108,34 @@ function rawTextLoader(): Plugin { } } +// Swap graphiql's webpack worker setup for its Vite one in client builds. +// +// App source imports `graphiql/setup-workers/webpack` (GraphiQLTab.tsx), +// which registers `MonacoEnvironment.getWorker` using +// `new Worker(new URL('monaco-editor/...', import.meta.url))` — the URL form +// webpack/turbopack rewrites at build time. Vite doesn't rewrite bare module +// specifiers inside `new URL(..., import.meta.url)`, so under the TanStack +// build the worker URLs 404 and Monaco falls back to running the json / +// editorWorkerService / graphql workers on the main thread ("Could not +// create web worker(s)..." console warning). graphiql also ships +// `setup-workers/vite`, which imports the same three workers via Vite's +// `?worker` suffix; importing that unconditionally would break the Next +// build, so the swap happens here instead of in app source. +// +// SSR resolution is left untouched: neither variant's `getWorker` ever runs +// during SSR, and the webpack flavor is a plain global assignment while the +// vite flavor's `?worker` imports don't belong in the server graph. +function graphiqlViteWorkers(): Plugin { + return { + name: 'studio-graphiql-vite-workers', + enforce: 'pre', + resolveId(id, importer, options) { + if (id !== 'graphiql/setup-workers/webpack' || options.ssr) return + return this.resolve('graphiql/setup-workers/vite', importer, { skipSelf: true }) + }, + } +} + // Short-circuit UMD wrappers' AMD branch by string-replacing the // `define.amd` check. Vite's `config.define` doesn't reach pre-bundled // deps (Vite 8's Rolldown-based optimizer doesn't honour member- @@ -430,6 +458,21 @@ export default defineConfig(({ command, mode }) => { ], }, ...(basePath && { base: basePath }), + optimizeDeps: { + // graphiql's Vite worker setup (swapped in for the webpack one by the + // `graphiqlViteWorkers` plugin above) imports Monaco's workers with + // Vite's `?worker` suffix. The dep optimizer can't load `?worker` ids + // (UNLOADABLE_DEPENDENCY: "No such file or directory" for + // `json.worker.js?worker` etc.), so keep the whole chain out of + // pre-bundling; the modules then go through the normal transform + // pipeline where Vite's built-in worker plugin turns each `?worker` + // import into a spawnable Worker constructor. + exclude: [ + 'graphiql/setup-workers/webpack', + 'graphiql/setup-workers/vite', + '@graphiql/react/setup-workers/vite', + ], + }, define: { ...publicEnvDefines, ...sharedDefines, @@ -524,6 +567,7 @@ export default defineConfig(({ command, mode }) => { plugins: [ nextCompat(), rawTextLoader(), + graphiqlViteWorkers(), ssrStubGraphiql(), umdAmdShortCircuit(), assertNoChunkCycles(),