Skip to main content

next_api/
project.rs

1use std::time::Duration;
2
3use anyhow::{Context, Result, bail};
4use bincode::{Decode, Encode};
5use indexmap::map::Entry;
6use next_core::{
7    app_structure::find_app_dir,
8    emit_assets, get_edge_chunking_context, get_edge_chunking_context_with_client_assets,
9    get_edge_compile_time_info, get_edge_resolve_options_context,
10    instrumentation::instrumentation_files,
11    middleware::middleware_files,
12    mode::NextMode,
13    next_app::{AppPage, AppPath},
14    next_client::{
15        ClientChunkingContextOptions, get_client_chunking_context, get_client_compile_time_info,
16    },
17    next_config::{
18        ModuleIds as ModuleIdStrategyConfig, NextConfig, TurbopackPluginRuntimeStrategy,
19    },
20    next_edge::context::EdgeChunkingContextOptions,
21    next_server::{
22        ServerChunkingContextOptions, ServerContextType, get_server_chunking_context,
23        get_server_chunking_context_with_client_assets, get_server_compile_time_info,
24        get_server_module_options_context, get_server_resolve_options_context,
25    },
26    next_telemetry::NextFeatureTelemetry,
27    parse_segment_config_from_source,
28    segment_config::ParseSegmentMode,
29    util::{NextRuntime, OptionEnvMap},
30};
31use rustc_hash::{FxHashMap, FxHashSet};
32use serde::{Deserialize, Serialize};
33use tracing::{Instrument, field::Empty};
34use turbo_rcstr::{RcStr, rcstr};
35use turbo_tasks::{
36    Completion, Completions, FxIndexMap, NonLocalValue, OperationValue, OperationVc, ReadRef,
37    ResolvedVc, State, TaskInput, TransientInstance, TryFlatJoinIterExt, Vc,
38    debug::ValueDebugFormat, fxindexmap, trace::TraceRawVcs,
39};
40use turbo_tasks_env::{EnvMap, ProcessEnv};
41use turbo_tasks_fs::{
42    DiskFileSystem, FileContent, FileSystem, FileSystemPath, VirtualFileSystem, invalidation,
43};
44use turbo_unix_path::{join_path, unix_to_sys};
45use turbopack::{
46    ModuleAssetContext, evaluate_context::node_build_environment,
47    global_module_ids::get_global_module_id_strategy, transition::TransitionOptions,
48};
49use turbopack_core::{
50    PROJECT_FILESYSTEM_NAME,
51    changed::content_changed,
52    chunk::{
53        ChunkingContext, EvaluatableAssets, UnusedReferences,
54        chunk_id_strategy::{ModuleIdFallback, ModuleIdStrategy},
55    },
56    compile_time_info::CompileTimeInfo,
57    context::AssetContext,
58    diagnostics::DiagnosticExt,
59    environment::NodeJsVersion,
60    file_source::FileSource,
61    ident::Layer,
62    issue::{
63        CollectibleIssuesExt, Issue, IssueExt, IssueFilter, IssueSeverity, IssueStage,
64        OptionStyledString, StyledString,
65    },
66    module::Module,
67    module_graph::{
68        GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
69        binding_usage_info::{
70            BindingUsageInfo, OptionBindingUsageInfo, compute_binding_usage_info,
71        },
72        chunk_group_info::ChunkGroupEntry,
73    },
74    output::{
75        ExpandOutputAssetsInput, ExpandedOutputAssets, OutputAsset, OutputAssets,
76        expand_output_assets,
77    },
78    reference::all_assets_from_entries,
79    resolve::{FindContextFileResult, find_context_file},
80    version::{
81        NotFoundVersion, OptionVersionedContent, Update, Version, VersionState, VersionedContent,
82    },
83};
84#[cfg(feature = "process_pool")]
85use turbopack_node::child_process_backend;
86use turbopack_node::execution_context::ExecutionContext;
87#[cfg(feature = "worker_pool")]
88use turbopack_node::worker_threads_backend;
89use turbopack_nodejs::NodeJsChunkingContext;
90
91use crate::{
92    app::{AppProject, OptionAppProject},
93    empty::EmptyEndpoint,
94    entrypoints::Entrypoints,
95    instrumentation::InstrumentationEndpoint,
96    middleware::MiddlewareEndpoint,
97    pages::PagesProject,
98    route::{
99        Endpoint, EndpointGroup, EndpointGroupEntry, EndpointGroupKey, EndpointGroups, Endpoints,
100        Route,
101    },
102    versioned_content_map::VersionedContentMap,
103};
104
105#[derive(
106    Debug,
107    Serialize,
108    Deserialize,
109    Clone,
110    TaskInput,
111    PartialEq,
112    Eq,
113    Hash,
114    TraceRawVcs,
115    NonLocalValue,
116    OperationValue,
117    Encode,
118    Decode,
119)]
120#[serde(rename_all = "camelCase")]
121pub struct DraftModeOptions {
122    pub preview_mode_id: RcStr,
123    pub preview_mode_encryption_key: RcStr,
124    pub preview_mode_signing_key: RcStr,
125}
126
127#[derive(
128    Debug,
129    Default,
130    Serialize,
131    Deserialize,
132    Copy,
133    Clone,
134    TaskInput,
135    PartialEq,
136    Eq,
137    Hash,
138    TraceRawVcs,
139    NonLocalValue,
140    OperationValue,
141    Encode,
142    Decode,
143)]
144#[serde(rename_all = "camelCase")]
145pub struct WatchOptions {
146    /// Whether to watch the filesystem for file changes.
147    pub enable: bool,
148
149    /// Enable polling at a certain interval if the native file watching doesn't work (e.g.
150    /// docker).
151    pub poll_interval: Option<Duration>,
152}
153
154#[derive(
155    Debug,
156    Default,
157    Serialize,
158    Deserialize,
159    Clone,
160    TaskInput,
161    PartialEq,
162    Eq,
163    Hash,
164    TraceRawVcs,
165    NonLocalValue,
166    OperationValue,
167    Encode,
168    Decode,
169)]
170#[serde(rename_all = "camelCase")]
171pub struct DebugBuildPaths {
172    pub app: Vec<RcStr>,
173    pub pages: Vec<RcStr>,
174}
175
176/// Target for HMR operations - client-side (browser) or server-side (Node.js).
177#[derive(
178    Debug,
179    Default,
180    Copy,
181    Clone,
182    TaskInput,
183    PartialEq,
184    Eq,
185    Hash,
186    TraceRawVcs,
187    NonLocalValue,
188    Encode,
189    Decode,
190)]
191pub enum HmrTarget {
192    #[default]
193    Client,
194    Server,
195}
196
197impl std::fmt::Display for HmrTarget {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        match self {
200            HmrTarget::Client => write!(f, "client"),
201            HmrTarget::Server => write!(f, "server"),
202        }
203    }
204}
205
206impl std::str::FromStr for HmrTarget {
207    type Err = String;
208
209    fn from_str(s: &str) -> Result<Self, Self::Err> {
210        match s {
211            "client" => Ok(HmrTarget::Client),
212            "server" => Ok(HmrTarget::Server),
213            _ => Err(format!(
214                "Invalid HMR target: '{}'. Expected 'client' or 'server'",
215                s
216            )),
217        }
218    }
219}
220
221/// Pre-converted route keys from debug build paths for O(1) lookups.
222struct DebugBuildPathsRouteKeys {
223    app: FxHashSet<RcStr>,
224    pages: FxHashSet<RcStr>,
225}
226
227impl DebugBuildPathsRouteKeys {
228    fn app_route_key_from_debug_path(path: &str) -> Result<RcStr> {
229        let mut segments = path
230            .trim_start_matches('/')
231            .split('/')
232            .filter(|segment| !segment.is_empty())
233            .collect::<Vec<_>>();
234
235        if let Some(last_segment) = segments.last()
236            && (*last_segment == "page"
237                || last_segment.starts_with("page.")
238                || *last_segment == "route"
239                || last_segment.starts_with("route."))
240        {
241            segments.pop();
242        }
243
244        let normalized_path = segments.join("/");
245        Ok(AppPath::from(AppPage::parse(&normalized_path)?)
246            .to_string()
247            .into())
248    }
249
250    fn from_debug_build_paths(paths: &DebugBuildPaths) -> Result<Self> {
251        Ok(Self {
252            app: paths
253                .app
254                .iter()
255                .map(|path| Self::app_route_key_from_debug_path(path))
256                .collect::<Result<FxHashSet<_>>>()?,
257            pages: paths
258                .pages
259                .iter()
260                .map(|path| {
261                    // Pages router: "/foo.tsx" -> "/foo"
262                    // Catch-all routes like "/foo/[...slug]" contain dots in the segment name;
263                    // only treat the suffix as an extension when it is a plain alphanumeric token.
264                    let file_name = path.rsplit('/').next().unwrap_or(path);
265                    if let Some(dot_idx) = file_name.rfind('.') {
266                        let ext = &file_name[dot_idx + 1..];
267                        if !ext.is_empty() && ext.chars().all(|c| c.is_ascii_alphanumeric()) {
268                            let trimmed_len = path.len() - (file_name.len() - dot_idx);
269                            return path[..trimmed_len].into();
270                        }
271                    }
272                    path.clone()
273                })
274                .collect(),
275        })
276    }
277
278    fn should_include_app_route(&self, route_key: &RcStr) -> bool {
279        // Special app router framework routes
280        if matches!(route_key.as_str(), "/_not-found" | "/_global-error") {
281            return true;
282        }
283        self.app.contains(route_key)
284    }
285
286    fn should_include_pages_route(&self, route_key: &RcStr) -> bool {
287        // Special pages router framework routes
288        if matches!(route_key.as_str(), "/_error" | "/_document" | "/_app") {
289            return true;
290        }
291        self.pages.contains(route_key)
292    }
293}
294
295#[derive(
296    Debug,
297    Serialize,
298    Deserialize,
299    Clone,
300    PartialEq,
301    Eq,
302    TraceRawVcs,
303    NonLocalValue,
304    OperationValue,
305    Encode,
306    Decode,
307)]
308#[serde(rename_all = "camelCase")]
309pub struct ProjectOptions {
310    /// An absolute root path (Unix or Windows path) from which all files must be nested under.
311    /// Trying to access a file outside this root will fail, so think of this as a chroot.
312    /// E.g. `/home/user/projects/my-repo`.
313    pub root_path: RcStr,
314
315    /// A path which contains the app/pages directories, relative to [`Project::project_path`],
316    /// always Unix path. E.g. `apps/my-app`
317    pub project_path: RcStr,
318
319    /// The contents of next.config.js, serialized to JSON.
320    pub next_config: RcStr,
321
322    /// A map of environment variables to use when compiling code.
323    pub env: Vec<(RcStr, RcStr)>,
324
325    /// A map of environment variables which should get injected at compile
326    /// time.
327    pub define_env: DefineEnv,
328
329    /// Filesystem watcher options.
330    pub watch: WatchOptions,
331
332    /// The mode in which Next.js is running.
333    pub dev: bool,
334
335    /// The server actions encryption key.
336    pub encryption_key: RcStr,
337
338    /// The build id.
339    pub build_id: RcStr,
340
341    /// Options for draft mode.
342    pub preview_props: DraftModeOptions,
343
344    /// The browserslist query to use for targeting browsers.
345    pub browserslist_query: RcStr,
346
347    /// When the code is minified, this opts out of the default mangling of
348    /// local names for variables, functions etc., which can be useful for
349    /// debugging/profiling purposes.
350    pub no_mangling: bool,
351
352    /// Whether to write the route hashes manifest.
353    pub write_routes_hashes_manifest: bool,
354
355    /// The version of Node.js that is available/currently running.
356    pub current_node_js_version: RcStr,
357
358    /// Debug build paths for selective builds.
359    /// When set, only routes matching these paths will be included in the build.
360    pub debug_build_paths: Option<DebugBuildPaths>,
361
362    /// App-router page routes that should be built after non-deferred routes.
363    pub deferred_entries: Option<Vec<RcStr>>,
364
365    /// Whether to enable persistent caching
366    pub is_persistent_caching_enabled: bool,
367
368    /// The version of Next.js that is running.
369    pub next_version: RcStr,
370
371    /// Whether server-side HMR is enabled (disabled with --no-server-fast-refresh).
372    pub server_hmr: bool,
373}
374
375#[derive(Default)]
376pub struct PartialProjectOptions {
377    /// A root path from which all files must be nested under. Trying to access
378    /// a file outside this root will fail. Think of this as a chroot.
379    pub root_path: Option<RcStr>,
380
381    /// A path inside the root_path which contains the app/pages directories.
382    pub project_path: Option<RcStr>,
383
384    /// The contents of next.config.js, serialized to JSON.
385    pub next_config: Option<RcStr>,
386
387    /// A map of environment variables to use when compiling code.
388    pub env: Option<Vec<(RcStr, RcStr)>>,
389
390    /// A map of environment variables which should get injected at compile
391    /// time.
392    pub define_env: Option<DefineEnv>,
393
394    /// Filesystem watcher options.
395    pub watch: Option<WatchOptions>,
396
397    /// The mode in which Next.js is running.
398    pub dev: Option<bool>,
399
400    /// The server actions encryption key.
401    pub encryption_key: Option<RcStr>,
402
403    /// The build id.
404    pub build_id: Option<RcStr>,
405
406    /// Options for draft mode.
407    pub preview_props: Option<DraftModeOptions>,
408
409    /// The browserslist query to use for targeting browsers.
410    pub browserslist_query: Option<RcStr>,
411
412    /// When the code is minified, this opts out of the default mangling of
413    /// local names for variables, functions etc., which can be useful for
414    /// debugging/profiling purposes.
415    pub no_mangling: Option<bool>,
416
417    /// Whether to write the route hashes manifest.
418    pub write_routes_hashes_manifest: Option<bool>,
419
420    /// Debug build paths for selective builds.
421    /// When set, only routes matching these paths will be included in the build.
422    pub debug_build_paths: Option<DebugBuildPaths>,
423}
424
425#[derive(
426    Debug,
427    Serialize,
428    Deserialize,
429    Clone,
430    TaskInput,
431    PartialEq,
432    Eq,
433    Hash,
434    TraceRawVcs,
435    NonLocalValue,
436    OperationValue,
437    Encode,
438    Decode,
439)]
440#[serde(rename_all = "camelCase")]
441pub struct DefineEnv {
442    pub client: Vec<(RcStr, Option<RcStr>)>,
443    pub edge: Vec<(RcStr, Option<RcStr>)>,
444    pub nodejs: Vec<(RcStr, Option<RcStr>)>,
445}
446
447#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
448pub struct Middleware {
449    pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
450    pub is_proxy: bool,
451}
452
453#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
454pub struct Instrumentation {
455    pub node_js: ResolvedVc<Box<dyn Endpoint>>,
456    pub edge: ResolvedVc<Box<dyn Endpoint>>,
457}
458
459#[turbo_tasks::value]
460pub struct ProjectContainer {
461    name: RcStr,
462    options_state: State<Option<ProjectOptions>>,
463    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
464}
465
466#[turbo_tasks::value_impl]
467impl ProjectContainer {
468    #[turbo_tasks::function(operation)]
469    pub fn new_operation(name: RcStr, dev: bool) -> Result<Vc<Self>> {
470        Ok(ProjectContainer {
471            name,
472            // we only need to enable versioning in dev mode, since build
473            // is assumed to be operating over a static snapshot
474            versioned_content_map: if dev {
475                Some(VersionedContentMap::new())
476            } else {
477                None
478            },
479            options_state: State::new(None),
480        }
481        .cell())
482    }
483}
484
485#[turbo_tasks::function(operation)]
486fn project_operation(project: ResolvedVc<ProjectContainer>) -> Vc<Project> {
487    project.project()
488}
489
490#[turbo_tasks::function(operation)]
491fn project_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
492    project.project_fs()
493}
494
495#[turbo_tasks::function(operation)]
496fn output_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
497    project.project_fs()
498}
499
500enum EnvDiffType {
501    Added,
502    Removed,
503    Modified,
504}
505
506fn env_diff(
507    old: &[(RcStr, Option<RcStr>)],
508    new: &[(RcStr, Option<RcStr>)],
509) -> Vec<(RcStr, EnvDiffType)> {
510    let mut diffs = Vec::new();
511    let mut old_map: FxHashMap<_, _> = old.iter().cloned().collect();
512
513    for (key, new_value) in new.iter() {
514        match old_map.remove(key) {
515            Some(old_value) => {
516                if &old_value != new_value {
517                    diffs.push((key.clone(), EnvDiffType::Modified));
518                }
519            }
520            None => {
521                diffs.push((key.clone(), EnvDiffType::Added));
522            }
523        }
524    }
525
526    for (key, _) in old.iter() {
527        if old_map.contains_key(key) {
528            diffs.push((key.clone(), EnvDiffType::Removed));
529        }
530    }
531
532    diffs
533}
534
535fn env_diff_report(old: &[(RcStr, Option<RcStr>)], new: &[(RcStr, Option<RcStr>)]) -> String {
536    use std::fmt::Write;
537
538    let diff = env_diff(old, new);
539
540    let mut report = String::new();
541    for (key, diff_type) in diff {
542        let symbol = match diff_type {
543            EnvDiffType::Added => "+",
544            EnvDiffType::Removed => "-",
545            EnvDiffType::Modified => "*",
546        };
547        if !report.is_empty() {
548            report.push_str(", ");
549        }
550        write!(report, "{}{}", symbol, key).unwrap();
551    }
552    report
553}
554
555fn define_env_diff_report(old: &DefineEnv, new: &DefineEnv) -> String {
556    use std::fmt::Write;
557
558    let mut report = String::new();
559    for (name, old, new) in [
560        ("client", &old.client, &new.client),
561        ("edge", &old.edge, &new.edge),
562        ("nodejs", &old.nodejs, &new.nodejs),
563    ] {
564        let diff = env_diff_report(old, new);
565        if !diff.is_empty() {
566            if !report.is_empty() {
567                report.push_str(", ");
568            }
569            write!(report, "{name}: {{ {diff} }}").unwrap();
570        }
571    }
572    report
573}
574
575impl ProjectContainer {
576    /// Set up filesystems, watchers, and construct the [`Project`] instance inside the container.
577    ///
578    /// This function is intended to be called inside of [`turbo_tasks::TurboTasks::run`], but not
579    /// part of a [`turbo_tasks::function`]. We don't want it to be possibly re-executed.
580    ///
581    /// This is an associated function instead of a method because we don't currently implement
582    /// [`std::ops::Receiver`] on [`OperationVc`].
583    pub async fn initialize(this_op: OperationVc<Self>, options: ProjectOptions) -> Result<()> {
584        let this = this_op.read_strongly_consistent().await?;
585        let span = tracing::info_span!(
586            "initialize project",
587            project_name = %this.name,
588            version = options.next_version.as_str(),
589            env_diff = Empty
590        );
591        let span_clone = span.clone();
592        async move {
593            let watch = options.watch;
594
595            if let Some(old_options) = &*this.options_state.get_untracked() {
596                span.record(
597                    "env_diff",
598                    define_env_diff_report(&old_options.define_env, &options.define_env).as_str(),
599                );
600            }
601            this.options_state.set(Some(options));
602
603            #[turbo_tasks::function(operation)]
604            fn project_from_container_operation(
605                container: OperationVc<ProjectContainer>,
606            ) -> Vc<Project> {
607                container.connect().project()
608            }
609            let project = project_from_container_operation(this_op)
610                .resolve_strongly_consistent()
611                .await?;
612            let project_fs = project_fs_operation(project)
613                .read_strongly_consistent()
614                .await?;
615            if watch.enable {
616                project_fs
617                    .start_watching_with_invalidation_reason(watch.poll_interval)
618                    .await?;
619            } else {
620                project_fs.invalidate_with_reason(|path| invalidation::Initialize {
621                    // this path is just used for display purposes
622                    path: RcStr::from(path.to_string_lossy()),
623                });
624            }
625            let output_fs = output_fs_operation(project)
626                .read_strongly_consistent()
627                .await?;
628            output_fs.invalidate_with_reason(|path| invalidation::Initialize {
629                path: RcStr::from(path.to_string_lossy()),
630            });
631            Ok(())
632        }
633        .instrument(span_clone)
634        .await
635    }
636
637    pub async fn update(self: ResolvedVc<Self>, options: PartialProjectOptions) -> Result<()> {
638        let span = tracing::info_span!(
639            "update project options",
640            project_name = %self.await?.name,
641            env_diff = Empty
642        );
643        let span_clone = span.clone();
644        async move {
645            // HACK: `update` is called from a top-level function. Top-level functions are not
646            // allowed to perform eventually consistent reads. Create a stub operation
647            // to upgrade the `ResolvedVc` to an `OperationVc`. This is mostly okay
648            // because we can assume the `ProjectContainer` was originally resolved with
649            // strong consistency, and is rarely updated.
650            #[turbo_tasks::function(operation)]
651            fn project_container_operation_hack(
652                container: ResolvedVc<ProjectContainer>,
653            ) -> Vc<ProjectContainer> {
654                *container
655            }
656            let this = project_container_operation_hack(self)
657                .read_strongly_consistent()
658                .await?;
659            let PartialProjectOptions {
660                root_path,
661                project_path,
662                next_config,
663                env,
664                define_env,
665                watch,
666                dev,
667                encryption_key,
668                build_id,
669                preview_props,
670                browserslist_query,
671                no_mangling,
672                write_routes_hashes_manifest,
673                debug_build_paths,
674            } = options;
675
676            let mut new_options = this
677                .options_state
678                .get()
679                .clone()
680                .context("ProjectContainer need to be initialized with initialize()")?;
681
682            if let Some(root_path) = root_path {
683                new_options.root_path = root_path;
684            }
685            if let Some(project_path) = project_path {
686                new_options.project_path = project_path;
687            }
688            if let Some(next_config) = next_config {
689                new_options.next_config = next_config;
690            }
691            if let Some(env) = env {
692                new_options.env = env;
693            }
694            if let Some(define_env) = define_env {
695                new_options.define_env = define_env;
696            }
697            if let Some(watch) = watch {
698                new_options.watch = watch;
699            }
700            if let Some(dev) = dev {
701                new_options.dev = dev;
702            }
703            if let Some(encryption_key) = encryption_key {
704                new_options.encryption_key = encryption_key;
705            }
706            if let Some(build_id) = build_id {
707                new_options.build_id = build_id;
708            }
709            if let Some(preview_props) = preview_props {
710                new_options.preview_props = preview_props;
711            }
712            if let Some(browserslist_query) = browserslist_query {
713                new_options.browserslist_query = browserslist_query;
714            }
715            if let Some(no_mangling) = no_mangling {
716                new_options.no_mangling = no_mangling;
717            }
718            if let Some(write_routes_hashes_manifest) = write_routes_hashes_manifest {
719                new_options.write_routes_hashes_manifest = write_routes_hashes_manifest;
720            }
721            if let Some(debug_build_paths) = debug_build_paths {
722                new_options.debug_build_paths = Some(debug_build_paths);
723            }
724
725            // TODO: Handle mode switch, should prevent mode being switched.
726            let watch = new_options.watch;
727
728            let project = project_operation(self)
729                .resolve_strongly_consistent()
730                .await?;
731            let prev_project_fs = project_fs_operation(project)
732                .read_strongly_consistent()
733                .await?;
734            let prev_output_fs = output_fs_operation(project)
735                .read_strongly_consistent()
736                .await?;
737
738            if let Some(old_options) = &*this.options_state.get_untracked() {
739                span.record(
740                    "env_diff",
741                    define_env_diff_report(&old_options.define_env, &new_options.define_env)
742                        .as_str(),
743                );
744            }
745            this.options_state.set(Some(new_options));
746            let project = project_operation(self)
747                .resolve_strongly_consistent()
748                .await?;
749            let project_fs = project_fs_operation(project)
750                .read_strongly_consistent()
751                .await?;
752            let output_fs = output_fs_operation(project)
753                .read_strongly_consistent()
754                .await?;
755
756            if !ReadRef::ptr_eq(&prev_project_fs, &project_fs) {
757                if watch.enable {
758                    // TODO stop watching: prev_project_fs.stop_watching()?;
759                    project_fs
760                        .start_watching_with_invalidation_reason(watch.poll_interval)
761                        .await?;
762                } else {
763                    project_fs.invalidate_with_reason(|path| invalidation::Initialize {
764                        // this path is just used for display purposes
765                        path: RcStr::from(path.to_string_lossy()),
766                    });
767                }
768            }
769            if !ReadRef::ptr_eq(&prev_output_fs, &output_fs) {
770                prev_output_fs.invalidate_with_reason(|path| invalidation::Initialize {
771                    path: RcStr::from(path.to_string_lossy()),
772                });
773            }
774
775            Ok(())
776        }
777        .instrument(span_clone)
778        .await
779    }
780}
781
782#[turbo_tasks::value_impl]
783impl ProjectContainer {
784    #[turbo_tasks::function]
785    pub async fn project(&self) -> Result<Vc<Project>> {
786        let env_map: Vc<EnvMap>;
787        let next_config;
788        let define_env;
789        let root_path;
790        let project_path;
791        let watch;
792        let dev;
793        let encryption_key;
794        let build_id;
795        let preview_props;
796        let browserslist_query;
797        let no_mangling;
798        let write_routes_hashes_manifest;
799        let current_node_js_version;
800        let debug_build_paths;
801        let deferred_entries;
802        let is_persistent_caching_enabled;
803        let server_hmr;
804        {
805            let options = self.options_state.get();
806            let options = options
807                .as_ref()
808                .context("ProjectContainer need to be initialized with initialize()")?;
809            env_map = Vc::cell(options.env.iter().cloned().collect());
810            define_env = ProjectDefineEnv {
811                client: ResolvedVc::cell(options.define_env.client.iter().cloned().collect()),
812                edge: ResolvedVc::cell(options.define_env.edge.iter().cloned().collect()),
813                nodejs: ResolvedVc::cell(options.define_env.nodejs.iter().cloned().collect()),
814            }
815            .cell();
816            next_config = NextConfig::from_string(Vc::cell(options.next_config.clone()));
817            root_path = options.root_path.clone();
818            project_path = options.project_path.clone();
819            watch = options.watch;
820            dev = options.dev;
821            encryption_key = options.encryption_key.clone();
822            build_id = options.build_id.clone();
823            preview_props = options.preview_props.clone();
824            browserslist_query = options.browserslist_query.clone();
825            no_mangling = options.no_mangling;
826            write_routes_hashes_manifest = options.write_routes_hashes_manifest;
827            current_node_js_version = options.current_node_js_version.clone();
828            debug_build_paths = options.debug_build_paths.clone();
829            deferred_entries = options.deferred_entries.clone().unwrap_or_default();
830            is_persistent_caching_enabled = options.is_persistent_caching_enabled;
831            server_hmr = options.server_hmr;
832        }
833
834        let dist_dir = next_config.dist_dir().owned().await?;
835        let dist_dir_root = next_config.dist_dir_root().owned().await?;
836        Ok(Project {
837            root_path,
838            project_path,
839            watch,
840            next_config: next_config.to_resolved().await?,
841            dist_dir,
842            dist_dir_root,
843            env: ResolvedVc::upcast(env_map.to_resolved().await?),
844            define_env: define_env.to_resolved().await?,
845            browserslist_query,
846            mode: if dev {
847                NextMode::Development.resolved_cell()
848            } else {
849                NextMode::Build.resolved_cell()
850            },
851            versioned_content_map: self.versioned_content_map,
852            build_id,
853            encryption_key,
854            preview_props,
855            no_mangling,
856            write_routes_hashes_manifest,
857            current_node_js_version,
858            debug_build_paths,
859            deferred_entries,
860            is_persistent_caching_enabled,
861            server_hmr,
862        }
863        .cell())
864    }
865
866    /// See [Project::entrypoints].
867    #[turbo_tasks::function]
868    pub fn entrypoints(self: Vc<Self>) -> Vc<Entrypoints> {
869        self.project().entrypoints()
870    }
871
872    /// See [`Project::hmr_chunk_names`].
873    #[turbo_tasks::function]
874    pub fn hmr_chunk_names(self: Vc<Self>, target: HmrTarget) -> Vc<Vec<RcStr>> {
875        self.project().hmr_chunk_names(target)
876    }
877
878    /// Gets a source map for a particular `file_path`. If `dev` mode is disabled, this will always
879    /// return [`FileContent::NotFound`].
880    #[turbo_tasks::function]
881    pub fn get_source_map(
882        &self,
883        file_path: FileSystemPath,
884        section: Option<RcStr>,
885    ) -> Vc<FileContent> {
886        if let Some(map) = self.versioned_content_map {
887            map.get_source_map(file_path, section)
888        } else {
889            FileContent::NotFound.cell()
890        }
891    }
892}
893
894#[derive(Clone)]
895#[turbo_tasks::value]
896pub struct Project {
897    /// An absolute root path (Windows or Unix path) from which all files must be nested under.
898    /// Trying to access a file outside this root will fail, so think of this as a chroot.
899    /// E.g. `/home/user/projects/my-repo`.
900    root_path: RcStr,
901
902    /// A path which contains the app/pages directories, relative to [`Project::root_path`], always
903    /// a Unix path.
904    /// E.g. `apps/my-app`
905    project_path: RcStr,
906
907    /// A path where to emit the build outputs, relative to [`Project::project_path`], always a
908    /// Unix path. Corresponds to next.config.js's `distDir`.
909    /// E.g. `.next`
910    dist_dir: RcStr,
911
912    /// The root directory of the distDir. In development mode, this is the parent directory of
913    /// `distDir` since development builds use `{distDir}/dev`. This is used to ensure that the
914    /// bundler doesn't traverse into the output directory.
915    dist_dir_root: RcStr,
916
917    /// Filesystem watcher options.
918    watch: WatchOptions,
919
920    /// Next config.
921    next_config: ResolvedVc<NextConfig>,
922
923    /// A map of environment variables to use when compiling code.
924    env: ResolvedVc<Box<dyn ProcessEnv>>,
925
926    /// A map of environment variables which should get injected at compile
927    /// time.
928    define_env: ResolvedVc<ProjectDefineEnv>,
929
930    /// The browserslist query to use for targeting browsers.
931    browserslist_query: RcStr,
932
933    mode: ResolvedVc<NextMode>,
934
935    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
936
937    build_id: RcStr,
938
939    encryption_key: RcStr,
940
941    preview_props: DraftModeOptions,
942
943    /// When the code is minified, this opts out of the default mangling of
944    /// local names for variables, functions etc., which can be useful for
945    /// debugging/profiling purposes.
946    no_mangling: bool,
947
948    /// Whether to write the route hashes manifest.
949    write_routes_hashes_manifest: bool,
950
951    current_node_js_version: RcStr,
952
953    /// Debug build paths for selective builds.
954    /// When set, only routes matching these paths will be included in the build.
955    debug_build_paths: Option<DebugBuildPaths>,
956
957    /// App-router page routes that should be built after non-deferred routes.
958    deferred_entries: Vec<RcStr>,
959
960    /// Whether to enable persistent caching
961    is_persistent_caching_enabled: bool,
962
963    /// Whether server-side HMR is enabled (disabled with --no-server-fast-refresh).
964    server_hmr: bool,
965}
966
967#[turbo_tasks::value]
968pub struct ProjectDefineEnv {
969    client: ResolvedVc<OptionEnvMap>,
970    edge: ResolvedVc<OptionEnvMap>,
971    nodejs: ResolvedVc<OptionEnvMap>,
972}
973
974#[turbo_tasks::value_impl]
975impl ProjectDefineEnv {
976    #[turbo_tasks::function]
977    pub fn client(&self) -> Vc<OptionEnvMap> {
978        *self.client
979    }
980
981    #[turbo_tasks::function]
982    pub fn edge(&self) -> Vc<OptionEnvMap> {
983        *self.edge
984    }
985
986    #[turbo_tasks::function]
987    pub fn nodejs(&self) -> Vc<OptionEnvMap> {
988        *self.nodejs
989    }
990}
991
992#[turbo_tasks::value(shared)]
993struct ConflictIssue {
994    path: FileSystemPath,
995    title: ResolvedVc<StyledString>,
996    description: ResolvedVc<StyledString>,
997    severity: IssueSeverity,
998}
999
1000#[turbo_tasks::value_impl]
1001impl Issue for ConflictIssue {
1002    #[turbo_tasks::function]
1003    fn stage(&self) -> Vc<IssueStage> {
1004        IssueStage::AppStructure.cell()
1005    }
1006
1007    fn severity(&self) -> IssueSeverity {
1008        self.severity
1009    }
1010
1011    #[turbo_tasks::function]
1012    fn file_path(&self) -> Vc<FileSystemPath> {
1013        self.path.clone().cell()
1014    }
1015
1016    #[turbo_tasks::function]
1017    fn title(&self) -> Vc<StyledString> {
1018        *self.title
1019    }
1020
1021    #[turbo_tasks::function]
1022    fn description(&self) -> Vc<OptionStyledString> {
1023        Vc::cell(Some(self.description))
1024    }
1025}
1026
1027#[turbo_tasks::value_impl]
1028impl Project {
1029    #[turbo_tasks::function]
1030    pub async fn app_project(self: Vc<Self>) -> Result<Vc<OptionAppProject>> {
1031        let app_dir = find_app_dir(self.project_path().owned().await?).await?;
1032
1033        Ok(match &*app_dir {
1034            Some(app_dir) => Vc::cell(Some(
1035                AppProject::new(self, app_dir.clone()).to_resolved().await?,
1036            )),
1037            None => Vc::cell(None),
1038        })
1039    }
1040
1041    #[turbo_tasks::function]
1042    pub fn pages_project(self: Vc<Self>) -> Vc<PagesProject> {
1043        PagesProject::new(self)
1044    }
1045
1046    #[turbo_tasks::function]
1047    pub fn project_fs(&self) -> Result<Vc<DiskFileSystem>> {
1048        let denied_path = match join_path(&self.project_path, &self.dist_dir_root) {
1049            Some(dist_dir_root) => dist_dir_root.into(),
1050            None => {
1051                bail!(
1052                    "Invalid distDirRoot: {:?}. distDirRoot should not navigate out of the \
1053                     projectPath.",
1054                    self.dist_dir_root
1055                );
1056            }
1057        };
1058
1059        Ok(DiskFileSystem::new_with_denied_paths(
1060            rcstr!(PROJECT_FILESYSTEM_NAME),
1061            self.root_path.clone(),
1062            vec![denied_path],
1063        ))
1064    }
1065
1066    #[turbo_tasks::function]
1067    pub fn client_fs(self: Vc<Self>) -> Vc<Box<dyn FileSystem>> {
1068        let virtual_fs = VirtualFileSystem::new_with_name(rcstr!("client-fs"));
1069        Vc::upcast(virtual_fs)
1070    }
1071
1072    #[turbo_tasks::function]
1073    pub fn output_fs(&self) -> Vc<DiskFileSystem> {
1074        DiskFileSystem::new(rcstr!("output"), self.root_path.clone())
1075    }
1076
1077    #[turbo_tasks::function]
1078    pub fn dist_dir_absolute(&self) -> Result<Vc<RcStr>> {
1079        Ok(Vc::cell(
1080            format!(
1081                "{}{}{}",
1082                self.root_path,
1083                std::path::MAIN_SEPARATOR,
1084                unix_to_sys(
1085                    &join_path(&self.project_path, &self.dist_dir)
1086                        .context("expected project_path to be inside of root_path")?
1087                )
1088            )
1089            .into(),
1090        ))
1091    }
1092
1093    #[turbo_tasks::function]
1094    pub async fn node_root(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1095        let this = self.await?;
1096        Ok(self
1097            .output_fs()
1098            .root()
1099            .await?
1100            .join(&this.project_path)?
1101            .join(&this.dist_dir)?
1102            .cell())
1103    }
1104
1105    #[turbo_tasks::function]
1106    pub fn client_root(self: Vc<Self>) -> Vc<FileSystemPath> {
1107        self.client_fs().root()
1108    }
1109
1110    #[turbo_tasks::function]
1111    pub fn project_root_path(self: Vc<Self>) -> Vc<FileSystemPath> {
1112        self.project_fs().root()
1113    }
1114
1115    #[turbo_tasks::function]
1116    pub async fn client_relative_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1117        let next_config = self.next_config();
1118        Ok(self
1119            .client_root()
1120            .await?
1121            .join(&format!(
1122                "{}/_next",
1123                next_config
1124                    .base_path()
1125                    .await?
1126                    .as_deref()
1127                    .unwrap_or_default(),
1128            ))?
1129            .cell())
1130    }
1131
1132    /// Returns the relative path from the node root to the output root.
1133    /// E.g. from `[project]/test/e2e/app-dir/non-root-project-monorepo/apps/web/app/
1134    /// import-meta-url-ssr/page.tsx` to `[project]/`.
1135    #[turbo_tasks::function]
1136    pub async fn node_root_to_root_path(self: Vc<Self>) -> Result<Vc<RcStr>> {
1137        Ok(Vc::cell(
1138            self.node_root()
1139                .await?
1140                .get_relative_path_to(&*self.output_fs().root().await?)
1141                .context("Expected node root to be inside of output fs")?,
1142        ))
1143    }
1144
1145    #[turbo_tasks::function]
1146    pub async fn project_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1147        let this = self.await?;
1148        let root = self.project_root_path().await?;
1149        Ok(root.join(&this.project_path)?.cell())
1150    }
1151
1152    #[turbo_tasks::function]
1153    pub(super) fn env(&self) -> Vc<Box<dyn ProcessEnv>> {
1154        *self.env
1155    }
1156
1157    #[turbo_tasks::function]
1158    pub async fn ci_has_next_support(&self) -> Result<Vc<bool>> {
1159        Ok(Vc::cell(
1160            self.env.read(rcstr!("NOW_BUILDER")).await?.is_some(),
1161        ))
1162    }
1163
1164    #[turbo_tasks::function]
1165    pub(super) fn current_node_js_version(&self) -> Vc<NodeJsVersion> {
1166        NodeJsVersion::Static(ResolvedVc::cell(self.current_node_js_version.clone())).cell()
1167    }
1168
1169    #[turbo_tasks::function]
1170    pub fn next_config(&self) -> Vc<NextConfig> {
1171        *self.next_config
1172    }
1173
1174    /// Build the `IssueFilter` for this project, incorporating any
1175    /// `turbopack.ignoreIssue` rules from the Next.js config.
1176    #[turbo_tasks::function]
1177    pub async fn issue_filter(self: Vc<Self>) -> Result<Vc<IssueFilter>> {
1178        let ignore_rules = self.next_config().turbopack_ignore_issue_rules().await?;
1179        Ok(IssueFilter::warnings_and_foreign_errors()
1180            .with_ignore_rules(ignore_rules.to_vec())
1181            .cell())
1182    }
1183
1184    #[turbo_tasks::function]
1185    pub(super) fn is_persistent_caching_enabled(&self) -> Vc<bool> {
1186        Vc::cell(self.is_persistent_caching_enabled)
1187    }
1188
1189    #[turbo_tasks::function]
1190    pub(super) fn next_mode(&self) -> Vc<NextMode> {
1191        *self.mode
1192    }
1193
1194    #[turbo_tasks::function]
1195    pub(super) fn is_watch_enabled(&self) -> Result<Vc<bool>> {
1196        Ok(Vc::cell(self.watch.enable))
1197    }
1198
1199    #[turbo_tasks::function]
1200    pub(super) fn should_write_routes_hashes_manifest(&self) -> Result<Vc<bool>> {
1201        Ok(Vc::cell(self.write_routes_hashes_manifest))
1202    }
1203
1204    #[turbo_tasks::function]
1205    pub fn deferred_entries(&self) -> Vc<Vec<RcStr>> {
1206        Vc::cell(self.deferred_entries.clone())
1207    }
1208
1209    #[turbo_tasks::function]
1210    pub(super) async fn per_page_module_graph(&self) -> Result<Vc<bool>> {
1211        Ok(Vc::cell(*self.mode.await? == NextMode::Development))
1212    }
1213
1214    #[turbo_tasks::function]
1215    pub(super) fn encryption_key(&self) -> Vc<RcStr> {
1216        Vc::cell(self.encryption_key.clone())
1217    }
1218
1219    #[turbo_tasks::function]
1220    pub(super) fn no_mangling(&self) -> Vc<bool> {
1221        Vc::cell(self.no_mangling)
1222    }
1223
1224    #[turbo_tasks::function]
1225    pub(super) async fn execution_context(self: Vc<Self>) -> Result<Vc<ExecutionContext>> {
1226        let node_root = self.node_root().owned().await?;
1227        let next_mode = self.next_mode().await?;
1228        let strategy = *self
1229            .next_config()
1230            .turbopack_plugin_runtime_strategy()
1231            .await?;
1232        let node_backend = match strategy {
1233            #[cfg(feature = "worker_pool")]
1234            TurbopackPluginRuntimeStrategy::WorkerThreads => worker_threads_backend(),
1235            #[cfg(feature = "process_pool")]
1236            TurbopackPluginRuntimeStrategy::ChildProcesses => child_process_backend(),
1237        };
1238
1239        let node_execution_chunking_context = Vc::upcast(
1240            NodeJsChunkingContext::builder(
1241                self.project_root_path().owned().await?,
1242                node_root.join("build")?,
1243                self.node_root_to_root_path().owned().await?,
1244                node_root.join("build")?,
1245                node_root.join("build/chunks")?,
1246                node_root.join("build/assets")?,
1247                node_build_environment().to_resolved().await?,
1248                next_mode.runtime_type(),
1249            )
1250            .source_maps(*self.next_config().server_source_maps().await?)
1251            .build(),
1252        );
1253
1254        Ok(ExecutionContext::new(
1255            self.project_path().owned().await?,
1256            node_execution_chunking_context,
1257            self.env(),
1258            node_backend,
1259        ))
1260    }
1261
1262    #[turbo_tasks::function]
1263    pub(super) async fn client_compile_time_info(&self) -> Result<Vc<CompileTimeInfo>> {
1264        let next_mode = self.mode.await?;
1265        Ok(get_client_compile_time_info(
1266            self.browserslist_query.clone(),
1267            self.define_env.client(),
1268            self.next_config.report_system_env_inlining(),
1269            next_mode.is_development(),
1270        ))
1271    }
1272
1273    #[turbo_tasks::function]
1274    pub async fn get_all_endpoint_groups(
1275        self: Vc<Self>,
1276        app_dir_only: bool,
1277    ) -> Result<Vc<EndpointGroups>> {
1278        Ok(self.get_all_endpoint_groups_with_app_route_filter(app_dir_only, None))
1279    }
1280
1281    #[turbo_tasks::function]
1282    pub async fn get_all_endpoint_groups_with_app_route_filter(
1283        self: Vc<Self>,
1284        app_dir_only: bool,
1285        app_route_filter: Option<Vec<RcStr>>,
1286    ) -> Result<Vc<EndpointGroups>> {
1287        let mut endpoint_groups = Vec::new();
1288
1289        let entrypoints = self
1290            .entrypoints_with_app_route_filter(app_route_filter)
1291            .await?;
1292        let mut add_pages_entries = false;
1293
1294        if let Some(middleware) = &entrypoints.middleware {
1295            endpoint_groups.push((
1296                EndpointGroupKey::Middleware,
1297                EndpointGroup::from(middleware.endpoint),
1298            ));
1299        }
1300
1301        if let Some(instrumentation) = &entrypoints.instrumentation {
1302            endpoint_groups.push((
1303                EndpointGroupKey::Instrumentation,
1304                EndpointGroup::from(instrumentation.node_js),
1305            ));
1306            endpoint_groups.push((
1307                EndpointGroupKey::InstrumentationEdge,
1308                EndpointGroup::from(instrumentation.edge),
1309            ));
1310        }
1311
1312        for (key, route) in entrypoints.routes.iter() {
1313            match route {
1314                Route::Page {
1315                    html_endpoint,
1316                    data_endpoint,
1317                } => {
1318                    if !app_dir_only {
1319                        endpoint_groups.push((
1320                            EndpointGroupKey::Route(key.clone()),
1321                            EndpointGroup {
1322                                primary: vec![EndpointGroupEntry {
1323                                    endpoint: *html_endpoint,
1324                                    sub_name: None,
1325                                }],
1326                                // This only exists in development mode for HMR
1327                                additional: data_endpoint
1328                                    .iter()
1329                                    .map(|endpoint| EndpointGroupEntry {
1330                                        endpoint: *endpoint,
1331                                        sub_name: None,
1332                                    })
1333                                    .collect(),
1334                            },
1335                        ));
1336                        add_pages_entries = true;
1337                    }
1338                }
1339                Route::PageApi { endpoint } => {
1340                    if !app_dir_only {
1341                        endpoint_groups.push((
1342                            EndpointGroupKey::Route(key.clone()),
1343                            EndpointGroup::from(*endpoint),
1344                        ));
1345                        add_pages_entries = true;
1346                    }
1347                }
1348                Route::AppPage(page_routes) => {
1349                    endpoint_groups.push((
1350                        EndpointGroupKey::Route(key.clone()),
1351                        EndpointGroup {
1352                            primary: page_routes
1353                                .iter()
1354                                .map(|r| EndpointGroupEntry {
1355                                    endpoint: r.html_endpoint,
1356                                    sub_name: Some(r.original_name.clone()),
1357                                })
1358                                .collect(),
1359                            additional: Vec::new(),
1360                        },
1361                    ));
1362                }
1363                Route::AppRoute {
1364                    original_name: _,
1365                    endpoint,
1366                } => {
1367                    endpoint_groups.push((
1368                        EndpointGroupKey::Route(key.clone()),
1369                        EndpointGroup::from(*endpoint),
1370                    ));
1371                }
1372                Route::Conflict => {
1373                    tracing::info!("WARN: conflict");
1374                }
1375            }
1376        }
1377
1378        if add_pages_entries {
1379            endpoint_groups.push((
1380                EndpointGroupKey::PagesError,
1381                EndpointGroup::from(entrypoints.pages_error_endpoint),
1382            ));
1383            endpoint_groups.push((
1384                EndpointGroupKey::PagesApp,
1385                EndpointGroup::from(entrypoints.pages_app_endpoint),
1386            ));
1387            endpoint_groups.push((
1388                EndpointGroupKey::PagesDocument,
1389                EndpointGroup::from(entrypoints.pages_document_endpoint),
1390            ));
1391        }
1392
1393        Ok(Vc::cell(endpoint_groups))
1394    }
1395
1396    #[turbo_tasks::function]
1397    pub async fn get_all_endpoints(self: Vc<Self>, app_dir_only: bool) -> Result<Vc<Endpoints>> {
1398        let mut endpoints = Vec::new();
1399        for (_key, group) in self.get_all_endpoint_groups(app_dir_only).await?.iter() {
1400            for entry in group.primary.iter() {
1401                endpoints.push(entry.endpoint);
1402            }
1403            for entry in group.additional.iter() {
1404                endpoints.push(entry.endpoint);
1405            }
1406        }
1407
1408        Ok(Vc::cell(endpoints))
1409    }
1410
1411    #[turbo_tasks::function]
1412    pub async fn get_all_entries(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
1413        let mut modules = self
1414            .get_all_endpoints(false)
1415            .await?
1416            .iter()
1417            .map(async |endpoint| Ok(endpoint.entries().owned().await?))
1418            .try_flat_join()
1419            .await?;
1420        modules.extend(self.client_main_modules().await?.iter().cloned());
1421        Ok(Vc::cell(modules))
1422    }
1423
1424    #[turbo_tasks::function]
1425    pub async fn get_all_additional_entries(
1426        self: Vc<Self>,
1427        graphs: Vc<ModuleGraph>,
1428    ) -> Result<Vc<GraphEntries>> {
1429        let modules = self
1430            .get_all_endpoints(false)
1431            .await?
1432            .iter()
1433            .map(async |endpoint| Ok(endpoint.additional_entries(graphs).owned().await?))
1434            .try_flat_join()
1435            .await?;
1436        Ok(Vc::cell(modules))
1437    }
1438
1439    #[turbo_tasks::function]
1440    pub async fn module_graph(
1441        self: Vc<Self>,
1442        entry: ResolvedVc<Box<dyn Module>>,
1443    ) -> Result<Vc<ModuleGraph>> {
1444        Ok(if *self.per_page_module_graph().await? {
1445            let is_production = self.next_mode().await?.is_production();
1446            ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entry(
1447                ChunkGroupEntry::Entry(vec![entry]),
1448                is_production,
1449                is_production,
1450            ))
1451            .connect()
1452        } else {
1453            *self.whole_app_module_graphs().await?.full
1454        })
1455    }
1456
1457    #[turbo_tasks::function]
1458    pub async fn module_graph_for_modules(
1459        self: Vc<Self>,
1460        evaluatable_assets: Vc<EvaluatableAssets>,
1461    ) -> Result<Vc<ModuleGraph>> {
1462        Ok(if *self.per_page_module_graph().await? {
1463            let is_production = self.next_mode().await?.is_production();
1464            let entries = evaluatable_assets
1465                .await?
1466                .iter()
1467                .copied()
1468                .map(ResolvedVc::upcast)
1469                .collect();
1470            ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries(
1471                ResolvedVc::cell(vec![ChunkGroupEntry::Entry(entries)]),
1472                is_production,
1473                is_production,
1474            ))
1475            .connect()
1476        } else {
1477            *self.whole_app_module_graphs().await?.full
1478        })
1479    }
1480
1481    #[turbo_tasks::function]
1482    pub async fn whole_app_module_graphs(
1483        self: ResolvedVc<Self>,
1484    ) -> Result<Vc<BaseAndFullModuleGraph>> {
1485        let module_graphs_op = whole_app_module_graph_operation(self);
1486        let module_graphs_vc = if self.next_mode().await?.is_production() {
1487            module_graphs_op.connect()
1488        } else {
1489            // In development mode, we need to to take and drop the issues, otherwise every
1490            // route will report all issues.
1491            let vc = module_graphs_op.resolve_strongly_consistent().await?;
1492            module_graphs_op.drop_issues();
1493            *vc
1494        };
1495
1496        // At this point all modules have been computed and we can get rid of the node.js
1497        // process pools
1498        let execution_context = self.execution_context().await?;
1499        let node_backend = execution_context.node_backend.into_trait_ref().await?;
1500        if *self.is_watch_enabled().await? {
1501            node_backend.scale_down()?;
1502        } else {
1503            node_backend.scale_zero()?;
1504        }
1505
1506        Ok(module_graphs_vc)
1507    }
1508
1509    #[turbo_tasks::function]
1510    pub(super) async fn server_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1511        let this = self.await?;
1512        Ok(get_server_compile_time_info(
1513            // `/ROOT` corresponds to `[project]/`, so we need exactly the `path` part.
1514            self.project_path(),
1515            this.define_env.nodejs(),
1516            self.current_node_js_version(),
1517            this.next_config.report_system_env_inlining(),
1518            this.server_hmr,
1519        ))
1520    }
1521
1522    #[turbo_tasks::function]
1523    pub(super) async fn edge_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1524        let this = self.await?;
1525        Ok(get_edge_compile_time_info(
1526            self.project_path().owned().await?,
1527            this.define_env.edge(),
1528            self.current_node_js_version(),
1529            this.next_config.report_system_env_inlining(),
1530        ))
1531    }
1532
1533    #[turbo_tasks::function]
1534    pub(super) fn edge_env(&self) -> Vc<EnvMap> {
1535        let edge_env = fxindexmap! {
1536            rcstr!("__NEXT_BUILD_ID") => self.build_id.clone(),
1537            rcstr!("NEXT_SERVER_ACTIONS_ENCRYPTION_KEY") => self.encryption_key.clone(),
1538            rcstr!("__NEXT_PREVIEW_MODE_ID") => self.preview_props.preview_mode_id.clone(),
1539            rcstr!("__NEXT_PREVIEW_MODE_ENCRYPTION_KEY") => self.preview_props.preview_mode_encryption_key.clone(),
1540            rcstr!("__NEXT_PREVIEW_MODE_SIGNING_KEY") => self.preview_props.preview_mode_signing_key.clone(),
1541        };
1542        Vc::cell(edge_env)
1543    }
1544
1545    #[turbo_tasks::function]
1546    pub(super) async fn client_chunking_context(
1547        self: Vc<Self>,
1548    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1549        let css_url_suffix = self.next_config().asset_suffix_path();
1550        Ok(get_client_chunking_context(ClientChunkingContextOptions {
1551            mode: self.next_mode(),
1552            root_path: self.project_root_path().owned().await?,
1553            client_root: self.client_relative_path().owned().await?,
1554            client_root_to_root_path: rcstr!("/ROOT"),
1555            asset_prefix: self.next_config().computed_asset_prefix(),
1556            environment: self.client_compile_time_info().environment(),
1557            module_id_strategy: self.module_ids(),
1558            export_usage: self.export_usage(),
1559            unused_references: self.unused_references(),
1560            minify: self.next_config().turbo_minify(self.next_mode()),
1561            source_maps: self.next_config().client_source_maps(self.next_mode()),
1562            no_mangling: self.no_mangling(),
1563            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1564            nested_async_chunking: self
1565                .next_config()
1566                .turbo_nested_async_chunking(self.next_mode(), true),
1567            debug_ids: self.next_config().turbopack_debug_ids(),
1568            should_use_absolute_url_references: self.next_config().inline_css(),
1569            css_url_suffix,
1570        }))
1571    }
1572
1573    #[turbo_tasks::function]
1574    pub(super) async fn server_chunking_context(
1575        self: Vc<Self>,
1576        client_assets: bool,
1577    ) -> Result<Vc<NodeJsChunkingContext>> {
1578        let css_url_suffix = self.next_config().asset_suffix_path();
1579        let options = ServerChunkingContextOptions {
1580            mode: self.next_mode(),
1581            root_path: self.project_root_path().owned().await?,
1582            node_root: self.node_root().owned().await?,
1583            node_root_to_root_path: self.node_root_to_root_path().owned().await?,
1584            environment: self.server_compile_time_info().environment(),
1585            module_id_strategy: self.module_ids(),
1586            export_usage: self.export_usage(),
1587            unused_references: self.unused_references(),
1588            minify: self.next_config().turbo_minify(self.next_mode()),
1589            source_maps: self.next_config().server_source_maps(),
1590            no_mangling: self.no_mangling(),
1591            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1592            nested_async_chunking: self
1593                .next_config()
1594                .turbo_nested_async_chunking(self.next_mode(), false),
1595            debug_ids: self.next_config().turbopack_debug_ids(),
1596            client_root: self.client_relative_path().owned().await?,
1597            asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1598            css_url_suffix,
1599        };
1600        Ok(if client_assets {
1601            get_server_chunking_context_with_client_assets(options)
1602        } else {
1603            get_server_chunking_context(options)
1604        })
1605    }
1606
1607    #[turbo_tasks::function]
1608    pub(super) async fn edge_chunking_context(
1609        self: Vc<Self>,
1610        client_assets: bool,
1611    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1612        let css_url_suffix = self.next_config().asset_suffix_path();
1613        let options = EdgeChunkingContextOptions {
1614            mode: self.next_mode(),
1615            root_path: self.project_root_path().owned().await?,
1616            node_root: self.node_root().owned().await?,
1617            output_root_to_root_path: self.node_root_to_root_path(),
1618            environment: self.edge_compile_time_info().environment(),
1619            module_id_strategy: self.module_ids(),
1620            export_usage: self.export_usage(),
1621            unused_references: self.unused_references(),
1622            turbo_minify: self.next_config().turbo_minify(self.next_mode()),
1623            turbo_source_maps: self.next_config().server_source_maps(),
1624            no_mangling: self.no_mangling(),
1625            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1626            nested_async_chunking: self
1627                .next_config()
1628                .turbo_nested_async_chunking(self.next_mode(), false),
1629            client_root: self.client_relative_path().owned().await?,
1630            asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1631            css_url_suffix,
1632        };
1633        Ok(if client_assets {
1634            get_edge_chunking_context_with_client_assets(options)
1635        } else {
1636            get_edge_chunking_context(options)
1637        })
1638    }
1639
1640    #[turbo_tasks::function]
1641    pub(super) fn runtime_chunking_context(
1642        self: Vc<Self>,
1643        client_assets: bool,
1644        runtime: NextRuntime,
1645    ) -> Vc<Box<dyn ChunkingContext>> {
1646        match runtime {
1647            NextRuntime::Edge => self.edge_chunking_context(client_assets),
1648            NextRuntime::NodeJs => Vc::upcast(self.server_chunking_context(client_assets)),
1649        }
1650    }
1651
1652    /// Emit a telemetry event corresponding to [webpack configuration telemetry](https://github.com/vercel/next.js/blob/9da305fe320b89ee2f8c3cfb7ecbf48856368913/packages/next/src/build/webpack-config.ts#L2516)
1653    /// to detect which feature is enabled.
1654    #[turbo_tasks::function]
1655    async fn collect_project_feature_telemetry(self: Vc<Self>) -> Result<Vc<()>> {
1656        let emit_event = |feature_name: &str, enabled: bool| {
1657            NextFeatureTelemetry::new(feature_name.into(), enabled)
1658                .resolved_cell()
1659                .emit();
1660        };
1661
1662        // First, emit an event for the binary target triple.
1663        // This is different to webpack-config; when this is being called,
1664        // it is always using SWC so we don't check swc here.
1665        emit_event(env!("VERGEN_CARGO_TARGET_TRIPLE"), true);
1666
1667        // Go over config and report enabled features.
1668        // [TODO]: useSwcLoader is not being reported as it is not directly corresponds (it checks babel config existence)
1669        // need to confirm what we'll do with turbopack.
1670        let config = self.next_config();
1671
1672        emit_event(
1673            "skipProxyUrlNormalize",
1674            *config.skip_proxy_url_normalize().await?,
1675        );
1676
1677        emit_event(
1678            "skipTrailingSlashRedirect",
1679            *config.skip_trailing_slash_redirect().await?,
1680        );
1681        emit_event(
1682            "persistentCaching",
1683            *self.is_persistent_caching_enabled().await?,
1684        );
1685
1686        emit_event(
1687            "modularizeImports",
1688            !config.modularize_imports().await?.is_empty(),
1689        );
1690        emit_event(
1691            "transpilePackages",
1692            !config.transpile_packages().await?.is_empty(),
1693        );
1694        emit_event("turbotrace", false);
1695
1696        // compiler options
1697        let compiler_options = config.compiler().await?;
1698        let swc_relay_enabled = compiler_options.relay.is_some();
1699        let styled_components_enabled = compiler_options
1700            .styled_components
1701            .as_ref()
1702            .map(|sc| sc.is_enabled())
1703            .unwrap_or_default();
1704        let react_remove_properties_enabled = compiler_options
1705            .react_remove_properties
1706            .as_ref()
1707            .map(|rc| rc.is_enabled())
1708            .unwrap_or_default();
1709        let remove_console_enabled = compiler_options
1710            .remove_console
1711            .as_ref()
1712            .map(|rc| rc.is_enabled())
1713            .unwrap_or_default();
1714        let emotion_enabled = compiler_options
1715            .emotion
1716            .as_ref()
1717            .map(|e| e.is_enabled())
1718            .unwrap_or_default();
1719
1720        emit_event("swcRelay", swc_relay_enabled);
1721        emit_event("swcStyledComponents", styled_components_enabled);
1722        emit_event("swcReactRemoveProperties", react_remove_properties_enabled);
1723        emit_event("swcRemoveConsole", remove_console_enabled);
1724        emit_event("swcEmotion", emotion_enabled);
1725
1726        Ok(Default::default())
1727    }
1728
1729    /// Scans the app/pages directories for entry points files (matching the
1730    /// provided page_extensions).
1731    #[turbo_tasks::function]
1732    pub async fn entrypoints(self: Vc<Self>) -> Result<Vc<Entrypoints>> {
1733        Ok(self.entrypoints_with_app_route_filter(None))
1734    }
1735
1736    #[turbo_tasks::function]
1737    pub async fn entrypoints_with_app_route_filter(
1738        self: Vc<Self>,
1739        app_route_filter: Option<Vec<RcStr>>,
1740    ) -> Result<Vc<Entrypoints>> {
1741        self.collect_project_feature_telemetry().await?;
1742
1743        let this = self.await?;
1744        let mut routes = FxIndexMap::default();
1745        let app_project = self.app_project();
1746        let pages_project = self.pages_project();
1747
1748        // Convert debug build paths to route keys once for O(1) lookups
1749        let debug_build_paths_route_keys = this
1750            .debug_build_paths
1751            .as_ref()
1752            .map(DebugBuildPathsRouteKeys::from_debug_build_paths)
1753            .transpose()?;
1754
1755        if let Some(app_project) = &*app_project.await? {
1756            let app_routes = app_project.routes_with_filter(app_route_filter);
1757            routes.extend(
1758                app_routes
1759                    .await?
1760                    .iter()
1761                    .filter(|(k, _)| {
1762                        debug_build_paths_route_keys
1763                            .as_ref()
1764                            .is_none_or(|keys| keys.should_include_app_route(k))
1765                    })
1766                    .map(|(k, v)| (k.clone(), v.clone())),
1767            );
1768        }
1769
1770        for (pathname, page_route) in &pages_project.routes().await? {
1771            if debug_build_paths_route_keys
1772                .as_ref()
1773                .is_some_and(|keys| !keys.should_include_pages_route(pathname))
1774            {
1775                continue;
1776            }
1777
1778            match routes.entry(pathname.clone()) {
1779                Entry::Occupied(mut entry) => {
1780                    ConflictIssue {
1781                        path: self.project_path().owned().await?,
1782                        title: StyledString::Text(
1783                            format!("App Router and Pages Router both match path: {pathname}")
1784                                .into(),
1785                        )
1786                        .resolved_cell(),
1787                        description: StyledString::Text(
1788                            "Next.js does not support having both App Router and Pages Router \
1789                             routes matching the same path. Please remove one of the conflicting \
1790                             routes."
1791                                .into(),
1792                        )
1793                        .resolved_cell(),
1794                        severity: IssueSeverity::Error,
1795                    }
1796                    .resolved_cell()
1797                    .emit();
1798                    *entry.get_mut() = Route::Conflict;
1799                }
1800                Entry::Vacant(entry) => {
1801                    entry.insert(page_route.clone());
1802                }
1803            }
1804        }
1805
1806        let pages_document_endpoint = self
1807            .pages_project()
1808            .document_endpoint()
1809            .to_resolved()
1810            .await?;
1811        let pages_app_endpoint = self.pages_project().app_endpoint().to_resolved().await?;
1812        let pages_error_endpoint = self.pages_project().error_endpoint().to_resolved().await?;
1813
1814        let middleware = self.find_middleware();
1815        let middleware = if let FindContextFileResult::Found(fs_path, _) = &*middleware.await? {
1816            let is_proxy = fs_path.file_stem() == Some("proxy");
1817            Some(Middleware {
1818                endpoint: self.middleware_endpoint().to_resolved().await?,
1819                is_proxy,
1820            })
1821        } else {
1822            None
1823        };
1824
1825        let instrumentation = self.find_instrumentation();
1826        let instrumentation = if let FindContextFileResult::Found(..) = *instrumentation.await? {
1827            Some(Instrumentation {
1828                node_js: self.instrumentation_endpoint(false).to_resolved().await?,
1829                edge: self.instrumentation_endpoint(true).to_resolved().await?,
1830            })
1831        } else {
1832            None
1833        };
1834
1835        Ok(Entrypoints {
1836            routes,
1837            middleware,
1838            instrumentation,
1839            pages_document_endpoint,
1840            pages_app_endpoint,
1841            pages_error_endpoint,
1842        }
1843        .cell())
1844    }
1845
1846    #[turbo_tasks::function]
1847    async fn edge_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1848        let mut transitions = vec![];
1849
1850        let app_dir = find_app_dir(self.project_path().owned().await?)
1851            .owned()
1852            .await?;
1853        let app_project = *self.app_project().await?;
1854
1855        let ecmascript_client_reference_transition_name =
1856            app_project.map(|_| AppProject::client_transition_name());
1857
1858        if let Some(app_project) = app_project {
1859            transitions.push((
1860                AppProject::client_transition_name(),
1861                app_project
1862                    .edge_ecmascript_client_reference_transition()
1863                    .to_resolved()
1864                    .await?,
1865            ));
1866        }
1867
1868        Ok(Vc::upcast(ModuleAssetContext::new(
1869            TransitionOptions {
1870                named_transitions: transitions.clone().into_iter().collect(),
1871                ..Default::default()
1872            }
1873            .cell(),
1874            self.edge_compile_time_info(),
1875            get_server_module_options_context(
1876                self.project_path().owned().await?,
1877                self.execution_context(),
1878                ServerContextType::Middleware {
1879                    app_dir: app_dir.clone(),
1880                    ecmascript_client_reference_transition_name:
1881                        ecmascript_client_reference_transition_name.clone(),
1882                },
1883                self.next_mode(),
1884                self.next_config(),
1885                NextRuntime::Edge,
1886                self.encryption_key(),
1887                self.edge_compile_time_info().environment(),
1888                self.client_compile_time_info().environment(),
1889            ),
1890            get_edge_resolve_options_context(
1891                self.project_path().owned().await?,
1892                ServerContextType::Middleware {
1893                    app_dir: app_dir.clone(),
1894                    ecmascript_client_reference_transition_name:
1895                        ecmascript_client_reference_transition_name.clone(),
1896                },
1897                self.next_mode(),
1898                self.next_config(),
1899                self.execution_context(),
1900                None, // root params can't be used in middleware
1901            ),
1902            Layer::new_with_user_friendly_name(
1903                rcstr!("middleware-edge"),
1904                rcstr!("Edge Middleware"),
1905            ),
1906        )))
1907    }
1908
1909    #[turbo_tasks::function]
1910    async fn node_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1911        let mut transitions = vec![];
1912
1913        let app_dir = find_app_dir(self.project_path().owned().await?)
1914            .owned()
1915            .await?;
1916        let app_project = *self.app_project().await?;
1917
1918        let ecmascript_client_reference_transition_name =
1919            app_project.map(|_| AppProject::client_transition_name());
1920
1921        if let Some(app_project) = app_project {
1922            transitions.push((
1923                AppProject::client_transition_name(),
1924                app_project
1925                    .edge_ecmascript_client_reference_transition()
1926                    .to_resolved()
1927                    .await?,
1928            ));
1929        }
1930
1931        Ok(Vc::upcast(ModuleAssetContext::new(
1932            TransitionOptions {
1933                named_transitions: transitions.clone().into_iter().collect(),
1934                ..Default::default()
1935            }
1936            .cell(),
1937            self.server_compile_time_info(),
1938            get_server_module_options_context(
1939                self.project_path().owned().await?,
1940                self.execution_context(),
1941                ServerContextType::Middleware {
1942                    app_dir: app_dir.clone(),
1943                    ecmascript_client_reference_transition_name:
1944                        ecmascript_client_reference_transition_name.clone(),
1945                },
1946                self.next_mode(),
1947                self.next_config(),
1948                NextRuntime::NodeJs,
1949                self.encryption_key(),
1950                self.server_compile_time_info().environment(),
1951                self.client_compile_time_info().environment(),
1952            ),
1953            get_server_resolve_options_context(
1954                self.project_path().owned().await?,
1955                ServerContextType::Middleware {
1956                    app_dir: app_dir.clone(),
1957                    ecmascript_client_reference_transition_name,
1958                },
1959                self.next_mode(),
1960                self.next_config(),
1961                self.execution_context(),
1962                None, // root params can't be used in middleware
1963            ),
1964            Layer::new_with_user_friendly_name(rcstr!("middleware"), rcstr!("Middleware")),
1965        )))
1966    }
1967
1968    #[turbo_tasks::function]
1969    async fn find_middleware(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
1970        Ok(find_context_file(
1971            self.project_path().owned().await?,
1972            middleware_files(self.next_config().page_extensions()),
1973            // our callers do not care about affecting sources
1974            false,
1975        ))
1976    }
1977
1978    #[turbo_tasks::function]
1979    async fn middleware_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
1980        let middleware = self.find_middleware();
1981        let FindContextFileResult::Found(fs_path, _) = &*middleware.await? else {
1982            return Ok(Vc::upcast(EmptyEndpoint::new(self)));
1983        };
1984        let source = Vc::upcast(FileSource::new(fs_path.clone()));
1985        let app_dir = find_app_dir(self.project_path().owned().await?)
1986            .owned()
1987            .await?;
1988        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
1989            .as_ref()
1990            .map(|_| AppProject::client_transition_name());
1991
1992        let is_proxy = fs_path.file_stem() == Some("proxy");
1993        let config = parse_segment_config_from_source(
1994            source,
1995            if is_proxy {
1996                ParseSegmentMode::Proxy
1997            } else {
1998                ParseSegmentMode::Base
1999            },
2000        );
2001        let runtime = config.await?.runtime.unwrap_or(if is_proxy {
2002            NextRuntime::NodeJs
2003        } else {
2004            NextRuntime::Edge
2005        });
2006
2007        let middleware_asset_context = match runtime {
2008            NextRuntime::NodeJs => self.node_middleware_context(),
2009            NextRuntime::Edge => self.edge_middleware_context(),
2010        };
2011
2012        Ok(Vc::upcast(MiddlewareEndpoint::new(
2013            self,
2014            middleware_asset_context,
2015            source,
2016            app_dir.clone(),
2017            ecmascript_client_reference_transition_name,
2018            config,
2019            runtime,
2020        )))
2021    }
2022
2023    #[turbo_tasks::function]
2024    async fn node_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2025        let mut transitions = vec![];
2026
2027        let app_dir = find_app_dir(self.project_path().owned().await?)
2028            .owned()
2029            .await?;
2030        let app_project = &*self.app_project().await?;
2031
2032        let ecmascript_client_reference_transition_name = app_project
2033            .as_ref()
2034            .map(|_| AppProject::client_transition_name());
2035
2036        if let Some(app_project) = app_project {
2037            transitions.push((
2038                AppProject::client_transition_name(),
2039                app_project
2040                    .ecmascript_client_reference_transition()
2041                    .to_resolved()
2042                    .await?,
2043            ));
2044        }
2045
2046        Ok(Vc::upcast(ModuleAssetContext::new(
2047            TransitionOptions {
2048                named_transitions: transitions.into_iter().collect(),
2049                ..Default::default()
2050            }
2051            .cell(),
2052            self.server_compile_time_info(),
2053            get_server_module_options_context(
2054                self.project_path().owned().await?,
2055                self.execution_context(),
2056                ServerContextType::Instrumentation {
2057                    app_dir: app_dir.clone(),
2058                    ecmascript_client_reference_transition_name:
2059                        ecmascript_client_reference_transition_name.clone(),
2060                },
2061                self.next_mode(),
2062                self.next_config(),
2063                NextRuntime::NodeJs,
2064                self.encryption_key(),
2065                self.server_compile_time_info().environment(),
2066                self.client_compile_time_info().environment(),
2067            ),
2068            get_server_resolve_options_context(
2069                self.project_path().owned().await?,
2070                ServerContextType::Instrumentation {
2071                    app_dir: app_dir.clone(),
2072                    ecmascript_client_reference_transition_name,
2073                },
2074                self.next_mode(),
2075                self.next_config(),
2076                self.execution_context(),
2077                None, // root params can't be used in instrumentation
2078            ),
2079            Layer::new_with_user_friendly_name(
2080                rcstr!("instrumentation"),
2081                rcstr!("Instrumentation"),
2082            ),
2083        )))
2084    }
2085
2086    #[turbo_tasks::function]
2087    async fn edge_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2088        let mut transitions = vec![];
2089
2090        let app_dir = find_app_dir(self.project_path().owned().await?)
2091            .owned()
2092            .await?;
2093        let app_project = &*self.app_project().await?;
2094
2095        let ecmascript_client_reference_transition_name = app_project
2096            .as_ref()
2097            .map(|_| AppProject::client_transition_name());
2098
2099        if let Some(app_project) = app_project {
2100            transitions.push((
2101                AppProject::client_transition_name(),
2102                app_project
2103                    .edge_ecmascript_client_reference_transition()
2104                    .to_resolved()
2105                    .await?,
2106            ));
2107        }
2108
2109        Ok(Vc::upcast(ModuleAssetContext::new(
2110            TransitionOptions {
2111                named_transitions: transitions.into_iter().collect(),
2112                ..Default::default()
2113            }
2114            .cell(),
2115            self.edge_compile_time_info(),
2116            get_server_module_options_context(
2117                self.project_path().owned().await?,
2118                self.execution_context(),
2119                ServerContextType::Instrumentation {
2120                    app_dir: app_dir.clone(),
2121                    ecmascript_client_reference_transition_name:
2122                        ecmascript_client_reference_transition_name.clone(),
2123                },
2124                self.next_mode(),
2125                self.next_config(),
2126                NextRuntime::Edge,
2127                self.encryption_key(),
2128                self.edge_compile_time_info().environment(),
2129                self.client_compile_time_info().environment(),
2130            ),
2131            get_edge_resolve_options_context(
2132                self.project_path().owned().await?,
2133                ServerContextType::Instrumentation {
2134                    app_dir: app_dir.clone(),
2135                    ecmascript_client_reference_transition_name,
2136                },
2137                self.next_mode(),
2138                self.next_config(),
2139                self.execution_context(),
2140                None, // root params can't be used in instrumentation
2141            ),
2142            Layer::new_with_user_friendly_name(
2143                rcstr!("instrumentation-edge"),
2144                rcstr!("Edge Instrumentation"),
2145            ),
2146        )))
2147    }
2148
2149    #[turbo_tasks::function]
2150    async fn find_instrumentation(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
2151        Ok(find_context_file(
2152            self.project_path().owned().await?,
2153            instrumentation_files(self.next_config().page_extensions()),
2154            // our callers do not care about affecting sources
2155            false,
2156        ))
2157    }
2158
2159    #[turbo_tasks::function]
2160    async fn instrumentation_endpoint(
2161        self: Vc<Self>,
2162        is_edge: bool,
2163    ) -> Result<Vc<Box<dyn Endpoint>>> {
2164        let instrumentation = self.find_instrumentation();
2165        let FindContextFileResult::Found(fs_path, _) = &*instrumentation.await? else {
2166            return Ok(Vc::upcast(EmptyEndpoint::new(self)));
2167        };
2168        let source = Vc::upcast(FileSource::new(fs_path.clone()));
2169        let app_dir = find_app_dir(self.project_path().owned().await?)
2170            .owned()
2171            .await?;
2172        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
2173            .as_ref()
2174            .map(|_| AppProject::client_transition_name());
2175
2176        let instrumentation_asset_context = if is_edge {
2177            self.edge_instrumentation_context()
2178        } else {
2179            self.node_instrumentation_context()
2180        };
2181
2182        Ok(Vc::upcast(InstrumentationEndpoint::new(
2183            self,
2184            instrumentation_asset_context,
2185            source,
2186            is_edge,
2187            app_dir.clone(),
2188            ecmascript_client_reference_transition_name,
2189        )))
2190    }
2191
2192    #[turbo_tasks::function]
2193    pub async fn emit_all_output_assets(
2194        self: Vc<Self>,
2195        output_assets: OperationVc<OutputAssets>,
2196    ) -> Result<()> {
2197        let span = tracing::info_span!("emitting");
2198        async move {
2199            let all_output_assets = all_assets_from_entries_operation(output_assets);
2200
2201            let client_relative_path = self.client_relative_path().owned().await?;
2202            let node_root = self.node_root().owned().await?;
2203
2204            if let Some(map) = self.await?.versioned_content_map {
2205                map.insert_output_assets(
2206                    all_output_assets,
2207                    node_root.clone(),
2208                    client_relative_path.clone(),
2209                    node_root.clone(),
2210                )
2211                .as_side_effect()
2212                .await?;
2213
2214                Ok(())
2215            } else {
2216                emit_assets(
2217                    all_output_assets.connect(),
2218                    node_root.clone(),
2219                    client_relative_path.clone(),
2220                    node_root.clone(),
2221                )
2222                .as_side_effect()
2223                .await?;
2224
2225                Ok(())
2226            }
2227        }
2228        .instrument(span)
2229        .await
2230    }
2231
2232    /// Returns the root path for HMR content based on the target.
2233    /// Client uses client_relative_path, Server uses node_root.
2234    #[turbo_tasks::function]
2235    async fn hmr_root_path(self: Vc<Self>, target: HmrTarget) -> Result<Vc<FileSystemPath>> {
2236        Ok(match target {
2237            HmrTarget::Client => self.client_relative_path(),
2238            HmrTarget::Server => self.node_root(),
2239        })
2240    }
2241
2242    /// Get HMR content by chunk_name for the specified target.
2243    #[turbo_tasks::function]
2244    async fn hmr_content(
2245        self: Vc<Self>,
2246        chunk_name: RcStr,
2247        target: HmrTarget,
2248    ) -> Result<Vc<OptionVersionedContent>> {
2249        if let Some(map) = self.await?.versioned_content_map {
2250            let content = map.get(self.hmr_root_path(target).await?.join(&chunk_name)?);
2251            Ok(content)
2252        } else {
2253            bail!("must be in dev mode to hmr")
2254        }
2255    }
2256
2257    /// Get the version state for an HMR session. Initialized with the first seen
2258    /// version in that session.
2259    #[turbo_tasks::function]
2260    pub async fn hmr_version_state(
2261        self: ResolvedVc<Self>,
2262        chunk_name: RcStr,
2263        target: HmrTarget,
2264        session: TransientInstance<()>,
2265    ) -> Result<Vc<VersionState>> {
2266        // The session argument is important to avoid caching this function between
2267        // sessions.
2268        let _ = session;
2269
2270        #[turbo_tasks::function(operation)]
2271        async fn hmr_version_operation(
2272            this: ResolvedVc<Project>,
2273            chunk_name: RcStr,
2274            target: HmrTarget,
2275        ) -> Result<Vc<Box<dyn Version>>> {
2276            let content = this.hmr_content(chunk_name, target).await?;
2277            if let Some(content) = &*content {
2278                Ok(content.version())
2279            } else {
2280                Ok(Vc::upcast(NotFoundVersion::new()))
2281            }
2282        }
2283        let version_op = hmr_version_operation(self, chunk_name, target);
2284
2285        // INVALIDATION: This is intentionally untracked to avoid invalidating this
2286        // function completely. We want to initialize the VersionState with the
2287        // first seen version of the session.
2288        let state = VersionState::new(
2289            version_op
2290                .read_trait_strongly_consistent()
2291                .untracked()
2292                .await?,
2293        )
2294        .await?;
2295        Ok(state)
2296    }
2297
2298    /// Emits opaque HMR events whenever a change is detected in the chunk group
2299    /// internally known as `chunk_name` for the specified target.
2300    #[turbo_tasks::function]
2301    pub async fn hmr_update(
2302        self: Vc<Self>,
2303        chunk_name: RcStr,
2304        target: HmrTarget,
2305        from: Vc<VersionState>,
2306    ) -> Result<Vc<Update>> {
2307        let from = from.get();
2308        let content = self.hmr_content(chunk_name, target).await?;
2309        if let Some(content) = *content {
2310            Ok(content.update(from))
2311        } else {
2312            Ok(Update::Missing.cell())
2313        }
2314    }
2315
2316    /// Gets a list of all HMR chunk names that can be subscribed to for the
2317    /// specified target. Used by the dev server to set up server-side HMR
2318    /// subscriptions for all Node.js App Router entries (pages and route
2319    /// handlers).
2320    #[turbo_tasks::function]
2321    pub async fn hmr_chunk_names(self: Vc<Self>, target: HmrTarget) -> Result<Vc<Vec<RcStr>>> {
2322        if let Some(map) = self.await?.versioned_content_map {
2323            Ok(map.keys_in_path(self.hmr_root_path(target).owned().await?))
2324        } else {
2325            bail!("must be in dev mode to hmr")
2326        }
2327    }
2328
2329    /// Completion when server side changes are detected in output assets
2330    /// referenced from the roots
2331    #[turbo_tasks::function]
2332    pub async fn server_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
2333        let path = self.node_root().owned().await?;
2334        Ok(any_output_changed(roots, path, true))
2335    }
2336
2337    /// Completion when client side changes are detected in output assets
2338    /// referenced from the roots
2339    #[turbo_tasks::function]
2340    pub async fn client_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
2341        let path = self.client_root().owned().await?;
2342        Ok(any_output_changed(roots, path, false))
2343    }
2344
2345    #[turbo_tasks::function]
2346    pub async fn client_main_modules(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
2347        let pages_project = self.pages_project();
2348        let mut modules = vec![ChunkGroupEntry::Entry(vec![
2349            pages_project.client_main_module().to_resolved().await?,
2350        ])];
2351
2352        if let Some(app_project) = *self.app_project().await? {
2353            modules.push(ChunkGroupEntry::Entry(vec![
2354                app_project.client_main_module().to_resolved().await?,
2355            ]));
2356        }
2357
2358        Ok(Vc::cell(modules))
2359    }
2360
2361    /// Gets the module id strategy for the project.
2362    #[turbo_tasks::function]
2363    pub async fn module_ids(self: Vc<Self>) -> Result<Vc<ModuleIdStrategy>> {
2364        let module_id_strategy = *self.next_config().module_ids(self.next_mode()).await?;
2365        match module_id_strategy {
2366            ModuleIdStrategyConfig::Named => Ok(ModuleIdStrategy {
2367                module_id_map: None,
2368                fallback: ModuleIdFallback::Ident,
2369            }
2370            .cell()),
2371            ModuleIdStrategyConfig::Deterministic => {
2372                let module_graphs = self.whole_app_module_graphs().await?;
2373                Ok(get_global_module_id_strategy(*module_graphs.full))
2374            }
2375        }
2376    }
2377
2378    /// Compute the used exports and unused imports for each module.
2379    #[turbo_tasks::function]
2380    async fn binding_usage_info(self: Vc<Self>) -> Result<Vc<BindingUsageInfo>> {
2381        let module_graphs = self.whole_app_module_graphs().await?;
2382        Ok(module_graphs
2383            .binding_usage_info
2384            .context("No binding usage info")?
2385            .connect())
2386    }
2387
2388    /// Compute the used exports for each module.
2389    #[turbo_tasks::function]
2390    pub async fn export_usage(self: Vc<Self>) -> Result<Vc<OptionBindingUsageInfo>> {
2391        if *self
2392            .next_config()
2393            .turbopack_remove_unused_exports(self.next_mode())
2394            .await?
2395        {
2396            Ok(Vc::cell(Some(
2397                self.binding_usage_info().to_resolved().await?,
2398            )))
2399        } else {
2400            Ok(Vc::cell(None))
2401        }
2402    }
2403
2404    /// Compute the unused references that were removed (inner graph tree shaking).
2405    #[turbo_tasks::function]
2406    pub async fn unused_references(self: Vc<Self>) -> Result<Vc<UnusedReferences>> {
2407        if *self
2408            .next_config()
2409            .turbopack_remove_unused_imports(self.next_mode())
2410            .await?
2411        {
2412            Ok(self.binding_usage_info().unused_references())
2413        } else {
2414            Ok(Vc::cell(Default::default()))
2415        }
2416    }
2417
2418    #[turbo_tasks::function]
2419    pub async fn with_next_config(&self, next_config: Vc<NextConfig>) -> Result<Vc<Self>> {
2420        Ok(Self {
2421            next_config: next_config.to_resolved().await?,
2422            ..(*self).clone()
2423        }
2424        .cell())
2425    }
2426}
2427
2428// This is a performance optimization. This function is a root aggregation function that
2429// aggregates over the whole subgraph.
2430#[turbo_tasks::function(operation, root)]
2431async fn whole_app_module_graph_operation(
2432    project: ResolvedVc<Project>,
2433) -> Result<Vc<BaseAndFullModuleGraph>> {
2434    let span = tracing::info_span!("whole app module graph", modules = Empty);
2435    let span_clone = span.clone();
2436    async move {
2437        let next_mode = project.next_mode();
2438        let next_mode_ref = next_mode.await?;
2439        let should_trace = next_mode_ref.is_production();
2440        let should_read_binding_usage = next_mode_ref.is_production();
2441        let base_single_module_graph = SingleModuleGraph::new_with_entries(
2442            project.get_all_entries().to_resolved().await?,
2443            should_trace,
2444            should_read_binding_usage,
2445        );
2446        let base_visited_modules = VisitedModules::from_graph(base_single_module_graph);
2447
2448        let base = ModuleGraph::from_single_graph(base_single_module_graph);
2449
2450        let turbopack_remove_unused_imports = *project
2451            .next_config()
2452            .turbopack_remove_unused_imports(next_mode)
2453            .await?;
2454
2455        let base = if turbopack_remove_unused_imports {
2456            // TODO suboptimal that we do compute_binding_usage_info twice (once for the base
2457            // graph and later for the full graph)
2458            let binding_usage_info = compute_binding_usage_info(base, true);
2459            ModuleGraph::from_single_graph_without_unused_references(
2460                base_single_module_graph,
2461                binding_usage_info,
2462            )
2463        } else {
2464            base
2465        };
2466
2467        let additional_entries = project
2468            .get_all_additional_entries(base.connect())
2469            .to_resolved()
2470            .await?;
2471
2472        let additional_module_graph = SingleModuleGraph::new_with_entries_visited(
2473            additional_entries,
2474            base_visited_modules,
2475            should_trace,
2476            should_read_binding_usage,
2477        );
2478
2479        if !span.is_disabled() {
2480            let base_module_count = base_single_module_graph
2481                .connect()
2482                .module_count()
2483                .untracked()
2484                .owned()
2485                .await?;
2486            let additional_module_count = additional_module_graph
2487                .connect()
2488                .module_count()
2489                .untracked()
2490                .owned()
2491                .await?;
2492            span.record("modules", base_module_count + additional_module_count);
2493        }
2494
2495        let graphs = vec![base_single_module_graph, additional_module_graph];
2496
2497        let (full, binding_usage_info) = if turbopack_remove_unused_imports {
2498            let full_with_unused_references = ModuleGraph::from_graphs(graphs.clone());
2499            let binding_usage_info = compute_binding_usage_info(full_with_unused_references, true);
2500            (
2501                ModuleGraph::from_graphs_without_unused_references(graphs, binding_usage_info),
2502                Some(binding_usage_info),
2503            )
2504        } else {
2505            (ModuleGraph::from_graphs(graphs), None)
2506        };
2507
2508        Ok(BaseAndFullModuleGraph {
2509            base: base.connect().to_resolved().await?,
2510            full: full.connect().to_resolved().await?,
2511            binding_usage_info,
2512        }
2513        .cell())
2514    }
2515    .instrument(span_clone)
2516    .await
2517}
2518
2519#[turbo_tasks::value(shared)]
2520pub struct BaseAndFullModuleGraph {
2521    /// The base module graph generated from the entry points.
2522    pub base: ResolvedVc<ModuleGraph>,
2523    /// `full_with_unused_references` but with unused references removed.
2524    pub full: ResolvedVc<ModuleGraph>,
2525    /// Information about binding usage in the module graph.
2526    pub binding_usage_info: Option<OperationVc<BindingUsageInfo>>,
2527}
2528
2529#[turbo_tasks::function]
2530async fn any_output_changed(
2531    roots: Vc<OutputAssets>,
2532    path: FileSystemPath,
2533    server: bool,
2534) -> Result<Vc<Completion>> {
2535    let all_assets = expand_output_assets(
2536        roots
2537            .await?
2538            .into_iter()
2539            .map(|&a| ExpandOutputAssetsInput::Asset(a)),
2540        true,
2541    )
2542    .await?;
2543    let completions = all_assets
2544        .into_iter()
2545        .map(|m| {
2546            let path = path.clone();
2547
2548            async move {
2549                let asset_path = m.path().await?;
2550                if !asset_path.path.ends_with(".map")
2551                    && (!server || !asset_path.path.ends_with(".css"))
2552                    && asset_path.is_inside_ref(&path)
2553                {
2554                    anyhow::Ok(Some(
2555                        content_changed(*ResolvedVc::upcast(m))
2556                            .to_resolved()
2557                            .await?,
2558                    ))
2559                } else {
2560                    Ok(None)
2561                }
2562            }
2563        })
2564        .try_flat_join()
2565        .await?;
2566
2567    Ok(Vc::<Completions>::cell(completions).completed())
2568}
2569
2570#[turbo_tasks::function(operation)]
2571fn all_assets_from_entries_operation(
2572    operation: OperationVc<OutputAssets>,
2573) -> Result<Vc<ExpandedOutputAssets>> {
2574    let assets = operation.connect();
2575    Ok(all_assets_from_entries(assets))
2576}