Files
Matt MastracciandGitHub d23af53268 Turbopack: Use a real file entrypoint for Workers (and SharedWorkers) (#88602)
# What

Closes https://github.com/vercel/next.js/issues/84782  
Closes https://github.com/vercel/next.js/issues/74842  
Closes https://github.com/vercel/next.js/issues/82520
Closes PACK-3723

Replace blob URLs for web workers with real entrypoint files, fixing relative URL resolution inside workers. Implements support for `SharedWorkers` since it's now a simple variation of the `Worker` case.

Note that all generated files (and shared-worker querystrings) are appropriately content-hashed and do not rely on deployment IDs and will work w/immutable resources.

# Why

Workers created via `new Worker(new URL('./worker.js', import.meta.url))` previously got blob URLs. This breaks relative URL resolution inside workers - WASM imports like `new URL('./file.wasm', import.meta.url)` fail because `import.meta.url` returns `blob:http://...` instead of a real path.

# How

We emit a real worker entrypoint file instead of blob URLs. The entrypoint reads bootstrap config from URL hash params (`#params=....`), then loads the worker's chunks via `importScripts`.

The `ChunkingContext` trait gets a new `worker_entrypoint()` method returning a singleton bootstrap asset. Node.js `bail!`s for now but could implement its own in the future. The browser implementation (`EcmascriptBrowserWorkerEntrypoint`) compiles and minifies `worker-entrypoint.ts`, which parses `#params={globals,chunks}` from the URL at runtime.

`WorkerLoaderChunkItem` now emits `__turbopack_worker_url__(entrypoint, chunks, shared)` instead of the old blob URL shim. SharedWorker passes `shared=true` which adds a querystring - the browser handles worker identity via URL, so same URL means same instance.

Generated code changes (pseudocode):

```javascript
__turbopack_export_value__(__turbopack_worker_blob_url__(["chunk1.js", "chunk2.js"]));
```

After:

```javascript
__turbopack_export_value__(__turbopack_worker_url__("worker-entrypoint.js", ["chunk1.js", "chunk2.js"], false));
```

# Notes

Webpack doesn't currently correctly support `SharedWorkers`. The e2e test behaves properly in turbopack after this change, but webpack spins up two separate workers where it should only be spinning up one.

I generated snapshot tests for workers in the first and last commits to help understand the generated code better.

# Testing

- Snapshot tests for Worker, SharedWorker, and minified output
- E2E tests for WASM loading in workers (`test/e2e/app-dir/worker/`)
2026-01-21 18:08:54 +01:00

9 lines
285 B
TypeScript

// SharedWorkers use onconnect to handle incoming connections
let count = 0
self.addEventListener('connect', function (e: MessageEvent) {
const port = e.ports[0]
import('./worker-dep').then((mod) => {
port.postMessage('shared-worker.ts:' + mod.default + ':' + ++count)
})
})