Skip to content

Store the Turbopack module cache in a Map - #98947

Draft
lukesandberg wants to merge 1 commit into
fewer_esm_tagsfrom
module_cache
Draft

lukesandberg wants to merge 1 commit into
fewer_esm_tagsfrom
module_cache

Conversation

@lukesandberg

@lukesandberg lukesandberg commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Maps are faster than Objects for map-like usecases such as the module cache

The one complexity is require.cache which exposes the module cache as an object, this however is an extremely rare usecase, so we support it via a runtime module that re-exposes the module cache as an Object using a Proxy.

Performance

Measured with bench/module-cost

Module execution time — the phase that actually hits the cache:

measurement obj map delta 95% CI rel
client commonjs 13.60 ms 13.00 ms −0.60 ms [−0.65, −0.30] −4.4%
pages api commonjs 21.59 ms 20.64 ms −0.95 ms [−1.39, −0.74] −4.4%
pages api esm 33.24 ms 32.55 ms −0.69 ms [−1.06, −0.47] −2.1%
client esm 20.55 ms 20.60 ms +0.05 ms [−0.25, +0.30] noise

Caveat: this benchmark is a deep require chain that deliberately slams this codepath, so this really just confirms the claim that Maps are faster and this is relevant to the bundled codepaths.

@lukesandberg
lukesandberg added this pull request to stack #98899 September 19, 2026 22:23
@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Failing test suites

Commit: 3922b78 | About building and testing Next.js

pnpm test-start-experimental-turbo test/production/app-dir/actions/app-action-body-finalize-output-standalone.test.ts (turbopack) (Experimental) (job)

  • app-dir action body finalize with nodejs middleware and output-standalone > should handle large payload through server action after nodejs middleware with delayed body finalize (DD)
Expand output

● app-dir action body finalize with nodejs middleware and output-standalone › should handle large payload through server action after nodejs middleware with delayed body finalize

TypeError: Cannot read properties of undefined (reading 'replace')

  23 |       '.next/standalone/node_modules/next/dist/server/body-streams.js',
  24 |       (content) => {
> 25 |         return content.replace(
     |                        ^
  26 |           'async finalize () {',
  27 |           'async finalize () { \nawait new Promise((resolve) => setTimeout(resolve, (Math.random() * 1000) + 1000));\n'
  28 |         )

  at replace (production/app-dir/actions/app-action-body-finalize-output-standalone.test.ts:25:24)
  at NextStartInstance.content [as patchFile] (lib/next-modes/base.ts:1078:39)
  at Object.<anonymous> (production/app-dir/actions/app-action-body-finalize-output-standalone.test.ts:22:5)

pnpm test-start-experimental-turbo test/e2e/app-dir/turbopack-additional-roots/turbopack-additional-roots.test.ts (turbopack) (Experimental) (job)

  • turbopack additional roots > resolves a linked package, sibling dependency, and next/dist (DD)
  • turbopack additional roots > reports initialization warnings when startup succeeds (DD)
  • turbopack additional roots > emits additional-root files and cross-root symlinks in the NFT (DD)
  • turbopack additional roots > runs after relocating standalone output away from the source root (DD)
Expand output

● turbopack additional roots › resolves a linked package, sibling dependency, and next/dist

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● turbopack additional roots › reports initialization warnings when startup succeeds

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● turbopack additional roots › emits additional-root files and cross-root symlinks in the NFT

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● turbopack additional roots › runs after relocating standalone output away from the source root

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

pnpm test-start-experimental-turbo test/e2e/twoslash/standalone.test.ts (turbopack) (Experimental) (job)

  • output: standalone with twoslash > should annotate twoslash types default (DD)
  • output: standalone with twoslash > should annotate twoslash types esnext (DD)
Expand output

● output: standalone with twoslash › should annotate twoslash types default

ENOENT: no such file or directory, stat '/tmp/next-install-b6fb9208a30552c5130b5fb4b45a32d8fede71dc7b599590a9fede037e8a0b86/.next/standalone'

● output: standalone with twoslash › should annotate twoslash types esnext

ENOENT: no such file or directory, stat '/tmp/next-install-b6fb9208a30552c5130b5fb4b45a32d8fede71dc7b599590a9fede037e8a0b86/.next/standalone'

pnpm test-start-turbo test/e2e/app-dir/turbopack-additional-roots/turbopack-additional-roots.test.ts (turbopack) (job)

  • turbopack additional roots > resolves a linked package, sibling dependency, and next/dist (DD)
  • turbopack additional roots > reports initialization warnings when startup succeeds (DD)
  • turbopack additional roots > emits additional-root files and cross-root symlinks in the NFT (DD)
  • turbopack additional roots > runs after relocating standalone output away from the source root (DD)
Expand output

● turbopack additional roots › resolves a linked package, sibling dependency, and next/dist

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● turbopack additional roots › reports initialization warnings when startup succeeds

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● turbopack additional roots › emits additional-root files and cross-root symlinks in the NFT

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● turbopack additional roots › runs after relocating standalone output away from the source root

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

pnpm test-start-turbo test/production/graceful-shutdown/index.test.ts (turbopack) (job)

  • Graceful Shutdown > production (standalone mode) > should not accept new requests during shutdown cleanup > should finish pending requests but refuse new ones (DD)
  • Graceful Shutdown > production (standalone mode) > should not accept new requests during shutdown cleanup > should stop accepting new requests when shutting down (DD)
Expand output

● Graceful Shutdown › production (standalone mode) › should wait for requests to complete before exiting

Failed to build next: 1

  150 |       const { exitCode } = await next.build()
  151 |       if (exitCode !== 0) {
> 152 |         throw new Error(`Failed to build next: ${exitCode}`)
      |               ^
  153 |       }
  154 |
  155 |       await fs.move(

  at Object.<anonymous> (production/graceful-shutdown/index.test.ts:152:15)

● Graceful Shutdown › production (standalone mode) › should not accept new requests during shutdown cleanup › should finish pending requests but refuse new ones

Failed to build next: 1

  150 |       const { exitCode } = await next.build()
  151 |       if (exitCode !== 0) {
> 152 |         throw new Error(`Failed to build next: ${exitCode}`)
      |               ^
  153 |       }
  154 |
  155 |       await fs.move(

  at Object.<anonymous> (production/graceful-shutdown/index.test.ts:152:15)

● Graceful Shutdown › production (standalone mode) › should not accept new requests during shutdown cleanup › should stop accepting new requests when shutting down

Failed to build next: 1

  150 |       const { exitCode } = await next.build()
  151 |       if (exitCode !== 0) {
> 152 |         throw new Error(`Failed to build next: ${exitCode}`)
      |               ^
  153 |       }
  154 |
  155 |       await fs.move(

  at Object.<anonymous> (production/graceful-shutdown/index.test.ts:152:15)

pnpm test-start-turbo test/production/next-server-nft/next-server-nft.test.ts (turbopack) (job)

  • next-server-nft > with adapters and output:standalone > should emit both whole-app server NFTs and complete the build (DD)
  • next-server-nft > with output:standalone > should not trace too many files in next-server.js.nft.json (DD)
Expand output

● next-server-nft › with output:standalone › should not trace too many files in next-server.js.nft.json

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● next-server-nft › with adapters and output:standalone › should emit both whole-app server NFTs and complete the build

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

pnpm test-start-turbo test/production/pnpm-support/index.test.ts (turbopack) (job)

  • pnpm support > build with dependencies installed via pnpm > should build with dependencies installed via pnpm (DD)
  • pnpm support > standalone mode with client-side JS > should execute client-side JS on each page in output: "standalone" (DD)
Expand output

● pnpm support › build with dependencies installed via pnpm › should build with dependencies installed via pnpm

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● pnpm support › standalone mode with client-side JS › should execute client-side JS on each page in output: "standalone"

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

pnpm test-start-turbo test/production/required-server-files-ssr-404/test/index.test.ts (turbopack) (job)

  • Required Server Files > production mode > should output required-server-files manifest correctly (DD)
  • Required Server Files > production mode > should render SSR page correctly (DD)
  • Required Server Files > production mode > should render dynamic SSR page correctly (DD)
  • Required Server Files > production mode > should render fallback page correctly (DD)
  • Required Server Files > production mode > should render SSR page correctly with x-matched-path (DD)
  • Required Server Files > production mode > should render dynamic SSR page correctly with x-matched-path (DD)
  • Required Server Files > production mode > should render fallback page correctly with x-matched-path and routes-matches (DD)
  • Required Server Files > production mode > should return data correctly with x-matched-path (DD)
  • Required Server Files > production mode > should render fallback optional catch-all route correctly with x-matched-path and routes-matches (DD)
  • Required Server Files > production mode > should return data correctly with x-matched-path for optional catch-all route (DD)
  • Required Server Files > production mode > should not apply trailingSlash redirect (DD)
  • Required Server Files > production mode > should normalize catch-all rewrite query values correctly (DD)
  • Required Server Files > production mode > should bubble error correctly for gip page (DD)
  • Required Server Files > production mode > should bubble error correctly for gssp page (DD)
  • Required Server Files > production mode > should bubble error correctly for gsp page (DD)
  • Required Server Files > production mode > should normalize optional values correctly for SSP page (DD)
  • Required Server Files > production mode > should normalize optional values correctly for SSG page (DD)
  • Required Server Files > production mode > should normalize optional values correctly for API page (DD)
  • Required Server Files > production mode > should match the index page correctly (DD)
  • Required Server Files > production mode > should match the root dynamic page correctly (DD)
  • Required Server Files > production mode > should preserve dynamic route identity from x-matched-path (DD)
  • Required Server Files > production mode > should rematch the concrete path after using x-matched-path (DD)
  • Required Server Files > production mode > should handle 404s properly (DD)
  • Required Server Files > production mode > partial optional catch-all route > should render /partial-catch-all/hello.com (DD)
  • Required Server Files > production mode > partial optional catch-all route > should render /partial-catch-all/hello.com/hello (DD)
  • Required Server Files > production mode > partial optional catch-all route > should render /partial-catch-all/hello.com/hello/world (DD)
Expand output

● Required Server Files › production mode › should output required-server-files manifest correctly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should render SSR page correctly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should render dynamic SSR page correctly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should render fallback page correctly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should render SSR page correctly with x-matched-path

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should render dynamic SSR page correctly with x-matched-path

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should render fallback page correctly with x-matched-path and routes-matches

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should return data correctly with x-matched-path

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should render fallback optional catch-all route correctly with x-matched-path and routes-matches

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › partial optional catch-all route › should render /partial-catch-all/hello.com

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › partial optional catch-all route › should render /partial-catch-all/hello.com/hello

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › partial optional catch-all route › should render /partial-catch-all/hello.com/hello/world

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should return data correctly with x-matched-path for optional catch-all route

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should not apply trailingSlash redirect

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should normalize catch-all rewrite query values correctly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should bubble error correctly for gip page

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should bubble error correctly for gssp page

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should bubble error correctly for gsp page

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should normalize optional values correctly for SSP page

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should normalize optional values correctly for SSG page

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should normalize optional values correctly for API page

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should match the index page correctly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should match the root dynamic page correctly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should preserve dynamic route identity from x-matched-path

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should rematch the concrete path after using x-matched-path

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

● Required Server Files › production mode › should handle 404s properly

ENOENT: no such file or directory, open '/tmp/next-install-98e753ae90b0586988dd0169fde262947b64a6c5fa3c874e739516289fe8c0a6/.next/required-server-files.json'

pnpm test-start-turbo test/production/standalone-mode/required-server-files/required-server-files-app.test.ts (turbopack) (job)

  • required server files app router > should send the right cache headers for an app route (DD)
  • required server files app router > should handle optional catchall (DD)
  • required server files app router > should send the right cache headers for an app page (DD)
  • required server files app router > should not fail caching (DD)
  • required server files app router > should properly handle prerender for bot request (DD)
  • required server files app router > should properly handle fallback for bot request (DD)
  • required server files app router > should send cache tags in minimal mode for ISR (DD)
  • required server files app router > should not send cache tags in minimal mode for SSR (DD)
  • required server files app router > should not send invalid soft tags to cache handler (DD)
  • required server files app router > should not override params with query params (DD)
  • required server files app router > should de-dupe HTML/RSC requests for ISR pages (DD)
  • required server files app router > should isolate cache between different ISR request groups (DD)
Expand output

● required server files app router › should send the right cache headers for an app route

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should handle optional catchall

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should send the right cache headers for an app page

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should not fail caching

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should properly handle prerender for bot request

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should properly handle fallback for bot request

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should send cache tags in minimal mode for ISR

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should not send cache tags in minimal mode for SSR

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should not send invalid soft tags to cache handler

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should not override params with query params

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should de-dupe HTML/RSC requests for ISR pages

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

● required server files app router › should isolate cache between different ISR request groups

Failed to build next: 1

  47 |     const { exitCode } = await next.build()
  48 |     if (exitCode !== 0) {
> 49 |       throw new Error(`Failed to build next: ${exitCode}`)
     |             ^
  50 |     }
  51 |
  52 |     await fs.move(

  at Object.<anonymous> (production/standalone-mode/required-server-files/required-server-files-app.test.ts:49:13)

pnpm test-start-turbo test/production/standalone-mode/no-app-routes/index.test.ts (turbopack) (job)

  • standalone mode - no app routes > should handle pages rendering correctly (DD)
Expand output

● standalone mode - no app routes › should handle pages rendering correctly

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

pnpm test-start-turbo test/production/standalone-mode/server-action-externals/standalone-mode-server-action-externals.test.ts (turbopack) (job)

  • standalone mode: server action externals > should execute a server action that uses external packages (DD)
Expand output

● standalone mode: server action externals › should execute a server action that uses external packages

ENOENT: no such file or directory, stat '/tmp/next-install-0fd7b9f96c092e1d746bdf2e09ef211abdaea9e0abc2e487d37ef7e862009fe5/.next/standalone'

pnpm test-start-turbo test/e2e/app-dir/cache-components-allow-otel-spans/cache-components-allow-otel-spans.test.ts (turbopack) (job)

  • cache-components OTEL spans > should allow creating Spans during prerendering during the build - inside a Cache Components (DD)
  • cache-components OTEL spans > should allow creating Spans during prerendering at runtime - inside a Cache Components (DD)
  • cache-components OTEL spans > should allow creating Spans during resuming a fallback - inside a Cache Component (DD)
  • cache-components OTEL spans > should allow creating Spans from a tracer acquired before provider registration (DD)
Expand output

● cache-components OTEL spans › should allow creating Spans during prerendering during the build - inside a Cache Components

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● cache-components OTEL spans › should allow creating Spans during prerendering at runtime - inside a Cache Components

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● cache-components OTEL spans › should allow creating Spans during resuming a fallback - inside a Cache Component

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● cache-components OTEL spans › should allow creating Spans from a tracer acquired before provider registration

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

pnpm test-start-turbo test/e2e/og-api/standalone.test.ts (turbopack) (job)

  • og-api > should respond from index (DD)
  • og-api > should work in pages/api (DD)
  • og-api > should work in app route (DD)
  • og-api > should work in app route in node runtime after image optimization (DD)
  • og-api > should work in middleware (DD)
  • og-api > should copy files correctly (DD)
Expand output

● og-api › should respond from index

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● og-api › should work in pages/api

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● og-api › should work in app route

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● og-api › should work in app route in node runtime after image optimization

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● og-api › should work in middleware

next build failed with code/signal 1

  137 |             if (code || signal)
  138 |               reject(
> 139 |                 new Error(
      |                 ^
  140 |                   `next build failed with code/signal ${code || signal}`
  141 |                 )
  142 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:139:17)

● og-api › should copy files correctly

next build failed with code/signal 1

  137 |             if (code || signal)
  138 | 

... truncated to fit in one GitHub comment ...

The runtime kept instantiated modules in a plain object keyed by module
id. Module ids are arbitrary strings, so that object lands in dictionary
mode and every lookup is a megamorphic property access; `delete` (used
throughout HMR) is worse still.

Switch the cache to a `Map`. The blocker was `require.cache`, which is
specified as a plain object and so can't be a `Map` directly. Rather
than keep the object for everyone's benefit, `require.cache` accesses
are now rewritten into an import of a new `@turbopack/module-cache`
helper that wraps the `Map` in a `Proxy` with object semantics. Going
through a real module reference means the shim is only pulled into
bundles that actually touch `require.cache`, which is rare.

`ModuleId` is `string | number` and production builds use numeric ids,
while property keys always reach a `Proxy` trap as strings. The id kind
is fixed for a build, and the shim is itself a module, so it reads its
own id to decide the conversion once and converts keys accordingly,
stringifying them again on the way out of `ownKeys`. Deciding up front
rather than probing for both keeps each trap to a single `Map` lookup,
and gives writes a definite answer for a key that isn't cached yet —
probing would have to guess, and guessing wrong strands an entry that
`Object.keys` reports but `require()` can never find.

The execution suite covers both id shapes: the new `numeric-ids` variant
builds with the numeric id strategy, enabled by a `numericModuleIds`
test option.

With that, `__turbopack_cache__` no longer needs to be reachable from
user code, so it's dropped from the free-var list along with the
now-unused `CjsRequireCacheAccess` codegen.

Measured with bench/module-cost (9331 modules per flavor, 40 samples
per arm, both arms built the same way). Module execution time, the
phase that actually hits the cache:

  client commonjs      -4.4%  95% CI [-4.7%, -2.2%]
  pages api commonjs   -4.4%  95% CI [-6.3%, -3.4%]
  pages api esm        -2.1%  95% CI [-3.2%, -1.4%]
  client esm           +0.2%  (noise)

Load times are flat, as expected. CommonJS shows the larger relative
gain, but not because it does more cache lookups: both flavors route
through `getOrInstantiateModuleFromParent` once per import. ESM just
does more work per module on top of that lookup (namespace setup,
`esmExport`/`interopEsm`), so the lookup is a smaller fraction of its
total. Note this benchmark is a deep require chain and deliberately
slams the codepath, so treat it as an upper bound rather than a typical
app.

The benchmark runner now keeps raw per-sample timings alongside the
summary statistics; without them an A/B can't tell a real difference
from run-to-run noise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant