fix(studio): spawn GraphiQL's Monaco workers off the main thread in the TanStack build

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
This commit is contained in:
Alaister Young committed 2026-07-07 09:41:39 +08:00
1 parent 4f5759085e
commit eb9bc4fd46
2 files changed
+67

No files matched your search

+23
View File
@@ -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`
+44
View File
@@ -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(),