Skip to content

Commit

Permalink
fix server-side changes events (vercel#55437)
Browse files Browse the repository at this point in the history
### What?

fix the problems that caused sideSideChanges to be emitted for client
side changes

### Why?

### How?


Closes WEB-1578
  • Loading branch information
sokra committed Sep 15, 2023
1 parent b02946c commit b3a916c
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 98 deletions.
181 changes: 98 additions & 83 deletions packages/next-swc/crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -487,15 +486,19 @@ impl AppEndpoint {
async fn output(self: Vc<Self>) -> Result<Vc<AppEndpointOutput>> {
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();
Expand Down Expand Up @@ -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<FileSystemPath>,
Expand Down Expand Up @@ -897,13 +904,21 @@ impl Endpoint for AppEndpoint {
}

#[turbo_tasks::function]
fn server_changed(self: Vc<Self>) -> Vc<Completion> {
any_content_changed_of_output_assets(self.output().server_assets())
async fn server_changed(self: Vc<Self>) -> Result<Vc<Completion>> {
Ok(self
.await?
.app_project
.project()
.server_changed(self.output().server_assets()))
}

#[turbo_tasks::function]
fn client_changed(self: Vc<Self>) -> Vc<Completion> {
any_content_changed_of_output_assets(self.output().client_assets())
async fn client_changed(self: Vc<Self>) -> Result<Vc<Completion>> {
Ok(self
.await?
.app_project
.project()
.client_changed(self.output().client_assets()))
}
}

Expand Down
5 changes: 2 additions & 3 deletions packages/next-swc/crates/next-api/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -209,8 +208,8 @@ impl Endpoint for MiddlewareEndpoint {
}

#[turbo_tasks::function]
fn server_changed(self: Vc<Self>) -> Vc<Completion> {
any_content_changed_of_output_assets(self.output_assets())
async fn server_changed(self: Vc<Self>) -> Result<Vc<Completion>> {
Ok(self.await?.project.server_changed(self.output_assets()))
}

#[turbo_tasks::function]
Expand Down
17 changes: 12 additions & 5 deletions packages/next-swc/crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -944,13 +943,21 @@ impl Endpoint for PageEndpoint {
}

#[turbo_tasks::function]
fn server_changed(self: Vc<Self>) -> Vc<Completion> {
any_content_changed_of_output_assets(self.output().server_assets())
async fn server_changed(self: Vc<Self>) -> Result<Vc<Completion>> {
Ok(self
.await?
.pages_project
.project()
.server_changed(self.output().server_assets()))
}

#[turbo_tasks::function]
fn client_changed(self: Vc<Self>) -> Vc<Completion> {
any_content_changed_of_output_assets(self.output().client_assets())
async fn client_changed(self: Vc<Self>) -> Result<Vc<Completion>> {
Ok(self
.await?
.pages_project
.project()
.client_changed(self.output().client_assets()))
}
}

Expand Down
59 changes: 56 additions & 3 deletions packages/next-swc/crates/next-api/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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,
Expand Down Expand Up @@ -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<Self>, roots: Vc<OutputAssets>) -> Vc<Completion> {
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<Self>, roots: Vc<OutputAssets>) -> Vc<Completion> {
let path = self.client_root();
any_output_changed(roots, path)
}
}

#[turbo_tasks::function]
async fn any_output_changed(
roots: Vc<OutputAssets>,
path: Vc<FileSystemPath>,
) -> Result<Vc<Completion>> {
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::<Completions>::cell(completions).completed())
}

async fn get_referenced_output_assets(
parent: Vc<Box<dyn OutputAsset>>,
) -> Result<impl Iterator<Item = Vc<Box<dyn OutputAsset>>> + Send> {
Ok(parent.references().await?.clone_value().into_iter())
}

#[turbo_tasks::function]
Expand Down
6 changes: 2 additions & 4 deletions test/development/basic/next-rs-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -381,8 +380,7 @@ describe('next.rs api', () => {
file: 'app/app/client.ts',
content: '"use client";\nexport default () => <div>hello world2</div>',
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',
Expand Down

0 comments on commit b3a916c

Please sign in to comment.