diff --git a/packages/next-swc/crates/next-api/src/app.rs b/packages/next-swc/crates/next-api/src/app.rs index 9fe4e8c66a018..da21b09f6a8bd 100644 --- a/packages/next-swc/crates/next-api/src/app.rs +++ b/packages/next-swc/crates/next-api/src/app.rs @@ -40,7 +40,6 @@ use turbopack_binding::{ turbopack::{ core::{ asset::{Asset, AssetContent}, - changed::any_content_changed_of_output_assets, chunk::{ChunkableModule, ChunkingContext, EvaluatableAssets}, file_source::FileSource, output::{OutputAsset, OutputAssets}, @@ -487,15 +486,19 @@ impl AppEndpoint { async fn output(self: Vc) -> Result> { let this = self.await?; - let (app_entry, ty) = match this.ty { - AppEndpointType::Page { ty: _, loader_tree } => { - (self.app_page_entry(loader_tree), "page") - } + let (app_entry, ty, ssr_and_client) = match this.ty { + AppEndpointType::Page { ty, loader_tree } => ( + self.app_page_entry(loader_tree), + "page", + matches!(ty, AppPageEndpointType::Html), + ), // NOTE(alexkirsz) For routes, technically, a lot of the following code is not needed, // as we know we won't have any client references. However, for now, for simplicity's // sake, we just do the same thing as for pages. - AppEndpointType::Route { path } => (self.app_route_entry(path), "route"), - AppEndpointType::Metadata { metadata } => (self.app_metadata_entry(metadata), "route"), + AppEndpointType::Route { path } => (self.app_route_entry(path), "route", false), + AppEndpointType::Metadata { metadata } => { + (self.app_metadata_entry(metadata), "route", false) + } }; let node_root = this.app_project.project().node_root(); @@ -562,82 +565,86 @@ impl AppEndpoint { .entry(Vc::upcast(app_entry.rsc_entry)) .await?; - let client_references_chunks = get_app_client_references_chunks( - client_reference_types, - this.app_project.project().client_chunking_context(), - this.app_project.project().ssr_chunking_context(), - ); - let client_references_chunks_ref = client_references_chunks.await?; - - let mut entry_client_chunks = vec![]; - // TODO(alexkirsz) In which manifest does this go? - let mut entry_ssr_chunks = vec![]; - for client_reference in app_entry_client_references.iter() { - let client_reference_chunks = client_references_chunks_ref - .get(client_reference.ty()) - .expect("client reference should have corresponding chunks"); - entry_client_chunks - .extend(client_reference_chunks.client_chunks.await?.iter().copied()); - entry_ssr_chunks.extend(client_reference_chunks.ssr_chunks.await?.iter().copied()); - } + if ssr_and_client { + let client_references_chunks = get_app_client_references_chunks( + client_reference_types, + this.app_project.project().client_chunking_context(), + this.app_project.project().ssr_chunking_context(), + ); + let client_references_chunks_ref = client_references_chunks.await?; + + let mut entry_client_chunks = vec![]; + // TODO(alexkirsz) In which manifest does this go? + let mut entry_ssr_chunks = vec![]; + for client_reference in app_entry_client_references.iter() { + let client_reference_chunks = client_references_chunks_ref + .get(client_reference.ty()) + .expect("client reference should have corresponding chunks"); + entry_client_chunks + .extend(client_reference_chunks.client_chunks.await?.iter().copied()); + entry_ssr_chunks.extend(client_reference_chunks.ssr_chunks.await?.iter().copied()); + } - client_assets.extend(entry_client_chunks.iter().copied()); - server_assets.extend(entry_ssr_chunks.iter().copied()); + client_assets.extend(entry_client_chunks.iter().copied()); + server_assets.extend(entry_ssr_chunks.iter().copied()); - let entry_client_chunks_paths = entry_client_chunks - .iter() - .map(|chunk| chunk.ident().path()) - .try_join() - .await?; - let mut entry_client_chunks_paths: Vec<_> = entry_client_chunks_paths - .iter() - .map(|path| { - client_relative_path_ref - .get_path_to(path) - .expect("asset path should be inside client root") - .to_string() - }) - .collect(); - entry_client_chunks_paths.extend(client_shared_chunks_paths.iter().cloned()); + let entry_client_chunks_paths = entry_client_chunks + .iter() + .map(|chunk| chunk.ident().path()) + .try_join() + .await?; + let mut entry_client_chunks_paths: Vec<_> = entry_client_chunks_paths + .iter() + .map(|path| { + client_relative_path_ref + .get_path_to(path) + .expect("asset path should be inside client root") + .to_string() + }) + .collect(); + entry_client_chunks_paths.extend(client_shared_chunks_paths.iter().cloned()); - let app_build_manifest = AppBuildManifest { - pages: [(app_entry.original_name.clone(), entry_client_chunks_paths)] - .into_iter() - .collect(), - }; - let manifest_path_prefix = get_asset_prefix_from_pathname(&app_entry.pathname); - let app_build_manifest_output = Vc::upcast(VirtualOutputAsset::new( - node_root.join(format!( - "server/app{manifest_path_prefix}/{ty}/app-build-manifest.json", - )), - AssetContent::file( - File::from(serde_json::to_string_pretty(&app_build_manifest)?).into(), - ), - )); - server_assets.push(app_build_manifest_output); + let app_build_manifest = AppBuildManifest { + pages: [(app_entry.original_name.clone(), entry_client_chunks_paths)] + .into_iter() + .collect(), + }; + let manifest_path_prefix = get_asset_prefix_from_pathname(&app_entry.pathname); + let app_build_manifest_output = Vc::upcast(VirtualOutputAsset::new( + node_root.join(format!( + "server/app{manifest_path_prefix}/{ty}/app-build-manifest.json", + )), + AssetContent::file( + File::from(serde_json::to_string_pretty(&app_build_manifest)?).into(), + ), + )); + server_assets.push(app_build_manifest_output); - let build_manifest = BuildManifest { - root_main_files: client_shared_chunks_paths, - ..Default::default() - }; - let build_manifest_output = Vc::upcast(VirtualOutputAsset::new( - node_root.join(format!( - "server/app{manifest_path_prefix}/{ty}/build-manifest.json", - )), - AssetContent::file(File::from(serde_json::to_string_pretty(&build_manifest)?).into()), - )); - server_assets.push(build_manifest_output); - - let entry_manifest = ClientReferenceManifest::build_output( - node_root, - client_relative_path, - app_entry.original_name.clone(), - client_references, - client_references_chunks, - this.app_project.project().client_chunking_context(), - Vc::upcast(this.app_project.project().ssr_chunking_context()), - ); - server_assets.push(entry_manifest); + let build_manifest = BuildManifest { + root_main_files: client_shared_chunks_paths, + ..Default::default() + }; + let build_manifest_output = Vc::upcast(VirtualOutputAsset::new( + node_root.join(format!( + "server/app{manifest_path_prefix}/{ty}/build-manifest.json", + )), + AssetContent::file( + File::from(serde_json::to_string_pretty(&build_manifest)?).into(), + ), + )); + server_assets.push(build_manifest_output); + + let entry_manifest = ClientReferenceManifest::build_output( + node_root, + client_relative_path, + app_entry.original_name.clone(), + client_references, + client_references_chunks, + this.app_project.project().client_chunking_context(), + Vc::upcast(this.app_project.project().ssr_chunking_context()), + ); + server_assets.push(entry_manifest); + } fn create_app_paths_manifest( node_root: Vc, @@ -897,13 +904,21 @@ impl Endpoint for AppEndpoint { } #[turbo_tasks::function] - fn server_changed(self: Vc) -> Vc { - any_content_changed_of_output_assets(self.output().server_assets()) + async fn server_changed(self: Vc) -> Result> { + Ok(self + .await? + .app_project + .project() + .server_changed(self.output().server_assets())) } #[turbo_tasks::function] - fn client_changed(self: Vc) -> Vc { - any_content_changed_of_output_assets(self.output().client_assets()) + async fn client_changed(self: Vc) -> Result> { + Ok(self + .await? + .app_project + .project() + .client_changed(self.output().client_assets())) } } diff --git a/packages/next-swc/crates/next-api/src/middleware.rs b/packages/next-swc/crates/next-api/src/middleware.rs index a2f365fbafa3a..3de3f0345a12f 100644 --- a/packages/next-swc/crates/next-api/src/middleware.rs +++ b/packages/next-swc/crates/next-api/src/middleware.rs @@ -14,7 +14,6 @@ use turbopack_binding::{ turbopack::{ core::{ asset::AssetContent, - changed::any_content_changed_of_output_assets, chunk::{ChunkableModule, ChunkingContext}, context::AssetContext, module::Module, @@ -209,8 +208,8 @@ impl Endpoint for MiddlewareEndpoint { } #[turbo_tasks::function] - fn server_changed(self: Vc) -> Vc { - any_content_changed_of_output_assets(self.output_assets()) + async fn server_changed(self: Vc) -> Result> { + Ok(self.await?.project.server_changed(self.output_assets())) } #[turbo_tasks::function] diff --git a/packages/next-swc/crates/next-api/src/pages.rs b/packages/next-swc/crates/next-api/src/pages.rs index cebb932770913..a744d2a1c9d6e 100644 --- a/packages/next-swc/crates/next-api/src/pages.rs +++ b/packages/next-swc/crates/next-api/src/pages.rs @@ -37,7 +37,6 @@ use turbopack_binding::{ build::BuildChunkingContext, core::{ asset::AssetContent, - changed::any_content_changed_of_output_assets, chunk::{ChunkableModule, ChunkingContext, EvaluatableAssets}, context::AssetContext, file_source::FileSource, @@ -944,13 +943,21 @@ impl Endpoint for PageEndpoint { } #[turbo_tasks::function] - fn server_changed(self: Vc) -> Vc { - any_content_changed_of_output_assets(self.output().server_assets()) + async fn server_changed(self: Vc) -> Result> { + Ok(self + .await? + .pages_project + .project() + .server_changed(self.output().server_assets())) } #[turbo_tasks::function] - fn client_changed(self: Vc) -> Vc { - any_content_changed_of_output_assets(self.output().client_assets()) + async fn client_changed(self: Vc) -> Result> { + Ok(self + .await? + .pages_project + .project() + .client_changed(self.output().client_assets())) } } diff --git a/packages/next-swc/crates/next-api/src/project.rs b/packages/next-swc/crates/next-api/src/project.rs index 1c2e71d5646a2..ce93149ab885e 100644 --- a/packages/next-swc/crates/next-api/src/project.rs +++ b/packages/next-swc/crates/next-api/src/project.rs @@ -19,8 +19,11 @@ use next_core::{ }; use serde::{Deserialize, Serialize}; use turbo_tasks::{ - debug::ValueDebugFormat, trace::TraceRawVcs, Completion, IntoTraitRef, State, TaskInput, - TransientInstance, Value, Vc, + debug::ValueDebugFormat, + graph::{AdjacencyMap, GraphTraversal}, + trace::TraceRawVcs, + Completion, Completions, IntoTraitRef, State, TaskInput, TransientInstance, TryFlatJoinIterExt, + Value, Vc, }; use turbopack_binding::{ turbo::{ @@ -30,13 +33,14 @@ use turbopack_binding::{ turbopack::{ build::BuildChunkingContext, core::{ + changed::content_changed, chunk::ChunkingContext, compile_time_info::CompileTimeInfo, context::AssetContext, diagnostics::DiagnosticExt, environment::ServerAddr, file_source::FileSource, - output::OutputAssets, + output::{OutputAsset, OutputAssets}, reference_type::{EntryReferenceSubType, ReferenceType}, resolve::{find_context_file, FindContextFileResult}, source::Source, @@ -657,6 +661,55 @@ impl Project { .versioned_content_map .keys_in_path(self.client_relative_path())) } + + /// Completion when server side changes are detected in output assets + /// referenced from the roots + #[turbo_tasks::function] + pub fn server_changed(self: Vc, roots: Vc) -> Vc { + let path = self.node_root(); + any_output_changed(roots, path) + } + + /// Completion when client side changes are detected in output assets + /// referenced from the roots + #[turbo_tasks::function] + pub fn client_changed(self: Vc, roots: Vc) -> Vc { + let path = self.client_root(); + any_output_changed(roots, path) + } +} + +#[turbo_tasks::function] +async fn any_output_changed( + roots: Vc, + path: Vc, +) -> Result> { + let path = &path.await?; + let completions = AdjacencyMap::new() + .skip_duplicates() + .visit(roots.await?.iter().copied(), get_referenced_output_assets) + .await + .completed()? + .into_inner() + .into_reverse_topological() + .map(|m| async move { + let asset_path = m.ident().path().await?; + if !asset_path.path.ends_with(".map") && asset_path.is_inside_ref(path) { + Ok(Some(content_changed(Vc::upcast(m)))) + } else { + Ok(None) + } + }) + .try_flat_join() + .await?; + + Ok(Vc::::cell(completions).completed()) +} + +async fn get_referenced_output_assets( + parent: Vc>, +) -> Result>> + Send> { + Ok(parent.references().await?.clone_value().into_iter()) } #[turbo_tasks::function] diff --git a/test/development/basic/next-rs-api.test.ts b/test/development/basic/next-rs-api.test.ts index d83833d64eb21..c8b0e604152b1 100644 --- a/test/development/basic/next-rs-api.test.ts +++ b/test/development/basic/next-rs-api.test.ts @@ -353,8 +353,7 @@ describe('next.rs api', () => { file: 'pages/index.js', content: pagesIndexCode('hello world2'), expectedUpdate: '/pages/index.js', - // TODO(sokra) this should be false, but source maps change on server side - expectedServerSideChange: true, + expectedServerSideChange: false, }, { name: 'server-side change on a page', @@ -381,8 +380,7 @@ describe('next.rs api', () => { file: 'app/app/client.ts', content: '"use client";\nexport default () =>
hello world2
', expectedUpdate: '/app/app/client.ts', - // TODO(sokra) this should be false, not sure why it's true - expectedServerSideChange: true, + expectedServerSideChange: false, }, { name: 'server-side change on a app page',