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