Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/next-core/src/next_client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,13 @@ pub async fn get_client_resolve_options_context(
|| *next_config
.enable_expose_testing_api_in_production_build()
.await?;
let concurrent_router_queue = *next_config.enable_concurrent_router_queue().await?;
let next_client_resolved_map = get_next_client_resolved_map(
project_path.clone(),
project_path.clone(),
*mode.await?,
expose_testing_api,
concurrent_router_queue,
)
.await?
.to_resolved()
Expand Down
8 changes: 8 additions & 0 deletions crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,9 @@ pub struct ExperimentalConfig {
swc_trace_profiling: Option<bool>,
transition_indicator: Option<bool>,
gesture_transition: Option<bool>,
/// Forks the client router's entry-point modules to the experimental
/// concurrent router queue implementation via the import map.
concurrent_router_queue: Option<bool>,
// `rename_all = "camelCase"` would lowercase the acronym to `blockingSsr`;
// rename explicitly so it deserializes from the public `blockingSSR` field.
#[serde(rename = "blockingSSR")]
Expand Down Expand Up @@ -2428,6 +2431,11 @@ impl NextConfig {
)
}

#[turbo_tasks::function]
pub fn enable_concurrent_router_queue(&self) -> Vc<bool> {
Vc::cell(self.experimental.concurrent_router_queue.unwrap_or(false))
}

#[turbo_tasks::function]
pub fn enable_cache_components(&self) -> Vc<bool> {
Vc::cell(self.cache_components.unwrap_or(false))
Expand Down
37 changes: 36 additions & 1 deletion crates/next-core/src/next_import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ pub async fn get_next_client_resolved_map(
root: FileSystemPath,
_mode: NextMode,
expose_testing_api: bool,
concurrent_router_queue: bool,
) -> Result<Vc<ResolvedMap>> {
// In the browser bundle, swap every module that has a `.browser` sibling (see
// BROWSER_VARIANT_MODULES, generated from the filesystem) for that sibling. The default
Expand Down Expand Up @@ -613,7 +614,7 @@ pub async fn get_next_client_resolved_map(
// alias in `create-compiler-aliases.ts`.
if !expose_testing_api {
glob_mappings.push((
fs_root,
fs_root.clone(),
Glob::new(
rcstr!("**/next/dist/client/components/segment-cache/navigation-testing-lock.js"),
GlobOptions::default(),
Expand All @@ -629,6 +630,40 @@ pub async fn get_next_client_resolved_map(
));
}

// When `experimental.concurrentRouterQueue` is enabled, resolve the
// router's forked entry-point modules (the navigator interface and the
// callServer action door) to the concurrent implementations. Neither the
// interface module nor the sequential implementation is bundled at all.
// This mirrors the webpack alias in `create-compiler-aliases.ts`.
if concurrent_router_queue {
glob_mappings.push((
fs_root.clone(),
Glob::new(
rcstr!("**/next/dist/client/components/navigator.js"),
GlobOptions::default(),
)
.to_resolved()
.await?,
request_to_import_mapping(
context_path.clone(),
rcstr!("next/dist/client/components/concurrent-router-queue"),
),
));
glob_mappings.push((
fs_root,
Glob::new(
rcstr!("**/next/dist/client/app-call-server.js"),
GlobOptions::default(),
)
.to_resolved()
.await?,
request_to_import_mapping(
context_path.clone(),
rcstr!("next/dist/client/concurrent-call-server"),
),
));
}

Ok(ResolvedMap {
by_glob: glob_mappings,
}
Expand Down
3 changes: 2 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -1473,5 +1473,6 @@
"1472": "Cannot convert a server response with no transport data and no base tree.",
"1473": "Invariant: image cache entry \"%s\" is empty",
"1474": "Invariant: cannot write an empty buffer to the image cache",
"1475": "Invariant: no direct app page entry found for %s"
"1475": "Invariant: no direct app page entry found for %s",
"1476": "Not implemented: this behavior is not yet supported when `experimental.concurrentRouterQueue` is enabled."
}
20 changes: 20 additions & 0 deletions packages/next/src/build/create-compiler-aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export function createWebpackAliases({
const isInstantNavigationTestingEnabled =
config.cacheComponents === true &&
(dev || config.experimental.exposeTestingApiInProductionBuild === true)
const isConcurrentRouterQueueEnabled =
config.experimental.concurrentRouterQueue === true

// tell webpack where to look for _app and _document
// using aliases to allow falling back to the default
Expand Down Expand Up @@ -199,6 +201,24 @@ export function createWebpackAliases({
'next/dist/client/components/segment-cache/navigation-testing-lock.disabled',
}
: {}),

// When `experimental.concurrentRouterQueue` is enabled, resolve the
// router's forked entry-point modules (the navigator interface and
// the callServer action door) to the concurrent implementations.
// Neither the interface module nor the sequential implementation is
// bundled at all. Same resolved-path matching as the swaps above.
...(isConcurrentRouterQueueEnabled
? {
[path.join(
NEXT_PROJECT_ROOT_DIST,
'client/components/navigator.js'
) + '$']: 'next/dist/client/components/concurrent-router-queue',
[path.join(
NEXT_PROJECT_ROOT_DIST,
'client/app-call-server.js'
) + '$']: 'next/dist/client/concurrent-call-server',
}
: {}),
}
: {}),

Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/build/define-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,8 @@ export function getDefineEnv({
config.experimental.gestureTransition ?? false,
'process.env.__NEXT_OPTIMISTIC_ROUTING':
config.experimental.optimisticRouting ?? false,
'process.env.__NEXT_CONCURRENT_ROUTER_QUEUE':
config.experimental.concurrentRouterQueue ?? false,
'process.env.__NEXT_INSTRUMENTATION_CLIENT_ROUTER_TRANSITION_EVENTS':
config.experimental.instrumentationClientRouterTransitionEvents ?? false,
'process.env.__NEXT_VARY_PARAMS': config.experimental.varyParams ?? false,
Expand Down
31 changes: 15 additions & 16 deletions packages/next/src/client/app-call-server.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { startTransition } from 'react'
import { ACTION_SERVER_ACTION } from './components/router-reducer/router-reducer-types'
import { dispatchAppRouterAction } from './components/use-action-queue'
// The entry point for Server Actions: the "action door" into the router.
// Server Actions are deliberately not a navigator operation (navigator.ts) —
// the action queue is semantically separate from the router state queue —
// but this module forks the same way: by default it re-exports the
// sequential implementation, and when `experimental.concurrentRouterQueue`
// is enabled, imports of this module resolve to './concurrent-call-server'
// instead at the bundler level (see create-compiler-aliases.ts and
// next_import_map.rs). Both implementations expose exactly this surface.

export async function callServer(actionId: string, actionArgs: any[]) {
return new Promise((resolve, reject) => {
startTransition(() => {
dispatchAppRouterAction({
type: ACTION_SERVER_ACTION,
actionId,
actionArgs,
resolve,
reject,
})
})
})
}
/**
* Invoke a Server Action. The returned promise resolves with the action's
* return value once the response has been processed. Navigation and
* revalidation side effects of the action are handled by the router; they are
* not observable through the returned promise.
*/
export { callServer } from './sequential-call-server'
24 changes: 11 additions & 13 deletions packages/next/src/client/app-dir/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,21 +301,19 @@ function linkClicked(
}
}

const { dispatchNavigateAction } =
const { navigate } =
// TODO(browser-variant): migrate to a .ts/.browser.ts split so the browser bundle drops the server branch; see scripts/generate-browser-variant-aliases.mjs
// ast-grep-ignore: no-typeof-window-require-tsx
require('../components/app-router-instance') as typeof import('../components/app-router-instance')

React.startTransition(() => {
dispatchNavigateAction(
href,
replace ? 'replace' : 'push',
scroll === false ? ScrollBehavior.NoScroll : ScrollBehavior.Default,
linkInstanceRef.current,
transitionTypes,
prefetchIntent
)
})
require('../components/navigator') as typeof import('../components/navigator')

navigate(
href,
replace ? 'replace' : 'push',
scroll === false ? ScrollBehavior.NoScroll : ScrollBehavior.Default,
linkInstanceRef.current,
transitionTypes,
prefetchIntent
)
}
}

Expand Down
Loading
Loading