Files
defac920cc feat(turbopack): add chunkLoadingGlobal config option (#93488)
### What?

Adds a new `chunkLoadingGlobal` option to the `turbopack` config in
`next.config.js` that overrides the global variable name used for
Turbopack chunk loading (defaults to `TURBOPACK`).

```js
// next.config.js
module.exports = {
  turbopack: {
    chunkLoadingGlobal: 'myApp',
  },
}
```

### Why?

When multiple Turbopack-built apps run on the same page (e.g. horizontal
micro-frontends), they each try to use `globalThis.TURBOPACK` which
causes conflicts. This option allows each app to use its own isolated
global, mirroring webpack's `output.chunkLoadingGlobal`.

The `chunk_loading_global` method was already wired into Turbopack in
#88790 — this PR exposes it via Next.js config.

A reason is
[vercel/remote-components#239](https://github.com/vercel/remote-components/pull/239),
which uses this option to eliminate four brittle `globalThis.TURBOPACK`
regex rename transforms in the remote-components runtime. Those
transforms target Turbopack's internal variable naming and have already
required updates as Turbopack's output format evolved. With
`chunkLoadingGlobal` set at build time, Turbopack handles the renaming
natively and those transforms become unnecessary.

### How?

- `config-shared.ts` / `config-schema.ts` — adds `chunkLoadingGlobal?:
string` to `TurbopackOptions` with Zod validation
- `next_config.rs` — adds the field to `TurbopackConfig` and a
`turbopack_chunk_loading_global()` accessor
- `next_client/context.rs` — threads it through
`ClientChunkingContextOptions` and calls
`builder.chunk_loading_global()` when set
- `project.rs` — wires the config into the client chunking context
options

Only applies to the browser/client chunking context — server and edge
runtimes don't have this concept.

### Test plan

- `pnpm test-start-turbo
test/production/app-dir/turbopack-chunk-loading-global/turbopack-chunk-loading-global.test.ts`

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Niklas Mischkulnig <[email protected]>
2026-05-08 09:56:41 +02:00

58 lines
1.9 KiB
TypeScript

import fs from 'fs'
import path from 'path'
import { nextTestSetup } from 'e2e-utils'
function collectJsFiles(dir: string): string[] {
if (!fs.existsSync(dir)) return []
return fs
.readdirSync(dir, { recursive: true, encoding: 'utf8' })
.flatMap((entry) => {
const full = path.join(dir, entry)
return entry.endsWith('.js') && fs.statSync(full).isFile() ? [full] : []
})
}
describe('turbopack-chunk-loading-global', () => {
const { next, isNextDeploy, isTurbopack } = nextTestSetup({
files: __dirname,
})
;(isNextDeploy || !isTurbopack ? describe.skip : describe)(
'chunk output',
() => {
it('uses the custom global name and drops the default TURBOPACK global', async () => {
const staticDir = path.join(next.testDir, '.next/static')
const jsFiles = collectJsFiles(staticDir)
expect(jsFiles.length).toBeGreaterThan(0)
const allContent = jsFiles
.map((f) => fs.readFileSync(f, 'utf8'))
.join('\n')
// Custom global must be used for chunk loading (auto-prefixed with TURBOPACK_)
expect(allContent).toContain('globalThis.TURBOPACK_myApp')
// Bare default TURBOPACK global must not appear — if it does,
// chunkLoadingGlobal was ignored by the Rust layer
expect(allContent).not.toMatch(/globalThis\.TURBOPACK(?!_)/)
})
}
)
describe('runtime behavior', () => {
it('renders content and handles interactions after hydration', async () => {
const browser = await next.browser('/')
// Initial render
expect(await browser.elementByCss('#count').text()).toBe('0')
// Click increments — proves chunks loaded and JS executed correctly
await browser.elementByCss('button').click()
expect(await browser.elementByCss('#count').text()).toBe('1')
await browser.elementByCss('button').click()
expect(await browser.elementByCss('#count').text()).toBe('2')
})
})
})