diff --git a/crates/napi/src/next_api/endpoint.rs b/crates/napi/src/next_api/endpoint.rs index 22d8272d5659b..34d8204b804f1 100644 --- a/crates/napi/src/next_api/endpoint.rs +++ b/crates/napi/src/next_api/endpoint.rs @@ -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)] @@ -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( - source_op: OperationVc, -) -> Result<( - Option>, - Arc>>, - Arc>>, - Arc, -)> { - 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>, diff --git a/crates/napi/src/next_api/project.rs b/crates/napi/src/next_api/project.rs index ad8f9b0e110b2..095af520189aa 100644 --- a/crates/napi/src/next_api/project.rs +++ b/crates/napi/src/next_api/project.rs @@ -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, @@ -39,6 +39,7 @@ use turbopack_core::{ diagnostics::PlainDiagnostic, error::PrettyPrintError, issue::PlainIssue, + output::{OutputAsset, OutputAssets}, source_map::{SourceMap, Token}, version::{PartialUpdate, TotalUpdate, Update, VersionState}, SOURCE_MAP_PREFIX, @@ -537,7 +538,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, @@ -547,7 +548,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 @@ -625,7 +626,7 @@ impl NapiRoute { } #[napi(object)] -struct NapiMiddleware { +pub struct NapiMiddleware { pub endpoint: External, } @@ -641,7 +642,7 @@ impl NapiMiddleware { } #[napi(object)] -struct NapiInstrumentation { +pub struct NapiInstrumentation { pub node_js: External, pub edge: External, } @@ -665,7 +666,7 @@ impl NapiInstrumentation { } #[napi(object)] -struct NapiEntrypoints { +pub struct NapiEntrypoints { pub routes: Vec, pub middleware: Option, pub instrumentation: Option, @@ -674,6 +675,49 @@ struct NapiEntrypoints { pub pages_error_endpoint: External, } +impl NapiEntrypoints { + fn from_entrypoints_op( + entrypoints: &EntrypointsOperation, + turbo_tasks: &NextTurboTasks, + ) -> Result { + 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, @@ -710,6 +754,109 @@ fn project_container_entrypoints_operation( container.entrypoints() } +#[turbo_tasks::value(serialization = "none")] +struct AllWrittenEntrypointsWithIssues { + entrypoints: Option>, + issues: Arc>>, + diagnostics: Arc>>, + effects: Arc, +} + +#[napi] +pub async fn project_write_all_entrypoints_to_disk( + #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External, + app_dir_only: bool, +) -> napi::Result> { + 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?, + ResolvedVc::cell(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, + app_dir_only: ResolvedVc, +) -> Result> { + 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, + app_dir_only: ResolvedVc, +) -> Result> { + 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, + app_dir_only: ResolvedVc, +) -> Result> { + let app_dir_only = *app_dir_only.await?; + + 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>> = 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, @@ -741,41 +888,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::>(), - 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)) diff --git a/crates/napi/src/next_api/utils.rs b/crates/napi/src/next_api/utils.rs index 681f6696a845c..fe1b5affd2a2d 100644 --- a/crates/napi/src/next_api/utils.rs +++ b/crates/napi/src/next_api/utils.rs @@ -10,8 +10,8 @@ use napi::{ }; 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, NoopBackingStorage, @@ -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, }; @@ -495,3 +497,27 @@ pub fn subscribe> + 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( + source_op: OperationVc, +) -> Result<( + Option>, + Arc>>, + Arc>>, + Arc, +)> { + 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)) +} diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index c296969b652d5..ac89ee8f111c0 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -773,14 +773,16 @@ impl Project { } #[turbo_tasks::function] - pub async fn get_all_endpoints(self: Vc) -> Result> { + pub async fn get_all_endpoints(self: Vc, app_dir_only: bool) -> Result> { let mut endpoints = Vec::new(); let entrypoints = self.entrypoints().await?; - endpoints.push(entrypoints.pages_error_endpoint); - endpoints.push(entrypoints.pages_app_endpoint); - endpoints.push(entrypoints.pages_document_endpoint); + if !app_dir_only { + endpoints.push(entrypoints.pages_error_endpoint); + endpoints.push(entrypoints.pages_app_endpoint); + endpoints.push(entrypoints.pages_document_endpoint); + } if let Some(middleware) = &entrypoints.middleware { endpoints.push(middleware.endpoint); @@ -797,10 +799,14 @@ impl Project { html_endpoint, data_endpoint: _, } => { - endpoints.push(*html_endpoint); + if !app_dir_only { + endpoints.push(*html_endpoint); + } } Route::PageApi { endpoint } => { - endpoints.push(*endpoint); + if !app_dir_only { + endpoints.push(*endpoint); + } } Route::AppPage(page_routes) => { for AppPageRoute { @@ -830,7 +836,7 @@ impl Project { #[turbo_tasks::function] pub async fn get_all_entries(self: Vc) -> Result> { let mut modules: Vec>> = self - .get_all_endpoints() + .get_all_endpoints(false) .await? .iter() .map(|endpoint| endpoint.root_modules()) @@ -849,7 +855,7 @@ impl Project { graphs: Vc, ) -> Result> { let mut modules: Vec>> = self - .get_all_endpoints() + .get_all_endpoints(false) .await? .iter() .map(|endpoint| endpoint.additional_root_modules(graphs)) diff --git a/packages/next/src/build/handle-entrypoints.ts b/packages/next/src/build/handle-entrypoints.ts index adcac7d45049f..250784d108119 100644 --- a/packages/next/src/build/handle-entrypoints.ts +++ b/packages/next/src/build/handle-entrypoints.ts @@ -1,60 +1,36 @@ -import type { CustomRoutes } from '../lib/load-custom-routes' import type { TurbopackManifestLoader } from '../shared/lib/turbopack/manifest-loader' import type { - TurbopackResult, - RawEntrypoints, Entrypoints, PageRoute, AppRoute, + RawEntrypoints, } from './swc/types' -import * as Log from './output/log' import { getEntryKey } from '../shared/lib/turbopack/entry-key' -import { - processIssues, - type EntryIssuesMap, -} from '../shared/lib/turbopack/utils' - -export async function handleEntrypoints({ - entrypoints, - currentEntrypoints, - currentEntryIssues, - manifestLoader, - productionRewrites, - logErrors, -}: { - entrypoints: TurbopackResult - currentEntrypoints: Entrypoints - currentEntryIssues: EntryIssuesMap - manifestLoader: TurbopackManifestLoader - productionRewrites: CustomRoutes['rewrites'] | undefined - logErrors: boolean -}) { - currentEntrypoints.global.app = entrypoints.pagesAppEndpoint - currentEntrypoints.global.document = entrypoints.pagesDocumentEndpoint - currentEntrypoints.global.error = entrypoints.pagesErrorEndpoint - - currentEntrypoints.global.instrumentation = entrypoints.instrumentation +import * as Log from './output/log' - currentEntrypoints.page.clear() - currentEntrypoints.app.clear() +export async function rawEntrypointsToEntrypoints( + entrypointsOp: RawEntrypoints +): Promise { + const page = new Map() + const app = new Map() - for (const [pathname, route] of entrypoints.routes) { + for (const [pathname, route] of entrypointsOp.routes) { switch (route.type) { case 'page': case 'page-api': - currentEntrypoints.page.set(pathname, route) + page.set(pathname, route) break case 'app-page': { - route.pages.forEach((page) => { - currentEntrypoints.app.set(page.originalName, { + for (const p of route.pages) { + app.set(p.originalName, { type: 'app-page', - ...page, + ...p, }) - }) + } break } case 'app-route': { - currentEntrypoints.app.set(route.originalName, route) + app.set(route.originalName, route) break } default: @@ -63,123 +39,27 @@ export async function handleEntrypoints({ } } - const { middleware, instrumentation } = entrypoints - - // We check for explicit true/false, since it's initialized to - // undefined during the first loop (middlewareChanges event is - // unnecessary during the first serve) - if (currentEntrypoints.global.middleware && !middleware) { - const key = getEntryKey('root', 'server', 'middleware') - // Went from middleware to no middleware - currentEntryIssues.delete(key) - } - - currentEntrypoints.global.middleware = middleware - - if (instrumentation) { - const processInstrumentation = async ( - name: string, - prop: 'nodeJs' | 'edge' - ) => { - const key = getEntryKey('root', 'server', name) - - const writtenEndpoint = await instrumentation[prop].writeToDisk() - processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) - } - await processInstrumentation('instrumentation.nodeJs', 'nodeJs') - await processInstrumentation('instrumentation.edge', 'edge') - await manifestLoader.loadMiddlewareManifest( - 'instrumentation', - 'instrumentation' - ) - await manifestLoader.writeManifests({ - devRewrites: undefined, - productionRewrites, - entrypoints: currentEntrypoints, - }) - } - - if (middleware) { - const key = getEntryKey('root', 'server', 'middleware') - - const endpoint = middleware.endpoint - - async function processMiddleware() { - const writtenEndpoint = await endpoint.writeToDisk() - processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) - await manifestLoader.loadMiddlewareManifest('middleware', 'middleware') - } - await processMiddleware() - } else { - manifestLoader.deleteMiddlewareManifest( - getEntryKey('root', 'server', 'middleware') - ) + return { + global: { + app: entrypointsOp.pagesAppEndpoint, + document: entrypointsOp.pagesDocumentEndpoint, + error: entrypointsOp.pagesErrorEndpoint, + instrumentation: entrypointsOp.instrumentation, + middleware: entrypointsOp.middleware, + }, + page, + app, } } -export async function handlePagesErrorRoute({ - currentEntryIssues, - entrypoints, - manifestLoader, - productionRewrites, - logErrors, -}: { - currentEntryIssues: EntryIssuesMap - entrypoints: Entrypoints - manifestLoader: TurbopackManifestLoader - productionRewrites: CustomRoutes['rewrites'] | undefined - logErrors: boolean -}) { - if (entrypoints.global.app) { - const key = getEntryKey('pages', 'server', '_app') - const writtenEndpoint = await entrypoints.global.app.writeToDisk() - processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) - } - await manifestLoader.loadBuildManifest('_app') - await manifestLoader.loadPagesManifest('_app') - await manifestLoader.loadFontManifest('_app') - - if (entrypoints.global.document) { - const key = getEntryKey('pages', 'server', '_document') - const writtenEndpoint = await entrypoints.global.document.writeToDisk() - processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) - } - await manifestLoader.loadPagesManifest('_document') - - if (entrypoints.global.error) { - const key = getEntryKey('pages', 'server', '_error') - const writtenEndpoint = await entrypoints.global.error.writeToDisk() - processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) - } - - await manifestLoader.loadBuildManifest('_error') - await manifestLoader.loadPagesManifest('_error') - await manifestLoader.loadFontManifest('_error') - - await manifestLoader.writeManifests({ - devRewrites: undefined, - productionRewrites, - entrypoints, - }) -} - export async function handleRouteType({ page, route, - currentEntryIssues, - entrypoints, manifestLoader, - productionRewrites, - logErrors, }: { page: string route: PageRoute | AppRoute - - currentEntryIssues: EntryIssuesMap - entrypoints: Entrypoints manifestLoader: TurbopackManifestLoader - productionRewrites: CustomRoutes['rewrites'] | undefined - logErrors: boolean }) { const shouldCreateWebpackStats = process.env.TURBOPACK_STATS != null @@ -187,46 +67,15 @@ export async function handleRouteType({ case 'page': { const serverKey = getEntryKey('pages', 'server', page) - if (entrypoints.global.app) { - const key = getEntryKey('pages', 'server', '_app') - - const writtenEndpoint = await entrypoints.global.app.writeToDisk() - processIssues( - currentEntryIssues, - key, - writtenEndpoint, - false, - logErrors - ) - } - await manifestLoader.loadBuildManifest('_app') - await manifestLoader.loadPagesManifest('_app') - - if (entrypoints.global.document) { - const key = getEntryKey('pages', 'server', '_document') - - const writtenEndpoint = await entrypoints.global.document.writeToDisk() - processIssues( - currentEntryIssues, - key, - writtenEndpoint, - false, - logErrors - ) - } - await manifestLoader.loadPagesManifest('_document') - - const writtenEndpoint = await route.htmlEndpoint.writeToDisk() - - const type = writtenEndpoint?.type - await manifestLoader.loadBuildManifest(page) await manifestLoader.loadPagesManifest(page) - if (type === 'edge') { + + const middlewareManifestWritten = await manifestLoader.loadMiddlewareManifest(page, 'pages') - } else { + if (!middlewareManifestWritten) { manifestLoader.deleteMiddlewareManifest(serverKey) } + await manifestLoader.loadFontManifest('/_app', 'pages') await manifestLoader.loadFontManifest(page, 'pages') @@ -234,54 +83,26 @@ export async function handleRouteType({ await manifestLoader.loadWebpackStats(page, 'pages') } - await manifestLoader.writeManifests({ - devRewrites: undefined, - productionRewrites, - entrypoints, - }) - - processIssues( - currentEntryIssues, - serverKey, - writtenEndpoint, - false, - logErrors - ) - break } case 'page-api': { const key = getEntryKey('pages', 'server', page) - const writtenEndpoint = await route.endpoint.writeToDisk() - - const type = writtenEndpoint.type - await manifestLoader.loadPagesManifest(page) - if (type === 'edge') { + const middlewareManifestWritten = await manifestLoader.loadMiddlewareManifest(page, 'pages') - } else { + if (!middlewareManifestWritten) { manifestLoader.deleteMiddlewareManifest(key) } - await manifestLoader.writeManifests({ - devRewrites: undefined, - productionRewrites, - entrypoints, - }) - - processIssues(currentEntryIssues, key, writtenEndpoint, true, logErrors) - break } case 'app-page': { const key = getEntryKey('app', 'server', page) - const writtenEndpoint = await route.htmlEndpoint.writeToDisk() - const type = writtenEndpoint.type - if (type === 'edge') { + const middlewareManifestWritten = await manifestLoader.loadMiddlewareManifest(page, 'app') - } else { + if (!middlewareManifestWritten) { manifestLoader.deleteMiddlewareManifest(key) } @@ -295,36 +116,20 @@ export async function handleRouteType({ await manifestLoader.loadWebpackStats(page, 'app') } - await manifestLoader.writeManifests({ - devRewrites: undefined, - productionRewrites, - entrypoints, - }) - - processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors) - break } case 'app-route': { const key = getEntryKey('app', 'server', page) - const writtenEndpoint = await route.endpoint.writeToDisk() - const type = writtenEndpoint.type await manifestLoader.loadAppPathsManifest(page) - if (type === 'edge') { + const middlewareManifestWritten = await manifestLoader.loadMiddlewareManifest(page, 'app') - } else { + + if (!middlewareManifestWritten) { manifestLoader.deleteMiddlewareManifest(key) } - await manifestLoader.writeManifests({ - devRewrites: undefined, - productionRewrites, - entrypoints, - }) - processIssues(currentEntryIssues, key, writtenEndpoint, true, logErrors) - break } default: { diff --git a/packages/next/src/build/swc/generated-native.d.ts b/packages/next/src/build/swc/generated-native.d.ts index e9e15ae7f4b7f..23837cc48daac 100644 --- a/packages/next/src/build/swc/generated-native.d.ts +++ b/packages/next/src/build/swc/generated-native.d.ts @@ -241,6 +241,10 @@ export interface NapiEntrypoints { pagesAppEndpoint: ExternalObject pagesErrorEndpoint: ExternalObject } +export declare function projectWriteAllEntrypointsToDisk( + project: { __napiType: 'Project' }, + appDirOnly: boolean +): Promise export declare function projectEntrypointsSubscribe( project: { __napiType: 'Project' }, func: (...args: any[]) => any diff --git a/packages/next/src/build/swc/index.ts b/packages/next/src/build/swc/index.ts index 58e17490b974c..d597692e9a744 100644 --- a/packages/next/src/build/swc/index.ts +++ b/packages/next/src/build/swc/index.ts @@ -32,6 +32,7 @@ import type { HmrIdentifiers, Project, ProjectOptions, + RawEntrypoints, Route, TurboEngineOptions, TurbopackResult, @@ -440,6 +441,58 @@ function bindingToApi( callback: (err: Error, value: T) => void ) => Promise<{ __napiType: 'RootTask' }> + type NapiEndpoint = { __napiType: 'Endpoint' } + + type NapiEntrypoints = { + routes: NapiRoute[] + middleware?: NapiMiddleware + instrumentation?: NapiInstrumentation + pagesDocumentEndpoint: NapiEndpoint + pagesAppEndpoint: NapiEndpoint + pagesErrorEndpoint: NapiEndpoint + } + + type NapiMiddleware = { + endpoint: NapiEndpoint + runtime: 'nodejs' | 'edge' + matcher?: string[] + } + + type NapiInstrumentation = { + nodeJs: NapiEndpoint + edge: NapiEndpoint + } + + type NapiRoute = { + pathname: string + } & ( + | { + type: 'page' + htmlEndpoint: NapiEndpoint + dataEndpoint: NapiEndpoint + } + | { + type: 'page-api' + endpoint: NapiEndpoint + } + | { + type: 'app-page' + pages: { + originalName: string + htmlEndpoint: NapiEndpoint + rscEndpoint: NapiEndpoint + }[] + } + | { + type: 'app-route' + originalName: string + endpoint: NapiEndpoint + } + | { + type: 'conflict' + } + ) + const cancel = new (class Cancel extends Error {})() /** @@ -586,59 +639,20 @@ function bindingToApi( ) } - entrypointsSubscribe() { - type NapiEndpoint = { __napiType: 'Endpoint' } - - type NapiEntrypoints = { - routes: NapiRoute[] - middleware?: NapiMiddleware - instrumentation?: NapiInstrumentation - pagesDocumentEndpoint: NapiEndpoint - pagesAppEndpoint: NapiEndpoint - pagesErrorEndpoint: NapiEndpoint - } - - type NapiMiddleware = { - endpoint: NapiEndpoint - runtime: 'nodejs' | 'edge' - matcher?: string[] - } - - type NapiInstrumentation = { - nodeJs: NapiEndpoint - edge: NapiEndpoint - } + async writeAllEntrypointsToDisk( + appDirOnly: boolean + ): Promise> { + return await withErrorCause(async () => { + const napiEndpoints = (await binding.projectWriteAllEntrypointsToDisk( + this._nativeProject, + appDirOnly + )) as TurbopackResult - type NapiRoute = { - pathname: string - } & ( - | { - type: 'page' - htmlEndpoint: NapiEndpoint - dataEndpoint: NapiEndpoint - } - | { - type: 'page-api' - endpoint: NapiEndpoint - } - | { - type: 'app-page' - pages: { - originalName: string - htmlEndpoint: NapiEndpoint - rscEndpoint: NapiEndpoint - }[] - } - | { - type: 'app-route' - originalName: string - endpoint: NapiEndpoint - } - | { - type: 'conflict' - } - ) + return napiEntrypointsToRawEntrypoints(napiEndpoints) + }) + } + entrypointsSubscribe() { const subscription = subscribe>( false, async (callback) => @@ -646,86 +660,7 @@ function bindingToApi( ) return (async function* () { for await (const entrypoints of subscription) { - const routes = new Map() - for (const { pathname, ...nativeRoute } of entrypoints.routes) { - let route: Route - const routeType = nativeRoute.type - switch (routeType) { - case 'page': - route = { - type: 'page', - htmlEndpoint: new EndpointImpl(nativeRoute.htmlEndpoint), - dataEndpoint: new EndpointImpl(nativeRoute.dataEndpoint), - } - break - case 'page-api': - route = { - type: 'page-api', - endpoint: new EndpointImpl(nativeRoute.endpoint), - } - break - case 'app-page': - route = { - type: 'app-page', - pages: nativeRoute.pages.map((page) => ({ - originalName: page.originalName, - htmlEndpoint: new EndpointImpl(page.htmlEndpoint), - rscEndpoint: new EndpointImpl(page.rscEndpoint), - })), - } - break - case 'app-route': - route = { - type: 'app-route', - originalName: nativeRoute.originalName, - endpoint: new EndpointImpl(nativeRoute.endpoint), - } - break - case 'conflict': - route = { - type: 'conflict', - } - break - default: - const _exhaustiveCheck: never = routeType - invariant( - nativeRoute, - () => `Unknown route type: ${_exhaustiveCheck}` - ) - } - routes.set(pathname, route) - } - const napiMiddlewareToMiddleware = (middleware: NapiMiddleware) => ({ - endpoint: new EndpointImpl(middleware.endpoint), - runtime: middleware.runtime, - matcher: middleware.matcher, - }) - const middleware = entrypoints.middleware - ? napiMiddlewareToMiddleware(entrypoints.middleware) - : undefined - const napiInstrumentationToInstrumentation = ( - instrumentation: NapiInstrumentation - ) => ({ - nodeJs: new EndpointImpl(instrumentation.nodeJs), - edge: new EndpointImpl(instrumentation.edge), - }) - const instrumentation = entrypoints.instrumentation - ? napiInstrumentationToInstrumentation(entrypoints.instrumentation) - : undefined - yield { - routes, - middleware, - instrumentation, - pagesDocumentEndpoint: new EndpointImpl( - entrypoints.pagesDocumentEndpoint - ), - pagesAppEndpoint: new EndpointImpl(entrypoints.pagesAppEndpoint), - pagesErrorEndpoint: new EndpointImpl( - entrypoints.pagesErrorEndpoint - ), - issues: entrypoints.issues, - diagnostics: entrypoints.diagnostics, - } + yield napiEntrypointsToRawEntrypoints(entrypoints) } })() } @@ -981,6 +916,90 @@ function bindingToApi( } } + function napiEntrypointsToRawEntrypoints( + entrypoints: TurbopackResult + ): TurbopackResult { + const routes = new Map() + for (const { pathname, ...nativeRoute } of entrypoints.routes) { + let route: Route + const routeType = nativeRoute.type + switch (routeType) { + case 'page': + route = { + type: 'page', + htmlEndpoint: new EndpointImpl(nativeRoute.htmlEndpoint), + dataEndpoint: new EndpointImpl(nativeRoute.dataEndpoint), + } + break + case 'page-api': + route = { + type: 'page-api', + endpoint: new EndpointImpl(nativeRoute.endpoint), + } + break + case 'app-page': + route = { + type: 'app-page', + pages: nativeRoute.pages.map((page) => ({ + originalName: page.originalName, + htmlEndpoint: new EndpointImpl(page.htmlEndpoint), + rscEndpoint: new EndpointImpl(page.rscEndpoint), + })), + } + break + case 'app-route': + route = { + type: 'app-route', + originalName: nativeRoute.originalName, + endpoint: new EndpointImpl(nativeRoute.endpoint), + } + break + case 'conflict': + route = { + type: 'conflict', + } + break + default: + const _exhaustiveCheck: never = routeType + invariant( + nativeRoute, + () => `Unknown route type: ${_exhaustiveCheck}` + ) + } + routes.set(pathname, route) + } + const napiMiddlewareToMiddleware = (middleware: NapiMiddleware) => ({ + endpoint: new EndpointImpl(middleware.endpoint), + runtime: middleware.runtime, + matcher: middleware.matcher, + }) + const middleware = entrypoints.middleware + ? napiMiddlewareToMiddleware(entrypoints.middleware) + : undefined + const napiInstrumentationToInstrumentation = ( + instrumentation: NapiInstrumentation + ) => ({ + nodeJs: new EndpointImpl(instrumentation.nodeJs), + edge: new EndpointImpl(instrumentation.edge), + }) + const instrumentation = entrypoints.instrumentation + ? napiInstrumentationToInstrumentation(entrypoints.instrumentation) + : undefined + + return { + routes, + middleware, + instrumentation, + pagesDocumentEndpoint: new EndpointImpl( + entrypoints.pagesDocumentEndpoint + ), + pagesAppEndpoint: new EndpointImpl(entrypoints.pagesAppEndpoint), + pagesErrorEndpoint: new EndpointImpl(entrypoints.pagesErrorEndpoint), + issues: entrypoints.issues, + diagnostics: entrypoints.diagnostics, + } + } + return async function createProject( options: ProjectOptions, turboEngineOptions diff --git a/packages/next/src/build/swc/types.ts b/packages/next/src/build/swc/types.ts index e4d2fb08562b9..cc96e807be53f 100644 --- a/packages/next/src/build/swc/types.ts +++ b/packages/next/src/build/swc/types.ts @@ -186,6 +186,10 @@ export interface UpdateInfo { export interface Project { update(options: Partial): Promise + writeAllEntrypointsToDisk( + appDirOnly: boolean + ): Promise> + entrypointsSubscribe(): AsyncIterableIterator> hmrEvents(identifier: string): AsyncIterableIterator> diff --git a/packages/next/src/build/turbopack-build/impl.ts b/packages/next/src/build/turbopack-build/impl.ts index c3f069e041852..058eb766bc7c8 100644 --- a/packages/next/src/build/turbopack-build/impl.ts +++ b/packages/next/src/build/turbopack-build/impl.ts @@ -4,21 +4,19 @@ import { formatIssue, getTurbopackJsConfig, isPersistentCachingEnabled, - isRelevantWarning, - type EntryIssuesMap, + shouldDisplayIssue, + // isRelevantWarning, + // type EntryIssuesMap, } from '../../shared/lib/turbopack/utils' import { NextBuildContext } from '../build-context' import { createDefineEnv, loadBindings } from '../swc' -import { Sema } from 'next/dist/compiled/async-sema' import { - handleEntrypoints, - handlePagesErrorRoute, + rawEntrypointsToEntrypoints, handleRouteType, } from '../handle-entrypoints' -import type { Entrypoints } from '../swc/types' +// import type { Entrypoints } from '../swc/types' import { TurbopackManifestLoader } from '../../shared/lib/turbopack/manifest-loader' -import { createProgress } from '../progress' -import * as Log from '../output/log' +// import * as Log from '../output/log' import { promises as fs } from 'fs' import { PHASE_PRODUCTION_BUILD } from '../../shared/lib/constants' import loadConfig from '../../server/config' @@ -114,22 +112,7 @@ export async function turbopackBuild(): Promise<{ ) // eslint-disable-next-line @typescript-eslint/no-unused-vars - const entrypointsSubscription = project.entrypointsSubscribe() - const currentEntrypoints: Entrypoints = { - global: { - app: undefined, - document: undefined, - error: undefined, - - middleware: undefined, - instrumentation: undefined, - }, - - app: new Map(), - page: new Map(), - } - - const currentEntryIssues: EntryIssuesMap = new Map() + const entrypoints = await project.writeAllEntrypointsToDisk(appDirOnly) const manifestLoader = new TurbopackManifestLoader({ buildId, @@ -137,18 +120,14 @@ export async function turbopackBuild(): Promise<{ encryptionKey, }) - const entrypointsResult = await entrypointsSubscription.next() - if (entrypointsResult.done) { - throw new Error('Turbopack did not return any entrypoints') - } - entrypointsSubscription.return?.().catch(() => {}) - - const entrypoints = entrypointsResult.value - const topLevelErrors: { message: string }[] = [] for (const issue of entrypoints.issues) { + if (!shouldDisplayIssue(issue)) { + continue + } + topLevelErrors.push({ message: formatIssue(issue), }) @@ -162,142 +141,59 @@ export async function turbopackBuild(): Promise<{ ) } - await handleEntrypoints({ - entrypoints, - currentEntrypoints, - currentEntryIssues, - manifestLoader, - productionRewrites: rewrites, - logErrors: false, - }) + const currentEntrypoints = await rawEntrypointsToEntrypoints(entrypoints) - const progress = createProgress( - currentEntrypoints.page.size + currentEntrypoints.app.size + 1, - 'Building' - ) const promises: Promise[] = [] - // Concurrency will start at INITIAL_CONCURRENCY and - // slowly ramp up to CONCURRENCY by increasing the - // concurrency by 1 every time a task is completed. - const INITIAL_CONCURRENCY = 5 - const CONCURRENCY = 10 - - const sema = new Sema(INITIAL_CONCURRENCY) - let remainingRampup = CONCURRENCY - INITIAL_CONCURRENCY - const enqueue = (fn: () => Promise) => { - promises.push( - (async () => { - await sema.acquire() - try { - await fn() - } finally { - sema.release() - if (remainingRampup > 0) { - remainingRampup-- - sema.release() - } - progress.run() - } - })() - ) - } - if (!appDirOnly) { for (const [page, route] of currentEntrypoints.page) { - enqueue(() => + promises.push( handleRouteType({ page, route, - currentEntryIssues, - entrypoints: currentEntrypoints, manifestLoader, - productionRewrites: rewrites, - logErrors: false, }) ) } } for (const [page, route] of currentEntrypoints.app) { - enqueue(() => + promises.push( handleRouteType({ page, route, - currentEntryIssues, - entrypoints: currentEntrypoints, manifestLoader, - productionRewrites: rewrites, - logErrors: false, }) ) } - enqueue(() => - handlePagesErrorRoute({ - currentEntryIssues, - entrypoints: currentEntrypoints, - manifestLoader, - productionRewrites: rewrites, - logErrors: false, - }) - ) await Promise.all(promises) + await Promise.all([ + manifestLoader.loadBuildManifest('_app'), + manifestLoader.loadPagesManifest('_app'), + manifestLoader.loadFontManifest('_app'), + manifestLoader.loadPagesManifest('_document'), + manifestLoader.loadBuildManifest('_error'), + manifestLoader.loadPagesManifest('_error'), + manifestLoader.loadFontManifest('_error'), + entrypoints.instrumentation && + manifestLoader.loadMiddlewareManifest( + 'instrumentation', + 'instrumentation' + ), + entrypoints.middleware && + (await manifestLoader.loadMiddlewareManifest('middleware', 'middleware')), + ]) + await manifestLoader.writeManifests({ devRewrites: undefined, productionRewrites: rewrites, entrypoints: currentEntrypoints, }) - const errors: { - page: string - message: string - }[] = [] - const warnings: { - page: string - message: string - }[] = [] - for (const [page, entryIssues] of currentEntryIssues) { - for (const issue of entryIssues.values()) { - if (issue.severity !== 'warning') { - errors.push({ - page, - message: formatIssue(issue), - }) - } else { - if (isRelevantWarning(issue)) { - warnings.push({ - page, - message: formatIssue(issue), - }) - } - } - } - } - const shutdownPromise = project.shutdown() - if (warnings.length > 0) { - Log.warn( - `Turbopack build collected ${warnings.length} warnings:\n${warnings - .map((e) => { - return 'Page: ' + e.page + '\n' + e.message - }) - .join('\n')}` - ) - } - - if (errors.length > 0) { - throw new Error( - `Turbopack build failed with ${errors.length} errors:\n${errors - .map((e) => { - return 'Page: ' + e.page + '\n' + e.message - }) - .join('\n')}` - ) - } - const time = process.hrtime(startTime) return { duration: time[0] + time[1] / 1e9, diff --git a/packages/next/src/shared/lib/turbopack/manifest-loader.ts b/packages/next/src/shared/lib/turbopack/manifest-loader.ts index cd62e5b53f884..d971f4a74463a 100644 --- a/packages/next/src/shared/lib/turbopack/manifest-loader.ts +++ b/packages/next/src/shared/lib/turbopack/manifest-loader.ts @@ -54,7 +54,17 @@ type TurbopackMiddlewareManifest = MiddlewareManifest & { instrumentation?: InstrumentationDefinition } -const getManifestPath = (page: string, distDir: string, name: string, type: string) => { +type ManifestName = | typeof MIDDLEWARE_MANIFEST +| typeof BUILD_MANIFEST +| typeof APP_BUILD_MANIFEST +| typeof PAGES_MANIFEST +| typeof WEBPACK_STATS +| typeof APP_PATHS_MANIFEST +| `${typeof SERVER_REFERENCE_MANIFEST}.json` +| `${typeof NEXT_FONT_MANIFEST}.json` +| typeof REACT_LOADABLE_MANIFEST + +const getManifestPath = (page: string, distDir: string, name: ManifestName, type: string, firstCall: boolean) => { let manifestPath = posix.join( distDir, `server`, @@ -66,38 +76,33 @@ const getManifestPath = (page: string, distDir: string, name: string, type: stri : getAssetPathFromRoute(page), name ) + + if(firstCall) { + const isSitemapRoute = /[\\/]sitemap(.xml)?\/route$/.test(page) + // Check the ambiguity of /sitemap and /sitemap.xml + if (isSitemapRoute && !existsSync(manifestPath)) { + manifestPath = getManifestPath(page.replace(/\/sitemap\/route$/, '/sitemap.xml/route'), distDir, name, type, false) + } + // existsSync is faster than using the async version + if(!existsSync(manifestPath) && page.endsWith('/route')) { + // TODO: Improve implementation of metadata routes, currently it requires this extra check for the variants of the files that can be written. + let metadataPage = addRouteSuffix(addMetadataIdToRoute(removeRouteSuffix(page))) + manifestPath = getManifestPath(metadataPage, distDir, name, type, false) + } + } + return manifestPath } async function readPartialManifest( distDir: string, name: - | typeof MIDDLEWARE_MANIFEST - | typeof BUILD_MANIFEST - | typeof APP_BUILD_MANIFEST - | typeof PAGES_MANIFEST - | typeof WEBPACK_STATS - | typeof APP_PATHS_MANIFEST - | `${typeof SERVER_REFERENCE_MANIFEST}.json` - | `${typeof NEXT_FONT_MANIFEST}.json` - | typeof REACT_LOADABLE_MANIFEST, + ManifestName, pageName: string, type: 'pages' | 'app' | 'middleware' | 'instrumentation' = 'pages' ): Promise { const page = pageName - const isSitemapRoute = /[\\/]sitemap(.xml)?\/route$/.test(page) - let manifestPath = getManifestPath(page, distDir, name, type) - - // Check the ambiguity of /sitemap and /sitemap.xml - if (isSitemapRoute && !existsSync(manifestPath)) { - manifestPath = getManifestPath(pageName.replace(/\/sitemap\/route$/, '/sitemap.xml/route'), distDir, name, type) - } - // existsSync is faster than using the async version - if(!existsSync(manifestPath) && page.endsWith('/route')) { - // TODO: Improve implementation of metadata routes, currently it requires this extra check for the variants of the files that can be written. - let metadataPage = addRouteSuffix(addMetadataIdToRoute(removeRouteSuffix(page))) - manifestPath = getManifestPath(metadataPage, distDir, name, type) - } + const manifestPath = getManifestPath(page, distDir, name, type, true) return JSON.parse(await readFile(posix.join(manifestPath), 'utf-8')) as T } @@ -564,10 +569,20 @@ export class TurbopackManifestLoader { ) } + /** + * @returns If the manifest was written or not + */ async loadMiddlewareManifest( pageName: string, type: 'pages' | 'app' | 'middleware' | 'instrumentation' - ): Promise { + ): Promise { + const middlewareManifestPath = getManifestPath(pageName, this.distDir, MIDDLEWARE_MANIFEST, type, true) + + // middlewareManifest is actually "edge manifest" and not all routes are edge runtime. If it is not written we skip it. + if(!existsSync(middlewareManifestPath)) { + return false + } + this.middlewareManifests.set( getEntryKey( type === 'middleware' || type === 'instrumentation' ? 'root' : type, @@ -581,6 +596,8 @@ export class TurbopackManifestLoader { type ) ) + + return true } getMiddlewareManifest(key: EntryKey) { diff --git a/packages/next/src/shared/lib/turbopack/utils.ts b/packages/next/src/shared/lib/turbopack/utils.ts index e200f4a5d4d55..fccbf7d3d1403 100644 --- a/packages/next/src/shared/lib/turbopack/utils.ts +++ b/packages/next/src/shared/lib/turbopack/utils.ts @@ -61,6 +61,39 @@ export async function getTurbopackJsConfig( return jsConfig ?? { compilerOptions: {} } } +export function processIssuesForProd( + result: TurbopackResult, + throwIssue: boolean, + logErrors:boolean +) { + const relevantIssues = new Set() + console.log('issues', result.issues) + for (const issue of result.issues) { + if ( + issue.severity !== 'error' && + issue.severity !== 'fatal' && + issue.severity !== 'warning' + ) + continue + + if (issue.severity !== 'warning') { + if (throwIssue) { + const formatted = formatIssue(issue) + relevantIssues.add(formatted) + } + // if we throw the issue it will most likely get handed and logged elsewhere + else if (logErrors && isWellKnownError(issue)) { + const formatted = formatIssue(issue) + Log.error(formatted) + } + } + } + + if (relevantIssues.size && throwIssue) { + throw new ModuleBuildError([...relevantIssues].join('\n\n')) + } +} + export function processIssues( currentEntryIssues: EntryIssuesMap, @@ -182,6 +215,10 @@ export function formatIssue(issue: Issue) { return message } +export function shouldDisplayIssue(issue: Issue): boolean { + return issue.severity === 'fatal' || issue.severity === 'error' || isRelevantWarning(issue) +} + export function isRelevantWarning(issue: Issue): boolean { return issue.severity === 'warning' && !isNodeModulesIssue(issue) }