diff --git a/crates/next-api/src/pages.rs b/crates/next-api/src/pages.rs index c0e26bd94196..bff0e3dd6616 100644 --- a/crates/next-api/src/pages.rs +++ b/crates/next-api/src/pages.rs @@ -233,11 +233,11 @@ impl PagesProject { } #[turbo_tasks::function] - async fn to_endpoint( + async fn to_page_endpoint( self: Vc, item: Vc, ty: PageEndpointType, - ) -> Result>> { + ) -> Result> { let PagesStructureItem { next_router_path, original_path, @@ -245,15 +245,23 @@ impl PagesProject { } = &*item.await?; let pathname: RcStr = format!("/{}", next_router_path.path).into(); let original_name = format!("/{}", original_path.path).into(); - let endpoint = Vc::upcast(PageEndpoint::new( + Ok(PageEndpoint::new( ty, self, pathname, original_name, item, self.pages_structure(), - )); - Ok(endpoint) + )) + } + + #[turbo_tasks::function] + async fn to_endpoint( + self: Vc, + item: Vc, + ty: PageEndpointType, + ) -> Result>> { + Ok(Vc::upcast(self.to_page_endpoint(item, ty))) } #[turbo_tasks::function] @@ -264,9 +272,16 @@ impl PagesProject { )) } + /// The `/_app` endpoint. Its client chunk group is generated first and seeds the availability + /// information of every other page, so it must never depend on an individual page. + #[turbo_tasks::function] + async fn app_page_endpoint(self: Vc) -> Result> { + Ok(self.to_page_endpoint(*self.pages_structure().await?.app, PageEndpointType::Html)) + } + #[turbo_tasks::function] pub async fn app_endpoint(self: Vc) -> Result>> { - Ok(self.to_endpoint(*self.pages_structure().await?.app, PageEndpointType::Html)) + Ok(Vc::upcast(self.app_page_endpoint())) } #[turbo_tasks::function] @@ -797,12 +812,24 @@ impl PageEndpoint { .iter() .map(|m| ResolvedVc::upcast(*m)) .collect(); + // Like App Router layouts, `/_app` is always loaded before the page. Chunk it first so + // the page's chunks don't include modules that the browser already downloaded with + // `/_app`. + let availability_info = if this.pathname == "/_app" { + AvailabilityInfo::root() + } else { + this.pages_project + .app_page_endpoint() + .client_chunk_group() + .await? + .availability_info + }; let client_chunk_group = client_chunking_context.evaluated_chunk_group( AssetIdent::from_path(this.page.await?.base_path.clone()).into_vc(), ChunkGroup::Entry(evaluatable_assets), module_graph, OutputAssets::empty(), - AvailabilityInfo::root(), + availability_info, ); Ok(client_chunk_group) diff --git a/crates/next-custom-transforms/src/transforms/react_server_components.rs b/crates/next-custom-transforms/src/transforms/react_server_components.rs index 03559971beae..dd5ff3a6cdcb 100644 --- a/crates/next-custom-transforms/src/transforms/react_server_components.rs +++ b/crates/next-custom-transforms/src/transforms/react_server_components.rs @@ -726,6 +726,7 @@ impl ReactServerComponentValidator { "cacheTag", "unstable_cacheTag", "unstable_navigation", + "unstable_prefetch", // "unstable_noStore" // no-op in client, but allowed for legacy reasons ], ), diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/input.js b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/input.js new file mode 100644 index 000000000000..8954009c7a83 --- /dev/null +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/input.js @@ -0,0 +1,6 @@ +import { unstable_prefetch } from 'next/cache' + +export async function test() { + await unstable_prefetch() + return null +} diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/output.js b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/output.js new file mode 100644 index 000000000000..2b30bf988768 --- /dev/null +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/output.js @@ -0,0 +1,5 @@ +import { unstable_prefetch } from 'next/cache'; +export async function test() { + await unstable_prefetch(); + return null; +} diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/output.stderr b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/output.stderr new file mode 100644 index 000000000000..c53340fc7672 --- /dev/null +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/app-dir/unstable-prefetch/output.stderr @@ -0,0 +1,9 @@ + x You're importing a module that depends on "unstable_prefetch" into a React Client Component module. This API is only available in Server Components but one of its parents is marked with "use + | client", so this module is also a Client Component. + | Learn more: https://nextjs.org/docs/app/building-your-application/rendering + | + | + ,-[input.js:1:1] + 1 | import { unstable_prefetch } from 'next/cache' + : ^^^^^^^^^^^^^^^^^ + `---- diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/input.js b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/input.js new file mode 100644 index 000000000000..8954009c7a83 --- /dev/null +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/input.js @@ -0,0 +1,6 @@ +import { unstable_prefetch } from 'next/cache' + +export async function test() { + await unstable_prefetch() + return null +} diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/output.js b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/output.js new file mode 100644 index 000000000000..2b30bf988768 --- /dev/null +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/output.js @@ -0,0 +1,5 @@ +import { unstable_prefetch } from 'next/cache'; +export async function test() { + await unstable_prefetch(); + return null; +} diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/output.stderr b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/output.stderr new file mode 100644 index 000000000000..b8232a355a9b --- /dev/null +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/unstable-prefetch/output.stderr @@ -0,0 +1,8 @@ + x You're importing a module that depends on "unstable_prefetch". This API is only available in Server Components in the App Router, but you are using it in the Pages Router. + | Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components + | + | + ,-[input.js:1:1] + 1 | import { unstable_prefetch } from 'next/cache' + : ^^^^^^^^^^^^^^^^^ + `---- diff --git a/docs/01-app/02-guides/migrating/app-router-migration.mdx b/docs/01-app/02-guides/migrating/app-router-migration.mdx index ef794a345737..5cdcb9a33200 100644 --- a/docs/01-app/02-guides/migrating/app-router-migration.mdx +++ b/docs/01-app/02-guides/migrating/app-router-migration.mdx @@ -108,7 +108,7 @@ To upgrade your links to Next.js 13, you can use the [`new-link` codemod](/docs/ The behavior of [`next/script`](/docs/app/api-reference/components/script) has been updated to support both `pages` and `app`, but some changes need to be made to ensure a smooth migration: -- Move any `beforeInteractive` scripts you previously included in `_document.js` to the root layout file (`app/layout.tsx`). +- Move any `beforeInteractive` scripts you previously included in `_document.js` to a [root layout](/docs/app/api-reference/file-conventions/layout#root-layout), such as `app/layout.tsx` or `app/[locale]/layout.tsx`. - The experimental `worker` strategy does not yet work in `app` and scripts denoted with this strategy will either have to be removed or modified to use a different strategy (e.g. `lazyOnload`). - `onLoad`, `onReady`, and `onError` handlers will not work in Server Components so make sure to move them to a [Client Component](/docs/app/getting-started/server-and-client-components) or remove them altogether. diff --git a/docs/01-app/03-api-reference/02-components/script.mdx b/docs/01-app/03-api-reference/02-components/script.mdx index 432cf82941ff..226031960ece 100644 --- a/docs/01-app/03-api-reference/02-components/script.mdx +++ b/docs/01-app/03-api-reference/02-components/script.mdx @@ -72,7 +72,7 @@ Scripts denoted with this strategy are preloaded and fetched before any first-pa -`beforeInteractive` scripts must be placed inside the root layout (`app/layout.tsx`) and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). +Scripts with the `beforeInteractive` strategy must be placed inside a [root layout](/docs/app/api-reference/file-conventions/layout#root-layout), such as `app/layout.tsx` or `app/[locale]/layout.tsx`, and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). @@ -155,6 +155,12 @@ export default function Document() { > **Good to know**: Scripts with `beforeInteractive` will always be injected inside the `head` of the HTML document regardless of where it's placed in the component. + + +> **Good to know**: These scripts run once per document load. A client-side navigation does not run them again, including one that only changes a root param, such as `/en` to `/fi`, since the root layout stays the same. + + + Some examples of scripts that should be fetched as soon as possible with `beforeInteractive` include: - Bot detectors diff --git a/docs/01-app/03-api-reference/04-functions/cacheLife.mdx b/docs/01-app/03-api-reference/04-functions/cacheLife.mdx index b04d33e7fe4a..c989c67cd26e 100644 --- a/docs/01-app/03-api-reference/04-functions/cacheLife.mdx +++ b/docs/01-app/03-api-reference/04-functions/cacheLife.mdx @@ -265,7 +265,7 @@ A short cache lifetime changes where the cached content can be delivered from: - **`revalidate` of `0`, or `expire` under 5 minutes**: excluded from prerenders, becoming a "dynamic hole" resolved at request time. - **`stale` under 30 seconds**: excluded from prerenders, because a prefetch would expire before the user could click. -- **`stale` from 30 seconds up to 5 minutes**: included in prerenders, but excluded from the route's [App Shell](/docs/app/glossary#app-shell). +- **`stale` of at least 30 seconds but under 5 minutes**: included in prerenders, but excluded from the route's [App Shell](/docs/app/glossary#app-shell). Of the presets, only `seconds` falls under any of these thresholds: its `expire` of 1 minute excludes it from prerenders. diff --git a/docs/01-app/03-api-reference/07-adapters/index.mdx b/docs/01-app/03-api-reference/07-adapters/index.mdx index ad82f66b1ff1..72c66258b7c0 100644 --- a/docs/01-app/03-api-reference/07-adapters/index.mdx +++ b/docs/01-app/03-api-reference/07-adapters/index.mdx @@ -4,35 +4,3 @@ description: Build deployment adapters for Next.js platforms and infrastructure. --- Use this section to build and validate deployment adapters that integrate with the Next.js build and runtime model. - - - -- [Configuration](/docs/app/api-reference/adapters/configuration) -- [Creating an Adapter](/docs/app/api-reference/adapters/creating-an-adapter) -- [API Reference](/docs/app/api-reference/adapters/api-reference) -- [Testing Adapters](/docs/app/api-reference/adapters/testing-adapters) -- [Routing with `@next/routing`](/docs/app/api-reference/adapters/routing-with-next-routing) -- [Implementing PPR in an Adapter](/docs/app/api-reference/adapters/implementing-ppr-in-an-adapter) -- [Runtime Integration](/docs/app/api-reference/adapters/runtime-integration) -- [Invoking Entrypoints](/docs/app/api-reference/adapters/invoking-entrypoints) -- [Output Types](/docs/app/api-reference/adapters/output-types) -- [Routing Information](/docs/app/api-reference/adapters/routing-information) -- [Use Cases](/docs/app/api-reference/adapters/use-cases) -- [Supporting Immutable Static Assets](/docs/app/api-reference/adapters/immutable-static-assets) - - - - - -- [Configuration](/docs/pages/api-reference/adapters/configuration) -- [Creating an Adapter](/docs/pages/api-reference/adapters/creating-an-adapter) -- [API Reference](/docs/pages/api-reference/adapters/api-reference) -- [Testing Adapters](/docs/pages/api-reference/adapters/testing-adapters) -- [Routing with `@next/routing`](/docs/pages/api-reference/adapters/routing-with-next-routing) -- [Runtime Integration](/docs/pages/api-reference/adapters/runtime-integration) -- [Invoking Entrypoints](/docs/pages/api-reference/adapters/invoking-entrypoints) -- [Output Types](/docs/pages/api-reference/adapters/output-types) -- [Routing Information](/docs/pages/api-reference/adapters/routing-information) -- [Use Cases](/docs/pages/api-reference/adapters/use-cases) - - diff --git a/errors/no-before-interactive-script-outside-document.mdx b/errors/no-before-interactive-script-outside-document.mdx index 68965e6758d6..c771e50ef10e 100644 --- a/errors/no-before-interactive-script-outside-document.mdx +++ b/errors/no-before-interactive-script-outside-document.mdx @@ -2,17 +2,17 @@ title: No Before Interactive Script Outside Document --- -> Prevent usage of `next/script`'s `beforeInteractive` strategy outside of `app/layout.jsx` or `pages/_document.js`. +> Prevent usage of `next/script`'s `beforeInteractive` strategy outside of a root layout or `pages/_document.js`. ## Why This Error Occurred -You cannot use the `next/script` component with the `beforeInteractive` strategy outside `app/layout.jsx` or `pages/_document.js`. That's because `beforeInteractive` strategy only works inside **`app/layout.jsx`** or **`pages/_document.js`** and is designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). +You cannot use the `next/script` component with the `beforeInteractive` strategy outside a root layout or `pages/_document.js`. That's because `beforeInteractive` strategy only works inside a **root layout** or **`pages/_document.js`** and is designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). ## Possible Ways to Fix It ### App Router -If you want a global script, and you are using the App Router, move the script inside `app/layout.jsx`. +If you want a global script, and you are using the App Router, move the script inside a [root layout](/docs/app/api-reference/file-conventions/layout#root-layout), any layout without a `layout.js` above it. ```jsx filename="app/layout.jsx" import Script from 'next/script' diff --git a/lerna.json b/lerna.json index 11b86399f1b5..fc4c9dc558e1 100644 --- a/lerna.json +++ b/lerna.json @@ -15,5 +15,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "16.3.1-canary.26" + "version": "16.4.0-canary.0" } \ No newline at end of file diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 869ce76e9229..25659c2a969b 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "keywords": [ "react", "next", diff --git a/packages/devlow-bench/package.json b/packages/devlow-bench/package.json index dd2d9805f14b..a8bb0272abe9 100644 --- a/packages/devlow-bench/package.json +++ b/packages/devlow-bench/package.json @@ -1,7 +1,7 @@ { "name": "@vercel/devlow-bench", "private": true, - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "Benchmarking tool for the developer workflow", "repository": { "type": "git", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 6a7e604efc7a..3ec4cb0e06e5 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "ESLint configuration used by Next.js.", "license": "MIT", "repository": { @@ -12,7 +12,7 @@ "dist" ], "dependencies": { - "@next/eslint-plugin-next": "16.3.1-canary.26", + "@next/eslint-plugin-next": "16.4.0-canary.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index c15546e2da98..c2cc9b692689 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,7 +1,7 @@ { "name": "@next/eslint-plugin-internal", "private": true, - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "ESLint plugin for working on Next.js.", "exports": { ".": "./src/eslint-plugin-internal.js" diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 0960d029f5bc..ad6f70a3f860 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/font/package.json b/packages/font/package.json index 7abb3f473558..9bd75a77ca93 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 5dc4875b6f87..f94720d4e020 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 4c97378be04f..2cd6f558df73 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 3cfd563db0fc..31906af5269a 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index eac8d15d5070..6165b322d7f0 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-playwright/package.json b/packages/next-playwright/package.json index 57514beb1f05..0fe6e3c379fa 100644 --- a/packages/next-playwright/package.json +++ b/packages/next-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@next/playwright", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "repository": { "url": "vercel/next.js", "directory": "packages/next-playwright" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 68ba66c5e341..1c01ba6fbdd1 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index c09840f99d73..76f382f51a1c 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index d7629d67a3d7..74db02b6d41e 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-routing/package.json b/packages/next-routing/package.json index d1dbef2e96b7..ad578bff1f1e 100644 --- a/packages/next-routing/package.json +++ b/packages/next-routing/package.json @@ -1,6 +1,6 @@ { "name": "@next/routing", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "keywords": [ "react", "next", diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json index 6b4cf0021831..92a767e099de 100644 --- a/packages/next-rspack/package.json +++ b/packages/next-rspack/package.json @@ -1,6 +1,6 @@ { "name": "next-rspack", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "repository": { "url": "vercel/next.js", "directory": "packages/next-rspack" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 4474bdc1285d..922ac5df7386 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "private": true, "files": [ "native/" diff --git a/packages/next/cache.d.ts b/packages/next/cache.d.ts index b52685ac04de..3a3750edfb8e 100644 --- a/packages/next/cache.d.ts +++ b/packages/next/cache.d.ts @@ -156,3 +156,4 @@ export const unstable_cacheLife: typeof cacheLife export const unstable_cacheTag: typeof cacheTag export { unstable_navigation } from 'next/dist/server/request/cache-stages' +export { unstable_prefetch } from 'next/dist/server/request/cache-stages' diff --git a/packages/next/cache.js b/packages/next/cache.js index 97c603e80671..3a205e88e92c 100644 --- a/packages/next/cache.js +++ b/packages/next/cache.js @@ -26,6 +26,7 @@ if (process.env.NEXT_RUNTIME === '') { cacheLife: notAvailableInClient('cacheLife'), cacheTag: notAvailableInClient('cacheTag'), unstable_navigation: notAvailableInClient('unstable_navigation'), + unstable_prefetch: notAvailableInClient('unstable_prefetch'), } } else { // Keep server requires in this branch so browser builds can DCE them. @@ -52,6 +53,8 @@ if (process.env.NEXT_RUNTIME === '') { cacheTag: require('next/dist/server/use-cache/cache-tag').cacheTag, unstable_navigation: require('next/dist/server/request/cache-stages') .unstable_navigation, + unstable_prefetch: require('next/dist/server/request/cache-stages') + .unstable_prefetch, } } @@ -99,3 +102,4 @@ exports.unstable_cacheTag = cacheExports.unstable_cacheTag exports.refresh = cacheExports.refresh exports.io = cacheExports.io exports.unstable_navigation = cacheExports.unstable_navigation +exports.unstable_prefetch = cacheExports.unstable_prefetch diff --git a/packages/next/errors.json b/packages/next/errors.json index c653ca8a00b2..6ab0ba34ae0c 100644 --- a/packages/next/errors.json +++ b/packages/next/errors.json @@ -1482,5 +1482,13 @@ "1481": "Route %s used \\`unstable_navigation()\\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents", "1482": "Route %s used \\`unstable_navigation()\\` inside \\`after()\\` while rendering. The \\`unstable_navigation()\\` function is used to indicate the subsequent code must only run during an actual navigation, but \\`after()\\` executes after the request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/after", "1483": "Route %s used \\`unstable_navigation()\\` inside \"use cache: private\". This is not currently supported. Instead, move the \"use cache\" directive to a function that's called below \\`await unstable_navigation()\\`, so that the cached content is deferred to the navigation without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache", - "1484": "Route %s used \\`unstable_navigation()\\` inside \"use cache\". This is not currently supported. Instead, move the \"use cache\" directive to a function that's called below \\`await unstable_navigation()\\`, so that the cached content is deferred to the navigation without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache" + "1484": "Route %s used \\`unstable_navigation()\\` inside \"use cache\". This is not currently supported. Instead, move the \"use cache\" directive to a function that's called below \\`await unstable_navigation()\\`, so that the cached content is deferred to the navigation without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache", + "1485": "\"unstable_prefetch() is not implemented yet.\"", + "1486": "Route %s used \\`unstable_prefetch()\\` inside \"use cache\". This is not currently supported. Instead, move the \"use cache\" directive to a function that's called below \\`await unstable_prefetch()\\`, so that the cached content is deferred to the prefetch without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache", + "1487": "`unstable_prefetch` must not be used within a Client Component. Next.js should be preventing `unstable_prefetch` from being included in Client Components statically, but did not in this case.", + "1488": "Route %s used \\`unstable_prefetch()\\` inside \\`generateStaticParams\\`. This is not supported because \\`generateStaticParams\\` runs at build time without a prefetch. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context", + "1489": "Route %s used \\`unstable_prefetch()\\` inside a function cached with \\`unstable_cache()\\`. The \\`unstable_prefetch()\\` function is used to indicate the subsequent code must not run in the app shell, but \\`unstable_cache()\\` caches must be able to be produced before a prefetch, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache", + "1490": "Route %s used \\`unstable_prefetch()\\` inside \\`after()\\` while rendering. The \\`unstable_prefetch()\\` function is used to indicate the subsequent code must not run in the app shell, but \\`after()\\` executes after the request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/after", + "1491": "Route %s used \\`unstable_prefetch()\\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents", + "1492": "Route %s used \\`unstable_prefetch()\\` inside \"use cache: private\". This is not currently supported. Instead, move the \"use cache\" directive to a function that's called below \\`await unstable_prefetch()\\`, so that the cached content is deferred to the prefetch without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache" } diff --git a/packages/next/package.json b/packages/next/package.json index 02846e46e059..025c161b891a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -100,7 +100,7 @@ ] }, "dependencies": { - "@next/env": "16.3.1-canary.26", + "@next/env": "16.4.0-canary.0", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -164,11 +164,11 @@ "@modelcontextprotocol/sdk": "1.18.1", "@mswjs/interceptors": "0.42.0", "@napi-rs/triples": "1.2.0", - "@next/font": "16.3.1-canary.26", - "@next/polyfill-module": "16.3.1-canary.26", - "@next/polyfill-nomodule": "16.3.1-canary.26", - "@next/react-refresh-utils": "16.3.1-canary.26", - "@next/swc": "16.3.1-canary.26", + "@next/font": "16.4.0-canary.0", + "@next/polyfill-module": "16.4.0-canary.0", + "@next/polyfill-nomodule": "16.4.0-canary.0", + "@next/react-refresh-utils": "16.4.0-canary.0", + "@next/swc": "16.4.0-canary.0", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.61.0", "@rspack/core": "1.6.7", diff --git a/packages/next/src/build/define-env.ts b/packages/next/src/build/define-env.ts index ebedb611ac56..556439d2c5bf 100644 --- a/packages/next/src/build/define-env.ts +++ b/packages/next/src/build/define-env.ts @@ -172,6 +172,9 @@ export function getDefineEnv({ 'process.env.__NEXT_TURBOPACK_SHARED_RUNTIME': Boolean( config.experimental.turbopackSharedRuntime ), + 'process.env.__NEXT_TURBOPACK_CHUNK_UPDATE_LISTENERS_GLOBAL': `${ + config.turbopack?.chunkLoadingGlobal ?? 'TURBOPACK' + }_CHUNK_UPDATE_LISTENERS`, 'process.env.__NEXT_CACHE_COMPONENTS': isCacheComponentsEnabled, 'process.env.__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS': Boolean( config.experimental.cachedNavigations diff --git a/packages/next/src/client/dev/hot-reloader/app/web-socket.ts b/packages/next/src/client/dev/hot-reloader/app/web-socket.ts index 8ad232bc747f..b3534e799f6a 100644 --- a/packages/next/src/client/dev/hot-reloader/app/web-socket.ts +++ b/packages/next/src/client/dev/hot-reloader/app/web-socket.ts @@ -201,6 +201,8 @@ export function createProcessTurbopackMessage( }, sendMessage, onUpdateError: (err: unknown) => performFullReload(err, sendMessage), + chunkUpdateListenersGlobal: + process.env.__NEXT_TURBOPACK_CHUNK_UPDATE_LISTENERS_GLOBAL!, }) }) diff --git a/packages/next/src/client/next-dev-turbopack.ts b/packages/next/src/client/next-dev-turbopack.ts index f8c47425f04c..bb887e210736 100644 --- a/packages/next/src/client/next-dev-turbopack.ts +++ b/packages/next/src/client/next-dev-turbopack.ts @@ -48,6 +48,8 @@ initialize({ }, sendMessage: devClient.sendTurbopackMessage, onUpdateError: devClient.handleUpdateError, + chunkUpdateListenersGlobal: + process.env.__NEXT_TURBOPACK_CHUNK_UPDATE_LISTENERS_GLOBAL!, }) return pageBootstrap(assetPrefix) diff --git a/packages/next/src/server/lib/router-utils/cache-life-type-utils.test.ts b/packages/next/src/server/lib/router-utils/cache-life-type-utils.test.ts index 6d1c22e7cca3..f3164893ede8 100644 --- a/packages/next/src/server/lib/router-utils/cache-life-type-utils.test.ts +++ b/packages/next/src/server/lib/router-utils/cache-life-type-utils.test.ts @@ -31,6 +31,7 @@ describe('cache-life-type-utils', () => { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' export { io } from 'next/dist/server/request/io' export { unstable_navigation } from 'next/dist/server/request/cache-stages' + export { unstable_prefetch } from 'next/dist/server/request/cache-stages' /** @@ -190,6 +191,7 @@ describe('cache-life-type-utils', () => { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' export { io } from 'next/dist/server/request/io' export { unstable_navigation } from 'next/dist/server/request/cache-stages' + export { unstable_prefetch } from 'next/dist/server/request/cache-stages' /** @@ -269,6 +271,7 @@ describe('cache-life-type-utils', () => { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' export { io } from 'next/dist/server/request/io' export { unstable_navigation } from 'next/dist/server/request/cache-stages' + export { unstable_prefetch } from 'next/dist/server/request/cache-stages' /** @@ -347,6 +350,7 @@ describe('cache-life-type-utils', () => { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' export { io } from 'next/dist/server/request/io' export { unstable_navigation } from 'next/dist/server/request/cache-stages' + export { unstable_prefetch } from 'next/dist/server/request/cache-stages' /** @@ -426,6 +430,7 @@ describe('cache-life-type-utils', () => { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' export { io } from 'next/dist/server/request/io' export { unstable_navigation } from 'next/dist/server/request/cache-stages' + export { unstable_prefetch } from 'next/dist/server/request/cache-stages' /** @@ -509,6 +514,7 @@ describe('cache-life-type-utils', () => { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' export { io } from 'next/dist/server/request/io' export { unstable_navigation } from 'next/dist/server/request/cache-stages' + export { unstable_prefetch } from 'next/dist/server/request/cache-stages' /** diff --git a/packages/next/src/server/lib/router-utils/cache-life-type-utils.ts b/packages/next/src/server/lib/router-utils/cache-life-type-utils.ts index 41b56de28848..8f4e39d0a757 100644 --- a/packages/next/src/server/lib/router-utils/cache-life-type-utils.ts +++ b/packages/next/src/server/lib/router-utils/cache-life-type-utils.ts @@ -179,6 +179,7 @@ declare module 'next/cache' { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' export { io } from 'next/dist/server/request/io' export { unstable_navigation } from 'next/dist/server/request/cache-stages' + export { unstable_prefetch } from 'next/dist/server/request/cache-stages' ${overloads} diff --git a/packages/next/src/server/request/cache-stages.ts b/packages/next/src/server/request/cache-stages.ts index 6c0c9b72c2ad..b1ebfb99265e 100644 --- a/packages/next/src/server/request/cache-stages.ts +++ b/packages/next/src/server/request/cache-stages.ts @@ -5,12 +5,150 @@ import { } from '../app-render/work-unit-async-storage.external' import { applyOwnerStack, + RENDER_STAGES_BY_DATA_KIND, trackIncompatibleShellContent, } from '../dynamic-rendering-utils' import { isRequestApiAllowedInCurrentPhase } from './utils' import { InvariantError } from '../../shared/lib/invariant-error' import { RenderStage } from '../app-render/staged-rendering' +/** + * When `partialPrefetching` is enabled, this function allows you to indicate + * that the subsequent code should be excluded from the shell. It will be deferred until + * a prefetch (i.e. when using ``) or a navigation. + * + * It has no effect during static prerendering — static output is computed + * once and shared across many clients, so there's no per-request cost to + * save — and no effect on the initial load of a page. + * + * Unlike `connection()`, it does not mark the subtree as request-dependent — + * content below `await unstable_prefetch()` remains fully cacheable. + */ +export function unstable_prefetch(): Promise { + const workStore = workAsyncStorage.getStore() + const workUnitStore = workUnitAsyncStorage.getStore() + + if (!workStore || !workUnitStore) { + const callingExpression = 'unstable_prefetch' + throwForMissingRequestStore(callingExpression) + } + if (!process.env.__NEXT_CACHE_COMPONENTS) { + throw new Error( + `Route ${workStore.route} used \`unstable_prefetch()\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents` + ) + } + + if (!isRequestApiAllowedInCurrentPhase(workUnitStore)) { + throw new Error( + `Route ${workStore.route} used \`unstable_prefetch()\` inside \`after()\` while rendering. The \`unstable_prefetch()\` function is used to indicate the subsequent code must not run in the app shell, but \`after()\` executes after the request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/after` + ) + } + + switch (workUnitStore.type) { + case 'prerender': { + // Content below `prefetch()` is excluded from the shell, but it's + // deliberately included in the static output (and thus in static + // prefetches), so we only delay it until the static prefetch stage. + const { stagedRendering } = workUnitStore + if (!stagedRendering) { + // Prospective prerender + return Promise.resolve(undefined) + } else { + // Final prerender + return stagedRendering.delayUntilStage( + RENDER_STAGES_BY_DATA_KIND.staticLinkData, + 'unstable_prefetch', + undefined + ) + } + } + case 'prerender-runtime': { + // In a shell render, prefetch() doesn't resolve, because it doesn't reach + // `Runtime`. It'll resolve in a runtime prefetch, and in a runtime + // prerender produced during a navigation. + // Note that this does not mark the subtree as dynamic -- content guarded by + // prefetch() is still considered cacheable. + const { stagedRendering } = workUnitStore + if (!stagedRendering) { + // Prospective prerender + return Promise.resolve(undefined) + } else { + // Final prerender + return stagedRendering.delayUntilStage( + RENDER_STAGES_BY_DATA_KIND.runtimeLinkData, + 'unstable_prefetch', + undefined + ) + } + } + case 'request': { + const { stagedRendering } = workUnitStore + if (stagedRendering) { + // We can either recover a static shell or a runtime shell, but not both. + trackIncompatibleShellContent(workUnitStore) + const stage = workUnitStore.needsAppShell + ? RENDER_STAGES_BY_DATA_KIND.runtimeLinkData // Match the timing of 'prerender-runtime'. + : RENDER_STAGES_BY_DATA_KIND.staticLinkData // Match the timing of 'prerender'. + + return stagedRendering.delayUntilStage( + stage, + 'unstable_prefetch', + undefined + ) + } + return Promise.resolve(undefined) + } + + case 'cache': { + const error = new Error( + `Route ${workStore.route} used \`unstable_prefetch()\` inside "use cache". This is not currently supported. Instead, move the "use cache" directive to a function that's called below \`await unstable_prefetch()\`, so that the cached content is deferred to the prefetch without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache` + ) + Error.captureStackTrace(error, unstable_prefetch) + applyOwnerStack(error) + workStore.invalidDynamicUsageError ??= error + throw error + } + case 'private-cache': { + const error = new Error( + `Route ${workStore.route} used \`unstable_prefetch()\` inside "use cache: private". This is not currently supported. Instead, move the "use cache" directive to a function that's called below \`await unstable_prefetch()\`, so that the cached content is deferred to the prefetch without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache` + ) + Error.captureStackTrace(error, unstable_prefetch) + applyOwnerStack(error) + workStore.invalidDynamicUsageError ??= error + throw error + } + case 'unstable-cache': { + throw new Error( + `Route ${workStore.route} used \`unstable_prefetch()\` inside a function cached with \`unstable_cache()\`. The \`unstable_prefetch()\` function is used to indicate the subsequent code must not run in the app shell, but \`unstable_cache()\` caches must be able to be produced before a prefetch, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache` + ) + } + case 'generate-static-params': { + throw new Error( + `Route ${workStore.route} used \`unstable_prefetch()\` inside \`generateStaticParams\`. This is not supported because \`generateStaticParams\` runs at build time without a prefetch. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` + ) + } + case 'prerender-client': + case 'validation-client': { + const exportName = '`unstable_prefetch`' + throw new InvariantError( + `${exportName} must not be used within a Client Component. Next.js should be preventing ${exportName} from being included in Client Components statically, but did not in this case.` + ) + } + case 'prerender-legacy': { + // NOTE: Should not be reachable, because we don't use this mode in cacheComponents, + // which we require at the top + throw new Error( + `Route ${workStore.route} used \`unstable_prefetch()\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents` + ) + } + + default: { + workUnitStore satisfies never + return Promise.resolve(undefined) + } + } +} + /** * This function allows you to indicate that the subsequent code should be * deferred to the actual navigation instead of rendering during a runtime diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index db5970d0938c..0f25c6edd49d 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index a5f9b4ee844c..e909ad8ca342 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "16.3.1-canary.26", + "version": "16.4.0-canary.0", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -26,7 +26,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "16.3.1-canary.26", + "next": "16.4.0-canary.0", "outdent": "0.8.0", "prettier": "2.5.1", "typescript": "6.0.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a483fe7d014f..5d1ebfcb597b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1024,7 +1024,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../eslint-plugin-next eslint: specifier: '>=9.0.0' @@ -1107,7 +1107,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../next-env '@swc/helpers': specifier: 0.5.23 @@ -1228,19 +1228,19 @@ importers: specifier: 1.2.0 version: 1.2.0 '@next/font': - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../font '@next/polyfill-module': - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../next-polyfill-nomodule '@next/react-refresh-utils': - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../react-refresh-utils '@next/swc': - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1983,7 +1983,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 16.3.1-canary.26 + specifier: 16.4.0-canary.0 version: link:../next outdent: specifier: 0.8.0 diff --git a/scripts/sync-react.js b/scripts/sync-react.js index 1201d90cbac4..21396355f4dd 100644 --- a/scripts/sync-react.js +++ b/scripts/sync-react.js @@ -301,6 +301,43 @@ async function findHighestNPMReactVersion(versionLike) { })[0] } +/** + * Assigns `actor` to the given Pull Request if they can be assigned. + * On scheduled runs `github.actor` often resolves to a bot like + * `github-actions[bot]`, which cannot be assigned. User tokens silently + * ignore non-assignable assignees but GitHub App tokens fail the whole + * request with 403, so check assignability first and skip instead. + * @param {InstanceType} octokit + * @param {string | undefined} actor + * @param {number} pullRequestNumber + */ +async function assignActorIfAssignable(octokit, actor, pullRequestNumber) { + if (actor === undefined) { + return null + } + try { + await octokit.rest.issues.checkUserCanBeAssigned({ + owner: repoOwner, + repo: repoName, + assignee: actor, + }) + } catch (error) { + if (error instanceof Error && 'status' in error && error.status === 404) { + console.warn( + `'${actor}' cannot be assigned in ${repoOwner}/${repoName}. Skipping assignment.` + ) + return null + } + throw error + } + return octokit.rest.issues.addAssignees({ + owner: repoOwner, + repo: repoName, + issue_number: pullRequestNumber, + assignees: [actor], + }) +} + async function main() { const cwd = process.cwd() const errors = [] @@ -733,15 +770,8 @@ Or run this command again without the --no-install flag to do both automatically { pullRequestId: pullRequest.data.node_id } ) - await Promise.all([ - actor - ? octokit.rest.issues.addAssignees({ - owner: repoOwner, - repo: repoName, - issue_number: pullRequest.data.number, - assignees: [actor], - }) - : Promise.resolve(), + const finalizeResults = await Promise.allSettled([ + assignActorIfAssignable(octokit, actor, pullRequest.data.number), octokit.rest.pulls.requestReviewers({ owner: repoOwner, repo: repoName, @@ -755,6 +785,16 @@ Or run this command again without the --no-install flag to do both automatically labels: pullRequestLabels, }), ]) + const failures = finalizeResults.filter( + (result) => result.status === 'rejected' + ) + if (failures.length > 0) { + // eslint-disable-next-line no-undef -- Defined in Node.js + throw new AggregateError( + failures.map((failure) => failure.reason), + `${failures.length} of ${finalizeResults.length} requests to finalize the Pull Request failed.` + ) + } } console.log(prDescription) diff --git a/test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts b/test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts index d1c1265edaa6..f9cbe381682a 100644 --- a/test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts +++ b/test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts @@ -94,6 +94,12 @@ runRscBuildErrorsTests(({ next, isTurbopack }) => { ) }) + // In Webpack, proxy runs in the Node.js server compiler, which is also + // invalidated when the app route is added on demand. If the initial proxy + // error reaches the browser before that follow-up build, the HMR client sees + // an update after a runtime error and reloads, clearing the overlay. Defer + // that case until after hydration. Turbopack does not have this compiler + // invalidation race, so its proxy case covers both initial and HMR errors. test.each([ ['middleware.js', 'export function middleware() {}'], ['proxy.js', 'export function proxy() {}'], @@ -101,6 +107,12 @@ runRscBuildErrorsTests(({ next, isTurbopack }) => { ])( 'should error when catchError from next/error is imported in %s', async (entryFile, exportCode) => { + const isProxy = entryFile === 'proxy.js' + const deferPoisonedImport = !isTurbopack && isProxy + const entryContent = outdent` + import { catchError } from 'next/error' + ${exportCode} + ` await using sandbox = await createSandbox( next, new Map([ @@ -112,21 +124,29 @@ runRscBuildErrorsTests(({ next, isTurbopack }) => { } `, ], - [ - entryFile, - outdent` - import { catchError } from 'next/error' - ${exportCode} - `, - ], + [entryFile, deferPoisonedImport ? exportCode : entryContent], ]) ) const { session } = sandbox - await session.waitForRedbox() - expect(await session.getRedboxSource()).toInclude( - 'You\'re importing a module that depends on `catchError` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.' - ) + if (deferPoisonedImport) { + await session.write(entryFile, entryContent) + } + + const expectPoisonedImportRedbox = async () => { + await session.waitForRedbox() + expect(await session.getRedboxSource()).toInclude( + 'You\'re importing a module that depends on `catchError` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.' + ) + } + await expectPoisonedImportRedbox() + + if (isTurbopack && isProxy) { + await session.patch(entryFile, exportCode) + await session.waitForNoRedbox() + await session.write(entryFile, entryContent) + await expectPoisonedImportRedbox() + } } ) }) diff --git a/test/development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts b/test/development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts index 9b2ca16200f8..8c2d0fa50854 100644 --- a/test/development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts +++ b/test/development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts @@ -341,6 +341,19 @@ export function runDevWarmupTests({ assertLog(logs, `after params`, 'Prefetch') assertLog(logs, `after searchParams`, 'Prefetch') + assertLog( + logs, + `after prefetch`, + // Same as navigation() below: static prerender timing on initial + // load, app-shell timing for a client nav when there's a runtime + // prefetch. + isInitialLoad + ? 'Prerender' + : partialPrefetching || hasRuntimePrefetch + ? 'Prefetch' + : 'Prerender' + ) + assertLog( logs, `after navigation`, diff --git a/test/development/app-dir/cache-components-dev-warmup/fixtures/with-prefetch-config/app/apis/[param]/page.tsx b/test/development/app-dir/cache-components-dev-warmup/fixtures/with-prefetch-config/app/apis/[param]/page.tsx index c5ae2e7de207..0302739ca439 100644 --- a/test/development/app-dir/cache-components-dev-warmup/fixtures/with-prefetch-config/app/apis/[param]/page.tsx +++ b/test/development/app-dir/cache-components-dev-warmup/fixtures/with-prefetch-config/app/apis/[param]/page.tsx @@ -1,7 +1,10 @@ import { cookies, headers } from 'next/headers' import { CachedData } from '../../data-fetching' import { connection } from 'next/server' -import { unstable_navigation as navigation } from 'next/cache' +import { + unstable_navigation as navigation, + unstable_prefetch, +} from 'next/cache' import { Suspense } from 'react' export const instant = true @@ -23,6 +26,7 @@ export default function Page({ params, searchParams }) { headers()} /> params} /> searchParams} /> + unstable_prefetch()} /> navigation()} /> {/* Dynamic */} diff --git a/test/development/app-dir/cache-components-dev-warmup/fixtures/without-prefetch-config/app/apis/[param]/page.tsx b/test/development/app-dir/cache-components-dev-warmup/fixtures/without-prefetch-config/app/apis/[param]/page.tsx index 151b6680bd96..737488ed9d64 100644 --- a/test/development/app-dir/cache-components-dev-warmup/fixtures/without-prefetch-config/app/apis/[param]/page.tsx +++ b/test/development/app-dir/cache-components-dev-warmup/fixtures/without-prefetch-config/app/apis/[param]/page.tsx @@ -1,7 +1,10 @@ import { cookies, headers } from 'next/headers' import { CachedData } from '../../data-fetching' import { connection } from 'next/server' -import { unstable_navigation as navigation } from 'next/cache' +import { + unstable_navigation as navigation, + unstable_prefetch, +} from 'next/cache' import { Suspense } from 'react' const CACHE_KEY = __dirname + '/__PAGE__' @@ -20,6 +23,7 @@ export default function Page({ params, searchParams }) { headers()} /> params} /> searchParams} /> + unstable_prefetch()} /> navigation()} /> {/* Dynamic */} diff --git a/test/development/app-hmr/fixtures/default-template/next.config.js b/test/development/app-hmr/fixtures/default-template/next.config.js index eba9d47557b5..c90c5aa5ab9f 100644 --- a/test/development/app-hmr/fixtures/default-template/next.config.js +++ b/test/development/app-hmr/fixtures/default-template/next.config.js @@ -1,4 +1,8 @@ /** * @type {import('next').NextConfig} */ -module.exports = {} +module.exports = { + turbopack: { + chunkLoadingGlobal: 'hmrApp', + }, +} diff --git a/test/development/basic/hmr/hot-module-reload-no-base-path-no-asset-prefix.test.ts b/test/development/basic/hmr/hot-module-reload-no-base-path-no-asset-prefix.test.ts index c95910722ada..3e4f3d376d33 100644 --- a/test/development/basic/hmr/hot-module-reload-no-base-path-no-asset-prefix.test.ts +++ b/test/development/basic/hmr/hot-module-reload-no-base-path-no-asset-prefix.test.ts @@ -1,6 +1,12 @@ import { runHotModuleReloadHmrTest } from './run-hot-module-reload-hmr-test.util' -const nextConfig = { basePath: '', assetPrefix: '' } +const nextConfig = { + basePath: '', + assetPrefix: '', + turbopack: { + chunkLoadingGlobal: 'hmrPages', + }, +} describe(`HMR - Hot Module Reload, nextConfig: ${JSON.stringify(nextConfig)}`, () => { runHotModuleReloadHmrTest(nextConfig) diff --git a/test/development/basic/hmr/run-hot-module-reload-hmr-test.util.ts b/test/development/basic/hmr/run-hot-module-reload-hmr-test.util.ts index 7191dc0f3829..dfff102668bc 100644 --- a/test/development/basic/hmr/run-hot-module-reload-hmr-test.util.ts +++ b/test/development/basic/hmr/run-hot-module-reload-hmr-test.util.ts @@ -5,6 +5,9 @@ import { nextTestSetup } from 'e2e-utils' export function runHotModuleReloadHmrTest(nextConfig: { basePath: string assetPrefix: string + turbopack?: { + chunkLoadingGlobal: string + } }) { const { next } = nextTestSetup({ files: __dirname, diff --git a/test/e2e/app-dir/instant-validation/app/shells/(default)/invalid-prefetch-without-suspense/page.tsx b/test/e2e/app-dir/instant-validation/app/shells/(default)/invalid-prefetch-without-suspense/page.tsx new file mode 100644 index 000000000000..47b21b5f7a24 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/shells/(default)/invalid-prefetch-without-suspense/page.tsx @@ -0,0 +1,25 @@ +import { Instant } from 'next' +import { unstable_prefetch } from 'next/cache' + +export const instant: Instant = { + level: 'experimental-error', +} + +export const prefetch = 'partial' + +export default async function Page() { + return ( +
+

+ This page is missing a suspense around prefetch(), so we can't render a + shell. +

+ +
+ ) +} + +async function PrefetchContent() { + await unstable_prefetch() + return
{`Prefetch content`}
+} diff --git a/test/e2e/app-dir/instant-validation/app/shells/(default)/page.tsx b/test/e2e/app-dir/instant-validation/app/shells/(default)/page.tsx index 3f5117dbfd1a..6d160dfb8748 100644 --- a/test/e2e/app-dir/instant-validation/app/shells/(default)/page.tsx +++ b/test/e2e/app-dir/instant-validation/app/shells/(default)/page.tsx @@ -44,6 +44,12 @@ export default async function Page() {
  • +
  • + +
  • +
  • + +
  • ) diff --git a/test/e2e/app-dir/instant-validation/app/shells/(default)/valid-prefetch-with-suspense/page.tsx b/test/e2e/app-dir/instant-validation/app/shells/(default)/valid-prefetch-with-suspense/page.tsx new file mode 100644 index 000000000000..e1fc0b4050f7 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/shells/(default)/valid-prefetch-with-suspense/page.tsx @@ -0,0 +1,28 @@ +import { Instant } from 'next' +import { unstable_prefetch } from 'next/cache' +import { Suspense } from 'react' + +export const instant: Instant = { + level: 'experimental-error', +} + +export const prefetch = 'partial' + +export default async function Page() { + return ( +
    +

    + This page has a suspense around prefetch(), so we can render a shell and + should have no validation errors. +

    + + + +
    + ) +} + +async function PrefetchContent() { + await unstable_prefetch() + return
    {`Prefetch content`}
    +} diff --git a/test/e2e/app-dir/instant-validation/app/suspense-in-root/non-app-shell/valid-unguarded-prefetch/page.tsx b/test/e2e/app-dir/instant-validation/app/suspense-in-root/non-app-shell/valid-unguarded-prefetch/page.tsx new file mode 100644 index 000000000000..28034e2d190d --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/suspense-in-root/non-app-shell/valid-unguarded-prefetch/page.tsx @@ -0,0 +1,23 @@ +import { Instant } from 'next' +import { unstable_prefetch } from 'next/cache' + +export const instant: Instant = { + level: 'experimental-error', +} + +export default async function Page() { + return ( +
    +

    + This page is missing a suspense around prefetch(), but that's allowed if + we're not in partialPrefetching and aren't rendering App Shells. +

    + +
    + ) +} + +async function PrefetchContent() { + await unstable_prefetch() + return
    {`Prefetch content`}
    +} diff --git a/test/e2e/app-dir/instant-validation/app/suspense-in-root/page.tsx b/test/e2e/app-dir/instant-validation/app/suspense-in-root/page.tsx index 92d1154ff029..a2debeac06f3 100644 --- a/test/e2e/app-dir/instant-validation/app/suspense-in-root/page.tsx +++ b/test/e2e/app-dir/instant-validation/app/suspense-in-root/page.tsx @@ -54,6 +54,9 @@ export default async function Page() {
  • +
  • + +
  • @@ -305,6 +308,9 @@ export default async function Page() {
  • +
  • + +
  • Blocking await attribution

    diff --git a/test/e2e/app-dir/instant-validation/app/suspense-in-root/sync-io/sync-io-after-prefetch/page.tsx b/test/e2e/app-dir/instant-validation/app/suspense-in-root/sync-io/sync-io-after-prefetch/page.tsx new file mode 100644 index 000000000000..f52200f3e3e2 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/suspense-in-root/sync-io/sync-io-after-prefetch/page.tsx @@ -0,0 +1,28 @@ +import { unstable_prefetch } from 'next/cache' +import { Suspense } from 'react' + +export const instant = { level: 'experimental-error' } + +export default async function Page() { + return ( +
    +

    + This page uses sync IO after awaiting prefetch(): + {/* + In partialPrefetching, prefetch() is not allowed in shells, + so we need a Suspense. + Before partialPrefetching everything is static, so we could skip it, + but that's not relevant to this test. + */} + + + +

    +
    + ) +} + +async function SyncIOAfterPrefetch() { + await unstable_prefetch() + return Date.now() +} diff --git a/test/e2e/app-dir/instant-validation/head-and-reporting.util.ts b/test/e2e/app-dir/instant-validation/head-and-reporting.util.ts index 43680a51f962..c6efa5031743 100644 --- a/test/e2e/app-dir/instant-validation/head-and-reporting.util.ts +++ b/test/e2e/app-dir/instant-validation/head-and-reporting.util.ts @@ -970,6 +970,80 @@ export function registerHeadAndReportingTests( } }) + it('invalid - unguarded prefetch() in a shell', async () => { + if (isNextDev) { + const browser = await navigateTo( + '/shells/invalid-prefetch-without-suspense' + ) + await expect(browser).toDisplayCollapsedRedbox(` + { + "cause": [ + { + "label": "Caused by: Instant Validation", + "source": "app/shells/(default)/invalid-prefetch-without-suspense/page.tsx (4:33) @ instant + > 4 | export const instant: Instant = { + | ^", + "stack": [ + "instant app/shells/(default)/invalid-prefetch-without-suspense/page.tsx (4:33)", + "Set.forEach ", + ], + }, + ], + "code": "E1439", + "description": "Next.js encountered URL data outside of Suspense.", + "environmentLabel": "Server", + "label": "Instant", + "source": "app/shells/(default)/invalid-prefetch-without-suspense/page.tsx (23:26) @ PrefetchContent + > 23 | await unstable_prefetch() + | ^", + "stack": [ + "PrefetchContent app/shells/(default)/invalid-prefetch-without-suspense/page.tsx (23:26)", + "Page app/shells/(default)/invalid-prefetch-without-suspense/page.tsx (17:7)", + ], + } + `) + } else { + const result = await prerender( + '/shells/(default)/invalid-prefetch-without-suspense' + ) + expect(extractBuildValidationError(result.cliOutput)) + .toMatchInlineSnapshot(` + "Error: Route "/shells/invalid-prefetch-without-suspense": Next.js encountered URL data during prerendering or a navigation. + + \`params\` or \`searchParams\` accessed outside of \`\` may prevent the navigation from being instant, leading to a slower user experience. + + Ways to fix this: + - [stream] Provide a placeholder with \`\` around the data access + - [block] Set \`export const instant = false\` to allow a blocking route + + Learn more: https://nextjs.org/docs/messages/instant-shell-url-data + at main () + at body () + at html () + Build-time instant validation failed for route "/shells/invalid-prefetch-without-suspense". + To get a more detailed stack trace and pinpoint the issue, try one of the following: + - Start the app in development mode by running \`next dev\`, then open "/shells/invalid-prefetch-without-suspense" in your browser to investigate the error. + - Rerun the production build with \`next build --debug-prerender\` to generate better stack traces. + Stopping prerender due to instant validation errors." + `) + expect(result.exitCode).toBe(1) + } + }) + + it('valid - prefetch() with suspense in a shell', async () => { + if (isNextDev) { + const browser = await navigateTo( + '/shells/valid-prefetch-with-suspense' + ) + await expectNoDevValidationErrors(browser, await browser.url()) + } else { + const result = await prerender( + '/shells/(default)/valid-prefetch-with-suspense' + ) + expectNoBuildValidationErrors(result) + } + }) + it('valid - unguarded root param', async () => { if (isNextDev) { const browser = await navigateTo( @@ -1026,6 +1100,19 @@ export function registerHeadAndReportingTests( expectNoBuildValidationErrors(result) } }) + it('valid - unguarded prefetch', async () => { + if (isNextDev) { + const browser = await navigateTo( + '/suspense-in-root/non-app-shell/valid-unguarded-prefetch' + ) + await expectNoDevValidationErrors(browser, await browser.url()) + } else { + const result = await prerender( + '/suspense-in-root/non-app-shell/valid-unguarded-prefetch' + ) + expectNoBuildValidationErrors(result) + } + }) }) } diff --git a/test/e2e/app-dir/instant-validation/sync-io-and-blocking.util.ts b/test/e2e/app-dir/instant-validation/sync-io-and-blocking.util.ts index bf14af5a0514..e592ee9140c7 100644 --- a/test/e2e/app-dir/instant-validation/sync-io-and-blocking.util.ts +++ b/test/e2e/app-dir/instant-validation/sync-io-and-blocking.util.ts @@ -134,6 +134,65 @@ export function registerSyncIoAndBlockingTests( } }) + it('sync IO after prefetch()', async () => { + if (isNextDev) { + const browser = await navigateTo( + '/suspense-in-root/sync-io/sync-io-after-prefetch' + ) + await expect(browser).toDisplayCollapsedRedbox(` + { + "code": "E1432", + "description": "Next.js encountered the unstable value Date.now() while prerendering.", + "environmentLabel": "Server", + "label": "Blocking Route", + "source": "app/suspense-in-root/sync-io/sync-io-after-prefetch/page.tsx (27:15) @ SyncIOAfterPrefetch + > 27 | return Date.now() + | ^", + "stack": [ + "SyncIOAfterPrefetch app/suspense-in-root/sync-io/sync-io-after-prefetch/page.tsx (27:15)", + "Page app/suspense-in-root/sync-io/sync-io-after-prefetch/page.tsx (18:11)", + ], + } + `) + } else { + const result = await prerender( + '/suspense-in-root/sync-io/sync-io-after-prefetch' + ) + // `await prefetch()` resolves during a static prerender, so + // we hit the sync IO there and error before reaching instant validation. + expect( + getPrerenderOutput(result.cliOutput, { + isMinified: true, + }) + ).toMatchInlineSnapshot(` + "Error: Route "/suspense-in-root/sync-io/sync-io-after-prefetch": Next.js encountered the unstable value \`Date.now()\` while prerendering. + + This value can change between renders, so it must be either prerendered or computed later. + + Ways to fix this: + - [dynamic] Render at request time by adding a dynamic data access (e.g. \`await connection()\`) before this call + - [cache] Prerender and cache the value with \`"use cache"\` + - [client] Render the value on the client with \`"use client"\` + - [measure] If the value is for telemetry, use a timing API such as \`performance.now()\` + + Learn more: https://nextjs.org/docs/messages/blocking-prerender-current-time + at a (app/suspense-in-root/sync-io/sync-io-after-prefetch/page.tsx:27:15) + 25 | async function SyncIOAfterPrefetch() { + 26 | await unstable_prefetch() + > 27 | return Date.now() + | ^ + 28 | } + 29 | + To get a more detailed stack trace and pinpoint the issue, try one of the following: + - Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/sync-io/sync-io-after-prefetch" in your browser to investigate the error. + - Rerun the production build with \`next build --debug-prerender\` to generate better stack traces. + Error occurred prerendering page "/suspense-in-root/sync-io/sync-io-after-prefetch". Read more: https://nextjs.org/docs/messages/prerender-error + Export encountered an error on /suspense-in-root/sync-io/sync-io-after-prefetch/page: /suspense-in-root/sync-io/sync-io-after-prefetch, exiting the build." + `) + expect(result.exitCode).toBe(1) + } + }) + it('sync IO after cache with session data input', async () => { if (isNextDev) { const browser = await navigateTo( diff --git a/test/e2e/app-dir/segment-cache/cached-navigations/cached-navigations-partial-prefetching.test.ts b/test/e2e/app-dir/segment-cache/cached-navigations/cached-navigations-partial-prefetching.test.ts index 72df150846c3..90cbb92ef937 100644 --- a/test/e2e/app-dir/segment-cache/cached-navigations/cached-navigations-partial-prefetching.test.ts +++ b/test/e2e/app-dir/segment-cache/cached-navigations/cached-navigations-partial-prefetching.test.ts @@ -75,6 +75,9 @@ describe('cached navigations - global partialPrefetching', () => { expect(await browser.elementById('navigation-boundary').text()).toContain( 'Navigation content' ) + expect(await browser.elementById('prefetch-boundary').text()).toContain( + 'Prefetch content' + ) // Only connection() shows a Suspense fallback — it's truly dynamic. expect(await browser.elementById('connection-boundary').text()).toBe( diff --git a/test/e2e/app-dir/segment-cache/cached-navigations/partial-prefetching/app/runtime-prefetchable/page.tsx b/test/e2e/app-dir/segment-cache/cached-navigations/partial-prefetching/app/runtime-prefetchable/page.tsx index c855984a78dc..683c7eae60d0 100644 --- a/test/e2e/app-dir/segment-cache/cached-navigations/partial-prefetching/app/runtime-prefetchable/page.tsx +++ b/test/e2e/app-dir/segment-cache/cached-navigations/partial-prefetching/app/runtime-prefetchable/page.tsx @@ -1,6 +1,9 @@ import { cookies, headers } from 'next/headers' import { connection } from 'next/server' -import { unstable_navigation as navigation } from 'next/cache' +import { + unstable_navigation as navigation, + unstable_prefetch, +} from 'next/cache' import { Suspense } from 'react' // Note: intentionally no `export const prefetch` and no `instant` config. This @@ -40,6 +43,11 @@ export default async function Page({ +
    + Loading prefetch...

    }> + +
    +
    ) } @@ -79,3 +87,8 @@ async function NavigationContent() { await navigation() return

    Navigation content

    } + +async function PrefetchContent() { + await unstable_prefetch() + return

    Prefetch content

    +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/page.tsx index bc74650e418e..d6da197397d2 100644 --- a/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/page.tsx +++ b/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/page.tsx @@ -148,6 +148,35 @@ export default function Page() { + +

    Prefetch posts

    +
      +
    • + + Static prefetch post 1 + +
    • +
    • + + Static prefetch post 2 (unprefetched) + +
    • +
    • + + Runtime prefetch post 1 + +
    • +
    • + + Runtime prefetch post 2 (unprefetched) + +
    • +
    • + + Runtime prefetch post "speculative-1" (prefetch=true) + +
    • +
    ) } diff --git a/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/runtime-prefetch/[id]/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/runtime-prefetch/[id]/page.tsx new file mode 100644 index 000000000000..048228c27999 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/runtime-prefetch/[id]/page.tsx @@ -0,0 +1,48 @@ +import { Suspense } from 'react' +import { unstable_prefetch } from 'next/cache' +import { cookies } from 'next/headers' +import { connection } from 'next/server' + +type Params = { id: string } + +export const prefetch = 'partial' + +export default async function Page({ params }: { params: Promise }) { + return ( +
    + Loading prefetch...

    }> + +
    + {/* The fallback is the App Shell — the part of the page that + doesn't depend on params. */} + App shell for prefetch

    }> + +
    +
    + ) +} + +async function PrefetchData() { + await cookies() // Makes sure this page uses a runtime prefetch + await unstable_prefetch() // Exclude the contents below from runtime app shells + return
    Prefetch content
    +} + +async function ParamsDependent({ params }: { params: Promise }) { + const { id } = await params + return ( + <> +

    {`Post ${id}`}

    + Loading dynamic content...

    } + > + +
    + + ) +} + +async function Dynamic({ id }: { id: string }) { + await connection() + return

    {`Post body for ${id}`}

    +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/static-prefetch/[id]/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/static-prefetch/[id]/page.tsx new file mode 100644 index 000000000000..9656d4677b6b --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-app-shell/app/(default)/static-prefetch/[id]/page.tsx @@ -0,0 +1,52 @@ +import { Suspense } from 'react' +import { unstable_prefetch } from 'next/cache' +import { connection } from 'next/server' + +export async function generateStaticParams() { + return [{ id: '1' }, { id: '2' }] +} + +type Params = { id: string } + +export const prefetch = 'partial' + +export default async function Page({ params }: { params: Promise }) { + return ( +
    + App shell for prefetch

    }> + +
    + Loading prefetch...

    }> + +
    +
    + ) +} + +async function PrefetchData() { + await unstable_prefetch() // Exclude the contents below from the shell + return
    Prefetch content
    +} + +async function ParamsDependent({ params }: { params: Promise }) { + // Make sure the static prefetch varies on params so that the client router + // has to extract a static shell from it when navigating to another param value. + // If the static prefetch doesn't vary on params, it'll be used instead of + // the app shell + const { id } = await params + return ( + <> +

    {`Post ${id}`}

    + Loading dynamic content...

    } + > + +
    + + ) +} + +async function Dynamic({ id }: { id: string }) { + await connection() + return

    {`Post body for ${id}`}

    +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-app-shell/prefetch-app-shell.test.ts b/test/e2e/app-dir/segment-cache/prefetch-app-shell/prefetch-app-shell.test.ts index ac032c51d3ca..ace61697277e 100644 --- a/test/e2e/app-dir/segment-cache/prefetch-app-shell/prefetch-app-shell.test.ts +++ b/test/e2e/app-dir/segment-cache/prefetch-app-shell/prefetch-app-shell.test.ts @@ -591,6 +591,180 @@ describe('App Shell prefetching', () => { }) }) + describe('prefetch()', () => { + it('excludes prefetch() from a static App Shell', async () => { + let page: Playwright.Page + const browser = await next.browser('/', { + beforePageLoad(p: Playwright.Page) { + page = p + }, + }) + const act = createRouterAct(page, { includeAppShellRequests: true }) + + // Caches the App Shell for /static-prefetch/[id]. + await act(async () => { + await browser + .elementByCss('input[data-link-accordion="/static-prefetch/1"]') + .click() + }, [{ includes: 'App shell for prefetch', kind: 'static' }]) + + await act(async () => { + // /static-prefetch/2 was never prefetched, so the router falls back to + // the cached App Shell. + await browser.elementByCss('a[href="/static-prefetch/2"]').click() + + expect(await browser.elementById('shell').text()).toEqual( + 'App shell for prefetch' + ) + // prefetch()-gated content is not part of the App Shell. + expect(await browser.locator('#prefetch-content').count()).toBe(0) + expect(await browser.elementByCss('#prefetch-loading').text()).toBe( + 'Loading prefetch...' + ) + }) + + expect(await browser.elementById('prefetch-content').text()).toEqual( + 'Prefetch content' + ) + expect(await browser.elementById('dynamic-content').text()).toEqual( + 'Post body for 2' + ) + }) + + it('excludes prefetch() from a runtime App Shell', async () => { + let page: Playwright.Page + const browser = await next.browser('/', { + beforePageLoad(p: Playwright.Page) { + page = p + }, + }) + const act = createRouterAct(page, { includeAppShellRequests: true }) + + // Caches the App Shell for /runtime-prefetch/[id]. The route reads + // request data, so its static-attempt hint is unset and the prefetch + // deopts to a runtime shell. + await act(async () => { + await browser + .elementByCss('input[data-link-accordion="/runtime-prefetch/1"]') + .click() + }, [ + { includes: 'App shell for prefetch', kind: 'runtime' }, + { includes: 'param-value', block: 'reject' }, // Only a shell, no URL data. + ]) + + await act(async () => { + // /runtime-prefetch/2 was never prefetched, so the router falls back to + // the cached App Shell. + await browser.elementByCss('a[href="/runtime-prefetch/2"]').click() + + expect(await browser.elementById('shell').text()).toEqual( + 'App shell for prefetch' + ) + // prefetch()-gated content is not part of the App Shell. + expect(await browser.locator('#prefetch-content').count()).toBe(0) + expect(await browser.elementByCss('#prefetch-loading').text()).toBe( + 'Loading prefetch...' + ) + }) + + expect(await browser.elementById('prefetch-content').text()).toEqual( + 'Prefetch content' + ) + expect(await browser.elementById('dynamic-content').text()).toEqual( + 'Post body for 2' + ) + }) + + it('includes prefetch() in a static prefetch', async () => { + let page: Playwright.Page + const browser = await next.browser('/', { + beforePageLoad(p: Playwright.Page) { + page = p + }, + }) + const act = createRouterAct(page, { includeAppShellRequests: true }) + + await act(async () => { + await browser + .elementByCss('input[data-link-accordion="/static-prefetch/1"]') + .click() + }, [{ includes: 'App shell for prefetch', kind: 'static' }]) + + await act(async () => { + // Navigate to the prefetched route. + await browser.elementByCss('a[href="/static-prefetch/1"]').click() + + expect(await browser.elementById('param-value').text()).toEqual( + 'Post 1' + ) + // prefetch()-gated content resolves in a static prefetch. + expect(await browser.locator('#prefetch-loading').count()).toBe(0) + expect(await browser.elementByCss('#prefetch-content').text()).toBe( + 'Prefetch content' + ) + // Dynamic content is not included. + expect(await browser.locator('#dynamic-content').count()).toBe(0) + expect(await browser.elementByCss('#dynamic-loading').text()).toBe( + 'Loading dynamic content...' + ) + }) + + expect(await browser.elementById('dynamic-content').text()).toEqual( + 'Post body for 1' + ) + }) + + it('includes prefetch() in a speculative runtime prefetch', async () => { + let page: Playwright.Page + const browser = await next.browser('/', { + beforePageLoad(p: Playwright.Page) { + page = p + }, + }) + const act = createRouterAct(page, { includeAppShellRequests: true }) + + // The Shell phase's runtime App Shell request, then the Speculative + // phase's per-link runtime prefetch (the link opts in with + // `prefetch={true}`). + await act(async () => { + await browser + .elementByCss( + 'input[data-link-accordion="/runtime-prefetch/speculative-1"]' + ) + .click() + }, [ + { includes: 'App shell for prefetch', kind: 'runtime' }, // Shell + { includes: 'Post speculative-1', kind: 'runtime' }, // Speculative + ]) + + await act(async () => { + // Navigate to the prefetched route. + await browser + .elementByCss('a[href="/runtime-prefetch/speculative-1"]') + .click() + + expect(await browser.elementById('param-value').text()).toEqual( + 'Post speculative-1' + ) + // Unlike navigation(), prefetch() resolves in a speculative runtime + // prefetch. + expect(await browser.locator('#prefetch-loading').count()).toBe(0) + expect(await browser.elementByCss('#prefetch-content').text()).toBe( + 'Prefetch content' + ) + // Dynamic content is not included. + expect(await browser.locator('#dynamic-content').count()).toBe(0) + expect(await browser.elementByCss('#dynamic-loading').text()).toBe( + 'Loading dynamic content...' + ) + }) + + expect(await browser.elementById('dynamic-content').text()).toEqual( + 'Post body for speculative-1' + ) + }) + }) + describe('root params', () => { it('includes root params in a runtime App Shell', async () => { let page: Playwright.Page diff --git a/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/dynamic-param/[slug]/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/dynamic-param/[slug]/page.tsx index 23a79cd1de6c..750abf98faaa 100644 --- a/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/dynamic-param/[slug]/page.tsx +++ b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/dynamic-param/[slug]/page.tsx @@ -1,5 +1,8 @@ import { Suspense } from 'react' -import { unstable_navigation as navigation } from 'next/cache' +import { + unstable_navigation as navigation, + unstable_prefetch, +} from 'next/cache' type Params = { slug: string } @@ -30,6 +33,11 @@ export default function Page({ params }: { params: Promise }) { >
    + Loading prefetch content...

    } + > + +
    ) } @@ -43,3 +51,8 @@ async function NavigationContent() { await navigation() return } + +async function PrefetchContent() { + await unstable_prefetch() + return

    Prefetch content

    +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/page.tsx index a2c4899c824d..d44acbfd8dac 100644 --- a/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/page.tsx +++ b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/page.tsx @@ -35,6 +35,16 @@ export default function Page() { Uses navigation() on a static page +
  • + + Uses runtime APIs after prefetch() + +
  • +
  • + + Uses prefetch() on a static page + +
  • Dynamic param one diff --git a/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/uses-prefetch-static/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/uses-prefetch-static/page.tsx new file mode 100644 index 000000000000..7f513d06a56f --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/uses-prefetch-static/page.tsx @@ -0,0 +1,17 @@ +import { Suspense } from 'react' +import { unstable_prefetch } from 'next/cache' + +export default function Page() { + return ( +
    + Loading prefetch...

    }> + +
    +
    + ) +} + +async function PrefetchContent() { + await unstable_prefetch() + return

    Fully static page content (with prefetch())

    +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/uses-runtime-after-prefetch/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/uses-runtime-after-prefetch/page.tsx new file mode 100644 index 000000000000..a1934c3feb66 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-static-shell/app/uses-runtime-after-prefetch/page.tsx @@ -0,0 +1,41 @@ +// A page that calls cookies and headers after awaiting `unstable_prefetch()`. +// Unlike `navigation()`, `prefetch()` doesn't stop runtime-data tracking — the +// reads below it still count — so the tree hint stays unset and this route +// can't be prefetched statically. + +import { cookies, headers } from 'next/headers' +import { unstable_prefetch } from 'next/cache' +import { Suspense } from 'react' + +export default async function Page() { + return ( +
    +

    Runtime APIs called after prefetch()

    + Loading prefetch content...

    } + > + +
    +
    + ) +} + +async function Prefetch() { + await unstable_prefetch() + + return ( + <> +
    Prefetch content
    + Loading runtime content...

    } + > + +
    + + ) +} + +async function RuntimeContent() { + await Promise.all([cookies(), headers()]) + return

    Runtime content

    +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-static-shell/prefetch-static-shell.test.ts b/test/e2e/app-dir/segment-cache/prefetch-static-shell/prefetch-static-shell.test.ts index df2c46b1f998..b95df1163cf6 100644 --- a/test/e2e/app-dir/segment-cache/prefetch-static-shell/prefetch-static-shell.test.ts +++ b/test/e2e/app-dir/segment-cache/prefetch-static-shell/prefetch-static-shell.test.ts @@ -144,6 +144,41 @@ describe('static App Shell prefetch attempt', () => { }, 'no-requests') }) + it('prefetches a fully static route that uses prefetch() with static requests only, then navigates instantly from cache', async () => { + let page: Playwright.Page + const browser = await next.browser('/', { + beforePageLoad(p: Playwright.Page) { + page = p + }, + }) + const act = createRouterAct(page, { includeAppShellRequests: true }) + + // prefetch() resolves during a static prerender, and the route accesses no + // runtime data, so the static attempt is sufficient. + await act(async () => { + await browser + .elementByCss('input[data-link-accordion="/uses-prefetch-static"]') + .click() + }, [ + { + includes: 'Fully static page content (with prefetch())', + kind: 'static', + }, + { + includes: 'Fully static page content (with prefetch())', + kind: 'runtime', + block: 'reject', + }, + ]) + + await act(async () => { + await browser.elementByCss('a[href="/uses-prefetch-static"]').click() + expect(await browser.elementById('page-content').text()).toBe( + 'Fully static page content (with prefetch())' + ) + }, 'no-requests') + }) + it('goes straight to a runtime shell prefetch when the shell reads cookies (hint unset)', async () => { let page: Playwright.Page const browser = await next.browser('/', { @@ -335,6 +370,35 @@ describe('static App Shell prefetch attempt', () => { ) }) + it('goes straight to a runtime shell prefetch for a partial segment that calls runtime APIs after prefetch() (hint unset)', async () => { + let page: Playwright.Page + const browser = await next.browser('/', { + beforePageLoad(p: Playwright.Page) { + page = p + }, + }) + const act = createRouterAct(page, { includeAppShellRequests: true }) + + await act(async () => { + await browser + .elementByCss( + 'input[data-link-accordion="/uses-runtime-after-prefetch"]' + ) + .click() + }, [ + // Unlike navigation(), prefetch() doesn't stop runtime-data tracking, so + // the cookies()/headers() reads below it leave the tree hint unset and + // the shell arrives in a runtime response. + { includes: 'Runtime APIs called after prefetch()', kind: 'runtime' }, + // No static attempt precedes it. + { + includes: 'Runtime APIs called after prefetch()', + kind: 'static', + block: 'reject', + }, + ]) + }) + it('reuses the static App Shell across different param values of a dynamic route', async () => { let page: Playwright.Page const browser = await next.browser('/', { @@ -405,6 +469,9 @@ describe('static App Shell prefetch attempt', () => { expect(await browser.elementById('navigation-loading').text()).toBe( 'Loading navigation content...' ) + expect(await browser.elementById('prefetch-loading').text()).toBe( + 'Loading prefetch content...' + ) }, // The param content arrives with the navigation response. { includes: 'Dynamic param content: two' } diff --git a/test/e2e/app-document-import-order/app-document-import-order.test.ts b/test/e2e/app-document-import-order/app-document-import-order.test.ts index e911fc32a74d..b248c886a9a1 100644 --- a/test/e2e/app-document-import-order/app-document-import-order.test.ts +++ b/test/e2e/app-document-import-order/app-document-import-order.test.ts @@ -2,7 +2,7 @@ import { nextTestSetup } from 'e2e-utils' describe('Root components import order', () => { - const { next, isTurbopack } = nextTestSetup({ + const { next, isTurbopack, isNextDev } = nextTestSetup({ files: __dirname, }) @@ -16,6 +16,34 @@ describe('Root components import order', () => { expect($(sideEffectCall).text()).toEqual(expectSideEffectsOrder[index]) }) }) + // Only asserted for production builds: in development each entry is chunked from its own + // per-page module graph, which can still merge a shared module into per-entry units. + ;(isNextDev ? it.skip : it)( + 'loads modules shared by _app and the page only once', + async () => { + const browser = await next.browser('/', { waitHydration: false }) + const markerCount = await browser.eval(async () => { + const chunkUrls = [ + ...new Set( + performance + .getEntriesByType('resource') + .map((entry) => entry.name) + .filter( + (url) => url.includes('/_next/static/') && url.endsWith('.js') + ) + ), + ] + const chunks = await Promise.all( + chunkUrls.map((url) => fetch(url).then((response) => response.text())) + ) + return chunks.filter((chunk) => + chunk.includes('APP_PAGE_SHARED_MODULE_MARKER') + ).length + }) + + expect(markerCount).toBe(1) + } + ) // Test relies on webpack splitChunks overrides. ;(isTurbopack ? it.skip : it)( diff --git a/test/e2e/app-document-import-order/sideEffectModule.js b/test/e2e/app-document-import-order/sideEffectModule.js index 378e2b39095d..d701d9538f90 100644 --- a/test/e2e/app-document-import-order/sideEffectModule.js +++ b/test/e2e/app-document-import-order/sideEffectModule.js @@ -7,4 +7,6 @@ const sideEffect = (arg) => { return sideEffect.callArguments } +globalThis.__appPageSharedModuleMarker = 'APP_PAGE_SHARED_MODULE_MARKER' + export default sideEffect diff --git a/turbopack/crates/turbopack-cli/js/src/entry/client.ts b/turbopack/crates/turbopack-cli/js/src/entry/client.ts index 6870201f6f2c..74f65b8f59cc 100644 --- a/turbopack/crates/turbopack-cli/js/src/entry/client.ts +++ b/turbopack/crates/turbopack-cli/js/src/entry/client.ts @@ -1,4 +1,7 @@ -import { connect } from '@vercel/turbopack-ecmascript-runtime/browser/dev/hmr-client/hmr-client' +import { + connect, + TURBOPACK_CHUNK_UPDATE_LISTENERS_GLOBAL, +} from '@vercel/turbopack-ecmascript-runtime/browser/dev/hmr-client/hmr-client' import { connectHMR, addMessageListener, sendMessage } from './websocket' export function initializeHMR(options: { assetPrefix: string }) { @@ -6,6 +9,7 @@ export function initializeHMR(options: { assetPrefix: string }) { addMessageListener, sendMessage, onUpdateError: console.error, + chunkUpdateListenersGlobal: TURBOPACK_CHUNK_UPDATE_LISTENERS_GLOBAL, }) connectHMR({ assetPrefix: options.assetPrefix, diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts index fabe2cfc0557..699af948c792 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts @@ -17,12 +17,17 @@ export type ClientOptions = { addMessageListener: (cb: (msg: WebSocketMessage) => void) => void sendMessage: SendMessage onUpdateError: (err: unknown) => void + chunkUpdateListenersGlobal: string } +export const TURBOPACK_CHUNK_UPDATE_LISTENERS_GLOBAL = + 'TURBOPACK_CHUNK_UPDATE_LISTENERS' + export function connect({ addMessageListener, sendMessage, onUpdateError = console.error, + chunkUpdateListenersGlobal, }: ClientOptions) { addMessageListener((msg) => { switch (msg.type) { @@ -55,11 +60,15 @@ export function connect({ } }) - const queued = globalThis.TURBOPACK_CHUNK_UPDATE_LISTENERS + const global = globalThis as unknown as Record< + string, + ChunkUpdateProvider | [ChunkListPath, UpdateCallback][] | undefined + > + const queued = global[chunkUpdateListenersGlobal] if (queued != null && !Array.isArray(queued)) { throw new Error('A separate HMR handler was already registered') } - globalThis.TURBOPACK_CHUNK_UPDATE_LISTENERS = { + global[chunkUpdateListenersGlobal] = { push: ([chunkPath, callback]: [ChunkListPath, UpdateCallback]) => { subscribeToChunkUpdate(chunkPath, sendMessage, callback) }, diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts index 68701619faf2..bcd2600d3138 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts @@ -579,7 +579,7 @@ function registerChunkList(chunkList: ChunkList) { const chunkListPath = getPathFromScript(chunkListScript) // The "chunk" is also registered to finish the loading in the backend BACKEND.registerChunk(chunkListPath as string as ChunkPath) - globalThis.TURBOPACK_CHUNK_UPDATE_LISTENERS!.push([ + CHUNK_UPDATE_LISTENERS.push([ chunkListPath, handleApply.bind(null, chunkListPath), ]) @@ -601,5 +601,3 @@ function registerChunkList(chunkList: ChunkList) { markChunkListAsRuntime(chunkListPath) } } - -globalThis.TURBOPACK_CHUNK_UPDATE_LISTENERS ??= [] diff --git a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime/dev-globals.d.ts b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime/dev-globals.d.ts index 26eab9347386..4b4ebd1f26f0 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime/dev-globals.d.ts +++ b/turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/runtime/dev-globals.d.ts @@ -11,10 +11,7 @@ type ChunkUpdateProvider = { push: (registration: [ChunkListPath, UpdateCallback]) => void } -declare var TURBOPACK_CHUNK_UPDATE_LISTENERS: - | ChunkUpdateProvider - | [ChunkListPath, UpdateCallback][] - | undefined +declare var CHUNK_UPDATE_LISTENERS: ChunkUpdateProvider // This is used by the Next.js integration test suite to notify it when HMR // updates have been completed. declare var __NEXT_HMR_CB: undefined | null | (() => void) diff --git a/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs b/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs index 31622c784933..a1abe0a302f3 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs +++ b/turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs @@ -14,6 +14,23 @@ use turbopack_ecmascript::utils::StringifyJs; use crate::{RuntimeType, embed_js::embed_static_code}; +pub fn chunk_update_listeners_global_name(chunk_loading_global: &str) -> String { + format!("{chunk_loading_global}_CHUNK_UPDATE_LISTENERS") +} + +#[cfg(test)] +mod tests { + use super::chunk_update_listeners_global_name; + + #[test] + fn scopes_chunk_update_listeners_to_chunk_loading_global() { + assert_eq!( + chunk_update_listeners_global_name("TURBOPACK_APP"), + "TURBOPACK_APP_CHUNK_UPDATE_LISTENERS" + ); + } +} + /// Returns the code for the ECMAScript runtime. #[turbo_tasks::function] pub async fn get_browser_runtime_code( @@ -94,6 +111,8 @@ pub async fn get_browser_runtime_code( let chunk_loading_global = chunk_loading_global.await?; let cross_origin = *cross_origin.await?; let chunk_lists_global = format!("{}_CHUNK_LISTS", chunk_loading_global); + let chunk_update_listeners_global = + chunk_update_listeners_global_name(chunk_loading_global.as_str()); if *environment .runtime_versions() @@ -130,6 +149,19 @@ pub async fn get_browser_runtime_code( support_component_chunks, )?; + if matches!(runtime_type, RuntimeType::Development) { + writedoc!( + code, + r#" + globalThis[{chunk_update_listeners_global}] ||= []; + var CHUNK_UPDATE_LISTENERS = {{ + push: (registration) => globalThis[{chunk_update_listeners_global}].push(registration), + }}; + "#, + chunk_update_listeners_global = StringifyJs(&chunk_update_listeners_global), + )?; + } + match &*asset_suffix { AssetSuffix::None => { writedoc!( diff --git a/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs b/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs index 13c5b544a6fa..02d6cbee972a 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs +++ b/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs @@ -8,7 +8,9 @@ pub(crate) mod embed_js; pub(crate) mod nodejs_runtime; pub(crate) mod runtime_type; -pub use browser_runtime::{get_browser_runtime_code, get_worker_runtime_code}; +pub use browser_runtime::{ + chunk_update_listeners_global_name, get_browser_runtime_code, get_worker_runtime_code, +}; #[cfg(feature = "test")] pub use dummy_runtime::get_dummy_runtime_code; pub use embed_js::{embed_file, embed_file_path, embed_fs, turbopack_runtime_import_map}; diff --git a/turbopack/crates/turbopack-ecmascript/src/references/import_meta_glob.rs b/turbopack/crates/turbopack-ecmascript/src/references/import_meta_glob.rs index 574afdaba4f2..6e88c23849c7 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/import_meta_glob.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/import_meta_glob.rs @@ -846,8 +846,9 @@ impl EcmascriptChunkPlaceable for ImportMetaGlobAsset { // Generate the value expression based on eager/lazy and import options let value_expr = if this.eager { - // Eager: direct synchronous require - let module_expr = pm.create_require(Cow::Borrowed(&key_expr)); + // Eager: synchronously evaluate the module and use its ESM namespace, + // matching what a static `import * as ns from "..."` would produce. + let module_expr = pm.create_esm_require(Cow::Borrowed(&key_expr)); // If `import` option is set, access the named export if let Some(named) = &this.import { quote!( diff --git a/turbopack/crates/turbopack-ecmascript/src/references/pattern_mapping.rs b/turbopack/crates/turbopack-ecmascript/src/references/pattern_mapping.rs index 7f2a11b948e1..ff88a59f408e 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/pattern_mapping.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/pattern_mapping.rs @@ -141,6 +141,34 @@ impl SinglePatternMapping { } } + /// Like [`Self::create_require`], but evaluates to the ESM *namespace* of the + /// module instead of its CommonJS `exports` object, so that the CommonJS + /// interop applies. This is what a static `import * as ns from "..."` + /// produces, and it is the difference between a JSON (or CommonJS) module + /// having a `default` export and not having one. + pub fn create_esm_require(&self, key_expr: Cow<'_, Expr>) -> Expr { + match self { + Self::Invalid => self.create_id(key_expr), + Self::Unresolvable(request) => throw_module_not_found_expr(request), + Self::Ignored => quote!("{}" as Expr), + Self::Dropped => quote!("0" as Expr), + Self::Module(_) | Self::ModuleLoader(_) => quote!( + "$turbopack_import($arg)" as Expr, + turbopack_import: Expr = TURBOPACK_IMPORT.into(), + arg: Expr = self.create_id(key_expr) + ), + Self::External(request, ExternalType::CommonJs) => quote!( + "$turbopack_external_require($arg, () => require($arg), true)" as Expr, + turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(), + arg: Expr = request.as_str().into() + ), + Self::External(request, ty) => throw_module_not_found_error_expr( + request, + &format!("Unsupported external type {ty:?} for esm reference"), + ), + } + } + pub fn create_import(&self, key_expr: Cow<'_, Expr>, import_externals: bool) -> Expr { match self { Self::Invalid => { diff --git a/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/cjs.js b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/cjs.js new file mode 100644 index 000000000000..26d1bed3d52c --- /dev/null +++ b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/cjs.js @@ -0,0 +1 @@ +module.exports = { hello: 'cjs' } diff --git a/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/data.json b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/data.json new file mode 100644 index 000000000000..de0657ddeaa9 --- /dev/null +++ b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/data.json @@ -0,0 +1 @@ +{ "hello": "world" } diff --git a/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/esm.mjs b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/esm.mjs new file mode 100644 index 000000000000..7b1a863b6c9a --- /dev/null +++ b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/data/esm.mjs @@ -0,0 +1,2 @@ +export default 'esm' +export const value = 7 diff --git a/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/index.js b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/index.js new file mode 100644 index 000000000000..5e828493f7dc --- /dev/null +++ b/turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-json/input/index.js @@ -0,0 +1,57 @@ +// An eager `import.meta.glob` must produce the same module namespace a +// hand-written `import * as ns from '...'` produces, including the `default` +// export that the CommonJS interop adds for JSON and CommonJS modules. + +import * as jsonNamespace from './data/data.json' +import * as cjsNamespace from './data/cjs.js' + +const eager = import.meta.glob('./data/*', { eager: true }) + +it('should expose the default export of a JSON module', () => { + expect(Object.keys(eager)).toEqual([ + './data/cjs.js', + './data/data.json', + './data/esm.mjs', + ]) + expect(eager['./data/data.json'].default).toEqual({ hello: 'world' }) +}) + +it('should match a hand-written namespace import for JSON', () => { + expect(jsonNamespace.default).toEqual({ hello: 'world' }) + expect({ ...eager['./data/data.json'] }).toEqual({ ...jsonNamespace }) +}) + +it('should match a hand-written namespace import for CommonJS', () => { + expect(eager['./data/cjs.js'].default).toEqual({ hello: 'cjs' }) + expect({ ...eager['./data/cjs.js'] }).toEqual({ ...cjsNamespace }) +}) + +it('should keep working for ES modules', () => { + expect(eager['./data/esm.mjs'].default).toBe('esm') + expect(eager['./data/esm.mjs'].value).toBe(7) +}) + +const eagerDefault = import.meta.glob('./data/*', { + eager: true, + import: 'default', +}) + +it('should support import: "default" eagerly', () => { + expect(eagerDefault['./data/data.json']).toEqual({ hello: 'world' }) + expect(eagerDefault['./data/cjs.js']).toEqual({ hello: 'cjs' }) + expect(eagerDefault['./data/esm.mjs']).toBe('esm') +}) + +const lazy = import.meta.glob('./data/*') + +it('should expose the default export of a JSON module lazily', async () => { + const mod = await lazy['./data/data.json']() + expect(mod.default).toEqual({ hello: 'world' }) +}) + +const lazyDefault = import.meta.glob('./data/*', { import: 'default' }) + +it('should support import: "default" lazily', async () => { + expect(await lazyDefault['./data/data.json']()).toEqual({ hello: 'world' }) + expect(await lazyDefault['./data/esm.mjs']()).toBe('esm') +}) diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/debug-ids/browser/output/1do3_crates_turbopack-tests_tests_snapshot_debug-ids_browser_input_index_19boa0e.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/debug-ids/browser/output/1do3_crates_turbopack-tests_tests_snapshot_debug-ids_browser_input_index_19boa0e.js.map index b89d466a304c..18a20f4787b5 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/debug-ids/browser/output/1do3_crates_turbopack-tests_tests_snapshot_debug-ids_browser_input_index_19boa0e.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/debug-ids/browser/output/1do3_crates_turbopack-tests_tests_snapshot_debug-ids_browser_input_index_19boa0e.js.map @@ -1,13 +1,13 @@ { "version": 3, "sources": [], - "debugId": "64c9678c-f7a9-2121-4902-35dfa89f5d34", + "debugId": "ee80707f-230b-76ad-c42e-fca99f56d165", "sections": [ - {"offset": {"line": 22, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/shared/runtime/runtime-utils.ts"],"sourcesContent":["/**\n * This file contains runtime types and functions that are shared between all\n * TurboPack ECMAScript runtimes.\n *\n * It will be prepended to the runtime code of each runtime.\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n/// \n/// \n\ntype EsmNamespaceObject = Record\n\n/**\n * Describes why a module was instantiated.\n * Shared between browser and Node.js runtimes.\n */\nenum SourceType {\n /**\n * The module was instantiated because it was included in an evaluated chunk's\n * runtime.\n * SourceData is a ChunkPath.\n */\n Runtime = 0,\n /**\n * The module was instantiated because a parent module imported it.\n * SourceData is a ModuleId.\n */\n Parent = 1,\n /**\n * The module was instantiated because it was included in a chunk's hot module\n * update.\n * SourceData is an array of ModuleIds or undefined.\n */\n Update = 2,\n}\n\ntype SourceData = ChunkPath | ModuleId | ModuleId[] | undefined\n\n// @ts-ignore Defined in `dev-base.ts`\ndeclare function getOrInstantiateModuleFromParent(\n id: ModuleId,\n sourceModule: M\n): M\n\n// @ts-ignore Defined in `hmr-runtime.ts` (dev mode only)\ndeclare let devModuleCache: Record | undefined\n\n/**\n * Flag indicating which module object type to create when a module is merged. Set to `true`\n * by each runtime that uses ModuleWithDirection (browser dev-base.ts, nodejs dev-base.ts,\n * nodejs build-base.ts). Browser production (build-base.ts) leaves it as `false` since it\n * uses plain Module objects.\n */\nlet createModuleWithDirectionFlag = false\n\nconst REEXPORTED_OBJECTS = new WeakMap()\n\n/**\n * Constructs the `__turbopack_context__` object for a module.\n */\nfunction Context(\n this: TurbopackBaseContext,\n module: Module,\n exports: Exports\n) {\n this.m = module\n // We need to store this here instead of accessing it from the module object to:\n // 1. Make it available to factories directly, since we rewrite `this` to\n // `__turbopack_context__.e` in CJS modules.\n // 2. Support async modules which rewrite `module.exports` to a promise, so we\n // can still access the original exports object from functions like\n // `esmExport`\n // Ideally we could find a new approach for async modules and drop this property altogether.\n this.e = exports\n}\nconst contextPrototype = Context.prototype as TurbopackBaseContext\n\ntype ModuleContextMap = Record\n\ninterface ModuleContextEntry {\n id: () => ModuleId\n module: () => any\n}\n\ninterface ModuleContext {\n // require call\n (moduleId: string): Exports | EsmNamespaceObject\n\n // async import call\n import(moduleId: string): Promise\n\n keys(): ModuleId[]\n\n resolve(moduleId: string): ModuleId\n}\n\ntype GetOrInstantiateModuleFromParent = (\n moduleId: M['id'],\n parentModule: M\n) => M\n\ndeclare function getOrInstantiateRuntimeModule(\n chunkPath: ChunkPath,\n moduleId: ModuleId\n): Module\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nconst toStringTag = typeof Symbol !== 'undefined' && Symbol.toStringTag\n\nfunction defineProp(\n obj: any,\n name: PropertyKey,\n options: PropertyDescriptor & ThisType\n) {\n if (!hasOwnProperty.call(obj, name)) Object.defineProperty(obj, name, options)\n}\n\nfunction getOverwrittenModule(\n moduleCache: ModuleCache,\n id: ModuleId\n): Module {\n let module = moduleCache[id]\n if (!module) {\n if (createModuleWithDirectionFlag) {\n // set in development modes for hmr support\n module = createModuleWithDirection(id)\n } else {\n module = createModuleObject(id)\n }\n moduleCache[id] = module\n }\n return module\n}\n\n/**\n * Creates the module object. Only done here to ensure all module objects have the same shape.\n */\nfunction createModuleObject(id: ModuleId): Module {\n return {\n exports: {},\n error: undefined,\n id,\n namespaceObject: undefined,\n }\n}\n\nfunction createModuleWithDirection(id: ModuleId): ModuleWithDirection {\n return {\n exports: {},\n error: undefined,\n id,\n namespaceObject: undefined,\n parents: [],\n children: [],\n }\n}\n\ntype BindingTag = 0\nconst BindingTag_Value = 0 as BindingTag\n\n// an arbitrary sequence of bindings as\n// - a prop name\n// - BindingTag_Value, a value to be bound directly, or\n// - 1 or 2 functions to bind as getters and sdetters\ntype EsmBindings = Array<\n string | BindingTag | (() => unknown) | ((v: unknown) => void) | unknown\n>\n\n/**\n * Adds the getters to the exports object.\n */\nfunction esm(exports: Exports, bindings: EsmBindings, dynamic?: boolean) {\n defineProp(exports, '__esModule', { value: true })\n if (toStringTag) defineProp(exports, toStringTag, { value: 'Module' })\n let i = 0\n while (i < bindings.length) {\n const propName = bindings[i++] as string\n const tagOrFunction = bindings[i++]\n if (typeof tagOrFunction === 'number') {\n if (tagOrFunction === BindingTag_Value) {\n defineProp(exports, propName, {\n value: bindings[i++],\n enumerable: true,\n writable: false,\n })\n } else {\n throw new Error(`unexpected tag: ${tagOrFunction}`)\n }\n } else {\n const getterFn = tagOrFunction as () => unknown\n if (typeof bindings[i] === 'function') {\n const setterFn = bindings[i++] as (v: unknown) => void\n defineProp(exports, propName, {\n get: getterFn,\n set: setterFn,\n enumerable: true,\n })\n } else {\n defineProp(exports, propName, {\n get: getterFn,\n enumerable: true,\n })\n }\n }\n }\n // The properties defined above are already non-configurable and\n // non-writable, so the namespace's existing exports are effectively\n // immutable. Sealing additionally makes the object non-extensible, matching\n // real ESM-namespace semantics. Modules with dynamic re-exports\n // (`export *` from a CommonJS module) must stay extensible so the dynamic\n // export proxy can surface keys discovered at runtime, so skip the seal for\n // them.\n if (!dynamic) Object.seal(exports)\n}\n\n/**\n * Makes the module an ESM with exports\n */\nfunction esmExport(\n this: TurbopackBaseContext,\n bindings: EsmBindings,\n id: ModuleId | undefined,\n dynamic?: boolean\n) {\n let module: Module\n let exports: Module['exports']\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n exports = module.exports\n } else {\n module = this.m\n exports = this.e\n }\n module.namespaceObject = exports\n esm(exports, bindings, dynamic)\n}\ncontextPrototype.s = esmExport\n\ntype ReexportedObjects = Record[]\nfunction ensureDynamicExports(\n module: Module,\n exports: Exports\n): ReexportedObjects {\n let reexportedObjects: ReexportedObjects | undefined =\n REEXPORTED_OBJECTS.get(module)\n\n if (!reexportedObjects) {\n REEXPORTED_OBJECTS.set(module, (reexportedObjects = []))\n // Returns the re-exported object that provides `prop` as an own property,\n // or `undefined` if none does. The traps share this logic so they always\n // agree on which keys are synthesized from `reexportedObjects`. `default`\n // is never re-exported by `export *`, so it is never synthesized.\n const reexportOwning = (prop: PropertyKey) => {\n if (prop !== 'default') {\n for (const obj of reexportedObjects!) {\n if (hasOwnProperty.call(obj, prop)) return obj\n }\n }\n return undefined\n }\n // Modules with dynamic re-exports are not sealed by `esm()`, so the\n // target beneath the namespace stays extensible. That is what lets the\n // `ownKeys` and `getOwnPropertyDescriptor` traps legally report keys that\n // exist on `reexportedObjects` but not on the target itself.\n module.exports = module.namespaceObject = new Proxy(exports, {\n get(target, prop) {\n if (\n hasOwnProperty.call(target, prop) ||\n prop === 'default' ||\n prop === '__esModule'\n ) {\n return Reflect.get(target, prop)\n }\n const obj = reexportOwning(prop)\n return obj && Reflect.get(obj, prop)\n },\n // The namespace is read-only, like a real esm namespace object. The\n // re-exported modules can still mutate their own exports (exposed live\n // via `get`), but mutating the namespace itself is rejected. Refusing\n // here, rather than forwarding to the extensible target, also prevents an\n // assignment/definition from shadowing a dynamic re-export. It also\n // prevents delete from removing a static export.\n set() {\n return false\n },\n defineProperty() {\n return false\n },\n deleteProperty() {\n return false\n },\n // The `has` trap ensures that `'exportName' in starImports` will reflect\n // the truth of whether a key is exported.\n has(target, prop) {\n if (Reflect.has(target, prop)) return true\n if (prop === 'default' || prop === '__esModule') return false\n return reexportOwning(prop) !== undefined\n },\n // ownKeys and getOwnPropertyDescriptor together make the keys enumerable.\n // If a value is returned from `ownKeys` but its property descriptor is\n // not enumerable, it will not be visible to iterator methods.\n // Collectively, they allow code like the following:\n //\n // ```\n // // module.js re-exports dynamic CJS exports\n // export * from './legacyModule.cjs'\n //\n // // from another JS file, reference the re-exported dynamic values\n // import * as Namespace from './module.js'\n // Object.keys(Namespace)\n // ```\n ownKeys(target) {\n const keys = Reflect.ownKeys(target)\n for (const obj of reexportedObjects!) {\n for (const key of Reflect.ownKeys(obj)) {\n if (key !== 'default' && !keys.includes(key)) keys.push(key)\n }\n }\n return keys\n },\n getOwnPropertyDescriptor(target, prop) {\n const own = Reflect.getOwnPropertyDescriptor(target, prop)\n if (own || prop === 'default' || prop === '__esModule') return own\n const obj = reexportOwning(prop)\n if (obj) {\n // Synthetic keys don't exist on the target, so they MUST be\n // reported as configurable. However the set/delete traps above will\n // prevent them from actually being changed\n return {\n enumerable: true,\n configurable: true,\n get: () => Reflect.get(obj, prop),\n }\n }\n return undefined\n },\n })\n }\n return reexportedObjects\n}\n\n/**\n * Dynamically exports properties from an object\n */\nfunction dynamicExport(\n this: TurbopackBaseContext,\n object: Record,\n id: ModuleId | undefined\n) {\n let module: Module\n let exports: Exports\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n exports = module.exports\n } else {\n module = this.m\n exports = this.e\n }\n const reexportedObjects = ensureDynamicExports(module, exports)\n\n if (typeof object === 'object' && object !== null) {\n reexportedObjects.push(object)\n }\n}\ncontextPrototype.j = dynamicExport\n\nfunction exportValue(\n this: TurbopackBaseContext,\n value: any,\n id: ModuleId | undefined\n) {\n let module: Module\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n } else {\n module = this.m\n }\n module.exports = value\n}\ncontextPrototype.v = exportValue\n\nfunction exportNamespace(\n this: TurbopackBaseContext,\n namespace: any,\n id: ModuleId | undefined\n) {\n let module: Module\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n } else {\n module = this.m\n }\n module.exports = module.namespaceObject = namespace\n}\ncontextPrototype.n = exportNamespace\n\nfunction createGetter(obj: Record, key: string | symbol) {\n return () => obj[key]\n}\n\n/**\n * @returns prototype of the object\n */\nconst getProto: (obj: any) => any = Object.getPrototypeOf\n ? (obj) => Object.getPrototypeOf(obj)\n : (obj) => obj.__proto__\n\n/** Prototypes that are not expanded for exports */\nconst LEAF_PROTOTYPES = [null, getProto({}), getProto([]), getProto(getProto)]\n\n/**\n * @param raw\n * @param ns\n * @param allowExportDefault\n * * `false`: will have the raw module as default export\n * * `true`: will have the default property as default export\n */\nfunction interopEsm(\n raw: Exports,\n ns: EsmNamespaceObject,\n allowExportDefault?: boolean\n) {\n const bindings: EsmBindings = []\n let defaultLocation = -1\n for (\n let current = raw;\n (typeof current === 'object' || typeof current === 'function') &&\n !LEAF_PROTOTYPES.includes(current);\n current = getProto(current)\n ) {\n for (const key of Object.getOwnPropertyNames(current)) {\n bindings.push(key, createGetter(raw, key))\n if (defaultLocation === -1 && key === 'default') {\n defaultLocation = bindings.length - 1\n }\n }\n }\n\n // this is not really correct\n // we should set the `default` getter if the imported module is a `.cjs file`\n if (!(allowExportDefault && defaultLocation >= 0)) {\n // Replace the binding with one for the namespace itself in order to preserve iteration order.\n if (defaultLocation >= 0) {\n // Replace the getter with the value\n bindings.splice(defaultLocation, 1, BindingTag_Value, raw)\n } else {\n bindings.push('default', BindingTag_Value, raw)\n }\n }\n\n esm(ns, bindings)\n return ns\n}\n\nfunction createNS(raw: Module['exports']): EsmNamespaceObject {\n if (typeof raw === 'function') {\n return function (this: any, ...args: any[]) {\n return raw.apply(this, args)\n }\n } else {\n return Object.create(null)\n }\n}\n\nfunction esmImport(\n this: TurbopackBaseContext,\n id: ModuleId\n): Exclude {\n const module = getOrInstantiateModuleFromParent(id, this.m)\n\n // any ES module has to have `module.namespaceObject` defined.\n if (module.namespaceObject) return module.namespaceObject\n\n // only ESM can be an async module, so we don't need to worry about exports being a promise here.\n const raw = module.exports\n return (module.namespaceObject = interopEsm(\n raw,\n createNS(raw),\n raw && (raw as any).__esModule\n ))\n}\ncontextPrototype.i = esmImport\n\nfunction asyncLoader(\n this: TurbopackBaseContext,\n moduleId: ModuleId\n): Promise {\n const loader = this.r(moduleId) as (\n importFunction: EsmImport\n ) => Promise\n return loader(esmImport.bind(this))\n}\ncontextPrototype.A = asyncLoader\n\n// Add a simple runtime require so that environments without one can still pass\n// `typeof require` CommonJS checks so that exports are correctly registered.\nconst runtimeRequire =\n // @ts-ignore\n typeof require === 'function'\n ? // @ts-ignore\n require\n : function require() {\n throw new Error('Unexpected use of runtime require')\n }\ncontextPrototype.t = runtimeRequire\n\nfunction commonJsRequire(\n this: TurbopackBaseContext,\n id: ModuleId\n): Exports {\n return getOrInstantiateModuleFromParent(id, this.m).exports\n}\ncontextPrototype.r = commonJsRequire\n\n/**\n * Remove fragments and query parameters since they are never part of the context map keys\n *\n * This matches how we parse patterns at resolving time. Arguably we should only do this for\n * strings passed to `import` but the resolve does it for `import` and `require` and so we do\n * here as well.\n */\nfunction parseRequest(request: string): string {\n // Per the URI spec fragments can contain `?` characters, so we should trim it off first\n // https://datatracker.ietf.org/doc/html/rfc3986#section-3.5\n const hashIndex = request.indexOf('#')\n if (hashIndex !== -1) {\n request = request.substring(0, hashIndex)\n }\n\n const queryIndex = request.indexOf('?')\n if (queryIndex !== -1) {\n request = request.substring(0, queryIndex)\n }\n\n return request\n}\n/**\n * `require.context` and require/import expression runtime.\n */\nfunction moduleContext(map: ModuleContextMap): ModuleContext {\n function moduleContext(id: string): Exports {\n id = parseRequest(id)\n if (hasOwnProperty.call(map, id)) {\n return map[id].module()\n }\n\n const e = new Error(`Cannot find module '${id}'`)\n ;(e as any).code = 'MODULE_NOT_FOUND'\n throw e\n }\n\n moduleContext.keys = (): string[] => {\n return Object.keys(map)\n }\n\n moduleContext.resolve = (id: string): ModuleId => {\n id = parseRequest(id)\n if (hasOwnProperty.call(map, id)) {\n return map[id].id()\n }\n\n const e = new Error(`Cannot find module '${id}'`)\n ;(e as any).code = 'MODULE_NOT_FOUND'\n throw e\n }\n\n moduleContext.import = async (id: string) => {\n return await (moduleContext(id) as Promise)\n }\n\n return moduleContext\n}\ncontextPrototype.f = moduleContext\n\n/**\n * Returns the path of a chunk defined by its data.\n */\nfunction getChunkPath(chunkData: ChunkData): ChunkPath {\n return typeof chunkData === 'string' ? chunkData : chunkData.path\n}\n\n// Load the CompressedmoduleFactories of a chunk into the `moduleFactories` Map.\n// The CompressedModuleFactories format is\n// - 1 or more module ids\n// - a module factory function\n// So walking this is a little complex but the flat structure is also fast to\n// traverse, we can use `typeof` operators to distinguish the two cases.\nfunction installCompressedModuleFactories(\n chunkModules: CompressedModuleFactories,\n offset: number,\n moduleFactories: ModuleFactories,\n newModuleId?: (id: ModuleId) => void\n) {\n let i = offset\n while (i < chunkModules.length) {\n let end = i + 1\n // Find our factory function\n while (\n end < chunkModules.length &&\n typeof chunkModules[end] !== 'function'\n ) {\n end++\n }\n if (end === chunkModules.length) {\n throw new Error('malformed chunk format, expected a factory function')\n }\n\n // Install the factory for each module ID that doesn't already have one.\n // When some IDs in this group already have a factory, reuse that existing\n // group factory for the missing IDs to keep all IDs in the group consistent.\n // Otherwise, install the factory from this chunk.\n const moduleFactoryFn = chunkModules[end] as Function\n let existingGroupFactory: Function | undefined = undefined\n for (let j = i; j < end; j++) {\n const id = chunkModules[j] as ModuleId\n const existingFactory = moduleFactories.get(id)\n if (existingFactory) {\n existingGroupFactory = existingFactory\n break\n }\n }\n const factoryToInstall = existingGroupFactory ?? moduleFactoryFn\n\n let didInstallFactory = false\n for (let j = i; j < end; j++) {\n const id = chunkModules[j] as ModuleId\n if (!moduleFactories.has(id)) {\n if (!didInstallFactory) {\n if (factoryToInstall === moduleFactoryFn) {\n applyModuleFactoryName(moduleFactoryFn)\n }\n didInstallFactory = true\n }\n moduleFactories.set(id, factoryToInstall)\n newModuleId?.(id)\n }\n }\n i = end + 1 // end is pointing at the last factory advance to the next id or the end of the array.\n }\n}\n\n/**\n * A pseudo \"fake\" URL object to resolve to its relative path.\n *\n * When UrlRewriteBehavior is set to relative, calls to the `new URL()` will construct url without base using this\n * runtime function to generate context-agnostic urls between different rendering context, i.e ssr / client to avoid\n * hydration mismatch.\n *\n * This is based on webpack's existing implementation:\n * https://github.com/webpack/webpack/blob/87660921808566ef3b8796f8df61bd79fc026108/lib/runtime/RelativeUrlRuntimeModule.js\n */\nconst relativeURL = function relativeURL(this: any, inputUrl: string) {\n const realUrl = new URL(inputUrl, 'x:/')\n const values: Record = {}\n for (const key in realUrl) values[key] = (realUrl as any)[key]\n values.href = inputUrl\n values.pathname = inputUrl.replace(/[?#].*/, '')\n values.origin = values.protocol = ''\n values.toString = values.toJSON = (..._args: Array) => inputUrl\n for (const key in values)\n Object.defineProperty(this, key, {\n enumerable: true,\n configurable: true,\n value: values[key],\n })\n}\nrelativeURL.prototype = URL.prototype\ncontextPrototype.U = relativeURL\n\n/**\n * Utility function to ensure all variants of an enum are handled.\n */\nfunction invariant(never: never, computeMessage: (arg: any) => string): never {\n throw new Error(`Invariant: ${computeMessage(never)}`)\n}\n\n/**\n * Constructs an error message for when a module factory is not available.\n */\nfunction factoryNotAvailableMessage(\n moduleId: ModuleId,\n sourceType: SourceType,\n sourceData: SourceData\n): string {\n let instantiationReason: string\n switch (sourceType) {\n case SourceType.Runtime:\n instantiationReason = `as a runtime entry of chunk ${sourceData}`\n break\n case SourceType.Parent:\n instantiationReason = `because it was required from module ${sourceData}`\n break\n case SourceType.Update:\n instantiationReason = 'because of an HMR update'\n break\n default:\n invariant(\n sourceType,\n (sourceType) => `Unknown source type: ${sourceType}`\n )\n }\n return `Module ${moduleId} was instantiated ${instantiationReason}, but the module factory is not available.`\n}\n\n/**\n * A stub function to make `require` available but non-functional in ESM.\n */\nfunction requireStub(_moduleId: ModuleId): never {\n throw new Error('dynamic usage of require is not supported')\n}\ncontextPrototype.z = requireStub\n\n// Make `globalThis` available to the module in a way that cannot be shadowed by a local variable.\ncontextPrototype.g = globalThis\n\ntype ContextConstructor = {\n new (module: Module, exports: Exports): TurbopackBaseContext\n}\n\nfunction applyModuleFactoryName(factory: Function) {\n // Give the module factory a nice name to improve stack traces.\n Object.defineProperty(factory, 'name', {\n value: 'module evaluation',\n })\n}\n"],"names":["SourceType","createModuleWithDirectionFlag","REEXPORTED_OBJECTS","WeakMap","Context","module","exports","m","e","contextPrototype","prototype","hasOwnProperty","Object","toStringTag","Symbol","defineProp","obj","name","options","call","defineProperty","getOverwrittenModule","moduleCache","id","createModuleWithDirection","createModuleObject","error","undefined","namespaceObject","parents","children","BindingTag_Value","esm","bindings","dynamic","value","i","length","propName","tagOrFunction","enumerable","writable","Error","getterFn","setterFn","get","set","seal","esmExport","c","s","ensureDynamicExports","reexportedObjects","reexportOwning","prop","Proxy","target","Reflect","deleteProperty","has","ownKeys","keys","key","includes","push","getOwnPropertyDescriptor","own","configurable","dynamicExport","object","j","exportValue","v","exportNamespace","namespace","n","createGetter","getProto","getPrototypeOf","__proto__","LEAF_PROTOTYPES","interopEsm","raw","ns","allowExportDefault","defaultLocation","current","getOwnPropertyNames","splice","createNS","args","apply","create","esmImport","getOrInstantiateModuleFromParent","__esModule","asyncLoader","moduleId","loader","r","bind","A","runtimeRequire","require","require1","t","commonJsRequire","parseRequest","request","hashIndex","indexOf","substring","queryIndex","moduleContext","map","code","resolve","import","f","getChunkPath","chunkData","path","installCompressedModuleFactories","chunkModules","offset","moduleFactories","newModuleId","end","moduleFactoryFn","existingGroupFactory","existingFactory","factoryToInstall","didInstallFactory","applyModuleFactoryName","relativeURL","inputUrl","realUrl","URL","values","href","pathname","replace","origin","protocol","toString","toJSON","_args","U","invariant","never","computeMessage","factoryNotAvailableMessage","sourceType","sourceData","instantiationReason","requireStub","_moduleId","z","g","globalThis","factory"],"mappings":"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","ignoreList":[0]}}, - {"offset": {"line": 547, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/shared/runtime/async-module.ts"],"sourcesContent":["/// \n/// \n\n/**\n * Top-level-await / async-module machinery. This is only included in the runtime\n * when the module graph actually contains an async module (a module with\n * top-level await, or one that transitively depends on one). When no async\n * module is present, the chunk items never reference `__turbopack_context__.a`,\n * so this whole file can be omitted.\n *\n * everything below is adapted from webpack\n * https://github.com/webpack/webpack/blob/6be4065ade1e252c1d8dcba4af0f43e32af1bdc1/lib/runtime/AsyncModuleRuntimeModule.js#L13\n */\n\nconst turbopackQueues = Symbol('turbopack queues')\nconst turbopackExports = Symbol('turbopack exports')\nconst turbopackError = Symbol('turbopack error')\n\nconst enum QueueStatus {\n Unknown = -1,\n Unresolved = 0,\n Resolved = 1,\n}\n\ntype AsyncQueueFn = (() => void) & { queueCount: number }\ntype AsyncQueue = AsyncQueueFn[] & {\n status: QueueStatus\n}\n\ntype Dep = Exports | AsyncModulePromise | Promise\n\ntype AsyncModuleExt = {\n [turbopackQueues]: (fn: (queue: AsyncQueue) => void) => void\n [turbopackExports]: Exports\n [turbopackError]?: any\n}\n\ntype AsyncModulePromise = Promise & AsyncModuleExt\n\nfunction isPromise(maybePromise: any): maybePromise is Promise {\n return (\n maybePromise != null &&\n typeof maybePromise === 'object' &&\n 'then' in maybePromise &&\n typeof maybePromise.then === 'function'\n )\n}\n\nfunction isAsyncModuleExt(obj: T): obj is AsyncModuleExt & T {\n return turbopackQueues in obj\n}\n\nfunction createPromise() {\n let resolve: (value: T | PromiseLike) => void\n let reject: (reason?: any) => void\n\n const promise = new Promise((res, rej) => {\n reject = rej\n resolve = res\n })\n\n return {\n promise,\n resolve: resolve!,\n reject: reject!,\n }\n}\n\nfunction resolveQueue(queue?: AsyncQueue) {\n if (queue && queue.status !== QueueStatus.Resolved) {\n queue.status = QueueStatus.Resolved\n queue.forEach((fn) => fn.queueCount--)\n queue.forEach((fn) => (fn.queueCount-- ? fn.queueCount++ : fn()))\n }\n}\n\nfunction wrapDeps(deps: Dep[]): AsyncModuleExt[] {\n return deps.map((dep): AsyncModuleExt => {\n if (dep !== null && typeof dep === 'object') {\n if (isAsyncModuleExt(dep)) return dep\n if (isPromise(dep)) {\n const queue: AsyncQueue = Object.assign([], {\n status: QueueStatus.Unresolved,\n })\n\n const obj: AsyncModuleExt = {\n [turbopackExports]: {},\n [turbopackQueues]: (fn: (queue: AsyncQueue) => void) => fn(queue),\n }\n\n dep.then(\n (res) => {\n obj[turbopackExports] = res\n resolveQueue(queue)\n },\n (err) => {\n obj[turbopackError] = err\n resolveQueue(queue)\n }\n )\n\n return obj\n }\n }\n\n return {\n [turbopackExports]: dep,\n [turbopackQueues]: () => {},\n }\n })\n}\n\nfunction asyncModule(\n this: TurbopackBaseContext,\n body: (\n handleAsyncDependencies: (\n deps: Dep[]\n ) => Exports[] | Promise<() => Exports[]>,\n asyncResult: (err?: any) => void\n ) => void,\n hasAwait: boolean\n) {\n const module = this.m\n const queue: AsyncQueue | undefined = hasAwait\n ? Object.assign([], { status: QueueStatus.Unknown })\n : undefined\n\n const depQueues: Set = new Set()\n\n const { resolve, reject, promise: rawPromise } = createPromise()\n\n const promise: AsyncModulePromise = Object.assign(rawPromise, {\n [turbopackExports]: module.exports,\n [turbopackQueues]: (fn) => {\n queue && fn(queue)\n depQueues.forEach(fn)\n promise['catch'](() => {})\n },\n } satisfies AsyncModuleExt)\n\n const attributes: PropertyDescriptor = {\n get(): any {\n return promise\n },\n set(v: any) {\n // Calling `esmExport` leads to this.\n if (v !== promise) {\n promise[turbopackExports] = v\n }\n },\n }\n\n Object.defineProperty(module, 'exports', attributes)\n Object.defineProperty(module, 'namespaceObject', attributes)\n\n function handleAsyncDependencies(deps: Dep[]) {\n const currentDeps = wrapDeps(deps)\n\n const getResult = () =>\n currentDeps.map((d) => {\n if (d[turbopackError]) throw d[turbopackError]\n return d[turbopackExports]\n })\n\n const { promise, resolve } = createPromise<() => Exports[]>()\n\n const fn: AsyncQueueFn = Object.assign(() => resolve(getResult), {\n queueCount: 0,\n })\n\n function fnQueue(q: AsyncQueue) {\n if (q !== queue && !depQueues.has(q)) {\n depQueues.add(q)\n if (q && q.status === QueueStatus.Unresolved) {\n fn.queueCount++\n q.push(fn)\n }\n }\n }\n\n currentDeps.map((dep) => dep[turbopackQueues](fnQueue))\n\n return fn.queueCount ? promise : getResult()\n }\n\n function asyncResult(err?: any) {\n if (err) {\n reject((promise[turbopackError] = err))\n } else {\n resolve(promise[turbopackExports])\n }\n\n resolveQueue(queue)\n }\n\n body(handleAsyncDependencies, asyncResult)\n\n if (queue && queue.status === QueueStatus.Unknown) {\n queue.status = QueueStatus.Unresolved\n }\n}\ncontextPrototype.a = asyncModule\n"],"names":["turbopackQueues","Symbol","turbopackExports","turbopackError","isPromise","maybePromise","then","isAsyncModuleExt","obj","createPromise","resolve","reject","promise","Promise","res","rej","resolveQueue","queue","status","forEach","fn","queueCount","wrapDeps","deps","map","dep","Object","assign","err","asyncModule","body","hasAwait","module","m","undefined","depQueues","Set","rawPromise","exports","attributes","get","set","v","defineProperty","handleAsyncDependencies","currentDeps","getResult","d","fnQueue","q","has","add","push","asyncResult","contextPrototype","a"],"mappings":"AAAA,6CAA6C;AAC7C,2CAA2C;AAE3C;;;;;;;;;CASC,GAED,MAAMA,kBAAkBC,OAAO;AAC/B,MAAMC,mBAAmBD,OAAO;AAChC,MAAME,iBAAiBF,OAAO;AAuB9B,SAASG,UAAmBC,YAAiB;IAC3C,OACEA,gBAAgB,QAChB,OAAOA,iBAAiB,YACxB,UAAUA,gBACV,OAAOA,aAAaC,IAAI,KAAK;AAEjC;AAEA,SAASC,iBAA+BC,GAAM;IAC5C,OAAOR,mBAAmBQ;AAC5B;AAEA,SAASC;IACP,IAAIC;IACJ,IAAIC;IAEJ,MAAMC,UAAU,IAAIC,QAAW,CAACC,KAAKC;QACnCJ,SAASI;QACTL,UAAUI;IACZ;IAEA,OAAO;QACLF;QACAF,SAASA;QACTC,QAAQA;IACV;AACF;AAEA,SAASK,aAAaC,KAAkB;IACtC,IAAIA,SAASA,MAAMC,MAAM,KAhDd,GAgDyC;QAClDD,MAAMC,MAAM,GAjDH;QAkDTD,MAAME,OAAO,CAAC,CAACC,KAAOA,GAAGC,UAAU;QACnCJ,MAAME,OAAO,CAAC,CAACC,KAAQA,GAAGC,UAAU,KAAKD,GAAGC,UAAU,KAAKD;IAC7D;AACF;AAEA,SAASE,SAASC,IAAW;IAC3B,OAAOA,KAAKC,GAAG,CAAC,CAACC;QACf,IAAIA,QAAQ,QAAQ,OAAOA,QAAQ,UAAU;YAC3C,IAAIlB,iBAAiBkB,MAAM,OAAOA;YAClC,IAAIrB,UAAUqB,MAAM;gBAClB,MAAMR,QAAoBS,OAAOC,MAAM,CAAC,EAAE,EAAE;oBAC1CT,QA9DK;gBA+DP;gBAEA,MAAMV,MAAsB;oBAC1B,CAACN,iBAAiB,EAAE,CAAC;oBACrB,CAACF,gBAAgB,EAAE,CAACoB,KAAoCA,GAAGH;gBAC7D;gBAEAQ,IAAInB,IAAI,CACN,CAACQ;oBACCN,GAAG,CAACN,iBAAiB,GAAGY;oBACxBE,aAAaC;gBACf,GACA,CAACW;oBACCpB,GAAG,CAACL,eAAe,GAAGyB;oBACtBZ,aAAaC;gBACf;gBAGF,OAAOT;YACT;QACF;QAEA,OAAO;YACL,CAACN,iBAAiB,EAAEuB;YACpB,CAACzB,gBAAgB,EAAE,KAAO;QAC5B;IACF;AACF;AAEA,SAAS6B,YAEPC,IAKS,EACTC,QAAiB;IAEjB,MAAMC,SAAS,IAAI,CAACC,CAAC;IACrB,MAAMhB,QAAgCc,WAClCL,OAAOC,MAAM,CAAC,EAAE,EAAE;QAAET,MAAM;IAAsB,KAChDgB;IAEJ,MAAMC,YAA6B,IAAIC;IAEvC,MAAM,EAAE1B,OAAO,EAAEC,MAAM,EAAEC,SAASyB,UAAU,EAAE,GAAG5B;IAEjD,MAAMG,UAA8Bc,OAAOC,MAAM,CAACU,YAAY;QAC5D,CAACnC,iBAAiB,EAAE8B,OAAOM,OAAO;QAClC,CAACtC,gBAAgB,EAAE,CAACoB;YAClBH,SAASG,GAAGH;YACZkB,UAAUhB,OAAO,CAACC;YAClBR,OAAO,CAAC,QAAQ,CAAC,KAAO;QAC1B;IACF;IAEA,MAAM2B,aAAiC;QACrCC;YACE,OAAO5B;QACT;QACA6B,KAAIC,CAAM;YACR,qCAAqC;YACrC,IAAIA,MAAM9B,SAAS;gBACjBA,OAAO,CAACV,iBAAiB,GAAGwC;YAC9B;QACF;IACF;IAEAhB,OAAOiB,cAAc,CAACX,QAAQ,WAAWO;IACzCb,OAAOiB,cAAc,CAACX,QAAQ,mBAAmBO;IAEjD,SAASK,wBAAwBrB,IAAW;QAC1C,MAAMsB,cAAcvB,SAASC;QAE7B,MAAMuB,YAAY,IAChBD,YAAYrB,GAAG,CAAC,CAACuB;gBACf,IAAIA,CAAC,CAAC5C,eAAe,EAAE,MAAM4C,CAAC,CAAC5C,eAAe;gBAC9C,OAAO4C,CAAC,CAAC7C,iBAAiB;YAC5B;QAEF,MAAM,EAAEU,OAAO,EAAEF,OAAO,EAAE,GAAGD;QAE7B,MAAMW,KAAmBM,OAAOC,MAAM,CAAC,IAAMjB,QAAQoC,YAAY;YAC/DzB,YAAY;QACd;QAEA,SAAS2B,QAAQC,CAAa;YAC5B,IAAIA,MAAMhC,SAAS,CAACkB,UAAUe,GAAG,CAACD,IAAI;gBACpCd,UAAUgB,GAAG,CAACF;gBACd,IAAIA,KAAKA,EAAE/B,MAAM,KAzJV,GAyJuC;oBAC5CE,GAAGC,UAAU;oBACb4B,EAAEG,IAAI,CAAChC;gBACT;YACF;QACF;QAEAyB,YAAYrB,GAAG,CAAC,CAACC,MAAQA,GAAG,CAACzB,gBAAgB,CAACgD;QAE9C,OAAO5B,GAAGC,UAAU,GAAGT,UAAUkC;IACnC;IAEA,SAASO,YAAYzB,GAAS;QAC5B,IAAIA,KAAK;YACPjB,OAAQC,OAAO,CAACT,eAAe,GAAGyB;QACpC,OAAO;YACLlB,QAAQE,OAAO,CAACV,iBAAiB;QACnC;QAEAc,aAAaC;IACf;IAEAa,KAAKc,yBAAyBS;IAE9B,IAAIpC,SAASA,MAAMC,MAAM,SAA0B;QACjDD,MAAMC,MAAM,GAlLD;IAmLb;AACF;AACAoC,iBAAiBC,CAAC,GAAG1B","ignoreList":[0]}}, - {"offset": {"line": 679, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/browser/runtime/base/runtime-base.ts"],"sourcesContent":["/**\n * This file contains runtime types and functions that are shared between all\n * Turbopack *browser* ECMAScript runtimes.\n *\n * It will be appended to the runtime code of each runtime right after the\n * shared runtime utils.\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n/// \n/// \n\n// Used in WebWorkers to tell the runtime about the chunk suffix\ndeclare var TURBOPACK_ASSET_SUFFIX: string\n// Used in WebWorkers to tell the runtime about the current chunk url since it\n// can't be detected via `document.currentScript`. Note it's stored in reversed\n// order to use `push` and `pop`\ndeclare var TURBOPACK_NEXT_CHUNK_URLS: ChunkUrl[] | undefined\n// Used in WebWorkers to override the regular chunk base path with the base\n// used for the worker entrypoint and its initial chunks.\ndeclare var TURBOPACK_CHUNK_BASE_PATH: string | undefined\n\n// Injected by rust code\ndeclare var CHUNK_BASE_PATH: string\ndeclare var ASSET_SUFFIX: string\ndeclare var CROSS_ORIGIN: 'anonymous' | 'use-credentials' | null\ndeclare var CHUNK_LOAD_RETRY_MAX_ATTEMPTS: number\ndeclare var CHUNK_LOAD_RETRY_BASE_DELAY_MS: number\ndeclare var CHUNK_LOAD_RETRY_MAX_JITTER_MS: number\ndeclare const SUPPORT_COMPONENT_CHUNKS: boolean\n\ninterface TurbopackBrowserBaseContext extends TurbopackBaseContext {\n R: ResolvePathFromModule\n}\n\nconst browserContextPrototype =\n Context.prototype as TurbopackBrowserBaseContext\n\nconst RUNTIME_CHUNK_BASE_PATH =\n typeof TURBOPACK_CHUNK_BASE_PATH === 'string'\n ? TURBOPACK_CHUNK_BASE_PATH\n : CHUNK_BASE_PATH\n\n// Provided by build or dev base\ndeclare function instantiateModule(\n id: ModuleId,\n sourceType: SourceType,\n sourceData: SourceData\n): Module\n\ntype RuntimeParams = {\n otherChunks: ChunkData[]\n runtimeModuleIds: ModuleId[]\n}\n\ntype ChunkRegistrationChunk =\n | ChunkPath\n | { getAttribute: (name: string) => string | null }\n | undefined\n\ntype ChunkRegistration = [\n chunkPath: ChunkRegistrationChunk,\n ...([RuntimeParams] | CompressedModuleFactories),\n]\n\ntype ChunkList = {\n script: ChunkRegistrationChunk\n chunks: ChunkData[]\n source: 'entry' | 'dynamic'\n}\n\ninterface RuntimeBackend {\n /**\n * Registers a chunk. `chunk` is `undefined` for an inlined entry-only registration\n * (no source chunk): the params' other chunks are loaded and its runtime modules run\n * with no self chunk identity.\n */\n registerChunk: (\n chunk: ChunkPath | ChunkScript | undefined,\n params?: RuntimeParams\n ) => void\n /**\n * Returns the same Promise for the same chunk URL.\n */\n loadChunkCached: (sourceType: SourceType, chunkUrl: ChunkUrl) => Promise\n}\n\ninterface DevRuntimeBackend {\n reloadChunk?: (chunkUrl: ChunkUrl) => Promise\n unloadChunk?: (chunkUrl: ChunkUrl) => void\n restart: () => void\n}\n\nconst moduleFactories: ModuleFactories = new Map()\ncontextPrototype.M = moduleFactories\n\nconst availableModules: Map | true> = new Map()\n\nconst availableModuleChunks: Map | true> = new Map()\n\n// Registry mapping a merged chunk's path to its constituent component chunk paths.\nconst chunkComponents: Map = new Map()\n\n// Registry mapping a component chunk's path to its size in bytes, used by the\n// split-vs-whole cost heuristic.\nconst componentChunkSizes: Map = new Map()\n\nfunction registerComponentChunkSizes(\n componentChunks: ChunkPath[],\n sizes: number[]\n): void {\n for (let i = 0; i < componentChunks.length; i++) {\n const size = sizes[i]\n if (size !== undefined) {\n componentChunkSizes.set(componentChunks[i], size)\n }\n }\n}\n\ntype ChunkUrlOrMerged = ChunkUrl | [ChunkUrl, ChunkPath[], number[]]\n\n// Memoizes the composite promise returned for a merged chunk loaded by URL, keyed by URL.\nconst splitChunkPromises: Map> = new Map()\n\nfunction loadChunk(\n this: TurbopackBrowserBaseContext,\n chunkData: ChunkData\n): Promise {\n return loadChunkInternal(SourceType.Parent, this.m.id, chunkData)\n}\nbrowserContextPrototype.l = loadChunk\n\n// `chunkPath` is the source chunk; it is `undefined` for entry-only registrations,\n// which have no self chunk.\nfunction loadInitialChunk(\n chunkPath: ChunkPath | undefined,\n chunkData: ChunkData\n) {\n return loadChunkInternal(SourceType.Runtime, chunkPath, chunkData)\n}\n\nasync function loadChunkInternal(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkData: ChunkData\n): Promise {\n if (typeof chunkData === 'string') {\n return loadChunkPath(sourceType, sourceData, chunkData)\n }\n\n const includedList = chunkData.included || []\n const modulesPromises = includedList.map((included) => {\n if (moduleFactories.has(included)) return true\n return availableModules.get(included)\n })\n if (modulesPromises.length > 0 && modulesPromises.every((p) => p)) {\n // When all included items are already loaded or loading, we can skip loading ourselves\n await Promise.all(modulesPromises)\n return\n }\n\n let promise: Promise\n if (SUPPORT_COMPONENT_CHUNKS) {\n const componentChunks = chunkData.moduleChunks || []\n // We already have this chunk's component list inline (chunkData.moduleChunks) and split on it\n // here, so the whole-chunk fallback uses loadChunkByUrlWhole to skip loadChunkByUrlInternal's\n // chunkComponents-registry lookup, which would just repeat the same split decision.\n promise = loadComponentChunksOrWhole(\n sourceType,\n sourceData,\n componentChunks,\n getChunkRelativeUrl(chunkData.path)\n )\n } else {\n promise = loadChunkByUrlWhole(\n sourceType,\n sourceData,\n getChunkRelativeUrl(chunkData.path)\n )\n }\n\n for (const included of includedList) {\n if (!availableModules.has(included)) {\n // It might be better to race old and new promises, but it's rare that the new promise will be faster than a request started earlier.\n // In production it's even more rare, because the chunk optimization tries to deduplicate modules anyway.\n availableModules.set(included, promise)\n }\n }\n\n await promise\n}\n\n/**\n * Approximate cost of an extra HTTP request, expressed in emitted (minified, uncompressed) chunk\n * bytes, used to decide whether splitting a merged chunk into individually-cached component\n * chunks is worthwhile.\n */\nconst REQUEST_COST_BYTES = 20_000\n\n/**\n * Decides whether to load a merged chunk's component chunks individually instead of the whole\n * merged chunk, weighing the bytes saved (the available components we avoid re-downloading)\n * against the extra network requests splitting incurs.\n *\n * Splitting issues one request per unavailable component vs. a single request for the merged\n * chunk, so it adds `unavailableCount - 1` extra requests. When at most one component needs the\n * network, splitting never costs more requests than the merged load (and transfers fewer bytes),\n * so it always wins. Otherwise it's only worth it when the available bytes exceed the extra\n * request cost.\n */\nfunction shouldLoadComponentChunks(\n availableBytes: number,\n unavailableCount: number\n): boolean {\n if (unavailableCount <= 1) {\n return true\n }\n return availableBytes > REQUEST_COST_BYTES * (unavailableCount - 1)\n}\n\n/**\n * Loads a chunk's component chunks individually when enough of them are already available\n * in memory (avoiding re-downloading the ones we have, per `shouldLoadComponentChunks`),\n * otherwise loads the whole chunk from `chunkUrl` and records its component chunks as available.\n */\nfunction loadComponentChunksOrWhole(\n sourceType: SourceType,\n sourceData: SourceData,\n componentChunks: ChunkPath[],\n chunkUrl: ChunkUrl\n): Promise {\n const componentChunkPromises: Array | true> = []\n let availableBytes = 0\n let unavailableCount = 0\n for (const componentChunk of componentChunks) {\n const available = availableModuleChunks.get(componentChunk)\n if (available) {\n componentChunkPromises.push(available)\n availableBytes += componentChunkSizes.get(componentChunk) ?? 0\n } else {\n unavailableCount++\n }\n }\n\n if (\n componentChunkPromises.length > 0 &&\n shouldLoadComponentChunks(availableBytes, unavailableCount)\n ) {\n // Enough component chunks are already loaded or loading that splitting saves more\n // bytes than the extra requests cost.\n for (const componentChunk of componentChunks) {\n if (!availableModuleChunks.has(componentChunk)) {\n const promise = loadChunkPath(sourceType, sourceData, componentChunk)\n availableModuleChunks.set(componentChunk, promise)\n componentChunkPromises.push(promise)\n }\n }\n return Promise.all(componentChunkPromises)\n }\n\n // Not enough is available in memory for splitting to pay off. Load the\n // whole chunk in a single request and record its component chunks as available.\n const promise = loadChunkByUrlWhole(sourceType, sourceData, chunkUrl)\n for (const componentChunk of componentChunks) {\n if (!availableModuleChunks.has(componentChunk)) {\n availableModuleChunks.set(componentChunk, promise)\n }\n }\n return promise\n}\n\nconst loadedChunk = Promise.resolve(undefined)\nconst instrumentedBackendLoadChunks = new WeakMap<\n Promise,\n Promise | typeof loadedChunk\n>()\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkByUrl(\n this: TurbopackBrowserBaseContext,\n chunkEntry: ChunkUrlOrMerged\n) {\n return loadChunkByUrlInternal(SourceType.Parent, this.m.id, chunkEntry)\n}\nbrowserContextPrototype.L = loadChunkByUrl\n\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkByUrlInternal(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkEntry: ChunkUrlOrMerged\n): Promise {\n if (SUPPORT_COMPONENT_CHUNKS) {\n // A merged chunk arrives as a `[url, componentChunkPaths, componentChunkSizes]` array. Register\n // the components so a by-URL load of this merged chunk — now or from a later navigation — can\n // be split, and so `registerChunk` can mark them available when the whole chunk loads.\n let chunkUrl: ChunkUrl\n let components: ChunkPath[] | undefined\n if (typeof chunkEntry === 'string') {\n chunkUrl = chunkEntry\n } else {\n let componentSizes: number[]\n ;[chunkUrl, components, componentSizes] = chunkEntry\n registerComponentChunkSizes(components, componentSizes)\n }\n const chunkPath = chunkUrlToPath(chunkUrl)\n if (components !== undefined) {\n chunkComponents.set(chunkPath, components)\n } else {\n // A plain URL may still be a merged chunk we already registered from its array.\n components = chunkComponents.get(chunkPath)\n }\n\n // If we have component chunks for this merged chunk, load only the ones we don't already have\n // instead of the whole merged chunk.\n if (components !== undefined) {\n let promise = splitChunkPromises.get(chunkUrl)\n if (promise === undefined) {\n promise = loadComponentChunksOrWhole(\n sourceType,\n sourceData,\n components,\n chunkUrl\n )\n splitChunkPromises.set(chunkUrl, promise)\n }\n return promise\n }\n\n // This is a non-merged chunk. If its modules were already loaded — e.g. this chunk is a\n // component of a merged chunk fetched on a previous navigation — reuse that load instead of\n // re-downloading.\n const existing = availableModuleChunks.get(chunkPath)\n if (existing !== undefined) {\n return existing === true ? loadedChunk : existing\n }\n const promise = loadChunkByUrlWhole(sourceType, sourceData, chunkUrl)\n availableModuleChunks.set(chunkPath, promise)\n return promise\n }\n\n // Component chunks are disabled, so the chunking context never emits merged arrays and every\n // entry is a plain chunk URL. Load it whole; the backend dedupes repeated URLs.\n return loadChunkByUrlWhole(sourceType, sourceData, chunkEntry as ChunkUrl)\n}\n\n// Convert a chunk URL back to its ChunkPath (strip base path, query/hash, decode), to\n// match the keys stored in `chunkComponents`.\nfunction chunkUrlToPath(chunkUrl: ChunkUrl): ChunkPath {\n const src = decodeURIComponent(chunkUrl.replace(/[?#].*$/, ''))\n return (\n src.startsWith(RUNTIME_CHUNK_BASE_PATH)\n ? src.slice(RUNTIME_CHUNK_BASE_PATH.length)\n : src\n ) as ChunkPath\n}\n\n/**\n * When a merged chunk finishes registering (e.g. an initial-load `