Skip to content

Commit aae734b

Browse files
authored
Turbopack: next build --analyze (#85197)
This implements a portion of the first iteration of a bundle analyzer for Turbopack in Next.js by writing data files to disk. A separate visualization and explorer tool will follow. This data is written when builds are run with `next build --experimental-analyze`. An upcoming PR will implement a dedicated `next analyze` command that will write the data files without writing a complete build. The data is derived from Turbopack’s module and chunk graphs, and precise module sizes are reversed from the source map data of each chunk. Importantly, this requires that source maps are generated within Turbopack and the project is recomputed with them -- however, no source maps are written as part of the build artifacts if the user did not configure their Next.js config that way. The binary format written in these files is subject to change, so the feature is experimental for the time being. Until it stabilizes, no guarantees will be provided between it and the visualizer tool. Test Plan: In a test app: - do not configure `productionBrowserSourceMaps` - run `next build` and confirm **no files are written** to `.next/diagnostics/analyze` - run `next build --analyze` and confirm files **are** written to `.next/diagnostics/analyze` - Open the data with the yet-to-be-published visualization tool and confirm data is visible
1 parent 014a254 commit aae734b

File tree

38 files changed

+1792
-84
lines changed

38 files changed

+1792
-84
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ turbopack-cli-utils = { path = "turbopack/crates/turbopack-cli-utils" }
317317
turbopack-core = { path = "turbopack/crates/turbopack-core" }
318318
turbopack-create-test-app = { path = "turbopack/crates/turbopack-create-test-app" }
319319
turbopack-css = { path = "turbopack/crates/turbopack-css" }
320+
turbopack-analyze = { path = "turbopack/crates/turbopack-analyze" }
320321
turbopack-browser = { path = "turbopack/crates/turbopack-browser" }
321322
turbopack-dev-server = { path = "turbopack/crates/turbopack-dev-server" }
322323
turbopack-ecmascript = { path = "turbopack/crates/turbopack-ecmascript" }
@@ -377,6 +378,7 @@ async-compression = { version = "0.3.13", default-features = false, features = [
377378
] }
378379
async-trait = "0.1.64"
379380
bitfield = "0.18.0"
381+
byteorder = "1.5.0"
380382
bytes = "1.1.0"
381383
bytes-str = "0.2.7"
382384
chrono = "0.4.23"
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use std::{iter::once, sync::Arc};
2+
3+
use anyhow::Result;
4+
use next_api::{
5+
analyze::{AnalyzeDataOutputAsset, ModulesDataOutputAsset},
6+
project::ProjectContainer,
7+
};
8+
use turbo_tasks::{Effects, ReadRef, ResolvedVc, TryJoinIterExt, Vc};
9+
use turbopack_core::{diagnostics::PlainDiagnostic, issue::PlainIssue, output::OutputAssets};
10+
11+
use crate::next_api::utils::strongly_consistent_catch_collectables;
12+
13+
#[turbo_tasks::value(serialization = "none")]
14+
pub struct WriteAnalyzeResult {
15+
pub issues: Arc<Vec<ReadRef<PlainIssue>>>,
16+
pub diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
17+
pub effects: Arc<Effects>,
18+
}
19+
20+
#[turbo_tasks::function(operation)]
21+
pub async fn write_analyze_data_with_issues_operation(
22+
project: ResolvedVc<ProjectContainer>,
23+
app_dir_only: bool,
24+
) -> Result<Vc<WriteAnalyzeResult>> {
25+
let analyze_data_op = write_analyze_data_with_issues_operation_inner(project, app_dir_only);
26+
27+
let (_analyze_data, issues, diagnostics, effects) =
28+
strongly_consistent_catch_collectables(analyze_data_op).await?;
29+
30+
Ok(WriteAnalyzeResult {
31+
issues,
32+
diagnostics,
33+
effects,
34+
}
35+
.cell())
36+
}
37+
38+
#[turbo_tasks::function(operation)]
39+
async fn write_analyze_data_with_issues_operation_inner(
40+
project: ResolvedVc<ProjectContainer>,
41+
app_dir_only: bool,
42+
) -> Result<()> {
43+
let analyze_data_op = get_analyze_data_operation(project, app_dir_only);
44+
45+
project
46+
.project()
47+
.emit_all_output_assets(analyze_data_op)
48+
.as_side_effect()
49+
.await?;
50+
51+
Ok(())
52+
}
53+
54+
#[turbo_tasks::function(operation)]
55+
async fn get_analyze_data_operation(
56+
container: ResolvedVc<ProjectContainer>,
57+
app_dir_only: bool,
58+
) -> Result<Vc<OutputAssets>> {
59+
let project = container.project();
60+
let project =
61+
project.with_next_config(project.next_config().with_production_browser_source_maps());
62+
63+
let analyze_output_root = project
64+
.node_root()
65+
.owned()
66+
.await?
67+
.join("diagnostics/analyze")?;
68+
let whole_app_module_graphs = project.whole_app_module_graphs();
69+
let analyze_output_root = &analyze_output_root;
70+
let analyze_data = project
71+
.get_all_endpoint_groups(app_dir_only)
72+
.await?
73+
.iter()
74+
.map(|(key, endpoint_group)| async move {
75+
let output_assets = endpoint_group.output_assets();
76+
let analyze_data = AnalyzeDataOutputAsset::new(
77+
analyze_output_root
78+
.join(&key.to_string())?
79+
.join("analyze.data")?,
80+
output_assets,
81+
)
82+
.to_resolved()
83+
.await?;
84+
85+
Ok(ResolvedVc::upcast(analyze_data))
86+
})
87+
.try_join()
88+
.await?;
89+
90+
whole_app_module_graphs.as_side_effect().await?;
91+
92+
let modules_data = ResolvedVc::upcast(
93+
ModulesDataOutputAsset::new(
94+
analyze_output_root.join("modules.data")?,
95+
Vc::cell(vec![whole_app_module_graphs.await?.full]),
96+
)
97+
.to_resolved()
98+
.await?,
99+
);
100+
101+
Ok(Vc::cell(
102+
analyze_data
103+
.iter()
104+
.cloned()
105+
.chain(once(modules_data))
106+
.collect(),
107+
))
108+
}

crates/napi/src/next_api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod analyze;
12
pub mod endpoint;
23
pub mod project;
34
pub mod turbopack_ctx;

crates/napi/src/next_api/project.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use url::Url;
6464

6565
use crate::{
6666
next_api::{
67+
analyze::{WriteAnalyzeResult, write_analyze_data_with_issues_operation},
6768
endpoint::ExternalEndpoint,
6869
turbopack_ctx::{
6970
NapiNextTurbopackCallbacks, NapiNextTurbopackCallbacksJsObject, NextTurboTasks,
@@ -915,6 +916,13 @@ fn project_container_entrypoints_operation(
915916
container.entrypoints()
916917
}
917918

919+
#[turbo_tasks::value(serialization = "none")]
920+
struct OperationResult {
921+
issues: Arc<Vec<ReadRef<PlainIssue>>>,
922+
diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
923+
effects: Arc<Effects>,
924+
}
925+
918926
#[turbo_tasks::value(serialization = "none")]
919927
struct AllWrittenEntrypointsWithIssues {
920928
entrypoints: Option<ReadRef<EntrypointsOperation>>,
@@ -995,9 +1003,10 @@ pub async fn all_entrypoints_write_to_disk_operation(
9951003
project: ResolvedVc<ProjectContainer>,
9961004
app_dir_only: bool,
9971005
) -> Result<Vc<Entrypoints>> {
1006+
let output_assets_operation = output_assets_operation(project, app_dir_only);
9981007
project
9991008
.project()
1000-
.emit_all_output_assets(output_assets_operation(project, app_dir_only))
1009+
.emit_all_output_assets(output_assets_operation)
10011010
.as_side_effect()
10021011
.await?;
10031012

@@ -1037,6 +1046,49 @@ async fn output_assets_operation(
10371046
}
10381047

10391048
#[tracing::instrument(level = "info", name = "get entrypoints", skip_all)]
1049+
#[napi]
1050+
pub async fn project_entrypoints(
1051+
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
1052+
) -> napi::Result<TurbopackResult<Option<NapiEntrypoints>>> {
1053+
let container = project.container;
1054+
1055+
let (entrypoints, issues, diags) = project
1056+
.turbopack_ctx
1057+
.turbo_tasks()
1058+
.run_once(async move {
1059+
let entrypoints_with_issues_op = get_entrypoints_with_issues_operation(container);
1060+
1061+
// Read and compile the files
1062+
let EntrypointsWithIssues {
1063+
entrypoints,
1064+
issues,
1065+
diagnostics,
1066+
effects: _,
1067+
} = &*entrypoints_with_issues_op
1068+
.read_strongly_consistent()
1069+
.await?;
1070+
1071+
Ok((entrypoints.clone(), issues.clone(), diagnostics.clone()))
1072+
})
1073+
.await
1074+
.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?;
1075+
1076+
let result = match entrypoints {
1077+
Some(entrypoints) => Some(NapiEntrypoints::from_entrypoints_op(
1078+
&entrypoints,
1079+
&project.turbopack_ctx,
1080+
)?),
1081+
None => None,
1082+
};
1083+
1084+
Ok(TurbopackResult {
1085+
result,
1086+
issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(),
1087+
diagnostics: diags.iter().map(|d| NapiDiagnostic::from(d)).collect(),
1088+
})
1089+
}
1090+
1091+
#[tracing::instrument(level = "info", name = "subscribe to entrypoints", skip_all)]
10401092
#[napi(ts_return_type = "{ __napiType: \"RootTask\" }")]
10411093
pub fn project_entrypoints_subscribe(
10421094
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
@@ -1718,3 +1770,37 @@ pub fn project_get_source_map_sync(
17181770
tokio::runtime::Handle::current().block_on(project_get_source_map(project, file_path))
17191771
})
17201772
}
1773+
1774+
#[napi]
1775+
pub async fn project_write_analyze_data(
1776+
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
1777+
app_dir_only: bool,
1778+
) -> napi::Result<TurbopackResult<()>> {
1779+
let container = project.container;
1780+
let (issues, diagnostics) = project
1781+
.turbopack_ctx
1782+
.turbo_tasks()
1783+
.run_once(async move {
1784+
let analyze_data_op = write_analyze_data_with_issues_operation(container, app_dir_only);
1785+
let WriteAnalyzeResult {
1786+
issues,
1787+
diagnostics,
1788+
effects,
1789+
} = &*analyze_data_op.read_strongly_consistent().await?;
1790+
1791+
// Write the files to disk
1792+
effects.apply().await?;
1793+
Ok((issues.clone(), diagnostics.clone()))
1794+
})
1795+
.await
1796+
.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?;
1797+
1798+
Ok(TurbopackResult {
1799+
result: (),
1800+
issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(),
1801+
diagnostics: diagnostics
1802+
.iter()
1803+
.map(|d| NapiDiagnostic::from(d))
1804+
.collect(),
1805+
})
1806+
}

crates/next-api/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ workspace = true
1414

1515
[dependencies]
1616
anyhow = { workspace = true, features = ["backtrace"] }
17+
byteorder = { workspace = true }
1718
either = { workspace = true }
1819
futures = { workspace = true }
1920
indexmap = { workspace = true }
@@ -31,6 +32,7 @@ turbo-tasks-env = { workspace = true }
3132
turbo-tasks-fs = { workspace = true }
3233
turbo-unix-path = { workspace = true }
3334
turbopack = { workspace = true }
35+
turbopack-analyze = { workspace = true }
3436
turbopack-browser = { workspace = true }
3537
turbopack-core = { workspace = true }
3638
turbopack-ecmascript = { workspace = true }

0 commit comments

Comments
 (0)