Skip to content

Commit 2a033f5

Browse files
authored
Merge branch 'canary' into feat/meta-third-party
2 parents 7e56524 + 1c22d14 commit 2a033f5

File tree

17 files changed

+290
-99
lines changed

17 files changed

+290
-99
lines changed

.github/workflows/build_and_deploy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,9 @@ jobs:
560560
# Matches the commit message written by turbopack/xtask/src/publish.rs:377
561561
if: "${{(github.ref == 'refs/heads/canary') && startsWith(github.event.head_commit.message, 'chore: release turbopack npm packages')}}"
562562
runs-on: ubuntu-latest
563+
permissions:
564+
contents: write
565+
id-token: write
563566
steps:
564567
- uses: actions/checkout@v3
565568

crates/next-api/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod global_module_id_strategy;
1212
mod instrumentation;
1313
mod loadable_manifest;
1414
mod middleware;
15+
mod nft_json;
1516
mod pages;
1617
pub mod paths;
1718
pub mod project;

crates/next-api/src/nft_json.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use anyhow::{bail, Result};
2+
use serde_json::json;
3+
use turbo_tasks::{RcStr, ResolvedVc, ValueToString, Vc};
4+
use turbo_tasks_fs::{DiskFileSystem, File, FileSystem, FileSystemPath, VirtualFileSystem};
5+
use turbopack_core::{
6+
asset::{Asset, AssetContent},
7+
ident::AssetIdent,
8+
output::OutputAsset,
9+
reference::all_assets_from_entries,
10+
};
11+
12+
/// A json file that produces references to all files that are needed by the given module
13+
/// at runtime. This will include, for example, node native modules, unanalyzable packages,
14+
/// client side chunks, etc.
15+
///
16+
/// With this file, users can determine the minimum set of files that are needed alongside
17+
/// their bundle.
18+
#[turbo_tasks::value(shared)]
19+
pub struct NftJsonAsset {
20+
/// The chunk for which the asset is being generated
21+
chunk: Vc<Box<dyn OutputAsset>>,
22+
output_fs: Vc<DiskFileSystem>,
23+
project_fs: Vc<DiskFileSystem>,
24+
client_fs: Vc<Box<dyn FileSystem>>,
25+
/// Additional assets to include in the nft json. This can be used to manually collect assets
26+
/// that are known to be required but are not in the graph yet, for whatever reason.
27+
///
28+
/// An example of this is the two-phase approach used by the `ClientReferenceManifest` in
29+
/// next.js.
30+
additional_assets: Vec<ResolvedVc<Box<dyn OutputAsset>>>,
31+
}
32+
33+
#[turbo_tasks::value_impl]
34+
impl NftJsonAsset {
35+
#[turbo_tasks::function]
36+
pub fn new(
37+
chunk: Vc<Box<dyn OutputAsset>>,
38+
output_fs: Vc<DiskFileSystem>,
39+
project_fs: Vc<DiskFileSystem>,
40+
client_fs: Vc<Box<dyn FileSystem>>,
41+
additional_assets: Vec<ResolvedVc<Box<dyn OutputAsset>>>,
42+
) -> Vc<Self> {
43+
NftJsonAsset {
44+
chunk,
45+
output_fs,
46+
project_fs,
47+
client_fs,
48+
additional_assets,
49+
}
50+
.cell()
51+
}
52+
}
53+
54+
#[turbo_tasks::value(transparent)]
55+
pub struct OutputSpecifier(Option<RcStr>);
56+
57+
#[turbo_tasks::value_impl]
58+
impl NftJsonAsset {
59+
#[turbo_tasks::function]
60+
async fn ident_in_project_fs(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
61+
let this = self.await?;
62+
let project_fs = this.project_fs.await?;
63+
let output_fs = this.output_fs.await?;
64+
let nft_folder = self.ident().path().parent().await?;
65+
66+
if let Some(subdir) = output_fs.root.strip_prefix(&*project_fs.root) {
67+
Ok(this
68+
.project_fs
69+
.root()
70+
.join(subdir.into())
71+
.join(nft_folder.path.clone()))
72+
} else {
73+
// TODO: what are the implications of this?
74+
bail!("output fs not inside project fs");
75+
}
76+
}
77+
78+
#[turbo_tasks::function]
79+
async fn ident_in_client_fs(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
80+
Ok(self
81+
.await?
82+
.client_fs
83+
.root()
84+
.join(self.ident().path().parent().await?.path.clone()))
85+
}
86+
87+
#[turbo_tasks::function]
88+
async fn get_output_specifier(
89+
self: Vc<Self>,
90+
path: Vc<FileSystemPath>,
91+
) -> Result<Vc<OutputSpecifier>> {
92+
let this = self.await?;
93+
let path_fs = path.fs().resolve().await?;
94+
let path_ref = path.await?;
95+
let nft_folder = self.ident().path().parent().await?;
96+
97+
if path_fs == Vc::upcast(this.output_fs.resolve().await?) {
98+
// e.g. a referenced chunk
99+
return Ok(Vc::cell(Some(
100+
nft_folder.get_relative_path_to(&path_ref).unwrap(),
101+
)));
102+
} else if path_fs == Vc::upcast(this.project_fs.resolve().await?) {
103+
return Ok(Vc::cell(Some(
104+
self.ident_in_project_fs()
105+
.await?
106+
.get_relative_path_to(&path_ref)
107+
.unwrap(),
108+
)));
109+
} else if path_fs == Vc::upcast(this.client_fs.resolve().await?) {
110+
return Ok(Vc::cell(Some(
111+
self.ident_in_client_fs()
112+
.await?
113+
.get_relative_path_to(&path_ref)
114+
.unwrap()
115+
.replace("/_next/", "/.next/")
116+
.into(),
117+
)));
118+
}
119+
120+
if let Some(path_fs) = Vc::try_resolve_downcast_type::<VirtualFileSystem>(path_fs).await? {
121+
if path_fs.await?.name == "externals" || path_fs.await?.name == "traced" {
122+
return Ok(Vc::cell(Some(
123+
self.ident_in_project_fs()
124+
.await?
125+
.get_relative_path_to(
126+
&*this.project_fs.root().join(path_ref.path.clone()).await?,
127+
)
128+
.unwrap(),
129+
)));
130+
}
131+
}
132+
133+
println!("Unknown filesystem for {}", path.to_string().await?);
134+
Ok(Vc::cell(None))
135+
}
136+
}
137+
138+
#[turbo_tasks::value_impl]
139+
impl OutputAsset for NftJsonAsset {
140+
#[turbo_tasks::function]
141+
async fn ident(&self) -> Result<Vc<AssetIdent>> {
142+
let path = self.chunk.ident().path().await?;
143+
Ok(AssetIdent::from_path(
144+
path.fs
145+
.root()
146+
.join(format!("{}.nft.json", path.path).into()),
147+
))
148+
}
149+
}
150+
151+
#[turbo_tasks::value_impl]
152+
impl Asset for NftJsonAsset {
153+
#[turbo_tasks::function]
154+
async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
155+
let this = &*self.await?;
156+
let mut result = Vec::new();
157+
158+
let chunk = this.chunk.to_resolved().await?;
159+
let entries = this
160+
.additional_assets
161+
.iter()
162+
.copied()
163+
.chain(std::iter::once(chunk))
164+
.collect();
165+
for referenced_chunk in all_assets_from_entries(Vc::cell(entries)).await? {
166+
if referenced_chunk.ident().path().await?.extension_ref() == Some("map") {
167+
continue;
168+
}
169+
170+
if chunk == referenced_chunk.to_resolved().await? {
171+
continue;
172+
}
173+
174+
let specifier = self
175+
.get_output_specifier(referenced_chunk.ident().path())
176+
.await?;
177+
if let Some(specifier) = &*specifier {
178+
result.push(specifier.clone());
179+
}
180+
}
181+
182+
result.sort();
183+
result.dedup();
184+
let json = json!({
185+
"version": 1,
186+
"files": result
187+
});
188+
189+
Ok(AssetContent::file(File::from(json.to_string()).into()))
190+
}
191+
}

crates/next-api/src/pages.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use turbopack_core::{
5050
file_source::FileSource,
5151
ident::AssetIdent,
5252
module::{Module, Modules},
53-
output::{OutputAsset, OutputAssets},
53+
output::{OptionOutputAsset, OutputAsset, OutputAssets},
5454
reference_type::{EcmaScriptModulesReferenceSubType, EntryReferenceSubType, ReferenceType},
5555
resolve::{origin::PlainResolveOrigin, parse::Request, pattern::Pattern},
5656
source::Source,
@@ -66,6 +66,7 @@ use crate::{
6666
},
6767
font::create_font_manifest,
6868
loadable_manifest::create_react_loadable_manifest,
69+
nft_json::NftJsonAsset,
6970
paths::{
7071
all_paths_in_root, all_server_paths, get_js_paths_from_root, get_paths_from_root,
7172
get_wasm_paths_from_root, paths_to_bindings, wasm_paths_to_bindings,
@@ -879,9 +880,32 @@ impl PageEndpoint {
879880
)
880881
.await?;
881882

883+
let nft = if this
884+
.pages_project
885+
.project()
886+
.next_mode()
887+
.await?
888+
.is_production()
889+
{
890+
ResolvedVc::cell(Some(ResolvedVc::upcast(
891+
NftJsonAsset::new(
892+
*ssr_entry_chunk,
893+
this.pages_project.project().output_fs(),
894+
this.pages_project.project().project_fs(),
895+
this.pages_project.project().client_fs(),
896+
vec![],
897+
)
898+
.to_resolved()
899+
.await?,
900+
)))
901+
} else {
902+
ResolvedVc::cell(None)
903+
};
904+
882905
Ok(SsrChunk::NodeJs {
883906
entry: ssr_entry_chunk,
884907
dynamic_import_entries,
908+
nft,
885909
}
886910
.cell())
887911
}
@@ -1097,10 +1121,14 @@ impl PageEndpoint {
10971121
SsrChunk::NodeJs {
10981122
entry,
10991123
dynamic_import_entries,
1124+
nft,
11001125
} => {
11011126
let pages_manifest = self.pages_manifest(*entry).to_resolved().await?;
11021127
server_assets.push(pages_manifest);
11031128
server_assets.push(entry);
1129+
if let Some(nft) = &*nft.await? {
1130+
server_assets.push(*nft);
1131+
}
11041132

11051133
let loadable_manifest_output = self.react_loadable_manifest(dynamic_import_entries);
11061134
server_assets.extend(loadable_manifest_output.await?.iter().copied());
@@ -1373,6 +1401,7 @@ pub enum SsrChunk {
13731401
NodeJs {
13741402
entry: ResolvedVc<Box<dyn OutputAsset>>,
13751403
dynamic_import_entries: Vc<DynamicImportedChunks>,
1404+
nft: ResolvedVc<OptionOutputAsset>,
13761405
},
13771406
Edge {
13781407
files: Vc<OutputAssets>,

crates/next-api/src/project.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ impl Project {
539539
}
540540

541541
#[turbo_tasks::function]
542-
fn project_fs(&self) -> Vc<DiskFileSystem> {
542+
pub fn project_fs(&self) -> Vc<DiskFileSystem> {
543543
DiskFileSystem::new(
544544
PROJECT_FILESYSTEM_NAME.into(),
545545
self.root_path.clone(),
@@ -548,7 +548,7 @@ impl Project {
548548
}
549549

550550
#[turbo_tasks::function]
551-
fn client_fs(self: Vc<Self>) -> Vc<Box<dyn FileSystem>> {
551+
pub fn client_fs(self: Vc<Self>) -> Vc<Box<dyn FileSystem>> {
552552
let virtual_fs = VirtualFileSystem::new();
553553
Vc::upcast(virtual_fs)
554554
}

examples/api-routes/.gitignore

Lines changed: 0 additions & 40 deletions
This file was deleted.

examples/route-handlers/.gitignore

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
# dependencies
44
/node_modules
55
/.pnp
6-
.pnp.js
7-
.yarn/install-state.gz
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
812

913
# testing
1014
/coverage
@@ -25,8 +29,8 @@ npm-debug.log*
2529
yarn-debug.log*
2630
yarn-error.log*
2731

28-
# local env files
29-
.env*.local
32+
# env files (can opt-in for committing if needed)
33+
.env*
3034

3135
# vercel
3236
.vercel

packages/create-next-app/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,5 @@ Options:
127127
- **Interactive Experience**: Running `npx create-next-app@latest` (with no arguments) launches an interactive experience that guides you through setting up a project.
128128
- **Zero Dependencies**: Initializing a project is as quick as one second. Create Next App has zero dependencies.
129129
- **Offline Support**: Create Next App will automatically detect if you're offline and bootstrap your project using your local package cache.
130-
- **Support for Examples**: Create Next App can bootstrap your application using an example from the Next.js examples collection (e.g. `npx create-next-app --example api-routes`).
130+
- **Support for Examples**: Create Next App can bootstrap your application using an example from the Next.js examples collection (e.g. `npx create-next-app --example route-handlers`).
131131
- **Tested**: The package is part of the Next.js monorepo and tested using the same integration test suite as Next.js itself, ensuring it works as expected with every release.

0 commit comments

Comments
 (0)