Skip to content

[pull] canary from vercel:canary #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 2, 2025
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/next-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ turbopack-ecmascript = { workspace = true }
turbopack-env = { workspace = true }
turbopack-node = { workspace = true }
turbopack-nodejs = { workspace = true }
turbopack-wasm = { workspace = true }

[build-dependencies]
anyhow = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1556,7 +1556,7 @@ impl AppEndpoint {
};
let edge_function_definition = EdgeFunctionDefinition {
files: file_paths_from_root.into_iter().collect(),
wasm: wasm_paths_to_bindings(wasm_paths_from_root.into_iter().collect()),
wasm: wasm_paths_to_bindings(wasm_paths_from_root).await?,
assets: paths_to_bindings(all_assets),
name: app_entry.pathname.clone(),
page: app_entry.original_name.clone(),
Expand Down
2 changes: 1 addition & 1 deletion crates/next-api/src/instrumentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl InstrumentationEndpoint {

let instrumentation_definition = InstrumentationDefinition {
files: file_paths_from_root,
wasm: wasm_paths_to_bindings(wasm_paths_from_root),
wasm: wasm_paths_to_bindings(wasm_paths_from_root).await?,
name: "instrumentation".into(),
..Default::default()
};
Expand Down
2 changes: 1 addition & 1 deletion crates/next-api/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ impl MiddlewareEndpoint {

let edge_function_definition = EdgeFunctionDefinition {
files: file_paths_from_root,
wasm: wasm_paths_to_bindings(wasm_paths_from_root),
wasm: wasm_paths_to_bindings(wasm_paths_from_root).await?,
assets: paths_to_bindings(all_assets),
name: "middleware".into(),
page: "/".into(),
Expand Down
8 changes: 4 additions & 4 deletions crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use serde::{Deserialize, Serialize};
use tracing::Instrument;
use turbo_rcstr::RcStr;
use turbo_tasks::{
fxindexmap, trace::TraceRawVcs, Completion, FxIndexMap, NonLocalValue, ResolvedVc, TaskInput,
Value, ValueToString, Vc,
fxindexmap, fxindexset, trace::TraceRawVcs, Completion, FxIndexMap, NonLocalValue, ResolvedVc,
TaskInput, Value, ValueToString, Vc,
};
use turbo_tasks_fs::{
self, File, FileContent, FileSystem, FileSystemPath, FileSystemPathOption, VirtualFileSystem,
Expand Down Expand Up @@ -1375,7 +1375,7 @@ impl PageEndpoint {
"server/middleware-build-manifest.js".into(),
"server/next-font-manifest.js".into(),
];
let mut wasm_paths_from_root = vec![];
let mut wasm_paths_from_root = fxindexset![];

let node_root_value = node_root.await?;

Expand Down Expand Up @@ -1404,7 +1404,7 @@ impl PageEndpoint {
let original_name = this.original_name.owned().await?;
let edge_function_definition = EdgeFunctionDefinition {
files: file_paths_from_root,
wasm: wasm_paths_to_bindings(wasm_paths_from_root),
wasm: wasm_paths_to_bindings(wasm_paths_from_root).await?,
assets: paths_to_bindings(all_assets),
name: pathname.clone(),
page: original_name.clone(),
Expand Down
65 changes: 30 additions & 35 deletions crates/next-api/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ use next_core::{all_assets_from_entries, next_manifests::AssetBinding};
use serde::{Deserialize, Serialize};
use tracing::Instrument;
use turbo_rcstr::RcStr;
use turbo_tasks::{trace::TraceRawVcs, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, Vc};
use turbo_tasks::{
trace::TraceRawVcs, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc,
};
use turbo_tasks_fs::FileSystemPath;
use turbopack_core::{
asset::{Asset, AssetContent},
output::{OutputAsset, OutputAssets},
};
use turbopack_wasm::wasm_edge_var_name;

/// A reference to a server file with content hash for change detection
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
Expand Down Expand Up @@ -109,8 +112,23 @@ pub(crate) async fn get_js_paths_from_root(
pub(crate) async fn get_wasm_paths_from_root(
root: &FileSystemPath,
output_assets: impl IntoIterator<Item = &ResolvedVc<Box<dyn OutputAsset>>>,
) -> Result<Vec<RcStr>> {
get_paths_from_root(root, output_assets, |path| path.ends_with(".wasm")).await
) -> Result<Vec<(RcStr, ResolvedVc<Box<dyn OutputAsset>>)>> {
output_assets
.into_iter()
.map(move |&file| async move {
let path = &*file.path().await?;
let Some(relative) = root.get_path_to(path) else {
return Ok(None);
};

Ok(if relative.ends_with(".wasm") {
Some((relative.into(), file))
} else {
None
})
})
.try_flat_join()
.await
}

pub(crate) async fn get_asset_paths_from_root(
Expand All @@ -137,42 +155,19 @@ pub(crate) async fn get_font_paths_from_root(
.await
}

fn get_file_stem(path: &str) -> &str {
let file_name = if let Some((_, file_name)) = path.rsplit_once('/') {
file_name
} else {
path
};

if let Some((stem, _)) = file_name.split_once('.') {
if stem.is_empty() {
file_name
} else {
stem
}
} else {
file_name
}
}

pub(crate) fn wasm_paths_to_bindings(paths: Vec<RcStr>) -> Vec<AssetBinding> {
pub(crate) async fn wasm_paths_to_bindings(
paths: impl IntoIterator<Item = (RcStr, ResolvedVc<Box<dyn OutputAsset>>)>,
) -> Result<Vec<AssetBinding>> {
paths
.into_iter()
.map(|path| {
let stem = get_file_stem(&path);

// very simple escaping just replacing unsupported characters with `_`
let escaped = stem.replace(
|c: char| !c.is_ascii_alphanumeric() && c != '$' && c != '_',
"_",
);

AssetBinding {
name: format!("wasm_{}", escaped).into(),
.map(async |(path, asset)| {
Ok(AssetBinding {
name: wasm_edge_var_name(Vc::upcast(*asset)).owned().await?,
file_path: path,
}
})
})
.collect()
.try_join()
.await
}

pub(crate) fn paths_to_bindings(paths: Vec<RcStr>) -> Vec<AssetBinding> {
Expand Down
17 changes: 17 additions & 0 deletions docs/01-app/01-getting-started/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,20 @@
title: Getting Started
description: Learn how to create full-stack web applications with the Next.js App Router.
---

Welcome to the Next.js documentation!

This **Getting Started** section will help you create your first Next.js app and learn the core features you'll use in every project.

## Pre-requisite knowledge

Our documentation assumes some familiarity with web development. Before getting started, it'll help if you're comfortable with:

- HTML
- CSS
- JavaScript
- React

If you're new to React or need a refresher, we recommend starting with our [React Foundations course](/learn/react-foundations), and the [Next.js Foundations course](/learn/dashboard-app) that has you building an application as you learn.

## Next Steps
50 changes: 23 additions & 27 deletions docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: Introduction
description: Welcome to the Next.js Documentation.
related:
title: Next Steps
description: Get started with Next.js by following the installation guide.
description: Create your first application and learn the core Next.js features.
links:
- app/getting-started/installation
- app/getting-started
---

Welcome to the Next.js documentation!
Expand All @@ -14,48 +14,44 @@ Welcome to the Next.js documentation!

Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations.

Under the hood, Next.js also abstracts and automatically configures tooling needed for React, like bundling, compiling, and more. This allows you to focus on building your application instead of spending time with configuration.
It also automatically configures lower-level tools like bundlers and compilers. You can instead focus on building your product and shipping quickly.

Whether you're an individual developer or part of a larger team, Next.js can help you build interactive, dynamic, and fast React applications.

## Main Features
## How to use the docs

Some of the main Next.js features include:
The docs are organized into 4 sections:

| Feature | Description |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [Routing](/docs/app/building-your-application/routing) | A file-system based router built on top of Server Components that supports layouts, nested routing, loading states, error handling, and more. |
| [Rendering](/docs/app/building-your-application/rendering) | Client-side and Server-side Rendering with Client and Server Components. Further optimized with Static and Dynamic Rendering on the server with Next.js. Streaming on Edge and Node.js runtimes. |
| [Data Fetching](/docs/app/building-your-application/data-fetching) | Simplified data fetching with async/await in Server Components, and an extended `fetch` API for request memoization, data caching and revalidation. |
| [Styling](/docs/app/building-your-application/styling) | Support for your preferred styling methods, including CSS Modules, Tailwind CSS, and CSS-in-JS |
| [Optimizations](/docs/app/building-your-application/optimizing) | Image, Fonts, and Script Optimizations to improve your application's Core Web Vitals and User Experience. |
| [TypeScript](/docs/app/api-reference/config/typescript) | Improved support for TypeScript, with better type checking and more efficient compilation, as well as custom TypeScript Plugin and type checker. |
- [Getting Started](/docs/app/getting-started): Step-by-step tutorials to help you create a new application and learn the core Next.js features.
- [Guides](/docs/app/guides): Tutorials on specific use cases, choose what's relevant to you.
- [Deep Dive](/docs/app/deep-dive): In-depth explanations on how Next.js works.
- [API Reference](/docs/app/api-reference): Detailed technical reference for every feature.

## How to Use These Docs
Use the sidebar to navigate through the sections, or search (`Ctrl+K` or `Cmd+K`) to quickly find a page.

On the left side of the screen, you'll find the docs navbar. The pages of the docs are organized sequentially, from basic to advanced, so you can follow them step-by-step when building your application. However, you can read them in any order or skip to the pages that apply to your use case.
## App Router and Pages Router

On the right side of the screen, you'll see a table of contents that makes it easier to navigate between sections of a page. If you need to quickly find a page, you can use the search bar at the top, or the search shortcut (`Ctrl+K` or `Cmd+K`).
Next.js has two different routers:

To get started, check out the [Installation](/docs/app/getting-started/installation) guide.
- **App Router**: The newer router that supports new React features like Server Components.
- **Pages Router**: The original router, still supported and being improved.

## App Router vs Pages Router
At the top of the sidebar, you'll notice a dropdown menu that allows you to switch between the [App Router](/docs/app) and the [Pages Router](/docs/pages) docs.

Next.js has two different routers: the App Router and the Pages Router. The App Router is a newer router that allows you to use React's latest features, such as Server Components and Streaming. The Pages Router is the original Next.js router, which allowed you to build server-rendered React applications and continues to be supported for older Next.js applications.
## Pre-requisite knowledge

At the top of the sidebar, you'll notice a dropdown menu that allows you to switch between the **App Router** and the **Pages Router** features. Since there are features that are unique to each directory, it's important to keep track of which tab is selected.
Our documentation assumes some familiarity with web development. Before getting started, it'll help if you're comfortable with:

The breadcrumbs at the top of the page will also indicate whether you're viewing App Router docs or Pages Router docs.
- HTML
- CSS
- JavaScript
- React

## Pre-Requisite Knowledge

Although our docs are designed to be beginner-friendly, we need to establish a baseline so that the docs can stay focused on Next.js functionality. We'll make sure to provide links to relevant documentation whenever we introduce a new concept.

To get the most out of our docs, it's recommended that you have a basic understanding of HTML, CSS, and React. If you need to brush up on your React skills, check out our [React Foundations Course](/learn/react-foundations), which will introduce you to the fundamentals. Then, learn more about Next.js by [building a dashboard application](/learn/dashboard-app).
If you're new to React or need a refresher, we recommend starting with our [React Foundations course](/learn/react-foundations), and the [Next.js Foundations course](/learn/dashboard-app) that has you building an application as you learn.

## Accessibility

For optimal accessibility when using a screen reader while reading the docs, we recommend using Firefox and NVDA, or Safari and VoiceOver.
For the best experience when using a screen reader, we recommend using Firefox and NVDA, or Safari and VoiceOver.

## Join our Community

Expand Down
22 changes: 21 additions & 1 deletion packages/next/src/server/lib/patch-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ export function createPatchedFetcher(
// We are dynamically rendering including dev mode. We want to return
// the response to the caller as soon as possible because it might stream
// over a very long time.
cloned1
const cacheSetPromise = cloned1
.arrayBuffer()
.then(async (arrayBuffer) => {
const bodyBuffer = Buffer.from(arrayBuffer)
Expand Down Expand Up @@ -710,6 +710,26 @@ export function createPatchedFetcher(
)
.finally(handleUnlock)

const pendingRevalidateKey = `cache-set-${cacheKey}`
workStore.pendingRevalidates ??= {}
if (pendingRevalidateKey in workStore.pendingRevalidates) {
// there is already a pending revalidate entry that
// we need to await to avoid race conditions
await workStore.pendingRevalidates[pendingRevalidateKey]
}
workStore.pendingRevalidates[pendingRevalidateKey] =
cacheSetPromise.finally(() => {
// If the pending revalidate is not present in the store, then
// we have nothing to delete.
if (
!workStore.pendingRevalidates?.[pendingRevalidateKey]
) {
return
}

delete workStore.pendingRevalidates[pendingRevalidateKey]
})

return cloned2
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ declare var BACKEND: RuntimeBackend
declare var loadWebAssembly: (
source: SourceInfo,
wasmChunkPath: ChunkPath,
edgeModule: () => WebAssembly.Module,
imports: WebAssembly.Imports
) => Exports
declare var loadWebAssemblyModule: (
source: SourceInfo,
wasmChunkPath: ChunkPath
wasmChunkPath: ChunkPath,
edgeModule: () => WebAssembly.Module
) => WebAssembly.Module
declare var relativeURL: (inputUrl: string) => void
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function fetchWebAssembly(wasmChunkPath: ChunkPath) {
async function loadWebAssembly(
_source: unknown,
wasmChunkPath: ChunkPath,
_edgeModule: () => WebAssembly.Module,
importsObj: WebAssembly.Imports
): Promise<Exports> {
const req = fetchWebAssembly(wasmChunkPath)
Expand All @@ -42,7 +43,8 @@ async function loadWebAssembly(

async function loadWebAssemblyModule(
_source: unknown,
wasmChunkPath: ChunkPath
wasmChunkPath: ChunkPath,
_edgeModule: () => WebAssembly.Module
): Promise<WebAssembly.Module> {
const req = fetchWebAssembly(wasmChunkPath)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ function augmentContext(
async function loadWebAssembly(
source: SourceInfo,
chunkPath: ChunkPath,
edgeModule: () => WebAssembly.Module,
imports: WebAssembly.Imports
): Promise<Exports> {
const module = await loadWebAssemblyModule(source, chunkPath)
const module = await loadWebAssemblyModule(source, chunkPath, edgeModule)

return await WebAssembly.instantiate(module, imports)
}
Expand All @@ -62,26 +63,19 @@ function getFileStem(path: string): string {
return stem
}

type GlobalWithInjectedWebAssembly = typeof globalThis & {
[key: `wasm_${string}`]: WebAssembly.Module
}

async function loadWebAssemblyModule(
_source: SourceInfo,
chunkPath: ChunkPath
chunkPath: ChunkPath,
edgeModule: () => WebAssembly.Module
): Promise<WebAssembly.Module> {
const stem = getFileStem(chunkPath)

// very simple escaping just replacing unsupported characters with `_`
const escaped = stem.replace(/[^a-zA-Z0-9$_]/gi, '_')

const identifier: `wasm_${string}` = `wasm_${escaped}`

const module = (globalThis as GlobalWithInjectedWebAssembly)[identifier]
let module
try {
module = edgeModule()
} catch (_e) {}

if (!module) {
throw new Error(
`dynamically loading WebAssembly is not supported in this runtime and global \`${identifier}\` was not injected`
`dynamically loading WebAssembly is not supported in this runtime as global was not injected for chunk '${chunkPath}'`
)
}

Expand Down
Loading
Loading