Skip to content

Commit 90c7061

Browse files
authored
Merge branch 'vercel:canary' into canary
2 parents ed7eed3 + a5d0ad1 commit 90c7061

File tree

17 files changed

+589
-29
lines changed

17 files changed

+589
-29
lines changed

crates/next-api/benches/hmr.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
extern crate turbo_tasks_malloc;
2-
31
use std::{
42
env,
53
fs::{create_dir_all, write},

crates/next-api/src/app.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,9 +1670,11 @@ impl AppEndpoint {
16701670
.await?
16711671
.is_production()
16721672
{
1673+
let page_name = app_entry.pathname.clone();
16731674
server_assets.insert(ResolvedVc::upcast(
16741675
NftJsonAsset::new(
16751676
project,
1677+
Some(page_name),
16761678
*rsc_chunk,
16771679
client_reference_manifest
16781680
.iter()

crates/next-api/src/instrumentation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ impl InstrumentationEndpoint {
216216
let mut output_assets = vec![chunk];
217217
if this.project.next_mode().await?.is_production() {
218218
output_assets.push(ResolvedVc::upcast(
219-
NftJsonAsset::new(*this.project, *chunk, vec![])
219+
NftJsonAsset::new(*this.project, None, *chunk, vec![])
220220
.to_resolved()
221221
.await?,
222222
));

crates/next-api/src/middleware.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ impl MiddlewareEndpoint {
248248
let mut output_assets = vec![chunk];
249249
if this.project.next_mode().await?.is_production() {
250250
output_assets.push(ResolvedVc::upcast(
251-
NftJsonAsset::new(*this.project, *chunk, vec![])
251+
NftJsonAsset::new(*this.project, None, *chunk, vec![])
252252
.to_resolved()
253253
.await?,
254254
));

crates/next-api/src/nft_json.rs

Lines changed: 143 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use std::collections::BTreeSet;
1+
use std::collections::{BTreeSet, VecDeque};
22

33
use anyhow::{Result, bail};
44
use serde_json::json;
55
use turbo_rcstr::RcStr;
66
use turbo_tasks::{ResolvedVc, Vc};
7-
use turbo_tasks_fs::{File, FileSystem, FileSystemPath};
7+
use turbo_tasks_fs::{DirectoryEntry, File, FileSystem, FileSystemPath, glob::Glob};
88
use turbopack_core::{
99
asset::{Asset, AssetContent},
1010
output::OutputAsset,
@@ -30,20 +30,23 @@ pub struct NftJsonAsset {
3030
/// An example of this is the two-phase approach used by the `ClientReferenceManifest` in
3131
/// next.js.
3232
additional_assets: Vec<ResolvedVc<Box<dyn OutputAsset>>>,
33+
page_name: Option<RcStr>,
3334
}
3435

3536
#[turbo_tasks::value_impl]
3637
impl NftJsonAsset {
3738
#[turbo_tasks::function]
3839
pub fn new(
3940
project: ResolvedVc<Project>,
41+
page_name: Option<RcStr>,
4042
chunk: ResolvedVc<Box<dyn OutputAsset>>,
4143
additional_assets: Vec<ResolvedVc<Box<dyn OutputAsset>>>,
4244
) -> Vc<Self> {
4345
NftJsonAsset {
4446
chunk,
4547
project,
4648
additional_assets,
49+
page_name,
4750
}
4851
.cell()
4952
}
@@ -95,6 +98,41 @@ fn get_output_specifier(
9598
bail!("NftJsonAsset: cannot handle filepath {}", path_ref);
9699
}
97100

101+
/// Apply outputFileTracingIncludes patterns to find additional files
102+
async fn apply_includes(
103+
project_root_path: Vc<FileSystemPath>,
104+
glob: Vc<Glob>,
105+
ident_folder: &FileSystemPath,
106+
) -> Result<BTreeSet<RcStr>> {
107+
// Read files matching the glob pattern from the project root
108+
let glob_result = project_root_path.read_glob(glob).await?;
109+
110+
// Walk the full glob_result using an explicit stack to avoid async recursion overheads.
111+
let mut result = BTreeSet::new();
112+
let mut stack = VecDeque::new();
113+
stack.push_back(glob_result);
114+
while let Some(glob_result) = stack.pop_back() {
115+
// Process direct results (files and directories at this level)
116+
for entry in glob_result.results.values() {
117+
let DirectoryEntry::File(file_path) = entry else {
118+
continue;
119+
};
120+
121+
let file_path_ref = file_path.await?;
122+
// Convert to relative path from ident_folder to the file
123+
if let Some(relative_path) = ident_folder.get_relative_path_to(&file_path_ref) {
124+
result.insert(relative_path);
125+
}
126+
}
127+
128+
for nested_result in glob_result.inner.values() {
129+
let nested_result_ref = nested_result.await?;
130+
stack.push_back(nested_result_ref);
131+
}
132+
}
133+
Ok(result)
134+
}
135+
98136
#[turbo_tasks::value_impl]
99137
impl Asset for NftJsonAsset {
100138
#[turbo_tasks::function]
@@ -104,15 +142,20 @@ impl Asset for NftJsonAsset {
104142

105143
let output_root_ref = this.project.output_fs().root().await?;
106144
let project_root_ref = this.project.project_fs().root().await?;
145+
let next_config = this.project.next_config();
146+
147+
// Parse outputFileTracingIncludes and outputFileTracingExcludes from config
148+
let output_file_tracing_includes = &*next_config.output_file_tracing_includes().await?;
149+
let output_file_tracing_excludes = &*next_config.output_file_tracing_excludes().await?;
150+
107151
let client_root = this.project.client_fs().root();
108152
let client_root_ref = client_root.await?;
153+
let project_root_path = this.project.project_root_path(); // Example: [project]
109154

110155
// Example: [output]/apps/my-website/.next/server/app -- without the `.nft.json`
111156
let ident_folder = self.path().parent().await?;
112157
// Example: [project]/apps/my-website/.next/server/app -- without the `.nft.json`
113-
let ident_folder_in_project_fs = this
114-
.project
115-
.project_root_path() // Example: [project]
158+
let ident_folder_in_project_fs = project_root_path
116159
.join(ident_folder.path.clone()) // apps/my-website/.next/server/app
117160
.await?;
118161

@@ -123,6 +166,51 @@ impl Asset for NftJsonAsset {
123166
.copied()
124167
.chain(std::iter::once(chunk))
125168
.collect();
169+
170+
let exclude_glob = if let Some(route) = &this.page_name {
171+
let project_path = this.project.project_path().await?;
172+
173+
if let Some(excludes_config) = output_file_tracing_excludes {
174+
let mut combined_excludes = BTreeSet::new();
175+
176+
if let Some(excludes_obj) = excludes_config.as_object() {
177+
for (glob_pattern, exclude_patterns) in excludes_obj {
178+
// Check if the route matches the glob pattern
179+
let glob = Glob::new(RcStr::from(glob_pattern.clone())).await?;
180+
if glob.matches(route)
181+
&& let Some(patterns) = exclude_patterns.as_array()
182+
{
183+
for pattern in patterns {
184+
if let Some(pattern_str) = pattern.as_str() {
185+
combined_excludes.insert(pattern_str);
186+
}
187+
}
188+
}
189+
}
190+
}
191+
192+
let glob = Glob::new(
193+
format!(
194+
"{project_path}/{{{}}}",
195+
combined_excludes
196+
.iter()
197+
.copied()
198+
.collect::<Vec<_>>()
199+
.join(",")
200+
)
201+
.into(),
202+
)
203+
.await?;
204+
205+
Some(glob)
206+
} else {
207+
None
208+
}
209+
} else {
210+
None
211+
};
212+
213+
// Collect base assets first
126214
for referenced_chunk in all_assets_from_entries(Vc::cell(entries)).await? {
127215
if chunk.eq(referenced_chunk) {
128216
continue;
@@ -133,6 +221,12 @@ impl Asset for NftJsonAsset {
133221
continue;
134222
}
135223

224+
if let Some(ref exclude_glob) = exclude_glob
225+
&& exclude_glob.matches(referenced_chunk_path.path.as_str())
226+
{
227+
continue;
228+
}
229+
136230
let Some(specifier) = get_output_specifier(
137231
&referenced_chunk_path,
138232
&ident_folder,
@@ -147,6 +241,50 @@ impl Asset for NftJsonAsset {
147241
result.insert(specifier);
148242
}
149243

244+
// Apply outputFileTracingIncludes and outputFileTracingExcludes
245+
// Extract route from chunk path for pattern matching
246+
if let Some(route) = &this.page_name {
247+
let project_path = this.project.project_path();
248+
let mut combined_includes = BTreeSet::new();
249+
250+
// Process includes
251+
if let Some(includes_config) = output_file_tracing_includes
252+
&& let Some(includes_obj) = includes_config.as_object()
253+
{
254+
for (glob_pattern, include_patterns) in includes_obj {
255+
// Check if the route matches the glob pattern
256+
let glob = Glob::new(glob_pattern.as_str().into()).await?;
257+
if glob.matches(route)
258+
&& let Some(patterns) = include_patterns.as_array()
259+
{
260+
for pattern in patterns {
261+
if let Some(pattern_str) = pattern.as_str() {
262+
combined_includes.insert(pattern_str);
263+
}
264+
}
265+
}
266+
}
267+
}
268+
269+
// Apply includes - find additional files that match the include patterns
270+
if !combined_includes.is_empty() {
271+
let glob = Glob::new(
272+
format!(
273+
"{{{}}}",
274+
combined_includes
275+
.iter()
276+
.copied()
277+
.collect::<Vec<_>>()
278+
.join(",")
279+
)
280+
.into(),
281+
);
282+
let additional_files =
283+
apply_includes(project_path, glob, &ident_folder_in_project_fs).await?;
284+
result.extend(additional_files);
285+
}
286+
}
287+
150288
let json = json!({
151289
"version": 1,
152290
"files": result

crates/next-api/src/pages.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ impl PageEndpoint {
11351135
ResolvedVc::cell(Some(ResolvedVc::upcast(
11361136
NftJsonAsset::new(
11371137
project,
1138+
Some(this.original_name.clone()),
11381139
*ssr_entry_chunk,
11391140
loadable_manifest_output
11401141
.await?

crates/next-core/src/next_config.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ pub struct NextConfig {
9898
pub output: Option<OutputType>,
9999
pub turbopack: Option<TurbopackConfig>,
100100
production_browser_source_maps: bool,
101+
output_file_tracing_includes: Option<serde_json::Value>,
102+
output_file_tracing_excludes: Option<serde_json::Value>,
103+
// TODO: This option is not respected, it uses Turbopack's root instead.
104+
output_file_tracing_root: Option<RcStr>,
101105

102106
/// Enables the bundling of node_modules packages (externals) for pages
103107
/// server-side bundles.
@@ -763,9 +767,6 @@ pub struct ExperimentalConfig {
763767
/// Automatically apply the "modularize_imports" optimization to imports of
764768
/// the specified packages.
765769
optimize_package_imports: Option<Vec<RcStr>>,
766-
output_file_tracing_ignores: Option<Vec<RcStr>>,
767-
output_file_tracing_includes: Option<serde_json::Value>,
768-
output_file_tracing_root: Option<RcStr>,
769770
/// Using this feature will enable the `react@experimental` for the `app`
770771
/// directory.
771772
ppr: Option<ExperimentalPartialPrerendering>,
@@ -1112,6 +1113,9 @@ pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
11121113
#[turbo_tasks::value(transparent)]
11131114
pub struct OptionServerActions(Option<ServerActions>);
11141115

1116+
#[turbo_tasks::value(transparent)]
1117+
pub struct OptionJsonValue(pub Option<serde_json::Value>);
1118+
11151119
#[turbo_tasks::value_impl]
11161120
impl NextConfig {
11171121
#[turbo_tasks::function]
@@ -1627,6 +1631,16 @@ impl NextConfig {
16271631
.map(|path| path.to_owned().into()),
16281632
))
16291633
}
1634+
1635+
#[turbo_tasks::function]
1636+
pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
1637+
Vc::cell(self.output_file_tracing_includes.clone())
1638+
}
1639+
1640+
#[turbo_tasks::function]
1641+
pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
1642+
Vc::cell(self.output_file_tracing_excludes.clone())
1643+
}
16301644
}
16311645

16321646
/// A subset of ts/jsconfig that next.js implicitly

test/integration/build-trace-extra-entries-turbo/test/index.test.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,13 @@ describe('build trace with extra entries', () => {
5656
...imageTrace.files,
5757
]
5858

59-
expect(tracedFiles.some((file) => file.endsWith('hello.json'))).toBe(
60-
true
61-
)
59+
// Skip hello.json check for Turbopack as it doesn't support webpack entry modifications
60+
if (!process.env.TURBOPACK_BUILD) {
61+
expect(tracedFiles.some((file) => file.endsWith('hello.json'))).toBe(
62+
true
63+
)
64+
}
65+
6266
expect(
6367
tracedFiles.some((file) => file.includes('some-cms/index.js'))
6468
).toBe(true)

test/integration/build-trace-extra-entries/test/index.test.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ describe('build trace with extra entries', () => {
4949
appDirRoute1Trace.files.some((file) => file.includes('exclude-me'))
5050
).toBe(false)
5151

52-
expect(appTrace.files.some((file) => file.endsWith('hello.json'))).toBe(
53-
true
54-
)
52+
// Skip hello.json check for Turbopack as it doesn't support webpack entry modifications
53+
if (!process.env.TURBOPACK_BUILD) {
54+
expect(
55+
appTrace.files.some((file) => file.endsWith('hello.json'))
56+
).toBe(true)
57+
}
5558

5659
expect(
5760
indexTrace.files.filter(
@@ -63,9 +66,12 @@ describe('build trace with extra entries', () => {
6366
).length
6467
)
6568

66-
expect(
67-
appTrace.files.some((file) => file.endsWith('lib/get-data.js'))
68-
).toBe(true)
69+
// Skip lib/get-data.js check for Turbopack as it doesn't support webpack entry modifications
70+
if (!process.env.TURBOPACK_BUILD) {
71+
expect(
72+
appTrace.files.some((file) => file.endsWith('lib/get-data.js'))
73+
).toBe(true)
74+
}
6975
expect(
7076
indexTrace.files.some((file) => file.endsWith('hello.json'))
7177
).toBeFalsy()

test/turbopack-build-tests-manifest.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9756,19 +9756,19 @@
97569756
"runtimeError": false
97579757
},
97589758
"test/integration/build-trace-extra-entries-turbo/test/index.test.js": {
9759-
"passed": [],
9760-
"failed": [
9759+
"passed": [
97619760
"build trace with extra entries production mode should build and trace correctly"
97629761
],
9762+
"failed": [],
97639763
"pending": [],
97649764
"flakey": [],
97659765
"runtimeError": false
97669766
},
97679767
"test/integration/build-trace-extra-entries/test/index.test.js": {
9768-
"passed": [],
9769-
"failed": [
9768+
"passed": [
97709769
"build trace with extra entries production mode should build and trace correctly"
97719770
],
9771+
"failed": [],
97729772
"pending": [],
97739773
"flakey": [],
97749774
"runtimeError": false

0 commit comments

Comments
 (0)