1use std::{path::Path, time::Duration};
2
3use anyhow::{Context, Result, bail};
4use async_trait::async_trait;
5use bincode::{Decode, Encode};
6use indexmap::map::Entry;
7use next_core::{
8 app_structure::find_app_dir,
9 emit_assets, get_edge_chunking_context, get_edge_chunking_context_with_client_assets,
10 get_edge_compile_time_info, get_edge_resolve_options_context,
11 instrumentation::instrumentation_files,
12 middleware::middleware_files,
13 mode::NextMode,
14 next_app::{AppPage, AppPath},
15 next_client::{
16 ClientChunkingContextOptions, ClientContextType, ServiceWorkerChunkingContextOptions,
17 get_client_chunking_context, get_client_compile_time_info,
18 get_client_module_options_context, get_client_resolve_options_context,
19 get_service_worker_chunking_context,
20 },
21 next_config::{
22 DIST_PROFILES_DIR_NAME, ModuleIds as ModuleIdStrategyConfig, NextConfig, OutputType,
23 TurbopackPluginRuntimeStrategy,
24 },
25 next_edge::context::EdgeChunkingContextOptions,
26 next_server::{
27 ServerChunkingContextOptions, ServerContextType, get_server_chunking_context,
28 get_server_chunking_context_with_client_assets, get_server_compile_time_info,
29 get_server_module_options_context, get_server_resolve_options_context,
30 get_tracing_compile_time_info,
31 },
32 next_telemetry::ProjectFeatureUsageSummary,
33 parse_segment_config_from_source,
34 segment_config::ParseSegmentMode,
35 util::{NextRuntime, OptionEnvMap},
36};
37use rustc_hash::{FxHashMap, FxHashSet};
38use serde::{Deserialize, Serialize};
39use tracing::{Instrument, field::Empty};
40use turbo_rcstr::{RcStr, rcstr};
41use turbo_tasks::{
42 Completion, Completions, FxIndexMap, NonLocalValue, OperationValue, OperationVc, ReadRef,
43 ResolvedVc, State, TransientInstance, TryFlatJoinIterExt, TryJoinIterExt, Vc,
44 debug::ValueDebugFormat, fxindexmap, trace::TraceRawVcs,
45};
46use turbo_tasks_env::{EnvMap, ProcessEnv};
47use turbo_tasks_fs::{
48 DiskFileSystem, DiskWatcherConfig, FileContent, FileSystem, FileSystemPath, VirtualFileSystem,
49 canonicalize_to_rcstr, invalidation,
50};
51use turbo_unix_path::join_path;
52use turbopack::{
53 ModuleAssetContext, evaluate_context::node_build_environment, externals_tracing_module_context,
54 global_module_ids::get_global_module_id_strategy, transition::TransitionOptions,
55};
56use turbopack_core::{
57 PROJECT_FILESYSTEM_NAME,
58 changed::content_changed,
59 chunk::{
60 ChunkingContext, EvaluatableAssets, UnusedReferences,
61 chunk_id_strategy::{ModuleIdFallback, ModuleIdStrategy},
62 },
63 compile_time_info::CompileTimeInfo,
64 context::AssetContext,
65 environment::NodeJsVersion,
66 file_source::FileSource,
67 ident::Layer,
68 issue::{
69 CollectibleIssuesExt, Issue, IssueExt, IssueFilter, IssueSeverity, IssueStage, StyledString,
70 },
71 module::{Module, Modules},
72 module_graph::{
73 GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
74 binding_usage_info::{
75 BindingUsageInfo, OptionBindingUsageInfo, compute_binding_usage_info,
76 },
77 chunk_group_info::{ChunkGroupEntry, EntryHeuristics},
78 },
79 output::{
80 ExpandOutputAssetsInput, ExpandedOutputAssets, OutputAsset, OutputAssets,
81 expand_output_assets,
82 },
83 reference::all_assets_from_entries,
84 reference_type::{CommonJsReferenceSubType, ReferenceType},
85 resolve::{FindContextFileResult, find_context_file},
86 version::{
87 NotFoundVersion, OptionVersionedContent, Update, Version, VersionState, VersionedContent,
88 },
89};
90#[cfg(all(feature = "process_pool", not(target_family = "wasm")))]
91use turbopack_node::child_process_backend;
92use turbopack_node::execution_context::ExecutionContext;
93#[cfg(feature = "worker_pool")]
94use turbopack_node::worker_threads_backend;
95use turbopack_nodejs::{NodeJsChunkingContext, fs::NodeModulesPathMatcher};
96
97use crate::{
98 aggregate_hmr::ServerHmrChunkLists,
99 app::{AppProject, OptionAppProject},
100 empty::EmptyEndpoint,
101 entrypoints::Entrypoints,
102 instrumentation::InstrumentationEndpoint,
103 middleware::MiddlewareEndpoint,
104 next_server_nft::{pages_renderer_modules, require_hook_modules},
105 pages::PagesProject,
106 route::{
107 Endpoint, EndpointGroup, EndpointGroupEntry, EndpointGroupKey, EndpointGroups, Endpoints,
108 Route,
109 },
110 versioned_content_map::VersionedContentMap,
111};
112
113#[turbo_tasks::task_input]
114#[derive(
115 Debug,
116 Serialize,
117 Deserialize,
118 Clone,
119 PartialEq,
120 Eq,
121 Hash,
122 TraceRawVcs,
123 OperationValue,
124 Encode,
125 Decode,
126)]
127#[serde(rename_all = "camelCase")]
128pub struct DraftModeOptions {
129 pub preview_mode_id: RcStr,
130 pub preview_mode_encryption_key: RcStr,
131 pub preview_mode_signing_key: RcStr,
132}
133
134#[turbo_tasks::task_input]
135#[derive(
136 Debug,
137 Default,
138 Serialize,
139 Deserialize,
140 Copy,
141 Clone,
142 PartialEq,
143 Eq,
144 Hash,
145 TraceRawVcs,
146 OperationValue,
147 Encode,
148 Decode,
149)]
150#[serde(rename_all = "camelCase")]
151pub struct WatchOptions {
152 pub enable: bool,
154
155 pub poll_interval: Option<Duration>,
158}
159
160#[turbo_tasks::task_input]
161#[derive(
162 Debug,
163 Default,
164 Serialize,
165 Deserialize,
166 Clone,
167 PartialEq,
168 Eq,
169 Hash,
170 TraceRawVcs,
171 OperationValue,
172 Encode,
173 Decode,
174)]
175#[serde(rename_all = "camelCase")]
176pub struct DebugBuildPaths {
177 pub app: Vec<RcStr>,
178 pub pages: Vec<RcStr>,
179}
180
181struct DebugBuildPathsRouteKeys {
183 app: FxHashSet<RcStr>,
184 pages: FxHashSet<RcStr>,
185}
186
187impl DebugBuildPathsRouteKeys {
188 fn app_route_key_from_debug_path(path: &str) -> Result<RcStr> {
189 let mut segments = path
190 .trim_start_matches('/')
191 .split('/')
192 .filter(|segment| !segment.is_empty())
193 .collect::<Vec<_>>();
194
195 if let Some(last_segment) = segments.last()
196 && (*last_segment == "page"
197 || last_segment.starts_with("page.")
198 || *last_segment == "route"
199 || last_segment.starts_with("route."))
200 {
201 segments.pop();
202 }
203
204 let normalized_path = segments.join("/");
205 Ok(AppPath::from(AppPage::parse(&normalized_path)?)
206 .to_string()
207 .into())
208 }
209
210 fn pages_route_key_from_debug_path(path: &RcStr) -> Result<RcStr> {
211 let file_name = path.rsplit('/').next().unwrap_or(path);
215 let result = if let Some(dot_idx) = file_name.rfind('.') {
216 let ext = &file_name[dot_idx + 1..];
217 if !ext.is_empty() && ext.chars().all(|c| c.is_ascii_alphanumeric()) {
218 let trimmed_len = path.len() - (file_name.len() - dot_idx);
219 path[..trimmed_len].into()
220 } else {
221 path.clone()
222 }
223 } else {
224 path.clone()
225 };
226
227 Ok(if let Some(stripped) = result.strip_suffix("/index") {
229 if stripped.is_empty() {
230 "/".into()
231 } else {
232 stripped.into()
233 }
234 } else {
235 result
236 })
237 }
238
239 fn from_debug_build_paths(paths: &DebugBuildPaths) -> Result<Self> {
240 Ok(Self {
241 app: paths
242 .app
243 .iter()
244 .map(|path| Self::app_route_key_from_debug_path(path))
245 .collect::<Result<_>>()?,
246 pages: paths
247 .pages
248 .iter()
249 .map(Self::pages_route_key_from_debug_path)
250 .collect::<Result<_>>()?,
251 })
252 }
253
254 fn should_include_app_route(&self, route_key: &RcStr) -> bool {
255 if matches!(route_key.as_str(), "/_not-found" | "/_global-error") {
257 return true;
258 }
259 self.app.contains(route_key)
260 }
261
262 fn should_include_pages_route(&self, route_key: &RcStr) -> bool {
263 if matches!(
265 route_key.as_str(),
266 "/_error" | "/_document" | "/_app" | "/404" | "/500"
267 ) {
268 return self.pages.iter().any(|page| {
269 let page = page.as_str();
270 page != "/api" && !page.starts_with("/api/")
271 });
272 }
273 self.pages.contains(route_key)
274 }
275}
276
277#[derive(
278 Debug,
279 Serialize,
280 Deserialize,
281 Clone,
282 PartialEq,
283 Eq,
284 TraceRawVcs,
285 NonLocalValue,
286 OperationValue,
287 Encode,
288 Decode,
289)]
290#[serde(rename_all = "camelCase")]
291pub struct ProjectOptions {
292 pub root_path: RcStr,
301
302 pub project_path: RcStr,
305
306 pub next_config: RcStr,
308
309 pub env: Vec<(RcStr, RcStr)>,
311
312 pub define_env: DefineEnv,
314
315 pub watch: WatchOptions,
317
318 pub dev: bool,
320
321 pub encryption_key: RcStr,
323
324 pub build_id: RcStr,
326
327 pub preview_props: DraftModeOptions,
329
330 pub browserslist_query: RcStr,
332
333 pub no_mangling: bool,
336
337 pub write_routes_hashes_manifest: bool,
339
340 pub current_node_js_version: RcStr,
342
343 pub debug_build_paths: Option<DebugBuildPaths>,
346
347 pub deferred_entries: Option<Vec<RcStr>>,
349
350 pub is_persistent_caching_enabled: bool,
352
353 pub next_version: RcStr,
355
356 pub server_hmr: bool,
358}
359
360#[derive(Default)]
365pub struct PartialProjectOptions {
366 pub root_path: Option<RcStr>,
367
368 pub project_path: Option<RcStr>,
369
370 pub next_config: Option<RcStr>,
371
372 pub env: Option<Vec<(RcStr, RcStr)>>,
373
374 pub define_env: Option<DefineEnv>,
375
376 pub watch: Option<WatchOptions>,
377
378 pub dev: Option<bool>,
379
380 pub encryption_key: Option<RcStr>,
381
382 pub build_id: Option<RcStr>,
383
384 pub preview_props: Option<DraftModeOptions>,
385
386 pub browserslist_query: Option<RcStr>,
387
388 pub no_mangling: Option<bool>,
389
390 pub write_routes_hashes_manifest: Option<bool>,
391
392 pub debug_build_paths: Option<DebugBuildPaths>,
393}
394
395#[turbo_tasks::task_input]
396#[derive(
397 Debug,
398 Serialize,
399 Deserialize,
400 Clone,
401 PartialEq,
402 Eq,
403 Hash,
404 TraceRawVcs,
405 OperationValue,
406 Encode,
407 Decode,
408)]
409#[serde(rename_all = "camelCase")]
410pub struct DefineEnv {
411 pub client: Vec<(RcStr, Option<RcStr>)>,
412 pub edge: Vec<(RcStr, Option<RcStr>)>,
413 pub nodejs: Vec<(RcStr, Option<RcStr>)>,
414}
415
416#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
417pub struct Middleware {
418 pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
419 pub is_proxy: bool,
420}
421
422#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
423pub struct Instrumentation {
424 pub node_js: ResolvedVc<Box<dyn Endpoint>>,
425 pub edge: ResolvedVc<Box<dyn Endpoint>>,
426}
427
428#[turbo_tasks::value]
429pub struct ProjectContainer {
430 name: RcStr,
431 options_state: State<Option<ProjectOptions>>,
432 versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
433}
434
435#[turbo_tasks::value_impl]
436impl ProjectContainer {
437 #[turbo_tasks::function(operation, root)]
438 pub fn new_operation(name: RcStr, dev: bool) -> Result<Vc<Self>> {
439 Ok(ProjectContainer {
440 name,
441 versioned_content_map: if dev {
444 Some(VersionedContentMap::new())
445 } else {
446 None
447 },
448 options_state: State::new(None),
449 }
450 .cell())
451 }
452}
453
454#[turbo_tasks::function(operation, root)]
455fn project_operation(project: ResolvedVc<ProjectContainer>) -> Vc<Project> {
456 project.project()
457}
458
459#[turbo_tasks::function(operation, root)]
460fn project_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
461 project.project_fs()
462}
463
464#[turbo_tasks::function(operation, root)]
465fn output_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
466 project.project_fs()
467}
468
469enum EnvDiffType {
470 Added,
471 Removed,
472 Modified,
473}
474
475fn env_diff(
476 old: &[(RcStr, Option<RcStr>)],
477 new: &[(RcStr, Option<RcStr>)],
478) -> Vec<(RcStr, EnvDiffType)> {
479 let mut diffs = Vec::new();
480 let mut old_map: FxHashMap<_, _> = old.iter().cloned().collect();
481
482 for (key, new_value) in new.iter() {
483 match old_map.remove(key) {
484 Some(old_value) => {
485 if &old_value != new_value {
486 diffs.push((key.clone(), EnvDiffType::Modified));
487 }
488 }
489 None => {
490 diffs.push((key.clone(), EnvDiffType::Added));
491 }
492 }
493 }
494
495 for (key, _) in old.iter() {
496 if old_map.contains_key(key) {
497 diffs.push((key.clone(), EnvDiffType::Removed));
498 }
499 }
500
501 diffs
502}
503
504fn env_diff_report(old: &[(RcStr, Option<RcStr>)], new: &[(RcStr, Option<RcStr>)]) -> String {
505 use std::fmt::Write;
506
507 let diff = env_diff(old, new);
508
509 let mut report = String::new();
510 for (key, diff_type) in diff {
511 let symbol = match diff_type {
512 EnvDiffType::Added => "+",
513 EnvDiffType::Removed => "-",
514 EnvDiffType::Modified => "*",
515 };
516 if !report.is_empty() {
517 report.push_str(", ");
518 }
519 write!(report, "{}{}", symbol, key).unwrap();
520 }
521 report
522}
523
524fn define_env_diff_report(old: &DefineEnv, new: &DefineEnv) -> String {
525 use std::fmt::Write;
526
527 let mut report = String::new();
528 for (name, old, new) in [
529 ("client", &old.client, &new.client),
530 ("edge", &old.edge, &new.edge),
531 ("nodejs", &old.nodejs, &new.nodejs),
532 ] {
533 let diff = env_diff_report(old, new);
534 if !diff.is_empty() {
535 if !report.is_empty() {
536 report.push_str(", ");
537 }
538 write!(report, "{name}: {{ {diff} }}").unwrap();
539 }
540 }
541 report
542}
543
544impl ProjectContainer {
545 pub async fn initialize(this_op: OperationVc<Self>, options: ProjectOptions) -> Result<()> {
553 let this = this_op.read_strongly_consistent().await?;
554 let span = tracing::info_span!(
555 "initialize project",
556 project_name = %this.name,
557 version = options.next_version.as_str(),
558 node_version = options.current_node_js_version.as_str(),
559 os = std::env::consts::OS,
560 arch = std::env::consts::ARCH,
561 turbo_tasks_available_parallelism =
562 turbo_tasks::parallel::available_parallelism().map(|n| n.get()).unwrap_or(0),
563 std_thread_available_parallelism =
564 std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0),
565 dev = options.dev,
566 env_diff = Empty
567 );
568 let span_clone = span.clone();
569 async move {
570 let watch = options.watch;
571
572 if let Some(old_options) = &*this.options_state.get_untracked() {
573 span.record(
574 "env_diff",
575 define_env_diff_report(&old_options.define_env, &options.define_env).as_str(),
576 );
577 }
578 this.options_state.set(Some(options));
579
580 #[turbo_tasks::function(operation, root)]
581 fn project_from_container_operation(
582 container: OperationVc<ProjectContainer>,
583 ) -> Vc<Project> {
584 container.connect().project()
585 }
586 let project = project_from_container_operation(this_op)
587 .resolve()
588 .strongly_consistent()
589 .await?;
590 let project_fs = project_fs_operation(project)
591 .read_strongly_consistent()
592 .await?;
593 if watch.enable {
594 project_fs.start_watching().await?;
595 } else {
596 project_fs.invalidate_with_reason(|path| invalidation::Initialize {
597 path: RcStr::from(path.to_string_lossy()),
599 });
600 }
601 let output_fs = output_fs_operation(project)
602 .read_strongly_consistent()
603 .await?;
604 output_fs.invalidate_with_reason(|path| invalidation::Initialize {
605 path: RcStr::from(path.to_string_lossy()),
606 });
607 Ok(())
608 }
609 .instrument(span_clone)
610 .await
611 }
612
613 pub async fn update(self: ResolvedVc<Self>, options: PartialProjectOptions) -> Result<()> {
614 let span = tracing::info_span!(
615 "update project options",
616 project_name = %self.await?.name,
617 env_diff = Empty
618 );
619 let span_clone = span.clone();
620 async move {
621 #[turbo_tasks::function(operation, root)]
627 fn project_container_operation_hack(
628 container: ResolvedVc<ProjectContainer>,
629 ) -> Vc<ProjectContainer> {
630 *container
631 }
632 let this = project_container_operation_hack(self)
633 .read_strongly_consistent()
634 .await?;
635 let PartialProjectOptions {
636 root_path,
637 project_path,
638 next_config,
639 env,
640 define_env,
641 watch,
642 dev,
643 encryption_key,
644 build_id,
645 preview_props,
646 browserslist_query,
647 no_mangling,
648 write_routes_hashes_manifest,
649 debug_build_paths,
650 } = options;
651
652 let mut new_options = this
653 .options_state
654 .get()
655 .clone()
656 .context("ProjectContainer need to be initialized with initialize()")?;
657
658 if let Some(root_path) = root_path {
659 new_options.root_path = canonicalize_to_rcstr(Path::new(&*root_path))?;
660 }
661 if let Some(project_path) = project_path {
662 new_options.project_path = project_path;
663 }
664 if let Some(next_config) = next_config {
665 new_options.next_config = next_config;
666 }
667 if let Some(env) = env {
668 new_options.env = env;
669 }
670 if let Some(define_env) = define_env {
671 new_options.define_env = define_env;
672 }
673 if let Some(watch) = watch {
674 new_options.watch = watch;
675 }
676 if let Some(dev) = dev {
677 new_options.dev = dev;
678 }
679 if let Some(encryption_key) = encryption_key {
680 new_options.encryption_key = encryption_key;
681 }
682 if let Some(build_id) = build_id {
683 new_options.build_id = build_id;
684 }
685 if let Some(preview_props) = preview_props {
686 new_options.preview_props = preview_props;
687 }
688 if let Some(browserslist_query) = browserslist_query {
689 new_options.browserslist_query = browserslist_query;
690 }
691 if let Some(no_mangling) = no_mangling {
692 new_options.no_mangling = no_mangling;
693 }
694 if let Some(write_routes_hashes_manifest) = write_routes_hashes_manifest {
695 new_options.write_routes_hashes_manifest = write_routes_hashes_manifest;
696 }
697 if let Some(debug_build_paths) = debug_build_paths {
698 new_options.debug_build_paths = Some(debug_build_paths);
699 }
700
701 let watch = new_options.watch;
703
704 let project = project_operation(self)
705 .resolve()
706 .strongly_consistent()
707 .await?;
708 let prev_project_fs = project_fs_operation(project)
709 .read_strongly_consistent()
710 .await?;
711 let prev_output_fs = output_fs_operation(project)
712 .read_strongly_consistent()
713 .await?;
714
715 if let Some(old_options) = &*this.options_state.get_untracked() {
716 span.record(
717 "env_diff",
718 define_env_diff_report(&old_options.define_env, &new_options.define_env)
719 .as_str(),
720 );
721 }
722 this.options_state.set(Some(new_options));
723 let project = project_operation(self)
724 .resolve()
725 .strongly_consistent()
726 .await?;
727 let project_fs = project_fs_operation(project)
728 .read_strongly_consistent()
729 .await?;
730 let output_fs = output_fs_operation(project)
731 .read_strongly_consistent()
732 .await?;
733
734 if !ReadRef::ptr_eq(&prev_project_fs, &project_fs) {
735 if watch.enable {
736 project_fs.start_watching().await?;
738 } else {
739 project_fs.invalidate_with_reason(|path| invalidation::Initialize {
740 path: RcStr::from(path.to_string_lossy()),
742 });
743 }
744 }
745 if !ReadRef::ptr_eq(&prev_output_fs, &output_fs) {
746 prev_output_fs.invalidate_with_reason(|path| invalidation::Initialize {
747 path: RcStr::from(path.to_string_lossy()),
748 });
749 }
750
751 Ok(())
752 }
753 .instrument(span_clone)
754 .await
755 }
756}
757
758#[turbo_tasks::value_impl]
759impl ProjectContainer {
760 #[turbo_tasks::function]
761 pub async fn project(&self) -> Result<Vc<Project>> {
762 let env_map: Vc<EnvMap>;
763 let next_config;
764 let define_env;
765 let root_path_str: RcStr;
766 let project_path;
767 let watch;
768 let dev;
769 let encryption_key;
770 let build_id;
771 let preview_props;
772 let browserslist_query;
773 let no_mangling;
774 let write_routes_hashes_manifest;
775 let current_node_js_version;
776 let debug_build_paths;
777 let deferred_entries;
778 let is_persistent_caching_enabled;
779 let server_hmr;
780 {
781 let options = self.options_state.get();
782 let options = options
783 .as_ref()
784 .context("ProjectContainer need to be initialized with initialize()")?;
785 env_map = Vc::cell(options.env.iter().cloned().collect());
786 define_env = ProjectDefineEnv {
787 client: ResolvedVc::cell(options.define_env.client.iter().cloned().collect()),
788 edge: ResolvedVc::cell(options.define_env.edge.iter().cloned().collect()),
789 nodejs: ResolvedVc::cell(options.define_env.nodejs.iter().cloned().collect()),
790 }
791 .cell();
792 next_config = NextConfig::from_string(Vc::cell(options.next_config.clone()));
793 root_path_str = options.root_path.clone();
794 project_path = options.project_path.clone();
795 watch = options.watch;
796 dev = options.dev;
797 encryption_key = options.encryption_key.clone();
798 build_id = options.build_id.clone();
799 preview_props = options.preview_props.clone();
800 browserslist_query = options.browserslist_query.clone();
801 no_mangling = options.no_mangling;
802 write_routes_hashes_manifest = options.write_routes_hashes_manifest;
803 current_node_js_version = options.current_node_js_version.clone();
804 debug_build_paths = options.debug_build_paths.clone();
805 deferred_entries = options.deferred_entries.clone().unwrap_or_default();
806 is_persistent_caching_enabled = options.is_persistent_caching_enabled;
807 server_hmr = options.server_hmr;
808 }
809
810 let root_path = ResolvedVc::cell(root_path_str);
811 let dist_dir = next_config.dist_dir().owned().await?;
812 let dist_dir_root = next_config.dist_dir_root().owned().await?;
813 Ok(Project {
814 root_path,
815 project_path,
816 watch,
817 next_config: next_config.to_resolved().await?,
818 dist_dir,
819 dist_dir_root,
820 env: ResolvedVc::upcast(env_map.to_resolved().await?),
821 define_env: define_env.to_resolved().await?,
822 browserslist_query,
823 mode: if dev {
824 NextMode::Development.resolved_cell()
825 } else {
826 NextMode::Build.resolved_cell()
827 },
828 versioned_content_map: self.versioned_content_map,
829 build_id,
830 encryption_key,
831 preview_props,
832 no_mangling,
833 write_routes_hashes_manifest,
834 current_node_js_version,
835 debug_build_paths,
836 deferred_entries,
837 is_persistent_caching_enabled,
838 server_hmr,
839 }
840 .cell())
841 }
842
843 #[turbo_tasks::function]
845 pub fn entrypoints(self: Vc<Self>) -> Vc<Entrypoints> {
846 self.project().entrypoints()
847 }
848
849 #[turbo_tasks::function]
851 pub fn hmr_chunk_names(self: Vc<Self>) -> Vc<Vec<RcStr>> {
852 self.project().hmr_chunk_names()
853 }
854
855 #[turbo_tasks::function]
858 pub fn get_source_map(
859 &self,
860 file_path: FileSystemPath,
861 section: Option<RcStr>,
862 ) -> Vc<FileContent> {
863 if let Some(map) = self.versioned_content_map {
864 map.get_source_map(file_path, section)
865 } else {
866 FileContent::NotFound.cell()
867 }
868 }
869}
870
871#[derive(Clone)]
872#[turbo_tasks::value]
873pub struct Project {
874 root_path: ResolvedVc<RcStr>,
878
879 project_path: RcStr,
883
884 dist_dir: RcStr,
888
889 dist_dir_root: RcStr,
893
894 watch: WatchOptions,
896
897 next_config: ResolvedVc<NextConfig>,
899
900 env: ResolvedVc<Box<dyn ProcessEnv>>,
902
903 define_env: ResolvedVc<ProjectDefineEnv>,
906
907 browserslist_query: RcStr,
909
910 mode: ResolvedVc<NextMode>,
911
912 versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
913
914 build_id: RcStr,
915
916 encryption_key: RcStr,
917
918 preview_props: DraftModeOptions,
919
920 no_mangling: bool,
924
925 write_routes_hashes_manifest: bool,
927
928 current_node_js_version: RcStr,
929
930 debug_build_paths: Option<DebugBuildPaths>,
933
934 deferred_entries: Vec<RcStr>,
936
937 is_persistent_caching_enabled: bool,
939
940 server_hmr: bool,
942}
943
944#[turbo_tasks::value]
945pub struct ProjectDefineEnv {
946 client: ResolvedVc<OptionEnvMap>,
947 edge: ResolvedVc<OptionEnvMap>,
948 nodejs: ResolvedVc<OptionEnvMap>,
949}
950
951async fn import_meta_env_base_url(next_config: ResolvedVc<NextConfig>) -> Result<RcStr> {
952 Ok(match &*next_config.base_path().await? {
953 Some(base_path) => format!("{base_path}/").into(),
954 None => rcstr!("/"),
955 })
956}
957
958#[turbo_tasks::value_impl]
959impl ProjectDefineEnv {
960 #[turbo_tasks::function]
961 pub fn client(&self) -> Vc<OptionEnvMap> {
962 *self.client
963 }
964
965 #[turbo_tasks::function]
966 pub fn edge(&self) -> Vc<OptionEnvMap> {
967 *self.edge
968 }
969
970 #[turbo_tasks::function]
971 pub fn nodejs(&self) -> Vc<OptionEnvMap> {
972 *self.nodejs
973 }
974}
975
976#[turbo_tasks::value(shared)]
977struct ConflictIssue {
978 path: FileSystemPath,
979 title: ResolvedVc<StyledString>,
980 description: ResolvedVc<StyledString>,
981 severity: IssueSeverity,
982}
983
984#[async_trait]
985#[turbo_tasks::value_impl]
986impl Issue for ConflictIssue {
987 fn stage(&self) -> IssueStage {
988 IssueStage::AppStructure
989 }
990
991 fn severity(&self) -> IssueSeverity {
992 self.severity
993 }
994
995 async fn file_path(&self) -> Result<FileSystemPath> {
996 Ok(self.path.clone())
997 }
998
999 async fn title(&self) -> Result<StyledString> {
1000 self.title.owned().await
1001 }
1002
1003 async fn description(&self) -> Result<Option<StyledString>> {
1004 Ok(Some(self.description.owned().await?))
1005 }
1006}
1007
1008#[turbo_tasks::value_impl]
1009impl Project {
1010 #[turbo_tasks::function]
1011 pub async fn app_project(self: Vc<Self>) -> Result<Vc<OptionAppProject>> {
1012 let app_dir = find_app_dir(self.project_path().owned().await?).await?;
1013
1014 Ok(match &*app_dir {
1015 Some(app_dir) => Vc::cell(Some(
1016 AppProject::new(self, app_dir.clone()).to_resolved().await?,
1017 )),
1018 None => Vc::cell(None),
1019 })
1020 }
1021
1022 #[turbo_tasks::function]
1023 pub fn pages_project(self: Vc<Self>) -> Vc<PagesProject> {
1024 PagesProject::new(self)
1025 }
1026
1027 #[turbo_tasks::function]
1028 pub fn project_fs(&self) -> Result<Vc<DiskFileSystem>> {
1029 let denied_path = match join_path(&self.project_path, &self.dist_dir_root) {
1030 Some(dist_dir_root) => dist_dir_root.into(),
1031 None => {
1032 bail!(
1033 "Invalid distDirRoot: {:?}. distDirRoot should not navigate out of the \
1034 projectPath.",
1035 self.dist_dir_root
1036 );
1037 }
1038 };
1039
1040 let denied_profiles_path = join_path(&self.project_path, DIST_PROFILES_DIR_NAME)
1043 .unwrap()
1044 .into();
1045
1046 Ok(DiskFileSystem::new_with_options(
1047 PROJECT_FILESYSTEM_NAME,
1048 *self.root_path,
1049 vec![denied_path, denied_profiles_path],
1050 DiskWatcherConfig {
1051 poll_interval: self.watch.poll_interval,
1052 report_invalidation_reason: true,
1054 extended_batch_delay_matcher: Some(ResolvedVc::upcast(
1055 NodeModulesPathMatcher.resolved_cell(),
1056 )),
1057 ..Default::default()
1058 },
1059 ))
1060 }
1061
1062 #[turbo_tasks::function]
1063 pub fn client_fs(self: Vc<Self>) -> Vc<Box<dyn FileSystem>> {
1064 let virtual_fs = VirtualFileSystem::new_with_name(rcstr!("client-fs"));
1065 Vc::upcast(virtual_fs)
1066 }
1067
1068 #[turbo_tasks::function]
1069 pub fn output_fs(&self) -> Vc<DiskFileSystem> {
1070 DiskFileSystem::new(rcstr!("output"), *self.root_path)
1071 }
1072
1073 #[turbo_tasks::function]
1074 pub async fn node_root(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1075 let this = self.await?;
1076 Ok(self
1077 .output_fs()
1078 .root()
1079 .await?
1080 .join(&this.project_path)?
1081 .join(&this.dist_dir)?
1082 .cell())
1083 }
1084
1085 #[turbo_tasks::function]
1086 pub fn client_root(self: Vc<Self>) -> Vc<FileSystemPath> {
1087 self.client_fs().root()
1088 }
1089
1090 #[turbo_tasks::function]
1091 pub fn project_root_path(self: Vc<Self>) -> Vc<FileSystemPath> {
1092 self.project_fs().root()
1093 }
1094
1095 #[turbo_tasks::function]
1096 pub async fn client_relative_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1097 let next_config = self.next_config();
1098 Ok(self
1099 .client_root()
1100 .await?
1101 .join(&format!(
1102 "{}/_next",
1103 next_config
1104 .base_path()
1105 .await?
1106 .as_deref()
1107 .unwrap_or_default(),
1108 ))?
1109 .cell())
1110 }
1111
1112 #[turbo_tasks::function]
1116 pub async fn node_root_to_root_path(self: Vc<Self>) -> Result<Vc<RcStr>> {
1117 Ok(Vc::cell(
1118 self.node_root()
1119 .await?
1120 .get_relative_path_to(&*self.output_fs().root().await?)
1121 .context("Expected node root to be inside of output fs")?,
1122 ))
1123 }
1124
1125 #[turbo_tasks::function]
1126 pub async fn project_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1127 let this = self.await?;
1128 let root = self.project_root_path().await?;
1129 Ok(root.join(&this.project_path)?.cell())
1130 }
1131
1132 #[turbo_tasks::function]
1133 pub(super) fn env(&self) -> Vc<Box<dyn ProcessEnv>> {
1134 *self.env
1135 }
1136
1137 #[turbo_tasks::function]
1138 pub async fn ci_has_next_support(&self) -> Result<Vc<bool>> {
1139 Ok(Vc::cell(
1140 self.env.read(rcstr!("NOW_BUILDER")).await?.is_some(),
1141 ))
1142 }
1143
1144 #[turbo_tasks::function]
1145 pub(super) fn current_node_js_version(&self) -> Vc<NodeJsVersion> {
1146 NodeJsVersion::Static(ResolvedVc::cell(self.current_node_js_version.clone())).cell()
1147 }
1148
1149 #[turbo_tasks::function]
1150 pub fn next_config(&self) -> Vc<NextConfig> {
1151 *self.next_config
1152 }
1153
1154 #[turbo_tasks::function]
1157 pub async fn issue_filter(self: Vc<Self>) -> Result<Vc<IssueFilter>> {
1158 let ignore_rules = self.next_config().turbopack_ignore_issue_rules().await?;
1159 Ok(IssueFilter::warnings_and_foreign_errors()
1160 .with_ignore_rules(ReadRef::into_owned(ignore_rules))
1161 .cell())
1162 }
1163
1164 #[turbo_tasks::function]
1165 pub(super) fn is_persistent_caching_enabled(&self) -> Vc<bool> {
1166 Vc::cell(self.is_persistent_caching_enabled)
1167 }
1168
1169 #[turbo_tasks::function]
1170 pub(super) fn next_mode(&self) -> Vc<NextMode> {
1171 *self.mode
1172 }
1173
1174 #[turbo_tasks::function]
1175 pub(super) fn is_watch_enabled(&self) -> Result<Vc<bool>> {
1176 Ok(Vc::cell(self.watch.enable))
1177 }
1178
1179 #[turbo_tasks::function]
1180 pub(super) fn should_write_routes_hashes_manifest(&self) -> Result<Vc<bool>> {
1181 Ok(Vc::cell(self.write_routes_hashes_manifest))
1182 }
1183
1184 #[turbo_tasks::function]
1185 pub(super) async fn should_write_nft_manifests(&self) -> Result<Vc<bool>> {
1186 Ok(Vc::cell(
1187 self.mode.await?.is_production()
1188 && *self.next_config.output().await? != Some(OutputType::Export),
1189 ))
1190 }
1191
1192 #[turbo_tasks::function]
1193 pub fn deferred_entries(&self) -> Vc<Vec<RcStr>> {
1194 Vc::cell(self.deferred_entries.clone())
1195 }
1196
1197 #[turbo_tasks::function]
1198 pub(super) async fn per_page_module_graph(&self) -> Result<Vc<bool>> {
1199 Ok(Vc::cell(*self.mode.await? == NextMode::Development))
1200 }
1201
1202 #[turbo_tasks::function]
1203 pub(super) fn encryption_key(&self) -> Vc<RcStr> {
1204 Vc::cell(self.encryption_key.clone())
1205 }
1206
1207 #[turbo_tasks::function]
1208 pub(super) fn no_mangling(&self) -> Vc<bool> {
1209 Vc::cell(self.no_mangling)
1210 }
1211
1212 #[turbo_tasks::function]
1213 pub(super) async fn execution_context(self: Vc<Self>) -> Result<Vc<ExecutionContext>> {
1214 let node_root = self.node_root().owned().await?;
1215 let next_mode = self.next_mode().await?;
1216 let strategy = *self
1217 .next_config()
1218 .turbopack_plugin_runtime_strategy()
1219 .await?;
1220 let node_backend = match strategy {
1221 #[cfg(feature = "worker_pool")]
1222 TurbopackPluginRuntimeStrategy::WorkerThreads => worker_threads_backend(),
1223 #[cfg(all(feature = "process_pool", not(target_family = "wasm")))]
1224 TurbopackPluginRuntimeStrategy::ChildProcesses => child_process_backend(),
1225 };
1226
1227 let node_execution_chunking_context = Vc::upcast(
1228 NodeJsChunkingContext::builder(
1229 self.project_root_path().owned().await?,
1230 node_root.join("build")?,
1231 self.node_root_to_root_path().owned().await?,
1232 node_root.join("build")?,
1233 node_root.join("build/chunks")?,
1234 node_root.join("build/assets")?,
1235 node_build_environment().to_resolved().await?,
1236 next_mode.runtime_type(),
1237 )
1238 .source_maps(*self.next_config().server_source_maps().await?)
1239 .shared_runtime_chunk(true)
1244 .build(),
1245 );
1246
1247 Ok(ExecutionContext::new(
1248 self.project_path().owned().await?,
1249 node_execution_chunking_context,
1250 self.env(),
1251 node_backend,
1252 ))
1253 }
1254
1255 #[turbo_tasks::function]
1256 pub(super) async fn client_compile_time_info(&self) -> Result<Vc<CompileTimeInfo>> {
1257 let next_mode = self.mode.await?;
1258 Ok(get_client_compile_time_info(
1259 self.browserslist_query.clone(),
1260 self.define_env.client(),
1261 self.next_config.report_system_env_inlining(),
1262 next_mode.is_development(),
1263 import_meta_env_base_url(self.next_config).await?,
1264 ))
1265 }
1266
1267 #[turbo_tasks::function]
1268 pub async fn get_all_endpoint_groups(
1269 self: Vc<Self>,
1270 app_dir_only: bool,
1271 ) -> Result<Vc<EndpointGroups>> {
1272 Ok(self.get_all_endpoint_groups_with_app_route_filter(app_dir_only, None))
1273 }
1274
1275 #[turbo_tasks::function]
1276 pub async fn get_all_endpoint_groups_with_app_route_filter(
1277 self: Vc<Self>,
1278 app_dir_only: bool,
1279 app_route_filter: Option<Vec<RcStr>>,
1280 ) -> Result<Vc<EndpointGroups>> {
1281 let mut endpoint_groups = Vec::new();
1282
1283 let entrypoints = self
1284 .entrypoints_with_app_route_filter(app_route_filter)
1285 .await?;
1286 let mut add_pages_entries = false;
1287
1288 if let Some(middleware) = &entrypoints.middleware {
1289 endpoint_groups.push((
1290 EndpointGroupKey::Middleware,
1291 EndpointGroup::from(middleware.endpoint),
1292 ));
1293 }
1294
1295 if let Some(instrumentation) = &entrypoints.instrumentation {
1296 endpoint_groups.push((
1297 EndpointGroupKey::Instrumentation,
1298 EndpointGroup::from(instrumentation.node_js),
1299 ));
1300 endpoint_groups.push((
1301 EndpointGroupKey::InstrumentationEdge,
1302 EndpointGroup::from(instrumentation.edge),
1303 ));
1304 }
1305
1306 for (key, route) in entrypoints.routes.iter() {
1307 match route {
1308 Route::Page {
1309 html_endpoint,
1310 data_endpoint,
1311 } => {
1312 if !app_dir_only {
1313 endpoint_groups.push((
1314 EndpointGroupKey::Route(key.clone()),
1315 EndpointGroup {
1316 primary: vec![EndpointGroupEntry {
1317 endpoint: *html_endpoint,
1318 sub_name: None,
1319 }],
1320 additional: data_endpoint
1322 .iter()
1323 .map(|endpoint| EndpointGroupEntry {
1324 endpoint: *endpoint,
1325 sub_name: None,
1326 })
1327 .collect(),
1328 },
1329 ));
1330 add_pages_entries = true;
1331 }
1332 }
1333 Route::PageApi { endpoint } => {
1334 if !app_dir_only {
1335 endpoint_groups.push((
1336 EndpointGroupKey::Route(key.clone()),
1337 EndpointGroup::from(*endpoint),
1338 ));
1339 add_pages_entries = true;
1340 }
1341 }
1342 Route::AppPage(page_routes) => {
1343 endpoint_groups.push((
1344 EndpointGroupKey::Route(key.clone()),
1345 EndpointGroup {
1346 primary: page_routes
1347 .iter()
1348 .map(|r| EndpointGroupEntry {
1349 endpoint: r.html_endpoint,
1350 sub_name: Some(r.original_name.clone()),
1351 })
1352 .collect(),
1353 additional: Vec::new(),
1354 },
1355 ));
1356 }
1357 Route::AppRoute {
1358 original_name: _,
1359 endpoint,
1360 ..
1361 } => {
1362 endpoint_groups.push((
1363 EndpointGroupKey::Route(key.clone()),
1364 EndpointGroup::from(*endpoint),
1365 ));
1366 }
1367 Route::Conflict => {
1368 tracing::info!("WARN: conflict");
1369 }
1370 }
1371 }
1372
1373 if add_pages_entries {
1374 endpoint_groups.push((
1375 EndpointGroupKey::PagesError,
1376 EndpointGroup::from(entrypoints.pages_error_endpoint),
1377 ));
1378 endpoint_groups.push((
1379 EndpointGroupKey::PagesApp,
1380 EndpointGroup::from(entrypoints.pages_app_endpoint),
1381 ));
1382 endpoint_groups.push((
1383 EndpointGroupKey::PagesDocument,
1384 EndpointGroup::from(entrypoints.pages_document_endpoint),
1385 ));
1386 }
1387
1388 Ok(Vc::cell(endpoint_groups))
1389 }
1390
1391 #[turbo_tasks::function]
1392 pub async fn get_all_endpoints(self: Vc<Self>, app_dir_only: bool) -> Result<Vc<Endpoints>> {
1393 let mut endpoints = Vec::new();
1394 for (_key, group) in self.get_all_endpoint_groups(app_dir_only).await?.iter() {
1395 for entry in group.primary.iter() {
1396 endpoints.push(entry.endpoint);
1397 }
1398 for entry in group.additional.iter() {
1399 endpoints.push(entry.endpoint);
1400 }
1401 }
1402
1403 Ok(Vc::cell(endpoints))
1404 }
1405
1406 #[turbo_tasks::function]
1407 pub async fn get_all_entries(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
1408 let endpoint_entries = self
1409 .get_all_endpoints(false)
1410 .await?
1411 .iter()
1412 .map(|endpoint| endpoint.entries().owned())
1413 .try_join()
1414 .await?;
1415
1416 let result = GraphEntries::concatenate(
1417 endpoint_entries
1418 .into_iter()
1419 .chain(std::iter::once(self.client_main_modules().owned().await?))
1420 .chain(std::iter::once(GraphEntries::new(
1421 vec![],
1422 self.pages_traced_modules().owned().await?,
1426 ))),
1427 );
1428
1429 Ok(result.cell())
1430 }
1431
1432 #[turbo_tasks::function]
1433 pub async fn get_all_additional_entries(
1434 self: Vc<Self>,
1435 graphs: Vc<ModuleGraph>,
1436 ) -> Result<Vc<GraphEntries>> {
1437 let result = GraphEntries::concatenate(
1438 self.get_all_endpoints(false)
1439 .await?
1440 .iter()
1441 .map(|endpoint| endpoint.additional_entries(graphs).owned())
1442 .try_join()
1443 .await?,
1444 );
1445 Ok(result.cell())
1446 }
1447
1448 #[turbo_tasks::function]
1449 pub async fn module_graph(
1450 self: Vc<Self>,
1451 entry: ResolvedVc<Box<dyn Module>>,
1452 ) -> Result<Vc<ModuleGraph>> {
1453 Ok(if *self.per_page_module_graph().await? {
1454 ModuleGraph::from_graphs(
1455 vec![SingleModuleGraph::new_with_entry(
1456 ChunkGroupEntry::Entry {
1457 modules: vec![entry],
1458 heuristics: EntryHeuristics::default(),
1459 },
1460 *self.should_write_nft_manifests().await?,
1461 self.next_mode().await?.is_production(),
1462 )],
1463 None,
1464 )
1465 .connect()
1466 } else {
1467 *self.whole_app_module_graphs().await?.full
1468 })
1469 }
1470
1471 #[turbo_tasks::function]
1472 pub async fn module_graph_for_modules(
1473 self: Vc<Self>,
1474 evaluatable_assets: Vc<EvaluatableAssets>,
1475 ) -> Result<Vc<ModuleGraph>> {
1476 Ok(if *self.per_page_module_graph().await? {
1477 let entries = evaluatable_assets
1478 .await?
1479 .iter()
1480 .copied()
1481 .map(ResolvedVc::upcast)
1482 .collect();
1483 ModuleGraph::from_graphs(
1484 vec![SingleModuleGraph::new_with_entries(
1485 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
1486 modules: entries,
1487 heuristics: EntryHeuristics::default(),
1488 }])
1489 .resolved_cell(),
1490 *self.should_write_nft_manifests().await?,
1491 self.next_mode().await?.is_production(),
1492 )],
1493 None,
1494 )
1495 .connect()
1496 } else {
1497 *self.whole_app_module_graphs().await?.full
1498 })
1499 }
1500
1501 #[turbo_tasks::function]
1506 pub async fn whole_app_module_graphs_without_dropping_issues(
1507 self: ResolvedVc<Self>,
1508 ) -> Result<Vc<BaseAndFullModuleGraph>> {
1509 let module_graphs_op = whole_app_module_graph_operation(self);
1510 let module_graphs_vc = module_graphs_op.connect();
1511 scale_down_node_pool(self).await?;
1512 Ok(module_graphs_vc)
1513 }
1514
1515 #[turbo_tasks::function(root)]
1518 pub async fn whole_app_module_graphs(
1519 self: ResolvedVc<Self>,
1520 ) -> Result<Vc<BaseAndFullModuleGraph>> {
1521 let module_graphs_op = whole_app_module_graph_operation(self);
1522 let module_graphs_vc = if self.next_mode().await?.is_production() {
1523 module_graphs_op.connect()
1524 } else {
1525 let vc = module_graphs_op.resolve().strongly_consistent().await?;
1526 module_graphs_op.drop_issues();
1527 *vc
1528 };
1529 scale_down_node_pool(self).await?;
1530 Ok(module_graphs_vc)
1531 }
1532
1533 #[turbo_tasks::function]
1534 pub(super) async fn server_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1535 let this = self.await?;
1536 Ok(get_server_compile_time_info(
1537 self.project_path(),
1539 this.define_env.nodejs(),
1540 self.current_node_js_version(),
1541 this.next_config.report_system_env_inlining(),
1542 this.server_hmr,
1543 import_meta_env_base_url(this.next_config).await?,
1544 ))
1545 }
1546
1547 #[turbo_tasks::function]
1548 pub(super) async fn edge_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1549 let this = self.await?;
1550 Ok(get_edge_compile_time_info(
1551 self.project_path().owned().await?,
1552 this.define_env.edge(),
1553 self.current_node_js_version(),
1554 this.next_config.report_system_env_inlining(),
1555 import_meta_env_base_url(this.next_config).await?,
1556 ))
1557 }
1558
1559 #[turbo_tasks::function]
1560 pub(super) fn edge_env(&self) -> Vc<EnvMap> {
1561 let edge_env = fxindexmap! {
1562 rcstr!("__NEXT_BUILD_ID") => self.build_id.clone(),
1563 rcstr!("NEXT_SERVER_ACTIONS_ENCRYPTION_KEY") => self.encryption_key.clone(),
1564 rcstr!("__NEXT_PREVIEW_MODE_ID") => self.preview_props.preview_mode_id.clone(),
1565 rcstr!("__NEXT_PREVIEW_MODE_ENCRYPTION_KEY") => self.preview_props.preview_mode_encryption_key.clone(),
1566 rcstr!("__NEXT_PREVIEW_MODE_SIGNING_KEY") => self.preview_props.preview_mode_signing_key.clone(),
1567 };
1568 Vc::cell(edge_env)
1569 }
1570
1571 #[turbo_tasks::function]
1572 pub(super) async fn client_chunking_context(
1573 self: Vc<Self>,
1574 ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1575 let css_url_suffix = self.next_config().asset_suffix_path();
1576 let turbopack_chunking = self.next_config().turbopack_chunking().await?;
1577 Ok(get_client_chunking_context(ClientChunkingContextOptions {
1578 mode: self.next_mode(),
1579 root_path: self.project_root_path().owned().await?,
1580 client_root: self.client_relative_path().owned().await?,
1581 client_root_to_root_path: rcstr!("/ROOT"),
1582 client_static_folder_name: self
1583 .next_config()
1584 .client_static_folder_name()
1585 .owned()
1586 .await?,
1587 asset_prefix: self.next_config().computed_asset_prefix(),
1588 service_worker_scope_base_path: self.next_config().base_path(),
1589 environment: self.client_compile_time_info().environment(),
1590 module_id_strategy: self.module_ids(),
1591 export_usage: self.export_usage(),
1592 unused_references: self.unused_references(),
1593 minify: self.next_config().turbo_client_minify(self.next_mode()),
1594 source_maps: self.next_config().client_source_maps(self.next_mode()),
1595 no_mangling: self.no_mangling(),
1596 scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1597 nested_async_chunking: self
1598 .next_config()
1599 .turbo_nested_async_chunking(self.next_mode(), true),
1600 shared_runtime: self.next_config().turbo_shared_runtime(self.next_mode()),
1601 per_page_module_graph: self.per_page_module_graph(),
1602 debug_ids: self.next_config().turbopack_debug_ids(),
1603 worker_asset_prefix: self.next_config().turbopack_worker_asset_prefix(),
1604 should_use_absolute_url_references: self.next_config().inline_css(),
1605 css_url_suffix,
1606 hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1607 cross_origin: self.next_config().cross_origin(),
1608 chunk_loading_global: self.next_config().turbopack_chunk_loading_global(),
1609 style_groups_algorithm: self.next_config().css_chunking().owned().await?,
1610 chunking_first_page_load_priority: turbopack_chunking.first_page_load_priority,
1611 chunking_priority_boost_percent: turbopack_chunking.priority_boost_percent,
1612 chunking_request_cost: turbopack_chunking.request_cost,
1613 chunking_min_chunk_size: turbopack_chunking.min_chunk_size,
1614 chunking_max_chunk_count_per_group: turbopack_chunking.max_chunk_count_per_group,
1615 chunking_max_merge_chunk_size: turbopack_chunking.max_merge_chunk_size,
1616 chunking_min_component_chunk_size: turbopack_chunking.min_component_chunk_size,
1617 generate_component_chunks: self.next_config().turbopack_generate_component_chunks(),
1618 }))
1619 }
1620
1621 #[turbo_tasks::function]
1622 pub(super) async fn service_worker_chunking_context(
1623 self: Vc<Self>,
1624 ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1625 Ok(get_service_worker_chunking_context(
1626 ServiceWorkerChunkingContextOptions {
1627 mode: self.next_mode(),
1628 root_path: self.project_root_path().owned().await?,
1629 output_root: self.node_root().owned().await?,
1630 output_root_to_root_path: self.node_root_to_root_path().owned().await?,
1631 environment: self.client_compile_time_info().environment(),
1632 minify: self.next_config().turbo_client_minify(self.next_mode()),
1633 source_maps: self.next_config().client_source_maps(self.next_mode()),
1634 no_mangling: self.no_mangling(),
1635 hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1636 },
1637 ))
1638 }
1639
1640 #[turbo_tasks::function]
1641 pub(super) async fn service_worker_asset_context(
1642 self: Vc<Self>,
1643 ) -> Result<Vc<Box<dyn AssetContext>>> {
1644 Ok(Vc::upcast(ModuleAssetContext::new(
1645 TransitionOptions::default().cell(),
1646 self.client_compile_time_info(),
1647 get_client_module_options_context(
1648 self.project_path().owned().await?,
1649 self.execution_context(),
1650 self.client_compile_time_info().environment(),
1651 ClientContextType::Other,
1652 self.next_mode(),
1653 self.next_config(),
1654 self.encryption_key(),
1655 ),
1656 get_client_resolve_options_context(
1657 self.project_path().owned().await?,
1658 ClientContextType::Other,
1659 self.next_mode(),
1660 self.next_config(),
1661 self.execution_context(),
1662 ),
1663 Layer::new_with_user_friendly_name(rcstr!("service-worker"), rcstr!("Service Worker")),
1664 )))
1665 }
1666
1667 #[turbo_tasks::function]
1668 pub(super) async fn server_chunking_context(
1669 self: Vc<Self>,
1670 client_assets: bool,
1671 ) -> Result<Vc<NodeJsChunkingContext>> {
1672 let css_url_suffix = self.next_config().asset_suffix_path();
1673 let options = ServerChunkingContextOptions {
1674 mode: self.next_mode(),
1675 root_path: self.project_root_path().owned().await?,
1676 node_root: self.node_root().owned().await?,
1677 node_root_to_root_path: self.node_root_to_root_path().owned().await?,
1678 environment: self.server_compile_time_info().environment(),
1679 module_id_strategy: self.module_ids(),
1680 export_usage: self.export_usage(),
1681 unused_references: self.unused_references(),
1682 minify: self.next_config().turbo_server_minify(self.next_mode()),
1683 source_maps: self.next_config().server_source_maps(),
1684 no_mangling: self.no_mangling(),
1685 scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1686 nested_async_chunking: self
1687 .next_config()
1688 .turbo_nested_async_chunking(self.next_mode(), false),
1689 debug_ids: self.next_config().turbopack_debug_ids(),
1690 client_root: self.client_relative_path().owned().await?,
1691 client_static_folder_name: self
1692 .next_config()
1693 .client_static_folder_name()
1694 .owned()
1695 .await?,
1696 asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1697 css_url_suffix,
1698 hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1699 style_groups_algorithm: self.next_config().css_chunking().owned().await?,
1700 per_page_module_graph: self.per_page_module_graph(),
1701 };
1702 Ok(if client_assets {
1703 get_server_chunking_context_with_client_assets(options)
1704 } else {
1705 get_server_chunking_context(options)
1706 })
1707 }
1708
1709 #[turbo_tasks::function]
1710 pub(super) async fn edge_chunking_context(
1711 self: Vc<Self>,
1712 client_assets: bool,
1713 ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1714 let css_url_suffix = self.next_config().asset_suffix_path();
1715 let options = EdgeChunkingContextOptions {
1716 mode: self.next_mode(),
1717 root_path: self.project_root_path().owned().await?,
1718 node_root: self.node_root().owned().await?,
1719 output_root_to_root_path: self.node_root_to_root_path(),
1720 environment: self.edge_compile_time_info().environment(),
1721 module_id_strategy: self.module_ids(),
1722 export_usage: self.export_usage(),
1723 unused_references: self.unused_references(),
1724 turbo_minify: self.next_config().turbo_edge_minify(self.next_mode()),
1725 turbo_source_maps: self.next_config().server_source_maps(),
1726 no_mangling: self.no_mangling(),
1727 scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1728 nested_async_chunking: self
1729 .next_config()
1730 .turbo_nested_async_chunking(self.next_mode(), false),
1731 client_root: self.client_relative_path().owned().await?,
1732 client_static_folder_name: self
1733 .next_config()
1734 .client_static_folder_name()
1735 .owned()
1736 .await?,
1737 asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1738 css_url_suffix,
1739 hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1740 cross_origin: self.next_config().cross_origin(),
1741 style_groups_algorithm: self.next_config().css_chunking().owned().await?,
1742 };
1743 Ok(if client_assets {
1744 get_edge_chunking_context_with_client_assets(options)
1745 } else {
1746 get_edge_chunking_context(options)
1747 })
1748 }
1749
1750 #[turbo_tasks::function]
1751 pub(super) fn runtime_chunking_context(
1752 self: Vc<Self>,
1753 client_assets: bool,
1754 runtime: NextRuntime,
1755 ) -> Vc<Box<dyn ChunkingContext>> {
1756 match runtime {
1757 NextRuntime::Edge => self.edge_chunking_context(client_assets),
1758 NextRuntime::NodeJs => Vc::upcast(self.server_chunking_context(client_assets)),
1759 }
1760 }
1761
1762 #[turbo_tasks::function]
1780 pub async fn project_feature_usage(
1781 self: ResolvedVc<Self>,
1782 ) -> Result<Vc<ProjectFeatureUsageSummary>> {
1783 if !self.next_mode().await?.is_production() {
1784 bail!("project_feature_usage() may only be called during `next build`");
1785 }
1786
1787 static FEATURE_MODULE_PATH_SUFFIXES: &[(&str, &str)] = &[
1801 ("next/image", "/next/image.js"),
1802 ("next/future/image", "/next/future/image.js"),
1803 ("next/legacy/image", "/next/legacy/image.js"),
1804 ("next/script", "/next/script.js"),
1805 ("next/dynamic", "/next/dynamic.js"),
1806 ("next/font/google", "/next/font/google/target.css"),
1807 ("next/font/local", "/next/font/local/target.css"),
1808 ("@next/font/google", "/@next/font/google/target.css"),
1809 ("@next/font/local", "/@next/font/local/target.css"),
1810 ];
1811
1812 let config = self.next_config();
1815 let compiler_options = config.compiler().await?;
1816 let mut features: Vec<(RcStr, u32)> = vec![
1817 (
1820 format!("swc/target/{}", env!("VERGEN_CARGO_TARGET_TRIPLE")).into(),
1821 1,
1822 ),
1823 (
1824 rcstr!("skipProxyUrlNormalize"),
1825 (*config.skip_proxy_url_normalize().await?) as u32,
1826 ),
1827 (
1828 rcstr!("skipTrailingSlashRedirect"),
1829 (*config.skip_trailing_slash_redirect().await?) as u32,
1830 ),
1831 (
1832 rcstr!("modularizeImports"),
1833 !config.modularize_imports().await?.is_empty() as u32,
1834 ),
1835 (
1836 rcstr!("transpilePackages"),
1837 !config.transpile_packages().await?.is_empty() as u32,
1838 ),
1839 (rcstr!("swcRelay"), compiler_options.relay.is_some() as u32),
1840 (
1841 rcstr!("swcStyledComponents"),
1842 compiler_options
1843 .styled_components
1844 .as_ref()
1845 .is_some_and(|sc| sc.is_enabled()) as u32,
1846 ),
1847 (
1848 rcstr!("swcReactRemoveProperties"),
1849 compiler_options
1850 .react_remove_properties
1851 .as_ref()
1852 .is_some_and(|rc| rc.is_enabled()) as u32,
1853 ),
1854 (
1855 rcstr!("swcRemoveConsole"),
1856 compiler_options
1857 .remove_console
1858 .as_ref()
1859 .is_some_and(|rc| rc.is_enabled()) as u32,
1860 ),
1861 (
1862 rcstr!("swcEmotion"),
1863 compiler_options
1864 .emotion
1865 .as_ref()
1866 .is_some_and(|e| e.is_enabled()) as u32,
1867 ),
1868 ];
1869
1870 let module_graph = self.whole_app_module_graphs().await?.full.await?;
1875
1876 let matching: FxHashMap<ResolvedVc<Box<dyn Module>>, &'static str> = module_graph
1877 .iter_nodes()
1878 .map(async |node| {
1879 let ident = node.ident().await?;
1880 let path = &ident.path.path;
1881 for &(feature, suffix) in FEATURE_MODULE_PATH_SUFFIXES {
1882 if path.ends_with(suffix) {
1883 return Ok(Some((node, feature)));
1884 }
1885 }
1886 Ok(None)
1887 })
1888 .try_flat_join()
1889 .await?
1890 .into_iter()
1891 .collect();
1892
1893 let mut pairs: FxHashSet<(&'static str, ResolvedVc<Box<dyn Module>>)> =
1901 FxHashSet::default();
1902 module_graph.traverse_edges_unordered(|parent, node| {
1903 if let Some((parent_node, _)) = parent
1904 && let Some(&feature) = matching.get(&node)
1905 {
1906 pairs.insert((feature, parent_node));
1907 }
1908 Ok(())
1909 })?;
1910
1911 let parent_source_keys = pairs
1916 .into_iter()
1917 .map(async |(feature, parent)| {
1918 let ident = parent.ident().await?;
1919 let key = (
1920 ident.path.path.clone(),
1921 ident.query.clone(),
1922 ident.fragment.clone(),
1923 );
1924 Ok((feature, key))
1925 })
1926 .try_join()
1927 .await?;
1928
1929 let mut importers: FxHashMap<&'static str, FxHashSet<(RcStr, RcStr, RcStr)>> =
1930 FxHashMap::default();
1931 for (feature, key) in parent_source_keys {
1932 importers.entry(feature).or_default().insert(key);
1933 }
1934 for (feature, unique_sources) in importers {
1935 features.push((RcStr::from(feature), unique_sources.len() as u32));
1936 }
1937
1938 features.sort_by(|a, b| a.0.cmp(&b.0));
1939 Ok(ProjectFeatureUsageSummary { features }.cell())
1940 }
1941
1942 #[turbo_tasks::function]
1945 pub async fn entrypoints(self: Vc<Self>) -> Result<Vc<Entrypoints>> {
1946 Ok(self.entrypoints_with_app_route_filter(None))
1947 }
1948
1949 #[turbo_tasks::function]
1950 pub async fn entrypoints_with_app_route_filter(
1951 self: Vc<Self>,
1952 app_route_filter: Option<Vec<RcStr>>,
1953 ) -> Result<Vc<Entrypoints>> {
1954 let this = self.await?;
1955 let mut routes = FxIndexMap::default();
1956 let app_project = self.app_project();
1957 let pages_project = self.pages_project();
1958
1959 let debug_build_paths_route_keys = this
1961 .debug_build_paths
1962 .as_ref()
1963 .map(DebugBuildPathsRouteKeys::from_debug_build_paths)
1964 .transpose()?;
1965
1966 if let Some(app_project) = &*app_project.await? {
1967 let app_routes = app_project.routes_with_filter(app_route_filter);
1968 routes.extend(
1969 app_routes
1970 .await?
1971 .iter()
1972 .filter(|(k, _)| {
1973 debug_build_paths_route_keys
1974 .as_ref()
1975 .is_none_or(|keys| keys.should_include_app_route(k))
1976 })
1977 .map(|(k, v)| (k.clone(), v.clone())),
1978 );
1979 }
1980
1981 for (pathname, page_route) in &pages_project.routes().await? {
1982 if debug_build_paths_route_keys
1983 .as_ref()
1984 .is_some_and(|keys| !keys.should_include_pages_route(pathname))
1985 {
1986 continue;
1987 }
1988
1989 match routes.entry(pathname.clone()) {
1990 Entry::Occupied(mut entry) => {
1991 ConflictIssue {
1992 path: self.project_path().owned().await?,
1993 title: StyledString::Text(
1994 format!("App Router and Pages Router both match path: {pathname}")
1995 .into(),
1996 )
1997 .resolved_cell(),
1998 description: StyledString::Text(
1999 "Next.js does not support having both App Router and Pages Router \
2000 routes matching the same path. Please remove one of the conflicting \
2001 routes."
2002 .into(),
2003 )
2004 .resolved_cell(),
2005 severity: IssueSeverity::Error,
2006 }
2007 .resolved_cell()
2008 .emit();
2009 *entry.get_mut() = Route::Conflict;
2010 }
2011 Entry::Vacant(entry) => {
2012 entry.insert(page_route.clone());
2013 }
2014 }
2015 }
2016
2017 let pages_document_endpoint = self
2018 .pages_project()
2019 .document_endpoint()
2020 .to_resolved()
2021 .await?;
2022 let pages_app_endpoint = self.pages_project().app_endpoint().to_resolved().await?;
2023 let pages_error_endpoint = self.pages_project().error_endpoint().to_resolved().await?;
2024
2025 let middleware = self.find_middleware();
2026 let middleware = if let FindContextFileResult::Found(fs_path, _) = &*middleware.await? {
2027 let is_proxy = fs_path.file_stem() == Some("proxy");
2028 Some(Middleware {
2029 endpoint: self.middleware_endpoint().to_resolved().await?,
2030 is_proxy,
2031 })
2032 } else {
2033 None
2034 };
2035
2036 let instrumentation = self.find_instrumentation();
2037 let instrumentation = if let FindContextFileResult::Found(..) = *instrumentation.await? {
2038 Some(Instrumentation {
2039 node_js: self.instrumentation_endpoint(false).to_resolved().await?,
2040 edge: self.instrumentation_endpoint(true).to_resolved().await?,
2041 })
2042 } else {
2043 None
2044 };
2045
2046 Ok(Entrypoints {
2047 routes,
2048 middleware,
2049 instrumentation,
2050 pages_document_endpoint,
2051 pages_app_endpoint,
2052 pages_error_endpoint,
2053 }
2054 .cell())
2055 }
2056
2057 #[turbo_tasks::function]
2058 async fn edge_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2059 let mut transitions = vec![];
2060
2061 let app_dir = find_app_dir(self.project_path().owned().await?)
2062 .owned()
2063 .await?;
2064 let app_project = *self.app_project().await?;
2065
2066 let ecmascript_client_reference_transition_name =
2067 app_project.map(|_| AppProject::client_transition_name());
2068
2069 if let Some(app_project) = app_project {
2070 transitions.push((
2071 AppProject::client_transition_name(),
2072 app_project
2073 .edge_ecmascript_client_reference_transition()
2074 .to_resolved()
2075 .await?,
2076 ));
2077 }
2078
2079 Ok(Vc::upcast(ModuleAssetContext::new(
2080 TransitionOptions {
2081 named_transitions: transitions.clone().into_iter().collect(),
2082 ..Default::default()
2083 }
2084 .cell(),
2085 self.edge_compile_time_info(),
2086 get_server_module_options_context(
2087 self.project_path().owned().await?,
2088 self.execution_context(),
2089 ServerContextType::Middleware {
2090 app_dir: app_dir.clone(),
2091 ecmascript_client_reference_transition_name:
2092 ecmascript_client_reference_transition_name.clone(),
2093 },
2094 self.next_mode(),
2095 self.next_config(),
2096 NextRuntime::Edge,
2097 self.encryption_key(),
2098 self.edge_compile_time_info().environment(),
2099 self.client_compile_time_info().environment(),
2100 false,
2102 ),
2103 get_edge_resolve_options_context(
2104 self.project_path().owned().await?,
2105 ServerContextType::Middleware {
2106 app_dir: app_dir.clone(),
2107 ecmascript_client_reference_transition_name:
2108 ecmascript_client_reference_transition_name.clone(),
2109 },
2110 self.next_mode(),
2111 self.next_config(),
2112 self.execution_context(),
2113 None, ),
2115 Layer::new_with_user_friendly_name(
2116 rcstr!("middleware-edge"),
2117 rcstr!("Edge Middleware"),
2118 ),
2119 )))
2120 }
2121
2122 #[turbo_tasks::function]
2123 async fn node_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2124 let mut transitions = vec![];
2125
2126 let app_dir = find_app_dir(self.project_path().owned().await?)
2127 .owned()
2128 .await?;
2129 let app_project = *self.app_project().await?;
2130
2131 let ecmascript_client_reference_transition_name =
2132 app_project.map(|_| AppProject::client_transition_name());
2133
2134 if let Some(app_project) = app_project {
2135 transitions.push((
2136 AppProject::client_transition_name(),
2137 app_project
2138 .edge_ecmascript_client_reference_transition()
2139 .to_resolved()
2140 .await?,
2141 ));
2142 }
2143
2144 Ok(Vc::upcast(ModuleAssetContext::new(
2145 TransitionOptions {
2146 named_transitions: transitions.clone().into_iter().collect(),
2147 ..Default::default()
2148 }
2149 .cell(),
2150 self.server_compile_time_info(),
2151 get_server_module_options_context(
2152 self.project_path().owned().await?,
2153 self.execution_context(),
2154 ServerContextType::Middleware {
2155 app_dir: app_dir.clone(),
2156 ecmascript_client_reference_transition_name:
2157 ecmascript_client_reference_transition_name.clone(),
2158 },
2159 self.next_mode(),
2160 self.next_config(),
2161 NextRuntime::NodeJs,
2162 self.encryption_key(),
2163 self.server_compile_time_info().environment(),
2164 self.client_compile_time_info().environment(),
2165 *self.should_write_nft_manifests().await?,
2166 ),
2167 get_server_resolve_options_context(
2168 self.project_path().owned().await?,
2169 ServerContextType::Middleware {
2170 app_dir: app_dir.clone(),
2171 ecmascript_client_reference_transition_name,
2172 },
2173 self.next_mode(),
2174 self.next_config(),
2175 self.execution_context(),
2176 None, ),
2178 Layer::new_with_user_friendly_name(rcstr!("middleware"), rcstr!("Middleware")),
2179 )))
2180 }
2181
2182 #[turbo_tasks::function]
2183 async fn find_middleware(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
2184 Ok(find_context_file(
2185 self.project_path().owned().await?,
2186 middleware_files(self.next_config().page_extensions()),
2187 false,
2189 ))
2190 }
2191
2192 #[turbo_tasks::function]
2193 async fn middleware_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
2194 let middleware = self.find_middleware();
2195 let FindContextFileResult::Found(fs_path, _) = &*middleware.await? else {
2196 return Ok(Vc::upcast(EmptyEndpoint::new(self)));
2197 };
2198 let source = Vc::upcast(FileSource::new(fs_path.clone()));
2199 let app_dir = find_app_dir(self.project_path().owned().await?)
2200 .owned()
2201 .await?;
2202 let ecmascript_client_reference_transition_name = (*self.app_project().await?)
2203 .as_ref()
2204 .map(|_| AppProject::client_transition_name());
2205
2206 let is_proxy = fs_path.file_stem() == Some("proxy");
2207 let config = parse_segment_config_from_source(
2208 source,
2209 if is_proxy {
2210 ParseSegmentMode::Proxy
2211 } else {
2212 ParseSegmentMode::Base
2213 },
2214 );
2215 let runtime = config.await?.runtime.unwrap_or(if is_proxy {
2216 NextRuntime::NodeJs
2217 } else {
2218 NextRuntime::Edge
2219 });
2220
2221 let middleware_asset_context = match runtime {
2222 NextRuntime::NodeJs => self.node_middleware_context(),
2223 NextRuntime::Edge => self.edge_middleware_context(),
2224 };
2225
2226 Ok(Vc::upcast(MiddlewareEndpoint::new(
2227 self,
2228 middleware_asset_context,
2229 source,
2230 app_dir.clone(),
2231 ecmascript_client_reference_transition_name,
2232 config,
2233 runtime,
2234 )))
2235 }
2236
2237 #[turbo_tasks::function]
2238 async fn node_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2239 let mut transitions = vec![];
2240
2241 let app_dir = find_app_dir(self.project_path().owned().await?)
2242 .owned()
2243 .await?;
2244 let app_project = &*self.app_project().await?;
2245
2246 let ecmascript_client_reference_transition_name = app_project
2247 .as_ref()
2248 .map(|_| AppProject::client_transition_name());
2249
2250 if let Some(app_project) = app_project {
2251 transitions.push((
2252 AppProject::client_transition_name(),
2253 app_project
2254 .ecmascript_client_reference_transition()
2255 .to_resolved()
2256 .await?,
2257 ));
2258 }
2259
2260 Ok(Vc::upcast(ModuleAssetContext::new(
2261 TransitionOptions {
2262 named_transitions: transitions.into_iter().collect(),
2263 ..Default::default()
2264 }
2265 .cell(),
2266 self.server_compile_time_info(),
2267 get_server_module_options_context(
2268 self.project_path().owned().await?,
2269 self.execution_context(),
2270 ServerContextType::Instrumentation {
2271 app_dir: app_dir.clone(),
2272 ecmascript_client_reference_transition_name:
2273 ecmascript_client_reference_transition_name.clone(),
2274 },
2275 self.next_mode(),
2276 self.next_config(),
2277 NextRuntime::NodeJs,
2278 self.encryption_key(),
2279 self.server_compile_time_info().environment(),
2280 self.client_compile_time_info().environment(),
2281 *self.should_write_nft_manifests().await?,
2282 ),
2283 get_server_resolve_options_context(
2284 self.project_path().owned().await?,
2285 ServerContextType::Instrumentation {
2286 app_dir: app_dir.clone(),
2287 ecmascript_client_reference_transition_name,
2288 },
2289 self.next_mode(),
2290 self.next_config(),
2291 self.execution_context(),
2292 None, ),
2294 Layer::new_with_user_friendly_name(
2295 rcstr!("instrumentation"),
2296 rcstr!("Instrumentation"),
2297 ),
2298 )))
2299 }
2300
2301 #[turbo_tasks::function]
2302 async fn edge_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2303 let mut transitions = vec![];
2304
2305 let app_dir = find_app_dir(self.project_path().owned().await?)
2306 .owned()
2307 .await?;
2308 let app_project = &*self.app_project().await?;
2309
2310 let ecmascript_client_reference_transition_name = app_project
2311 .as_ref()
2312 .map(|_| AppProject::client_transition_name());
2313
2314 if let Some(app_project) = app_project {
2315 transitions.push((
2316 AppProject::client_transition_name(),
2317 app_project
2318 .edge_ecmascript_client_reference_transition()
2319 .to_resolved()
2320 .await?,
2321 ));
2322 }
2323
2324 Ok(Vc::upcast(ModuleAssetContext::new(
2325 TransitionOptions {
2326 named_transitions: transitions.into_iter().collect(),
2327 ..Default::default()
2328 }
2329 .cell(),
2330 self.edge_compile_time_info(),
2331 get_server_module_options_context(
2332 self.project_path().owned().await?,
2333 self.execution_context(),
2334 ServerContextType::Instrumentation {
2335 app_dir: app_dir.clone(),
2336 ecmascript_client_reference_transition_name:
2337 ecmascript_client_reference_transition_name.clone(),
2338 },
2339 self.next_mode(),
2340 self.next_config(),
2341 NextRuntime::Edge,
2342 self.encryption_key(),
2343 self.edge_compile_time_info().environment(),
2344 self.client_compile_time_info().environment(),
2345 false,
2347 ),
2348 get_edge_resolve_options_context(
2349 self.project_path().owned().await?,
2350 ServerContextType::Instrumentation {
2351 app_dir: app_dir.clone(),
2352 ecmascript_client_reference_transition_name,
2353 },
2354 self.next_mode(),
2355 self.next_config(),
2356 self.execution_context(),
2357 None, ),
2359 Layer::new_with_user_friendly_name(
2360 rcstr!("instrumentation-edge"),
2361 rcstr!("Edge Instrumentation"),
2362 ),
2363 )))
2364 }
2365
2366 #[turbo_tasks::function]
2367 async fn find_instrumentation(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
2368 Ok(find_context_file(
2369 self.project_path().owned().await?,
2370 instrumentation_files(self.next_config().page_extensions()),
2371 false,
2373 ))
2374 }
2375
2376 #[turbo_tasks::function]
2377 async fn instrumentation_endpoint(
2378 self: Vc<Self>,
2379 is_edge: bool,
2380 ) -> Result<Vc<Box<dyn Endpoint>>> {
2381 let instrumentation = self.find_instrumentation();
2382 let FindContextFileResult::Found(fs_path, _) = &*instrumentation.await? else {
2383 return Ok(Vc::upcast(EmptyEndpoint::new(self)));
2384 };
2385 let source = Vc::upcast(FileSource::new(fs_path.clone()));
2386 let app_dir = find_app_dir(self.project_path().owned().await?)
2387 .owned()
2388 .await?;
2389 let ecmascript_client_reference_transition_name = (*self.app_project().await?)
2390 .as_ref()
2391 .map(|_| AppProject::client_transition_name());
2392
2393 let instrumentation_asset_context = if is_edge {
2394 self.edge_instrumentation_context()
2395 } else {
2396 self.node_instrumentation_context()
2397 };
2398
2399 Ok(Vc::upcast(InstrumentationEndpoint::new(
2400 self,
2401 instrumentation_asset_context,
2402 source,
2403 is_edge,
2404 app_dir.clone(),
2405 ecmascript_client_reference_transition_name,
2406 )))
2407 }
2408
2409 #[turbo_tasks::function]
2410 pub async fn emit_all_output_assets(
2411 self: Vc<Self>,
2412 output_assets: OperationVc<OutputAssets>,
2413 ) -> Result<()> {
2414 let span = tracing::info_span!("emitting");
2415 async move {
2416 let all_output_assets = all_assets_from_entries_operation(output_assets);
2417
2418 let client_relative_path = self.client_relative_path().owned().await?;
2419 let node_root = self.node_root().owned().await?;
2420
2421 if let Some(map) = self.await?.versioned_content_map {
2422 map.insert_output_assets(
2423 all_output_assets,
2424 node_root.clone(),
2425 client_relative_path.clone(),
2426 node_root.clone(),
2427 )
2428 .as_side_effect()
2429 .await?;
2430
2431 Ok(())
2432 } else {
2433 emit_assets(
2434 all_output_assets.connect(),
2435 node_root.clone(),
2436 client_relative_path.clone(),
2437 node_root.clone(),
2438 )
2439 .as_side_effect()
2440 .await?;
2441
2442 Ok(())
2443 }
2444 }
2445 .instrument(span)
2446 .await
2447 }
2448
2449 #[turbo_tasks::function]
2450 async fn server_hmr_root_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
2451 Ok(self.node_root().await?.join("server/app")?.cell())
2452 }
2453
2454 #[turbo_tasks::function]
2456 async fn hmr_content(self: Vc<Self>, chunk_name: RcStr) -> Result<Vc<OptionVersionedContent>> {
2457 if let Some(map) = self.await?.versioned_content_map {
2458 let content = map.get(self.client_relative_path().await?.join(&chunk_name)?);
2459 Ok(content)
2460 } else {
2461 bail!("must be in dev mode to hmr")
2462 }
2463 }
2464
2465 #[turbo_tasks::function]
2468 pub async fn hmr_version_state(
2469 self: ResolvedVc<Self>,
2470 chunk_name: RcStr,
2471 session: TransientInstance<()>,
2472 ) -> Result<Vc<VersionState>> {
2473 let _ = session;
2476
2477 #[tracing::instrument(
2478 level = "info",
2479 name = "get HMR version",
2480 skip_all,
2481 fields(chunk_name = %chunk_name),
2482 )]
2483 #[turbo_tasks::function(operation, root)]
2484 async fn hmr_version_operation(
2485 this: ResolvedVc<Project>,
2486 chunk_name: RcStr,
2487 ) -> Result<Vc<Box<dyn Version>>> {
2488 tracing::info!(chunk_name = %chunk_name, "hmr subscription");
2489 let content = this.hmr_content(chunk_name).await?;
2490 if let Some(content) = &*content {
2491 Ok(content.version())
2492 } else {
2493 Ok(Vc::upcast(NotFoundVersion::new()))
2494 }
2495 }
2496 let version_op = hmr_version_operation(self, chunk_name);
2497
2498 let state = VersionState::new(
2502 version_op
2503 .read_trait_strongly_consistent()
2504 .untracked()
2505 .await?,
2506 )
2507 .await?;
2508 Ok(state)
2509 }
2510
2511 #[turbo_tasks::function]
2514 pub async fn hmr_update(
2515 self: Vc<Self>,
2516 chunk_name: RcStr,
2517 from: Vc<VersionState>,
2518 ) -> Result<Vc<Update>> {
2519 let from = from.get();
2520 let content = self.hmr_content(chunk_name).await?;
2521 if let Some(content) = *content {
2522 Ok(content.update(from))
2523 } else {
2524 Ok(Update::Missing.cell())
2525 }
2526 }
2527
2528 #[turbo_tasks::function]
2530 pub async fn server_hmr_chunks(self: Vc<Self>) -> Result<Vc<ServerHmrChunkLists>> {
2531 let Some(map) = self.await?.versioned_content_map else {
2532 bail!("must be in dev mode to hmr")
2533 };
2534 let root = self.server_hmr_root_path().owned().await?;
2535 Ok(map.server_hmr_chunks_in_path(root))
2536 }
2537
2538 #[turbo_tasks::function]
2539 pub async fn server_hmr_chunks_for_entries(
2540 self: Vc<Self>,
2541 entry_paths: Vec<RcStr>,
2542 ) -> Result<Vc<ServerHmrChunkLists>> {
2543 let mut chunk_lists =
2544 ServerHmrChunkLists::new(self.server_hmr_chunks().await?.as_slice().to_vec());
2545 chunk_lists.retain_entry_paths(&entry_paths.into_iter().collect());
2546 Ok(chunk_lists.cell())
2547 }
2548
2549 #[turbo_tasks::function]
2551 pub async fn hmr_chunk_names(self: Vc<Self>) -> Result<Vc<Vec<RcStr>>> {
2552 if let Some(map) = self.await?.versioned_content_map {
2553 Ok(map.keys_in_path(self.client_relative_path().owned().await?))
2554 } else {
2555 bail!("must be in dev mode to hmr")
2556 }
2557 }
2558
2559 #[turbo_tasks::function]
2562 pub async fn server_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
2563 let path = self.node_root().owned().await?;
2564 Ok(any_output_changed(roots, path, true))
2565 }
2566
2567 #[turbo_tasks::function]
2570 pub async fn client_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
2571 let path = self.client_root().owned().await?;
2572 Ok(any_output_changed(roots, path, false))
2573 }
2574
2575 #[turbo_tasks::function]
2576 pub async fn client_main_modules(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
2577 let pages_project = self.pages_project();
2578 let mut chunk_groups = vec![ChunkGroupEntry::Entry {
2579 modules: vec![pages_project.client_main_module().to_resolved().await?],
2580 heuristics: EntryHeuristics::high_priority(),
2581 }];
2582
2583 if let Some(app_project) = *self.app_project().await? {
2584 chunk_groups.push(ChunkGroupEntry::Entry {
2585 modules: vec![app_project.client_main_module().to_resolved().await?],
2586 heuristics: EntryHeuristics::high_priority(),
2587 });
2588 }
2589
2590 Ok(GraphEntries::from_chunk_groups(chunk_groups).cell())
2591 }
2592
2593 #[turbo_tasks::function]
2595 pub async fn module_ids(self: Vc<Self>) -> Result<Vc<ModuleIdStrategy>> {
2596 let module_id_strategy = *self.next_config().module_ids(self.next_mode()).await?;
2597 match module_id_strategy {
2598 ModuleIdStrategyConfig::Named => Ok(ModuleIdStrategy {
2599 module_id_map: None,
2600 fallback: ModuleIdFallback::Ident,
2601 }
2602 .cell()),
2603 ModuleIdStrategyConfig::Deterministic => {
2604 let module_graphs = self.whole_app_module_graphs().await?;
2605 Ok(get_global_module_id_strategy(*module_graphs.full))
2606 }
2607 }
2608 }
2609
2610 #[turbo_tasks::function]
2612 async fn binding_usage_info(self: Vc<Self>) -> Result<Vc<BindingUsageInfo>> {
2613 let module_graphs = self.whole_app_module_graphs().await?;
2614 Ok(module_graphs
2615 .binding_usage_info
2616 .context("No binding usage info")?
2617 .connect())
2618 }
2619
2620 #[turbo_tasks::function]
2622 pub async fn export_usage(self: Vc<Self>) -> Result<Vc<OptionBindingUsageInfo>> {
2623 if *self
2624 .next_config()
2625 .turbopack_remove_unused_exports(self.next_mode())
2626 .await?
2627 {
2628 Ok(Vc::cell(Some(
2629 self.binding_usage_info().to_resolved().await?,
2630 )))
2631 } else {
2632 Ok(Vc::cell(None))
2633 }
2634 }
2635
2636 #[turbo_tasks::function]
2638 pub async fn unused_references(self: Vc<Self>) -> Result<Vc<UnusedReferences>> {
2639 if *self
2640 .next_config()
2641 .turbopack_remove_unused_imports(self.next_mode())
2642 .await?
2643 {
2644 Ok(self.binding_usage_info().unused_references())
2645 } else {
2646 Ok(Vc::cell(Default::default()))
2647 }
2648 }
2649
2650 #[turbo_tasks::function]
2651 pub async fn with_next_config(&self, next_config: Vc<NextConfig>) -> Result<Vc<Self>> {
2652 Ok(Self {
2653 next_config: next_config.to_resolved().await?,
2654 ..(*self).clone()
2655 }
2656 .cell())
2657 }
2658
2659 #[turbo_tasks::function]
2661 pub async fn additional_traced_modules(self: Vc<Self>) -> Result<Vc<Modules>> {
2662 let project_path = self.project_path().owned().await?;
2663 let cache_handler = self
2664 .next_config()
2665 .cache_handler(project_path.clone())
2666 .await?;
2667 let cache_handlers = self
2668 .next_config()
2669 .cache_handlers(project_path.clone())
2670 .await?;
2671
2672 let asset_context =
2673 externals_tracing_module_context(get_tracing_compile_time_info(), false, None);
2674
2675 Ok(Vc::cell(
2676 cache_handler
2677 .iter()
2678 .chain(cache_handlers.iter())
2679 .map(|f| {
2680 asset_context
2681 .process(
2682 Vc::upcast(FileSource::new(f.clone())),
2683 ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined),
2684 )
2685 .module()
2686 })
2687 .map(|m| m.to_resolved())
2688 .try_join()
2689 .await?,
2690 ))
2691 }
2692
2693 #[turbo_tasks::function]
2697 pub async fn pages_traced_modules(self: Vc<Self>) -> Result<Vc<Modules>> {
2698 let asset_context = Vc::upcast(externals_tracing_module_context(
2699 get_tracing_compile_time_info(),
2700 false,
2701 None,
2702 ));
2703 let hook_modules = require_hook_modules(self.project_path().owned().await?, asset_context)
2704 .owned()
2705 .await?;
2706 let renderer_modules = pages_renderer_modules(self.project_path().owned().await?)
2707 .owned()
2708 .await?;
2709
2710 Ok(Vc::cell(
2711 self.additional_traced_modules()
2712 .owned()
2713 .await?
2714 .into_iter()
2715 .chain(hook_modules)
2716 .chain(renderer_modules)
2717 .collect(),
2718 ))
2719 }
2720}
2721
2722async fn scale_down_node_pool(project: ResolvedVc<Project>) -> Result<()> {
2724 let execution_context = project.execution_context().await?;
2725 let node_backend = execution_context.node_backend.into_trait_ref().await?;
2726 if *project.is_watch_enabled().await? {
2727 node_backend.scale_down()?;
2728 } else {
2729 node_backend.scale_zero()?;
2730 }
2731 Ok(())
2732}
2733
2734#[turbo_tasks::function(operation, root)]
2737async fn whole_app_module_graph_operation(
2738 project: ResolvedVc<Project>,
2739) -> Result<Vc<BaseAndFullModuleGraph>> {
2740 let span = tracing::info_span!("whole app module graph", modules = Empty, edges = Empty);
2741 let span_clone = span.clone();
2742 async move {
2743 let next_mode = project.next_mode();
2744 let should_trace = *project.should_write_nft_manifests().await?;
2745 let should_read_binding_usage = next_mode.await?.is_production();
2746 let base_single_module_graph = SingleModuleGraph::new_with_entries(
2747 project.get_all_entries().to_resolved().await?,
2748 should_trace,
2749 should_read_binding_usage,
2750 );
2751 let base_visited_modules = VisitedModules::from_graph(base_single_module_graph);
2752
2753 let base = ModuleGraph::from_graphs(vec![base_single_module_graph], None);
2754
2755 let turbopack_remove_unused_imports = *project
2756 .next_config()
2757 .turbopack_remove_unused_imports(next_mode)
2758 .await?;
2759
2760 let base = if turbopack_remove_unused_imports {
2761 let binding_usage_info = compute_binding_usage_info(base, true);
2764 ModuleGraph::from_graphs(vec![base_single_module_graph], Some(binding_usage_info))
2765 } else {
2766 base
2767 };
2768
2769 let additional_entries = project
2770 .get_all_additional_entries(base.connect())
2771 .to_resolved()
2772 .await?;
2773
2774 let additional_module_graph = SingleModuleGraph::new_with_entries_visited(
2775 additional_entries,
2776 base_visited_modules,
2777 should_trace,
2778 should_read_binding_usage,
2779 );
2780
2781 if !span.is_disabled() {
2782 let base_module_count = base_single_module_graph
2783 .connect()
2784 .module_count()
2785 .untracked()
2786 .await?;
2787 let additional_module_count = additional_module_graph
2788 .connect()
2789 .module_count()
2790 .untracked()
2791 .await?;
2792 span.record("modules", *base_module_count + *additional_module_count);
2793 let base_edge_count = base_single_module_graph
2794 .connect()
2795 .edge_count()
2796 .untracked()
2797 .await?;
2798 let additional_edge_count = additional_module_graph
2799 .connect()
2800 .edge_count()
2801 .untracked()
2802 .await?;
2803 span.record("edges", *base_edge_count + *additional_edge_count);
2804 }
2805
2806 let graphs = vec![base_single_module_graph, additional_module_graph];
2807
2808 let (full, binding_usage_info) = if turbopack_remove_unused_imports {
2809 let full_with_unused_references = ModuleGraph::from_graphs(graphs.clone(), None);
2810 let binding_usage_info = compute_binding_usage_info(full_with_unused_references, true);
2811 (
2812 ModuleGraph::from_graphs(graphs, Some(binding_usage_info)),
2813 Some(binding_usage_info),
2814 )
2815 } else {
2816 (ModuleGraph::from_graphs(graphs, None), None)
2817 };
2818
2819 Ok(BaseAndFullModuleGraph {
2820 base: base.connect().to_resolved().await?,
2821 full: full.connect().to_resolved().await?,
2822 binding_usage_info,
2823 }
2824 .cell())
2825 }
2826 .instrument(span_clone)
2827 .await
2828}
2829
2830#[turbo_tasks::value(shared)]
2831pub struct BaseAndFullModuleGraph {
2832 pub base: ResolvedVc<ModuleGraph>,
2834 pub full: ResolvedVc<ModuleGraph>,
2836 pub binding_usage_info: Option<OperationVc<BindingUsageInfo>>,
2838}
2839
2840#[turbo_tasks::function]
2841async fn any_output_changed(
2842 roots: Vc<OutputAssets>,
2843 path: FileSystemPath,
2844 server: bool,
2845) -> Result<Vc<Completion>> {
2846 let all_assets = expand_output_assets(
2847 roots.await?.into_iter().map(ExpandOutputAssetsInput::Asset),
2848 true,
2849 )
2850 .await?;
2851 let completions = all_assets
2852 .into_iter()
2853 .map(|m| {
2854 let path = path.clone();
2855
2856 async move {
2857 let asset_path = m.path().await?;
2858 if !asset_path.path.ends_with(".map")
2859 && (!server || !asset_path.path.ends_with(".css"))
2860 && asset_path.is_inside_ref(&path)
2861 {
2862 anyhow::Ok(Some(
2863 content_changed(*ResolvedVc::upcast(m))
2864 .to_resolved()
2865 .await?,
2866 ))
2867 } else {
2868 Ok(None)
2869 }
2870 }
2871 })
2872 .try_flat_join()
2873 .await?;
2874
2875 Ok(Vc::<Completions>::cell(completions).completed())
2876}
2877
2878#[turbo_tasks::function(operation, root)]
2879fn all_assets_from_entries_operation(
2880 operation: OperationVc<OutputAssets>,
2881) -> Result<Vc<ExpandedOutputAssets>> {
2882 let assets = operation.connect();
2883 Ok(all_assets_from_entries(assets))
2884}