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