mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
### What? Adds `experimental.turbopackWorkerAssetPrefix` — a Turbopack counterpart to webpack's [`output.workerPublicPath`](https://webpack.js.org/configuration/output/#outputworkerpublicpath). Opt-in, off by default, fully backward compatible. ```js // next.config.js module.exports = { assetPrefix: 'https://cdn.example.com', experimental: { // Same shape as `assetPrefix`: bare prefix, no trailing slash, no `/_next`. // `/_next/` is appended automatically. `''` → same-origin `/_next/...`. turbopackWorkerAssetPrefix: '', }, } ``` ### Why? When `assetPrefix` points to a cross-origin CDN (common production setup), Turbopack emits Worker URLs under that CDN origin. Browsers reject cross-origin `new Worker(url)` with `SecurityError`, breaking every feature that uses `new Worker(new URL('./worker.ts', import.meta.url))` — image decoders, WebP/GIF renderers, compression, OffscreenCanvas, etc. webpack solved this with `output.workerPublicPath`. Turbopack ignores the `webpack()` callback in `next.config.js`, so the existing same-origin workaround silently breaks when projects switch to Turbopack. Full design doc, alternatives considered, related issues (#88602, #92676, #74621, #29468): **#93044** Repro repo (`pnpm dev` reproduces, `pnpm dev:webpack` works): https://github.com/GuinsooRocky/next-turbopack-worker-cdn-repro ### How? Threads a new optional `turbopack_worker_asset_prefix: Option<RcStr>` from `ExperimentalConfig` → `BrowserChunkingContext` → the JS runtime, where `createWorker` reads it via a new `WORKER_BASE_PATH` global emitted alongside the existing `CHUNK_BASE_PATH`. When unset, behavior is identical to today. Semantics, modeled on `assetPrefix`: - **Bare prefix.** `/_next/` is appended automatically (same as `computed_asset_prefix`); the user supplies just the origin (or empty string for same-origin). - **`undefined` vs `''`.** `undefined` falls back to the regular chunk base path; `''` is a literal empty prefix that resolves to same-origin `/_next/...`. The runtime distinguishes the two by injecting a JS `null` literal vs a quoted string, and uses `WORKER_BASE_PATH ?? CHUNK_BASE_PATH` (not `||`). - **Applies to entrypoint and module chunks.** The Turbopack worker bootstrap rejects cross-origin module chunks (`Refusing to load script from foreign origin`), so the override has to cover both — overriding only the entrypoint would leave the worker unable to load any chunks under a cross-origin `assetPrefix`. Touchpoints: - `packages/next/src/server/config-{shared,schema}.ts` — public option + zod under `experimental` - `crates/next-{api,core}/...` — wiring through to `ClientChunkingContextOptions`; `NextConfig::turbopack_worker_asset_prefix()` resolves to `Some("<prefix>/_next/")` or `None` - `turbopack/crates/turbopack-browser/src/chunking_context.rs` — new field + builder + getter - `turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs` — emit `WORKER_BASE_PATH` declaration as `null` or a quoted string - `turbopack/.../runtime-base.ts` — `createWorker` resolves `workerBasePath` and uses it for both the entrypoint URL and module-chunk URLs delivered via params - `test/e2e/turbopack-worker-asset-prefix/` — e2e fixture using real cross-origin (`localhost` page, `127.0.0.1` `assetPrefix`); intercepts `new Worker()` to assert on the URL the runtime helper resolved. Three cases: no override (cross-origin URL → `SecurityError`), explicit override origin, and `''` (relative `/_next/...`). - `docs/01-app/03-api-reference/08-turbopack.mdx` — option reference - `telemetry/events/version.ts` — `useTurbopackWorkerAssetPrefix: boolean` (set on any explicit value, including empty string) ### Notes - Opening as **Draft** to invite API-shape feedback before going to review. Per the contributing guide, the design discussion is at #93044. - All checklist items the PR template requires for a feature are covered: e2e tests, docs, telemetry, no error-link path applies. cc @sokra Closes #93044 <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <[email protected]>
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
const NONE = '(none)'
|
|
|
|
export default function ClientComponent() {
|
|
const [pageOrigin, setPageOrigin] = useState<string>(NONE)
|
|
const [workerCtorUrl, setWorkerCtorUrl] = useState<string>(NONE)
|
|
const [workerCtorError, setWorkerCtorError] = useState<string>(NONE)
|
|
|
|
useEffect(() => {
|
|
setPageOrigin(window.location.origin)
|
|
|
|
// Capture the URL the Worker constructor receives (computed by the
|
|
// turbopack runtime helper from `turbopackWorkerAssetPrefix`), so the
|
|
// test can assert on it. Browsers reject cross-origin Worker URLs
|
|
// synchronously, so we surface that too.
|
|
const OriginalWorker = window.Worker
|
|
;(window as any).Worker = function PatchedWorker(
|
|
url: URL | string,
|
|
options?: object
|
|
) {
|
|
const urlString = typeof url === 'string' ? url : url.toString()
|
|
setWorkerCtorUrl(urlString)
|
|
try {
|
|
return new OriginalWorker(url, options)
|
|
} catch (err) {
|
|
setWorkerCtorError(err instanceof Error ? err.name : String(err))
|
|
throw err
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Trigger the turbopack `new Worker(new URL(..., import.meta.url))`
|
|
// pattern. Result is intercepted by the patched Worker above.
|
|
|
|
new Worker(new URL('./worker.ts', import.meta.url))
|
|
} catch {
|
|
// Already captured by the patched constructor; React state will
|
|
// reflect it on the next render.
|
|
}
|
|
;(window as any).Worker = OriginalWorker
|
|
}, [])
|
|
|
|
return (
|
|
<>
|
|
<p>page origin:</p>
|
|
<pre id="page-origin">{pageOrigin}</pre>
|
|
<p>URL passed to the Worker constructor (from runtime helper):</p>
|
|
<pre id="worker-ctor-url">{workerCtorUrl}</pre>
|
|
<p>Worker constructor error (if any):</p>
|
|
<pre id="worker-ctor-error">{workerCtorError}</pre>
|
|
</>
|
|
)
|
|
}
|