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