Skip to content

Commit 9f72146

Browse files
fix(turbopack): don't emit issues for deleted pages (#62012)
### What? We now keep track of all client assets emitted for each route in `ClientAssetMapper` and remove all issues associated with them when a route get deleted. Fixes #61384 Closes PACK-2487
1 parent 603b377 commit 9f72146

File tree

20 files changed

+654
-328
lines changed

20 files changed

+654
-328
lines changed

packages/next-swc/crates/napi/src/next_api/endpoint.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::{ops::Deref, sync::Arc};
33
use anyhow::Result;
44
use napi::{bindgen_prelude::External, JsFunction};
55
use next_api::{
6+
paths::ServerPath,
67
route::{Endpoint, WrittenEndpoint},
7-
server_paths::ServerPath,
88
};
99
use tracing::Instrument;
1010
use turbo_tasks::{ReadRef, Vc};
@@ -28,10 +28,10 @@ pub struct NapiServerPath {
2828
pub content_hash: String,
2929
}
3030

31-
impl From<&ServerPath> for NapiServerPath {
32-
fn from(server_path: &ServerPath) -> Self {
31+
impl From<ServerPath> for NapiServerPath {
32+
fn from(server_path: ServerPath) -> Self {
3333
Self {
34-
path: server_path.path.clone(),
34+
path: server_path.path,
3535
content_hash: format!("{:x}", server_path.content_hash),
3636
}
3737
}
@@ -42,25 +42,32 @@ impl From<&ServerPath> for NapiServerPath {
4242
pub struct NapiWrittenEndpoint {
4343
pub r#type: String,
4444
pub entry_path: Option<String>,
45-
pub server_paths: Option<Vec<NapiServerPath>>,
45+
pub client_paths: Vec<String>,
46+
pub server_paths: Vec<NapiServerPath>,
4647
pub config: NapiEndpointConfig,
4748
}
4849

49-
impl From<&WrittenEndpoint> for NapiWrittenEndpoint {
50-
fn from(written_endpoint: &WrittenEndpoint) -> Self {
50+
impl From<WrittenEndpoint> for NapiWrittenEndpoint {
51+
fn from(written_endpoint: WrittenEndpoint) -> Self {
5152
match written_endpoint {
5253
WrittenEndpoint::NodeJs {
5354
server_entry_path,
5455
server_paths,
56+
client_paths,
5557
} => Self {
5658
r#type: "nodejs".to_string(),
57-
entry_path: Some(server_entry_path.clone()),
58-
server_paths: Some(server_paths.iter().map(From::from).collect()),
59+
entry_path: Some(server_entry_path),
60+
client_paths,
61+
server_paths: server_paths.into_iter().map(From::from).collect(),
5962
..Default::default()
6063
},
61-
WrittenEndpoint::Edge { server_paths } => Self {
64+
WrittenEndpoint::Edge {
65+
server_paths,
66+
client_paths,
67+
} => Self {
6268
r#type: "edge".to_string(),
63-
server_paths: Some(server_paths.iter().map(From::from).collect()),
69+
client_paths,
70+
server_paths: server_paths.into_iter().map(From::from).collect(),
6471
..Default::default()
6572
},
6673
}
@@ -127,7 +134,7 @@ pub async fn endpoint_write_to_disk(
127134
.await
128135
.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?;
129136
Ok(TurbopackResult {
130-
result: NapiWrittenEndpoint::from(&*written),
137+
result: NapiWrittenEndpoint::from(written.clone_value()),
131138
issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(),
132139
diagnostics: diags.iter().map(|d| NapiDiagnostic::from(d)).collect(),
133140
})

packages/next-swc/crates/next-api/src/app.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ use crate::{
6363
DynamicImportedChunks,
6464
},
6565
font::create_font_manifest,
66-
middleware::{get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings},
66+
paths::{
67+
all_paths_in_root, all_server_paths, get_js_paths_from_root, get_wasm_paths_from_root,
68+
wasm_paths_to_bindings,
69+
},
6770
project::Project,
6871
route::{AppPageRoute, Endpoint, Route, Routes, WrittenEndpoint},
6972
server_actions::create_server_actions_manifest,
70-
server_paths::all_server_paths,
7173
};
7274

7375
#[turbo_tasks::value]
@@ -1110,25 +1112,34 @@ impl Endpoint for AppEndpoint {
11101112

11111113
let node_root_ref = &node_root.await?;
11121114

1113-
let node_root = this.app_project.project().node_root();
11141115
this.app_project
11151116
.project()
11161117
.emit_all_output_assets(Vc::cell(output_assets))
11171118
.await?;
11181119

1120+
let node_root = this.app_project.project().node_root();
11191121
let server_paths = all_server_paths(output_assets, node_root)
11201122
.await?
11211123
.clone_value();
11221124

1125+
let client_relative_root = this.app_project.project().client_relative_path();
1126+
let client_paths = all_paths_in_root(output_assets, client_relative_root)
1127+
.await?
1128+
.clone_value();
1129+
11231130
let written_endpoint = match *output.await? {
11241131
AppEndpointOutput::NodeJs { rsc_chunk, .. } => WrittenEndpoint::NodeJs {
11251132
server_entry_path: node_root_ref
11261133
.get_path_to(&*rsc_chunk.ident().path().await?)
11271134
.context("Node.js chunk entry path must be inside the node root")?
11281135
.to_string(),
11291136
server_paths,
1137+
client_paths,
1138+
},
1139+
AppEndpointOutput::Edge { .. } => WrittenEndpoint::Edge {
1140+
server_paths,
1141+
client_paths,
11301142
},
1131-
AppEndpointOutput::Edge { .. } => WrittenEndpoint::Edge { server_paths },
11321143
};
11331144
Ok(written_endpoint.cell())
11341145
}

packages/next-swc/crates/next-api/src/font.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use turbopack_binding::{
1010
},
1111
};
1212

13-
use crate::middleware::get_font_paths_from_root;
13+
use crate::paths::get_font_paths_from_root;
1414

1515
pub(crate) async fn create_font_manifest(
1616
client_root: Vc<FileSystemPath>,

packages/next-swc/crates/next-api/src/instrumentation.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ use turbopack_binding::{
2626
};
2727

2828
use crate::{
29-
middleware::{get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings},
29+
paths::{
30+
all_server_paths, get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings,
31+
},
3032
project::Project,
3133
route::{Endpoint, WrittenEndpoint},
32-
server_paths::all_server_paths,
3334
};
3435

3536
#[turbo_tasks::value]
@@ -202,7 +203,11 @@ impl Endpoint for InstrumentationEndpoint {
202203
.await?
203204
.clone_value();
204205

205-
Ok(WrittenEndpoint::Edge { server_paths }.cell())
206+
Ok(WrittenEndpoint::Edge {
207+
server_paths,
208+
client_paths: vec![],
209+
}
210+
.cell())
206211
}
207212
.instrument(span)
208213
.await

packages/next-swc/crates/next-api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ mod font;
88
mod instrumentation;
99
mod middleware;
1010
mod pages;
11+
pub mod paths;
1112
pub mod project;
1213
pub mod route;
1314
mod server_actions;
14-
pub mod server_paths;
1515
mod versioned_content_map;
1616

1717
// Declare build-time information variables generated in build.rs

packages/next-swc/crates/next-api/src/middleware.rs

Lines changed: 19 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@ use next_core::{
33
all_assets_from_entries,
44
middleware::get_middleware_module,
55
next_edge::entry::wrap_edge_entry,
6-
next_manifests::{
7-
AssetBinding, EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2,
8-
},
6+
next_manifests::{EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2},
97
next_server::{get_server_runtime_entries, ServerContextType},
108
util::parse_config_from_source,
119
};
1210
use tracing::Instrument;
13-
use turbo_tasks::{Completion, TryFlatJoinIterExt, Value, Vc};
11+
use turbo_tasks::{Completion, Value, Vc};
1412
use turbopack_binding::{
15-
turbo::tasks_fs::{File, FileContent, FileSystemPath},
13+
turbo::tasks_fs::{File, FileContent},
1614
turbopack::{
1715
core::{
1816
asset::AssetContent,
1917
chunk::{availability_info::AvailabilityInfo, ChunkingContextExt},
2018
context::AssetContext,
2119
module::Module,
22-
output::{OutputAsset, OutputAssets},
20+
output::OutputAssets,
2321
reference_type::{EntryReferenceSubType, ReferenceType},
2422
source::Source,
2523
virtual_output::VirtualOutputAsset,
@@ -29,9 +27,12 @@ use turbopack_binding::{
2927
};
3028

3129
use crate::{
30+
paths::{
31+
all_paths_in_root, all_server_paths, get_js_paths_from_root, get_wasm_paths_from_root,
32+
wasm_paths_to_bindings,
33+
},
3234
project::Project,
3335
route::{Endpoint, WrittenEndpoint},
34-
server_paths::all_server_paths,
3536
};
3637

3738
#[turbo_tasks::value]
@@ -197,7 +198,17 @@ impl Endpoint for MiddlewareEndpoint {
197198
.await?
198199
.clone_value();
199200

200-
Ok(WrittenEndpoint::Edge { server_paths }.cell())
201+
// Middleware could in theory have a client path (e.g. `new URL`).
202+
let client_relative_root = this.project.client_relative_path();
203+
let client_paths = all_paths_in_root(output_assets, client_relative_root)
204+
.await?
205+
.clone_value();
206+
207+
Ok(WrittenEndpoint::Edge {
208+
server_paths,
209+
client_paths,
210+
}
211+
.cell())
201212
}
202213
.instrument(span)
203214
.await
@@ -213,94 +224,3 @@ impl Endpoint for MiddlewareEndpoint {
213224
Completion::immutable()
214225
}
215226
}
216-
217-
pub(crate) async fn get_paths_from_root(
218-
root: &FileSystemPath,
219-
output_assets: &[Vc<Box<dyn OutputAsset>>],
220-
filter: impl FnOnce(&str) -> bool + Copy,
221-
) -> Result<Vec<String>> {
222-
output_assets
223-
.iter()
224-
.map({
225-
move |&file| async move {
226-
let path = &*file.ident().path().await?;
227-
let Some(relative) = root.get_path_to(path) else {
228-
return Ok(None);
229-
};
230-
231-
Ok(if filter(relative) {
232-
Some(relative.to_string())
233-
} else {
234-
None
235-
})
236-
}
237-
})
238-
.try_flat_join()
239-
.await
240-
}
241-
242-
pub(crate) async fn get_js_paths_from_root(
243-
root: &FileSystemPath,
244-
output_assets: &[Vc<Box<dyn OutputAsset>>],
245-
) -> Result<Vec<String>> {
246-
get_paths_from_root(root, output_assets, |path| path.ends_with(".js")).await
247-
}
248-
249-
pub(crate) async fn get_wasm_paths_from_root(
250-
root: &FileSystemPath,
251-
output_assets: &[Vc<Box<dyn OutputAsset>>],
252-
) -> Result<Vec<String>> {
253-
get_paths_from_root(root, output_assets, |path| path.ends_with(".wasm")).await
254-
}
255-
256-
pub(crate) async fn get_font_paths_from_root(
257-
root: &FileSystemPath,
258-
output_assets: &[Vc<Box<dyn OutputAsset>>],
259-
) -> Result<Vec<String>> {
260-
get_paths_from_root(root, output_assets, |path| {
261-
path.ends_with(".woff")
262-
|| path.ends_with(".woff2")
263-
|| path.ends_with(".eot")
264-
|| path.ends_with(".ttf")
265-
|| path.ends_with(".otf")
266-
})
267-
.await
268-
}
269-
270-
fn get_file_stem(path: &str) -> &str {
271-
let file_name = if let Some((_, file_name)) = path.rsplit_once('/') {
272-
file_name
273-
} else {
274-
path
275-
};
276-
277-
if let Some((stem, _)) = file_name.split_once('.') {
278-
if stem.is_empty() {
279-
file_name
280-
} else {
281-
stem
282-
}
283-
} else {
284-
file_name
285-
}
286-
}
287-
288-
pub(crate) fn wasm_paths_to_bindings(paths: Vec<String>) -> Vec<AssetBinding> {
289-
paths
290-
.into_iter()
291-
.map(|path| {
292-
let stem = get_file_stem(&path);
293-
294-
// very simple escaping just replacing unsupported characters with `_`
295-
let escaped = stem.replace(
296-
|c: char| !c.is_ascii_alphanumeric() && c != '$' && c != '_',
297-
"_",
298-
);
299-
300-
AssetBinding {
301-
name: format!("wasm_{}", escaped),
302-
file_path: path,
303-
}
304-
})
305-
.collect()
306-
}

packages/next-swc/crates/next-api/src/pages.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@ use crate::{
7272
DynamicImportedChunks,
7373
},
7474
font::create_font_manifest,
75-
middleware::{get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings},
75+
paths::{
76+
all_paths_in_root, all_server_paths, get_js_paths_from_root, get_wasm_paths_from_root,
77+
wasm_paths_to_bindings,
78+
},
7679
project::Project,
7780
route::{Endpoint, Route, Routes, WrittenEndpoint},
78-
server_paths::all_server_paths,
7981
};
8082

8183
#[turbo_tasks::value]
@@ -1171,6 +1173,11 @@ impl Endpoint for PageEndpoint {
11711173
.await?
11721174
.clone_value();
11731175

1176+
let client_relative_root = this.pages_project.project().client_relative_path();
1177+
let client_paths = all_paths_in_root(output_assets, client_relative_root)
1178+
.await?
1179+
.clone_value();
1180+
11741181
let node_root = &node_root.await?;
11751182
let written_endpoint = match *output.await? {
11761183
PageEndpointOutput::NodeJs { entry_chunk, .. } => WrittenEndpoint::NodeJs {
@@ -1179,8 +1186,12 @@ impl Endpoint for PageEndpoint {
11791186
.context("ssr chunk entry path must be inside the node root")?
11801187
.to_string(),
11811188
server_paths,
1189+
client_paths,
1190+
},
1191+
PageEndpointOutput::Edge { .. } => WrittenEndpoint::Edge {
1192+
server_paths,
1193+
client_paths,
11821194
},
1183-
PageEndpointOutput::Edge { .. } => WrittenEndpoint::Edge { server_paths },
11841195
};
11851196

11861197
Ok(written_endpoint.cell())

0 commit comments

Comments
 (0)