Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Production builds: write endpoints all at once #75304

Merged
merged 26 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0900539
fix hanging bug caused by incorrect active counter handling on stale …
sokra Feb 5, 2025
a0b8cc4
avoid duplicates in output assets list
sokra Feb 6, 2025
600969b
Production builds: write endpoints all at once
wbinnssmith Feb 3, 2025
8449d11
cleanup
wbinnssmith Jan 30, 2025
1b3ba49
Remove requirement for knowledge about runtime in handleRouteType
timneutkens Jan 31, 2025
36302f3
Remove runtime() from interface
timneutkens Jan 31, 2025
1b96632
Remove semaphore as handleRouteType now only loads stuff from the fil…
timneutkens Jan 31, 2025
7069d3e
Prevent recursion
timneutkens Jan 31, 2025
61136db
Update generated type
timneutkens Jan 31, 2025
3ddb46e
Fix lint
timneutkens Feb 2, 2025
3afb50a
Remove rust-side changes for runtime() api that is removed.
timneutkens Feb 2, 2025
14902dd
Run output() in parallel
timneutkens Feb 3, 2025
3a7d342
Fix rust check
timneutkens Feb 3, 2025
7d3e068
add strongly consistent resolve to avoid many recomputations of the task
sokra Feb 6, 2025
8a49fb1
fix passing a bool to task
sokra Feb 6, 2025
1b636e7
remove indirection
sokra Feb 6, 2025
842ff5e
Merge remote-tracking branch 'origin/canary' into wbinnssmith/write-a…
wbinnssmith Feb 19, 2025
44d1cb6
cleanup
wbinnssmith Feb 19, 2025
8dc87ef
Merge remote-tracking branch 'origin/canary' into wbinnssmith/write-a…
wbinnssmith Mar 6, 2025
e26b962
Don't fail builds with only warnings
wbinnssmith Mar 7, 2025
04b862c
Merge remote-tracking branch 'origin/canary' into wbinnssmith/write-a…
wbinnssmith Mar 7, 2025
d94352d
Always include base page routes
wbinnssmith Mar 7, 2025
080488c
update snapshots
wbinnssmith Mar 7, 2025
c1ad738
fixup! update snapshots
wbinnssmith Mar 7, 2025
753a662
Merge remote-tracking branch 'origin/canary' into wbinnssmith/write-a…
wbinnssmith Mar 10, 2025
9c189e1
Batch warnings too
wbinnssmith Mar 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 4 additions & 32 deletions crates/napi/src/next_api/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@ use next_api::{
},
};
use tracing::Instrument;
use turbo_tasks::{get_effects, Completion, Effects, OperationVc, ReadRef, Vc, VcValueType};
use turbopack_core::{
diagnostics::PlainDiagnostic,
error::PrettyPrintError,
issue::{IssueSeverity, PlainIssue},
};
use turbo_tasks::{Completion, Effects, OperationVc, ReadRef, Vc};
use turbopack_core::{diagnostics::PlainDiagnostic, error::PrettyPrintError, issue::PlainIssue};

use super::utils::{
get_diagnostics, get_issues, subscribe, NapiDiagnostic, NapiIssue, RootTask, TurbopackResult,
VcArc,
strongly_consistent_catch_collectables, subscribe, NapiDiagnostic, NapiIssue, RootTask,
TurbopackResult, VcArc,
};

#[napi(object)]
Expand Down Expand Up @@ -99,30 +95,6 @@ impl Deref for ExternalEndpoint {
}
}

// Await the source and return fatal issues if there are any, otherwise
// propagate any actual error results.
async fn strongly_consistent_catch_collectables<R: VcValueType + Send>(
source_op: OperationVc<R>,
) -> Result<(
Option<ReadRef<R>>,
Arc<Vec<ReadRef<PlainIssue>>>,
Arc<Vec<ReadRef<PlainDiagnostic>>>,
Arc<Effects>,
)> {
let result = source_op.read_strongly_consistent().await;
let issues = get_issues(source_op).await?;
let diagnostics = get_diagnostics(source_op).await?;
let effects = Arc::new(get_effects(source_op).await?);

let result = if result.is_err() && issues.iter().any(|i| i.severity <= IssueSeverity::Error) {
None
} else {
Some(result?)
};

Ok((result, issues, diagnostics, effects))
}

#[turbo_tasks::value(serialization = "none")]
struct WrittenEndpointWithIssues {
written: Option<ReadRef<EndpointOutputPaths>>,
Expand Down
195 changes: 153 additions & 42 deletions crates/napi/src/next_api/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use tracing::Instrument;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Registry};
use turbo_rcstr::RcStr;
use turbo_tasks::{
get_effects, Completion, Effects, OperationVc, ReadRef, ResolvedVc, TransientInstance,
UpdateInfo, Vc,
get_effects, Completion, Effects, FxIndexSet, OperationVc, ReadRef, ResolvedVc,
TransientInstance, TryJoinIterExt, UpdateInfo, Vc,
};
use turbo_tasks_fs::{
get_relative_path_to, util::uri_from_file, DiskFileSystem, FileContent, FileSystem,
Expand All @@ -39,6 +39,7 @@ use turbopack_core::{
diagnostics::PlainDiagnostic,
error::PrettyPrintError,
issue::PlainIssue,
output::{OutputAsset, OutputAssets},
source_map::{OptionSourceMap, OptionStringifiedSourceMap, SourceMap, Token},
version::{PartialUpdate, TotalUpdate, Update, VersionState},
PROJECT_FILESYSTEM_NAME, SOURCE_URL_PROTOCOL,
Expand Down Expand Up @@ -549,7 +550,7 @@ pub async fn project_shutdown(

#[napi(object)]
#[derive(Default)]
struct AppPageNapiRoute {
pub struct AppPageNapiRoute {
/// The relative path from project_path to the route file
pub original_name: Option<String>,

Expand All @@ -559,7 +560,7 @@ struct AppPageNapiRoute {

#[napi(object)]
#[derive(Default)]
struct NapiRoute {
pub struct NapiRoute {
/// The router path
pub pathname: String,
/// The relative path from project_path to the route file
Expand Down Expand Up @@ -637,7 +638,7 @@ impl NapiRoute {
}

#[napi(object)]
struct NapiMiddleware {
pub struct NapiMiddleware {
pub endpoint: External<ExternalEndpoint>,
}

Expand All @@ -653,7 +654,7 @@ impl NapiMiddleware {
}

#[napi(object)]
struct NapiInstrumentation {
pub struct NapiInstrumentation {
pub node_js: External<ExternalEndpoint>,
pub edge: External<ExternalEndpoint>,
}
Expand All @@ -677,7 +678,7 @@ impl NapiInstrumentation {
}

#[napi(object)]
struct NapiEntrypoints {
pub struct NapiEntrypoints {
pub routes: Vec<NapiRoute>,
pub middleware: Option<NapiMiddleware>,
pub instrumentation: Option<NapiInstrumentation>,
Expand All @@ -686,6 +687,49 @@ struct NapiEntrypoints {
pub pages_error_endpoint: External<ExternalEndpoint>,
}

impl NapiEntrypoints {
fn from_entrypoints_op(
entrypoints: &EntrypointsOperation,
turbo_tasks: &NextTurboTasks,
) -> Result<Self> {
let routes = entrypoints
.routes
.iter()
.map(|(k, v)| NapiRoute::from_route(k.to_string(), v.clone(), turbo_tasks))
.collect();
let middleware = entrypoints
.middleware
.as_ref()
.map(|m| NapiMiddleware::from_middleware(m, turbo_tasks))
.transpose()?;
let instrumentation = entrypoints
.instrumentation
.as_ref()
.map(|i| NapiInstrumentation::from_instrumentation(i, turbo_tasks))
.transpose()?;
let pages_document_endpoint = External::new(ExternalEndpoint(VcArc::new(
turbo_tasks.clone(),
entrypoints.pages_document_endpoint,
)));
let pages_app_endpoint = External::new(ExternalEndpoint(VcArc::new(
turbo_tasks.clone(),
entrypoints.pages_app_endpoint,
)));
let pages_error_endpoint = External::new(ExternalEndpoint(VcArc::new(
turbo_tasks.clone(),
entrypoints.pages_error_endpoint,
)));
Ok(NapiEntrypoints {
routes,
middleware,
instrumentation,
pages_document_endpoint,
pages_app_endpoint,
pages_error_endpoint,
})
}
}

#[turbo_tasks::value(serialization = "none")]
struct EntrypointsWithIssues {
entrypoints: ReadRef<EntrypointsOperation>,
Expand Down Expand Up @@ -722,6 +766,107 @@ fn project_container_entrypoints_operation(
container.entrypoints()
}

#[turbo_tasks::value(serialization = "none")]
struct AllWrittenEntrypointsWithIssues {
entrypoints: Option<ReadRef<Entrypoints>>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
effects: Arc<Effects>,
}

#[napi]
pub async fn project_write_all_entrypoints_to_disk(
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
app_dir_only: bool,
) -> napi::Result<TurbopackResult<NapiEntrypoints>> {
let turbo_tasks = project.turbo_tasks.clone();
let (entrypoints, issues, diags) = turbo_tasks
.run_once(async move {
let entrypoints_with_issues_op = get_all_written_entrypoints_with_issues_operation(
project.container.to_resolved().await?,
app_dir_only,
);

let EntrypointsWithIssues {
entrypoints,
issues,
diagnostics,
effects,
} = &*entrypoints_with_issues_op
.read_strongly_consistent()
.await?;
effects.apply().await?;

Ok((entrypoints.clone(), issues.clone(), diagnostics.clone()))
})
.await
.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?;

Ok(TurbopackResult {
result: NapiEntrypoints::from_entrypoints_op(&entrypoints, &turbo_tasks)?,
issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(),
diagnostics: diags.iter().map(|d| NapiDiagnostic::from(d)).collect(),
})
}

#[turbo_tasks::function(operation)]
async fn get_all_written_entrypoints_with_issues_operation(
container: ResolvedVc<ProjectContainer>,
app_dir_only: bool,
) -> Result<Vc<EntrypointsWithIssues>> {
let entrypoints_operation = EntrypointsOperation::new(all_entrypoints_write_to_disk_operation(
container,
app_dir_only,
));
let entrypoints = entrypoints_operation.read_strongly_consistent().await?;
let issues = get_issues(entrypoints_operation).await?;
let diagnostics = get_diagnostics(entrypoints_operation).await?;
let effects = Arc::new(get_effects(entrypoints_operation).await?);
Ok(EntrypointsWithIssues {
entrypoints,
issues,
diagnostics,
effects,
}
.cell())
}

#[turbo_tasks::function(operation)]
pub async fn all_entrypoints_write_to_disk_operation(
project: ResolvedVc<ProjectContainer>,
app_dir_only: bool,
) -> Result<Vc<Entrypoints>> {
let _ = project
.project()
.emit_all_output_assets(output_assets_operation(project, app_dir_only))
.resolve()
.await?;

Ok(project.entrypoints())
}

#[turbo_tasks::function(operation)]
async fn output_assets_operation(
container: ResolvedVc<ProjectContainer>,
app_dir_only: bool,
) -> Result<Vc<OutputAssets>> {
let endpoint_assets = container
.project()
.get_all_endpoints(app_dir_only)
.await?
.iter()
.map(|endpoint| async move { endpoint.output().await?.output_assets.await })
.try_join()
.await?;

let mut output_assets: FxIndexSet<ResolvedVc<Box<dyn OutputAsset>>> = FxIndexSet::default();
for assets in endpoint_assets {
output_assets.extend(assets.iter());
}

Ok(Vc::cell(output_assets.into_iter().collect()))
}

#[napi(ts_return_type = "{ __napiType: \"RootTask\" }")]
pub fn project_entrypoints_subscribe(
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
Expand Down Expand Up @@ -753,41 +898,7 @@ pub fn project_entrypoints_subscribe(
let (entrypoints, issues, diags) = ctx.value;

Ok(vec![TurbopackResult {
result: NapiEntrypoints {
routes: entrypoints
.routes
.iter()
.map(|(pathname, route)| {
NapiRoute::from_route(
pathname.clone().into(),
route.clone(),
&turbo_tasks,
)
})
.collect::<Vec<_>>(),
middleware: entrypoints
.middleware
.as_ref()
.map(|m| NapiMiddleware::from_middleware(m, &turbo_tasks))
.transpose()?,
instrumentation: entrypoints
.instrumentation
.as_ref()
.map(|m| NapiInstrumentation::from_instrumentation(m, &turbo_tasks))
.transpose()?,
pages_document_endpoint: External::new(ExternalEndpoint(VcArc::new(
turbo_tasks.clone(),
entrypoints.pages_document_endpoint,
))),
pages_app_endpoint: External::new(ExternalEndpoint(VcArc::new(
turbo_tasks.clone(),
entrypoints.pages_app_endpoint,
))),
pages_error_endpoint: External::new(ExternalEndpoint(VcArc::new(
turbo_tasks.clone(),
entrypoints.pages_error_endpoint,
))),
},
result: NapiEntrypoints::from_entrypoints_op(&entrypoints, &turbo_tasks)?,
issues: issues
.iter()
.map(|issue| NapiIssue::from(&**issue))
Expand Down
32 changes: 29 additions & 3 deletions crates/napi/src/next_api/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use napi::{
use rustc_hash::FxHashMap;
use serde::Serialize;
use turbo_tasks::{
task_statistics::TaskStatisticsApi, trace::TraceRawVcs, OperationVc, ReadRef, TaskId,
TryJoinIterExt, TurboTasks, TurboTasksApi, UpdateInfo, Vc,
get_effects, task_statistics::TaskStatisticsApi, trace::TraceRawVcs, Effects, OperationVc,
ReadRef, TaskId, TryJoinIterExt, TurboTasks, TurboTasksApi, UpdateInfo, Vc, VcValueType,
};
use turbo_tasks_backend::{
default_backing_storage, noop_backing_storage, DefaultBackingStorage, GitVersionInfo,
Expand All @@ -20,7 +20,9 @@ use turbo_tasks_fs::FileContent;
use turbopack_core::{
diagnostics::{Diagnostic, DiagnosticContextExt, PlainDiagnostic},
error::PrettyPrintError,
issue::{IssueDescriptionExt, PlainIssue, PlainIssueSource, PlainSource, StyledString},
issue::{
IssueDescriptionExt, IssueSeverity, PlainIssue, PlainIssueSource, PlainSource, StyledString,
},
source_pos::SourcePos,
};

Expand Down Expand Up @@ -481,3 +483,27 @@ pub fn subscribe<T: 'static + Send + Sync, F: Future<Output = Result<T>> + Send,
task_id: Some(task_id),
}))
}

// Await the source and return fatal issues if there are any, otherwise
// propagate any actual error results.
pub async fn strongly_consistent_catch_collectables<R: VcValueType + Send>(
source_op: OperationVc<R>,
) -> Result<(
Option<ReadRef<R>>,
Arc<Vec<ReadRef<PlainIssue>>>,
Arc<Vec<ReadRef<PlainDiagnostic>>>,
Arc<Effects>,
)> {
let result = source_op.read_strongly_consistent().await;
let issues = get_issues(source_op).await?;
let diagnostics = get_diagnostics(source_op).await?;
let effects = Arc::new(get_effects(source_op).await?);

let result = if result.is_err() && issues.iter().any(|i| i.severity <= IssueSeverity::Error) {
None
} else {
Some(result?)
};

Ok((result, issues, diagnostics, effects))
}
8 changes: 1 addition & 7 deletions crates/next-api/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,13 @@ pub struct EntrypointsOperation {
pub pages_error_endpoint: OperationVc<Box<dyn Endpoint>>,
}

/// HACK: Wraps an `OperationVc<Entrypoints>` inside of a second `OperationVc`.
#[turbo_tasks::function(operation)]
fn entrypoints_wrapper(entrypoints: OperationVc<Entrypoints>) -> Vc<Entrypoints> {
entrypoints.connect()
}

/// Removes diagnostics, issues, and effects from the top-level `entrypoints` operation so that
/// they're not duplicated across many different individual entrypoints or routes.
#[turbo_tasks::function(operation)]
async fn entrypoints_without_collectibles_operation(
entrypoints: OperationVc<Entrypoints>,
) -> Result<Vc<Entrypoints>> {
let entrypoints = entrypoints_wrapper(entrypoints);
let _ = entrypoints.resolve_strongly_consistent().await?;
let _ = entrypoints.take_collectibles::<Box<dyn Diagnostic>>();
let _ = entrypoints.take_issues_with_path().await?;
let _ = get_effects(entrypoints).await?;
Expand Down
Loading
Loading