next_api/
project.rs

1use std::time::Duration;
2
3use anyhow::{Context, Result, bail};
4use indexmap::map::Entry;
5use next_core::{
6    all_assets_from_entries,
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::{JsConfig, 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    util::{NextRuntime, OptionEnvMap, parse_config_from_source},
25};
26use serde::{Deserialize, Serialize};
27use tracing::Instrument;
28use turbo_rcstr::{RcStr, rcstr};
29use turbo_tasks::{
30    Completion, Completions, FxIndexMap, IntoTraitRef, NonLocalValue, OperationValue, OperationVc,
31    ReadRef, ResolvedVc, State, TaskInput, TransientInstance, TryFlatJoinIterExt, Vc,
32    debug::ValueDebugFormat,
33    fxindexmap,
34    graph::{AdjacencyMap, GraphTraversal},
35    mark_root,
36    trace::TraceRawVcs,
37};
38use turbo_tasks_env::{EnvMap, ProcessEnv};
39use turbo_tasks_fs::{DiskFileSystem, FileSystem, FileSystemPath, VirtualFileSystem, invalidation};
40use turbo_unix_path::{join_path, unix_to_sys};
41use turbopack::{
42    ModuleAssetContext, evaluate_context::node_build_environment,
43    global_module_ids::get_global_module_id_strategy, transition::TransitionOptions,
44};
45use turbopack_core::{
46    PROJECT_FILESYSTEM_NAME,
47    changed::content_changed,
48    chunk::{
49        ChunkingContext, EvaluatableAssets, SourceMapsType,
50        module_id_strategies::{DevModuleIdStrategy, ModuleIdStrategy},
51    },
52    compile_time_info::CompileTimeInfo,
53    context::AssetContext,
54    diagnostics::DiagnosticExt,
55    environment::NodeJsVersion,
56    file_source::FileSource,
57    ident::Layer,
58    issue::{
59        Issue, IssueDescriptionExt, IssueExt, IssueSeverity, IssueStage, OptionStyledString,
60        StyledString,
61    },
62    module::Module,
63    module_graph::{
64        GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
65        chunk_group_info::ChunkGroupEntry,
66        export_usage::{OptionExportUsageInfo, compute_export_usage_info},
67    },
68    output::{OutputAsset, OutputAssets},
69    reference_type::{EntryReferenceSubType, ReferenceType},
70    resolve::{FindContextFileResult, find_context_file},
71    source_map::OptionStringifiedSourceMap,
72    version::{
73        NotFoundVersion, OptionVersionedContent, Update, Version, VersionState, VersionedContent,
74    },
75};
76use turbopack_node::execution_context::ExecutionContext;
77use turbopack_nodejs::NodeJsChunkingContext;
78
79use crate::{
80    app::{AppProject, OptionAppProject},
81    empty::EmptyEndpoint,
82    entrypoints::Entrypoints,
83    instrumentation::InstrumentationEndpoint,
84    middleware::MiddlewareEndpoint,
85    pages::PagesProject,
86    route::{AppPageRoute, Endpoint, Endpoints, Route},
87    versioned_content_map::VersionedContentMap,
88};
89
90#[derive(
91    Debug,
92    Serialize,
93    Deserialize,
94    Clone,
95    TaskInput,
96    PartialEq,
97    Eq,
98    Hash,
99    TraceRawVcs,
100    NonLocalValue,
101    OperationValue,
102)]
103#[serde(rename_all = "camelCase")]
104pub struct DraftModeOptions {
105    pub preview_mode_id: RcStr,
106    pub preview_mode_encryption_key: RcStr,
107    pub preview_mode_signing_key: RcStr,
108}
109
110#[derive(
111    Debug,
112    Default,
113    Serialize,
114    Deserialize,
115    Copy,
116    Clone,
117    TaskInput,
118    PartialEq,
119    Eq,
120    Hash,
121    TraceRawVcs,
122    NonLocalValue,
123    OperationValue,
124)]
125#[serde(rename_all = "camelCase")]
126pub struct WatchOptions {
127    /// Whether to watch the filesystem for file changes.
128    pub enable: bool,
129
130    /// Enable polling at a certain interval if the native file watching doesn't work (e.g.
131    /// docker).
132    pub poll_interval: Option<Duration>,
133}
134
135#[derive(
136    Debug,
137    Serialize,
138    Deserialize,
139    Clone,
140    TaskInput,
141    PartialEq,
142    Eq,
143    Hash,
144    TraceRawVcs,
145    NonLocalValue,
146    OperationValue,
147)]
148#[serde(rename_all = "camelCase")]
149pub struct ProjectOptions {
150    /// An absolute root path (Unix or Windows path) from which all files must be nested under.
151    /// Trying to access a file outside this root will fail, so think of this as a chroot.
152    /// E.g. `/home/user/projects/my-repo`.
153    pub root_path: RcStr,
154
155    /// A path which contains the app/pages directories, relative to [`Project::root_path`], always
156    /// Unix path. E.g. `apps/my-app`
157    pub project_path: RcStr,
158
159    /// The contents of next.config.js, serialized to JSON.
160    pub next_config: RcStr,
161
162    /// The contents of ts/config read by load-jsconfig, serialized to JSON.
163    pub js_config: RcStr,
164
165    /// A map of environment variables to use when compiling code.
166    pub env: Vec<(RcStr, RcStr)>,
167
168    /// A map of environment variables which should get injected at compile
169    /// time.
170    pub define_env: DefineEnv,
171
172    /// Filesystem watcher options.
173    pub watch: WatchOptions,
174
175    /// The mode in which Next.js is running.
176    pub dev: bool,
177
178    /// The server actions encryption key.
179    pub encryption_key: RcStr,
180
181    /// The build id.
182    pub build_id: RcStr,
183
184    /// Options for draft mode.
185    pub preview_props: DraftModeOptions,
186
187    /// The browserslist query to use for targeting browsers.
188    pub browserslist_query: RcStr,
189
190    /// When the code is minified, this opts out of the default mangling of
191    /// local names for variables, functions etc., which can be useful for
192    /// debugging/profiling purposes.
193    pub no_mangling: bool,
194
195    /// The version of Node.js that is available/currently running.
196    pub current_node_js_version: RcStr,
197}
198
199#[derive(
200    Debug, Serialize, Deserialize, Clone, TaskInput, PartialEq, Eq, Hash, TraceRawVcs, NonLocalValue,
201)]
202#[serde(rename_all = "camelCase")]
203pub struct PartialProjectOptions {
204    /// A root path from which all files must be nested under. Trying to access
205    /// a file outside this root will fail. Think of this as a chroot.
206    pub root_path: Option<RcStr>,
207
208    /// A path inside the root_path which contains the app/pages directories.
209    pub project_path: Option<RcStr>,
210
211    /// The contents of next.config.js, serialized to JSON.
212    pub next_config: Option<RcStr>,
213
214    /// The contents of ts/config read by load-jsconfig, serialized to JSON.
215    pub js_config: Option<RcStr>,
216
217    /// A map of environment variables to use when compiling code.
218    pub env: Option<Vec<(RcStr, RcStr)>>,
219
220    /// A map of environment variables which should get injected at compile
221    /// time.
222    pub define_env: Option<DefineEnv>,
223
224    /// Filesystem watcher options.
225    pub watch: Option<WatchOptions>,
226
227    /// The mode in which Next.js is running.
228    pub dev: Option<bool>,
229
230    /// The server actions encryption key.
231    pub encryption_key: Option<RcStr>,
232
233    /// The build id.
234    pub build_id: Option<RcStr>,
235
236    /// Options for draft mode.
237    pub preview_props: Option<DraftModeOptions>,
238}
239
240#[derive(
241    Debug,
242    Serialize,
243    Deserialize,
244    Clone,
245    TaskInput,
246    PartialEq,
247    Eq,
248    Hash,
249    TraceRawVcs,
250    NonLocalValue,
251    OperationValue,
252)]
253#[serde(rename_all = "camelCase")]
254pub struct DefineEnv {
255    pub client: Vec<(RcStr, Option<RcStr>)>,
256    pub edge: Vec<(RcStr, Option<RcStr>)>,
257    pub nodejs: Vec<(RcStr, Option<RcStr>)>,
258}
259
260#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
261pub struct Middleware {
262    pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
263}
264
265#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
266pub struct Instrumentation {
267    pub node_js: ResolvedVc<Box<dyn Endpoint>>,
268    pub edge: ResolvedVc<Box<dyn Endpoint>>,
269}
270
271#[turbo_tasks::value]
272pub struct ProjectContainer {
273    name: RcStr,
274    options_state: State<Option<ProjectOptions>>,
275    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
276}
277
278#[turbo_tasks::value_impl]
279impl ProjectContainer {
280    #[turbo_tasks::function]
281    pub fn new(name: RcStr, dev: bool) -> Result<Vc<Self>> {
282        Ok(ProjectContainer {
283            name,
284            // we only need to enable versioning in dev mode, since build
285            // is assumed to be operating over a static snapshot
286            versioned_content_map: if dev {
287                Some(VersionedContentMap::new())
288            } else {
289                None
290            },
291            options_state: State::new(None),
292        }
293        .cell())
294    }
295}
296
297#[turbo_tasks::function(operation)]
298fn project_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
299    project.project_fs()
300}
301
302#[turbo_tasks::function(operation)]
303fn output_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
304    project.project_fs()
305}
306
307impl ProjectContainer {
308    #[tracing::instrument(level = "info", name = "initialize project", skip_all)]
309    pub async fn initialize(self: ResolvedVc<Self>, options: ProjectOptions) -> Result<()> {
310        let watch = options.watch;
311
312        self.await?.options_state.set(Some(options));
313
314        let project = self.project().to_resolved().await?;
315        let project_fs = project_fs_operation(project)
316            .read_strongly_consistent()
317            .await?;
318        if watch.enable {
319            project_fs
320                .start_watching_with_invalidation_reason(watch.poll_interval)
321                .await?;
322        } else {
323            project_fs.invalidate_with_reason(|path| invalidation::Initialize {
324                // this path is just used for display purposes
325                path: RcStr::from(path.to_string_lossy()),
326            });
327        }
328        let output_fs = output_fs_operation(project)
329            .read_strongly_consistent()
330            .await?;
331        output_fs.invalidate_with_reason(|path| invalidation::Initialize {
332            path: RcStr::from(path.to_string_lossy()),
333        });
334        Ok(())
335    }
336
337    #[tracing::instrument(level = "info", name = "update project", skip_all)]
338    pub async fn update(self: Vc<Self>, options: PartialProjectOptions) -> Result<()> {
339        let PartialProjectOptions {
340            root_path,
341            project_path,
342            next_config,
343            js_config,
344            env,
345            define_env,
346            watch,
347            dev,
348            encryption_key,
349            build_id,
350            preview_props,
351        } = options;
352
353        let this = self.await?;
354
355        let mut new_options = this
356            .options_state
357            .get()
358            .clone()
359            .context("ProjectContainer need to be initialized with initialize()")?;
360
361        if let Some(root_path) = root_path {
362            new_options.root_path = root_path;
363        }
364        if let Some(project_path) = project_path {
365            new_options.project_path = project_path;
366        }
367        if let Some(next_config) = next_config {
368            new_options.next_config = next_config;
369        }
370        if let Some(js_config) = js_config {
371            new_options.js_config = js_config;
372        }
373        if let Some(env) = env {
374            new_options.env = env;
375        }
376        if let Some(define_env) = define_env {
377            new_options.define_env = define_env;
378        }
379        if let Some(watch) = watch {
380            new_options.watch = watch;
381        }
382        if let Some(dev) = dev {
383            new_options.dev = dev;
384        }
385        if let Some(encryption_key) = encryption_key {
386            new_options.encryption_key = encryption_key;
387        }
388        if let Some(build_id) = build_id {
389            new_options.build_id = build_id;
390        }
391        if let Some(preview_props) = preview_props {
392            new_options.preview_props = preview_props;
393        }
394
395        // TODO: Handle mode switch, should prevent mode being switched.
396        let watch = new_options.watch;
397
398        let project = self.project().to_resolved().await?;
399        let prev_project_fs = project_fs_operation(project)
400            .read_strongly_consistent()
401            .await?;
402        let prev_output_fs = output_fs_operation(project)
403            .read_strongly_consistent()
404            .await?;
405
406        this.options_state.set(Some(new_options));
407        let project = self.project().to_resolved().await?;
408        let project_fs = project_fs_operation(project)
409            .read_strongly_consistent()
410            .await?;
411        let output_fs = output_fs_operation(project)
412            .read_strongly_consistent()
413            .await?;
414
415        if !ReadRef::ptr_eq(&prev_project_fs, &project_fs) {
416            if watch.enable {
417                // TODO stop watching: prev_project_fs.stop_watching()?;
418                project_fs
419                    .start_watching_with_invalidation_reason(watch.poll_interval)
420                    .await?;
421            } else {
422                project_fs.invalidate_with_reason(|path| invalidation::Initialize {
423                    // this path is just used for display purposes
424                    path: RcStr::from(path.to_string_lossy()),
425                });
426            }
427        }
428        if !ReadRef::ptr_eq(&prev_output_fs, &output_fs) {
429            prev_output_fs.invalidate_with_reason(|path| invalidation::Initialize {
430                path: RcStr::from(path.to_string_lossy()),
431            });
432        }
433
434        Ok(())
435    }
436}
437
438#[turbo_tasks::value_impl]
439impl ProjectContainer {
440    #[turbo_tasks::function]
441    pub async fn project(&self) -> Result<Vc<Project>> {
442        let env_map: Vc<EnvMap>;
443        let next_config;
444        let define_env;
445        let js_config;
446        let root_path;
447        let project_path;
448        let watch;
449        let dev;
450        let encryption_key;
451        let build_id;
452        let preview_props;
453        let browserslist_query;
454        let no_mangling;
455        let current_node_js_version;
456        {
457            let options = self.options_state.get();
458            let options = options
459                .as_ref()
460                .context("ProjectContainer need to be initialized with initialize()")?;
461            env_map = Vc::cell(options.env.iter().cloned().collect());
462            define_env = ProjectDefineEnv {
463                client: ResolvedVc::cell(options.define_env.client.iter().cloned().collect()),
464                edge: ResolvedVc::cell(options.define_env.edge.iter().cloned().collect()),
465                nodejs: ResolvedVc::cell(options.define_env.nodejs.iter().cloned().collect()),
466            }
467            .cell();
468            next_config = NextConfig::from_string(Vc::cell(options.next_config.clone()));
469            js_config = JsConfig::from_string(Vc::cell(options.js_config.clone()));
470            root_path = options.root_path.clone();
471            project_path = options.project_path.clone();
472            watch = options.watch;
473            dev = options.dev;
474            encryption_key = options.encryption_key.clone();
475            build_id = options.build_id.clone();
476            preview_props = options.preview_props.clone();
477            browserslist_query = options.browserslist_query.clone();
478            no_mangling = options.no_mangling;
479            current_node_js_version = options.current_node_js_version.clone();
480        }
481
482        let dist_dir = next_config
483            .await?
484            .dist_dir
485            .as_ref()
486            .map_or_else(|| rcstr!(".next"), |d| d.clone());
487
488        Ok(Project {
489            root_path,
490            project_path,
491            watch,
492            next_config: next_config.to_resolved().await?,
493            js_config: js_config.to_resolved().await?,
494            dist_dir,
495            env: ResolvedVc::upcast(env_map.to_resolved().await?),
496            define_env: define_env.to_resolved().await?,
497            browserslist_query,
498            mode: if dev {
499                NextMode::Development.resolved_cell()
500            } else {
501                NextMode::Build.resolved_cell()
502            },
503            versioned_content_map: self.versioned_content_map,
504            build_id,
505            encryption_key,
506            preview_props,
507            no_mangling,
508            current_node_js_version,
509        }
510        .cell())
511    }
512
513    /// See [Project::entrypoints].
514    #[turbo_tasks::function]
515    pub fn entrypoints(self: Vc<Self>) -> Vc<Entrypoints> {
516        self.project().entrypoints()
517    }
518
519    /// See [Project::hmr_identifiers].
520    #[turbo_tasks::function]
521    pub fn hmr_identifiers(self: Vc<Self>) -> Vc<Vec<RcStr>> {
522        self.project().hmr_identifiers()
523    }
524
525    /// Gets a source map for a particular `file_path`. If `dev` mode is disabled, this will always
526    /// return [`OptionStringifiedSourceMap::none`].
527    #[turbo_tasks::function]
528    pub fn get_source_map(
529        &self,
530        file_path: FileSystemPath,
531        section: Option<RcStr>,
532    ) -> Vc<OptionStringifiedSourceMap> {
533        if let Some(map) = self.versioned_content_map {
534            map.get_source_map(file_path, section)
535        } else {
536            OptionStringifiedSourceMap::none()
537        }
538    }
539}
540
541#[turbo_tasks::value]
542pub struct Project {
543    /// An absolute root path (Windows or Unix path) from which all files must be nested under.
544    /// Trying to access a file outside this root will fail, so think of this as a chroot.
545    /// E.g. `/home/user/projects/my-repo`.
546    root_path: RcStr,
547
548    /// A path which contains the app/pages directories, relative to [`Project::root_path`], always
549    /// a Unix path.
550    /// E.g. `apps/my-app`
551    project_path: RcStr,
552
553    /// A path where to emit the build outputs, relative to [`Project::project_path`], always a
554    /// Unix path. Corresponds to next.config.js's `distDir`.
555    /// E.g. `.next`
556    dist_dir: RcStr,
557
558    /// Filesystem watcher options.
559    watch: WatchOptions,
560
561    /// Next config.
562    next_config: ResolvedVc<NextConfig>,
563
564    /// Js/Tsconfig read by load-jsconfig
565    js_config: ResolvedVc<JsConfig>,
566
567    /// A map of environment variables to use when compiling code.
568    env: ResolvedVc<Box<dyn ProcessEnv>>,
569
570    /// A map of environment variables which should get injected at compile
571    /// time.
572    define_env: ResolvedVc<ProjectDefineEnv>,
573
574    /// The browserslist query to use for targeting browsers.
575    browserslist_query: RcStr,
576
577    mode: ResolvedVc<NextMode>,
578
579    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
580
581    build_id: RcStr,
582
583    encryption_key: RcStr,
584
585    preview_props: DraftModeOptions,
586
587    /// When the code is minified, this opts out of the default mangling of
588    /// local names for variables, functions etc., which can be useful for
589    /// debugging/profiling purposes.
590    no_mangling: bool,
591
592    current_node_js_version: RcStr,
593}
594
595#[turbo_tasks::value]
596pub struct ProjectDefineEnv {
597    client: ResolvedVc<OptionEnvMap>,
598    edge: ResolvedVc<OptionEnvMap>,
599    nodejs: ResolvedVc<OptionEnvMap>,
600}
601
602#[turbo_tasks::value_impl]
603impl ProjectDefineEnv {
604    #[turbo_tasks::function]
605    pub fn client(&self) -> Vc<OptionEnvMap> {
606        *self.client
607    }
608
609    #[turbo_tasks::function]
610    pub fn edge(&self) -> Vc<OptionEnvMap> {
611        *self.edge
612    }
613
614    #[turbo_tasks::function]
615    pub fn nodejs(&self) -> Vc<OptionEnvMap> {
616        *self.nodejs
617    }
618}
619
620#[turbo_tasks::value(shared)]
621struct ConflictIssue {
622    path: FileSystemPath,
623    title: ResolvedVc<StyledString>,
624    description: ResolvedVc<StyledString>,
625    severity: IssueSeverity,
626}
627
628#[turbo_tasks::value_impl]
629impl Issue for ConflictIssue {
630    #[turbo_tasks::function]
631    fn stage(&self) -> Vc<IssueStage> {
632        IssueStage::AppStructure.cell()
633    }
634
635    fn severity(&self) -> IssueSeverity {
636        self.severity
637    }
638
639    #[turbo_tasks::function]
640    fn file_path(&self) -> Vc<FileSystemPath> {
641        self.path.clone().cell()
642    }
643
644    #[turbo_tasks::function]
645    fn title(&self) -> Vc<StyledString> {
646        *self.title
647    }
648
649    #[turbo_tasks::function]
650    fn description(&self) -> Vc<OptionStyledString> {
651        Vc::cell(Some(self.description))
652    }
653}
654
655#[turbo_tasks::value_impl]
656impl Project {
657    #[turbo_tasks::function]
658    pub async fn app_project(self: Vc<Self>) -> Result<Vc<OptionAppProject>> {
659        let app_dir = find_app_dir(self.project_path().owned().await?).await?;
660
661        Ok(match &*app_dir {
662            Some(app_dir) => Vc::cell(Some(
663                AppProject::new(self, app_dir.clone()).to_resolved().await?,
664            )),
665            None => Vc::cell(None),
666        })
667    }
668
669    #[turbo_tasks::function]
670    pub fn pages_project(self: Vc<Self>) -> Vc<PagesProject> {
671        PagesProject::new(self)
672    }
673
674    #[turbo_tasks::function]
675    pub fn project_fs(&self) -> Vc<DiskFileSystem> {
676        DiskFileSystem::new(PROJECT_FILESYSTEM_NAME.into(), self.root_path.clone())
677    }
678
679    #[turbo_tasks::function]
680    pub fn client_fs(self: Vc<Self>) -> Vc<Box<dyn FileSystem>> {
681        let virtual_fs = VirtualFileSystem::new_with_name(rcstr!("client-fs"));
682        Vc::upcast(virtual_fs)
683    }
684
685    #[turbo_tasks::function]
686    pub fn output_fs(&self) -> Vc<DiskFileSystem> {
687        DiskFileSystem::new(rcstr!("output"), self.root_path.clone())
688    }
689
690    #[turbo_tasks::function]
691    pub fn dist_dir_absolute(&self) -> Result<Vc<RcStr>> {
692        Ok(Vc::cell(
693            format!(
694                "{}{}{}",
695                self.root_path,
696                std::path::MAIN_SEPARATOR,
697                unix_to_sys(
698                    &join_path(&self.project_path, &self.dist_dir)
699                        .context("expected project_path to be inside of root_path")?
700                )
701            )
702            .into(),
703        ))
704    }
705
706    #[turbo_tasks::function]
707    pub async fn node_root(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
708        let this = self.await?;
709        Ok(self
710            .output_fs()
711            .root()
712            .await?
713            .join(&this.project_path)?
714            .join(&this.dist_dir)?
715            .cell())
716    }
717
718    #[turbo_tasks::function]
719    pub fn client_root(self: Vc<Self>) -> Vc<FileSystemPath> {
720        self.client_fs().root()
721    }
722
723    #[turbo_tasks::function]
724    pub fn project_root_path(self: Vc<Self>) -> Vc<FileSystemPath> {
725        self.project_fs().root()
726    }
727
728    #[turbo_tasks::function]
729    pub async fn client_relative_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
730        let next_config = self.next_config().await?;
731        Ok(self
732            .client_root()
733            .await?
734            .join(&format!(
735                "{}/_next",
736                next_config.base_path.clone().unwrap_or_default(),
737            ))?
738            .cell())
739    }
740
741    /// Returns the relative path from the node root to the output root.
742    /// E.g. from `[project]/test/e2e/app-dir/non-root-project-monorepo/apps/web/app/
743    /// import-meta-url-ssr/page.tsx` to `[project]/`.
744    #[turbo_tasks::function]
745    pub async fn node_root_to_root_path(self: Vc<Self>) -> Result<Vc<RcStr>> {
746        Ok(Vc::cell(
747            self.node_root()
748                .await?
749                .get_relative_path_to(&*self.output_fs().root().await?)
750                .context("Expected node root to be inside of output fs")?,
751        ))
752    }
753
754    #[turbo_tasks::function]
755    pub async fn project_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
756        let this = self.await?;
757        let root = self.project_root_path().await?;
758        Ok(root.join(&this.project_path)?.cell())
759    }
760
761    #[turbo_tasks::function]
762    pub(super) fn env(&self) -> Vc<Box<dyn ProcessEnv>> {
763        *self.env
764    }
765
766    #[turbo_tasks::function]
767    pub(super) fn current_node_js_version(&self) -> Vc<NodeJsVersion> {
768        NodeJsVersion::Static(ResolvedVc::cell(self.current_node_js_version.clone())).cell()
769    }
770
771    #[turbo_tasks::function]
772    pub(super) fn next_config(&self) -> Vc<NextConfig> {
773        *self.next_config
774    }
775
776    #[turbo_tasks::function]
777    pub(super) fn next_mode(&self) -> Vc<NextMode> {
778        *self.mode
779    }
780
781    #[turbo_tasks::function]
782    pub(super) fn is_watch_enabled(&self) -> Result<Vc<bool>> {
783        Ok(Vc::cell(self.watch.enable))
784    }
785
786    #[turbo_tasks::function]
787    pub(super) async fn per_page_module_graph(&self) -> Result<Vc<bool>> {
788        Ok(Vc::cell(*self.mode.await? == NextMode::Development))
789    }
790
791    #[turbo_tasks::function]
792    pub(super) fn js_config(&self) -> Vc<JsConfig> {
793        *self.js_config
794    }
795
796    #[turbo_tasks::function]
797    pub(super) fn encryption_key(&self) -> Vc<RcStr> {
798        Vc::cell(self.encryption_key.clone())
799    }
800
801    #[turbo_tasks::function]
802    pub(super) fn no_mangling(&self) -> Vc<bool> {
803        Vc::cell(self.no_mangling)
804    }
805
806    #[turbo_tasks::function]
807    pub(super) async fn should_create_webpack_stats(&self) -> Result<Vc<bool>> {
808        Ok(Vc::cell(
809            self.env.read(rcstr!("TURBOPACK_STATS")).await?.is_some(),
810        ))
811    }
812
813    #[turbo_tasks::function]
814    pub(super) async fn execution_context(self: Vc<Self>) -> Result<Vc<ExecutionContext>> {
815        let node_root = self.node_root().owned().await?;
816        let next_mode = self.next_mode().await?;
817
818        let node_execution_chunking_context = Vc::upcast(
819            NodeJsChunkingContext::builder(
820                self.project_root_path().owned().await?,
821                node_root.clone(),
822                self.node_root_to_root_path().owned().await?,
823                node_root.clone(),
824                node_root.join("build/chunks")?,
825                node_root.join("build/assets")?,
826                node_build_environment().to_resolved().await?,
827                next_mode.runtime_type(),
828            )
829            .source_maps(if *self.next_config().server_source_maps().await? {
830                SourceMapsType::Full
831            } else {
832                SourceMapsType::None
833            })
834            .build(),
835        );
836
837        Ok(ExecutionContext::new(
838            self.project_path().owned().await?,
839            node_execution_chunking_context,
840            self.env(),
841        ))
842    }
843
844    #[turbo_tasks::function]
845    pub(super) fn client_compile_time_info(&self) -> Vc<CompileTimeInfo> {
846        get_client_compile_time_info(self.browserslist_query.clone(), self.define_env.client())
847    }
848
849    #[turbo_tasks::function]
850    pub async fn get_all_endpoints(self: Vc<Self>, app_dir_only: bool) -> Result<Vc<Endpoints>> {
851        let mut endpoints = Vec::new();
852
853        let entrypoints = self.entrypoints().await?;
854
855        // Always include these basic pages endpoints regardless of `app_dir_only`. The user's
856        // page routes themselves are excluded below.
857        endpoints.push(entrypoints.pages_error_endpoint);
858        endpoints.push(entrypoints.pages_app_endpoint);
859        endpoints.push(entrypoints.pages_document_endpoint);
860
861        if let Some(middleware) = &entrypoints.middleware {
862            endpoints.push(middleware.endpoint);
863        }
864
865        if let Some(instrumentation) = &entrypoints.instrumentation {
866            endpoints.push(instrumentation.node_js);
867            endpoints.push(instrumentation.edge);
868        }
869
870        for (_, route) in entrypoints.routes.iter() {
871            match route {
872                Route::Page {
873                    html_endpoint,
874                    data_endpoint: _,
875                } => {
876                    if !app_dir_only {
877                        endpoints.push(*html_endpoint);
878                    }
879                }
880                Route::PageApi { endpoint } => {
881                    if !app_dir_only {
882                        endpoints.push(*endpoint);
883                    }
884                }
885                Route::AppPage(page_routes) => {
886                    for AppPageRoute {
887                        original_name: _,
888                        html_endpoint,
889                        rsc_endpoint: _,
890                    } in page_routes
891                    {
892                        endpoints.push(*html_endpoint);
893                    }
894                }
895                Route::AppRoute {
896                    original_name: _,
897                    endpoint,
898                } => {
899                    endpoints.push(*endpoint);
900                }
901                Route::Conflict => {
902                    tracing::info!("WARN: conflict");
903                }
904            }
905        }
906
907        Ok(Vc::cell(endpoints))
908    }
909
910    #[turbo_tasks::function]
911    pub async fn get_all_entries(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
912        let mut modules = self
913            .get_all_endpoints(false)
914            .await?
915            .iter()
916            .map(async |endpoint| Ok(endpoint.entries().owned().await?))
917            .try_flat_join()
918            .await?;
919        modules.extend(self.client_main_modules().await?.iter().cloned());
920        Ok(Vc::cell(modules))
921    }
922
923    #[turbo_tasks::function]
924    pub async fn get_all_additional_entries(
925        self: Vc<Self>,
926        graphs: Vc<ModuleGraph>,
927    ) -> Result<Vc<GraphEntries>> {
928        let modules = self
929            .get_all_endpoints(false)
930            .await?
931            .iter()
932            .map(async |endpoint| Ok(endpoint.additional_entries(graphs).owned().await?))
933            .try_flat_join()
934            .await?;
935        Ok(Vc::cell(modules))
936    }
937
938    #[turbo_tasks::function]
939    pub async fn module_graph(
940        self: Vc<Self>,
941        entry: ResolvedVc<Box<dyn Module>>,
942    ) -> Result<Vc<ModuleGraph>> {
943        Ok(if *self.per_page_module_graph().await? {
944            ModuleGraph::from_entry_module(*entry, self.next_mode().await?.is_production())
945        } else {
946            *self.whole_app_module_graphs().await?.full
947        })
948    }
949
950    #[turbo_tasks::function]
951    pub async fn module_graph_for_modules(
952        self: Vc<Self>,
953        evaluatable_assets: Vc<EvaluatableAssets>,
954    ) -> Result<Vc<ModuleGraph>> {
955        Ok(if *self.per_page_module_graph().await? {
956            let entries = evaluatable_assets
957                .await?
958                .iter()
959                .copied()
960                .map(ResolvedVc::upcast)
961                .collect();
962            ModuleGraph::from_modules(
963                Vc::cell(vec![ChunkGroupEntry::Entry(entries)]),
964                self.next_mode().await?.is_production(),
965            )
966        } else {
967            *self.whole_app_module_graphs().await?.full
968        })
969    }
970
971    #[turbo_tasks::function]
972    pub async fn whole_app_module_graphs(self: ResolvedVc<Self>) -> Result<Vc<ModuleGraphs>> {
973        async move {
974            let module_graphs_op = whole_app_module_graph_operation(self);
975            let module_graphs_vc = module_graphs_op.resolve_strongly_consistent().await?;
976            let _ = module_graphs_op.take_issues_with_path().await?;
977
978            // At this point all modules have been computed and we can get rid of the node.js
979            // process pools
980            if *self.is_watch_enabled().await? {
981                turbopack_node::evaluate::scale_down();
982            } else {
983                turbopack_node::evaluate::scale_zero();
984            }
985
986            Ok(*module_graphs_vc)
987        }
988        .instrument(tracing::info_span!("module graph for app"))
989        .await
990    }
991
992    #[turbo_tasks::function]
993    pub(super) async fn server_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
994        let this = self.await?;
995        Ok(get_server_compile_time_info(
996            // `/ROOT` corresponds to `[project]/`, so we need exactly the `path` part.
997            format!("/ROOT/{}", self.project_path().await?.path).into(),
998            this.define_env.nodejs(),
999            self.current_node_js_version(),
1000        ))
1001    }
1002
1003    #[turbo_tasks::function]
1004    pub(super) async fn edge_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1005        let this = self.await?;
1006        Ok(get_edge_compile_time_info(
1007            self.project_path().owned().await?,
1008            this.define_env.edge(),
1009            self.current_node_js_version(),
1010        ))
1011    }
1012
1013    #[turbo_tasks::function]
1014    pub(super) fn edge_env(&self) -> Vc<EnvMap> {
1015        let edge_env = fxindexmap! {
1016            rcstr!("__NEXT_BUILD_ID") => self.build_id.clone(),
1017            rcstr!("NEXT_SERVER_ACTIONS_ENCRYPTION_KEY") => self.encryption_key.clone(),
1018            rcstr!("__NEXT_PREVIEW_MODE_ID") => self.preview_props.preview_mode_id.clone(),
1019            rcstr!("__NEXT_PREVIEW_MODE_ENCRYPTION_KEY") => self.preview_props.preview_mode_encryption_key.clone(),
1020            rcstr!("__NEXT_PREVIEW_MODE_SIGNING_KEY") => self.preview_props.preview_mode_signing_key.clone(),
1021        };
1022        Vc::cell(edge_env)
1023    }
1024
1025    #[turbo_tasks::function]
1026    pub(super) async fn client_chunking_context(
1027        self: Vc<Self>,
1028    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1029        Ok(get_client_chunking_context(ClientChunkingContextOptions {
1030            mode: self.next_mode(),
1031            root_path: self.project_root_path().owned().await?,
1032            client_root: self.client_relative_path().owned().await?,
1033            client_root_to_root_path: rcstr!("/ROOT"),
1034            asset_prefix: self.next_config().computed_asset_prefix(),
1035            chunk_suffix_path: self.next_config().chunk_suffix_path(),
1036            environment: self.client_compile_time_info().environment(),
1037            module_id_strategy: self.module_ids(),
1038            export_usage: self.export_usage(),
1039            minify: self.next_config().turbo_minify(self.next_mode()),
1040            source_maps: self.next_config().client_source_maps(self.next_mode()),
1041            no_mangling: self.no_mangling(),
1042            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1043        }))
1044    }
1045
1046    #[turbo_tasks::function]
1047    pub(super) async fn server_chunking_context(
1048        self: Vc<Self>,
1049        client_assets: bool,
1050    ) -> Result<Vc<NodeJsChunkingContext>> {
1051        let options = ServerChunkingContextOptions {
1052            mode: self.next_mode(),
1053            root_path: self.project_root_path().owned().await?,
1054            node_root: self.node_root().owned().await?,
1055            node_root_to_root_path: self.node_root_to_root_path().owned().await?,
1056            environment: self.server_compile_time_info().environment(),
1057            module_id_strategy: self.module_ids(),
1058            export_usage: self.export_usage(),
1059            turbo_minify: self.next_config().turbo_minify(self.next_mode()),
1060            turbo_source_maps: self.next_config().server_source_maps(),
1061            no_mangling: self.no_mangling(),
1062            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1063        };
1064        Ok(if client_assets {
1065            get_server_chunking_context_with_client_assets(
1066                options,
1067                self.client_relative_path().owned().await?,
1068                self.next_config().computed_asset_prefix().owned().await?,
1069            )
1070        } else {
1071            get_server_chunking_context(options)
1072        })
1073    }
1074
1075    #[turbo_tasks::function]
1076    pub(super) async fn edge_chunking_context(
1077        self: Vc<Self>,
1078        client_assets: bool,
1079    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1080        let options = EdgeChunkingContextOptions {
1081            mode: self.next_mode(),
1082            root_path: self.project_root_path().owned().await?,
1083            node_root: self.node_root().owned().await?,
1084            output_root_to_root_path: self.node_root_to_root_path(),
1085            environment: self.edge_compile_time_info().environment(),
1086            module_id_strategy: self.module_ids(),
1087            export_usage: self.export_usage(),
1088            turbo_minify: self.next_config().turbo_minify(self.next_mode()),
1089            turbo_source_maps: self.next_config().server_source_maps(),
1090            no_mangling: self.no_mangling(),
1091            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1092        };
1093        Ok(if client_assets {
1094            get_edge_chunking_context_with_client_assets(
1095                options,
1096                self.client_relative_path().owned().await?,
1097                self.next_config().computed_asset_prefix(),
1098            )
1099        } else {
1100            get_edge_chunking_context(options)
1101        })
1102    }
1103
1104    #[turbo_tasks::function]
1105    pub(super) fn runtime_chunking_context(
1106        self: Vc<Self>,
1107        client_assets: bool,
1108        runtime: NextRuntime,
1109    ) -> Vc<Box<dyn ChunkingContext>> {
1110        match runtime {
1111            NextRuntime::Edge => self.edge_chunking_context(client_assets),
1112            NextRuntime::NodeJs => Vc::upcast(self.server_chunking_context(client_assets)),
1113        }
1114    }
1115
1116    /// 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)
1117    /// to detect which feature is enabled.
1118    #[turbo_tasks::function]
1119    async fn collect_project_feature_telemetry(self: Vc<Self>) -> Result<Vc<()>> {
1120        let emit_event = |feature_name: &str, enabled: bool| {
1121            NextFeatureTelemetry::new(feature_name.into(), enabled)
1122                .resolved_cell()
1123                .emit();
1124        };
1125
1126        // First, emit an event for the binary target triple.
1127        // This is different to webpack-config; when this is being called,
1128        // it is always using SWC so we don't check swc here.
1129        emit_event(env!("VERGEN_CARGO_TARGET_TRIPLE"), true);
1130
1131        // Go over jsconfig and report enabled features.
1132        let compiler_options = self.js_config().compiler_options().await?;
1133        let compiler_options = compiler_options.as_object();
1134        let experimental_decorators_enabled = compiler_options
1135            .as_ref()
1136            .and_then(|compiler_options| compiler_options.get("experimentalDecorators"))
1137            .is_some();
1138        let jsx_import_source_enabled = compiler_options
1139            .as_ref()
1140            .and_then(|compiler_options| compiler_options.get("jsxImportSource"))
1141            .is_some();
1142
1143        emit_event("swcExperimentalDecorators", experimental_decorators_enabled);
1144        emit_event("swcImportSource", jsx_import_source_enabled);
1145
1146        // Go over config and report enabled features.
1147        // [TODO]: useSwcLoader is not being reported as it is not directly corresponds (it checks babel config existence)
1148        // need to confirm what we'll do with turbopack.
1149        let config = self.next_config();
1150
1151        emit_event(
1152            "skipMiddlewareUrlNormalize",
1153            *config.skip_middleware_url_normalize().await?,
1154        );
1155
1156        emit_event(
1157            "skipTrailingSlashRedirect",
1158            *config.skip_trailing_slash_redirect().await?,
1159        );
1160        emit_event(
1161            "persistentCaching",
1162            *config.persistent_caching_enabled().await?,
1163        );
1164
1165        let config = &config.await?;
1166
1167        emit_event("modularizeImports", config.modularize_imports.is_some());
1168        emit_event("transpilePackages", config.transpile_packages.is_some());
1169        emit_event("turbotrace", false);
1170
1171        // compiler options
1172        let compiler_options = config.compiler.as_ref();
1173        let swc_relay_enabled = compiler_options.and_then(|c| c.relay.as_ref()).is_some();
1174        let styled_components_enabled = compiler_options
1175            .and_then(|c| c.styled_components.as_ref().map(|sc| sc.is_enabled()))
1176            .unwrap_or_default();
1177        let react_remove_properties_enabled = compiler_options
1178            .and_then(|c| c.react_remove_properties.as_ref().map(|rc| rc.is_enabled()))
1179            .unwrap_or_default();
1180        let remove_console_enabled = compiler_options
1181            .and_then(|c| c.remove_console.as_ref().map(|rc| rc.is_enabled()))
1182            .unwrap_or_default();
1183        let emotion_enabled = compiler_options
1184            .and_then(|c| c.emotion.as_ref().map(|e| e.is_enabled()))
1185            .unwrap_or_default();
1186
1187        emit_event("swcRelay", swc_relay_enabled);
1188        emit_event("swcStyledComponents", styled_components_enabled);
1189        emit_event("swcReactRemoveProperties", react_remove_properties_enabled);
1190        emit_event("swcRemoveConsole", remove_console_enabled);
1191        emit_event("swcEmotion", emotion_enabled);
1192
1193        Ok(Default::default())
1194    }
1195
1196    /// Scans the app/pages directories for entry points files (matching the
1197    /// provided page_extensions).
1198    #[turbo_tasks::function]
1199    pub async fn entrypoints(self: Vc<Self>) -> Result<Vc<Entrypoints>> {
1200        self.collect_project_feature_telemetry().await?;
1201
1202        let mut routes = FxIndexMap::default();
1203        let app_project = self.app_project();
1204        let pages_project = self.pages_project();
1205
1206        if let Some(app_project) = &*app_project.await? {
1207            let app_routes = app_project.routes();
1208            routes.extend(
1209                app_routes
1210                    .await?
1211                    .iter()
1212                    .map(|(k, v)| (k.clone(), v.clone())),
1213            );
1214        }
1215
1216        for (pathname, page_route) in pages_project.routes().await?.iter() {
1217            match routes.entry(pathname.clone()) {
1218                Entry::Occupied(mut entry) => {
1219                    ConflictIssue {
1220                        path: self.project_path().owned().await?,
1221                        title: StyledString::Text(
1222                            format!("App Router and Pages Router both match path: {pathname}")
1223                                .into(),
1224                        )
1225                        .resolved_cell(),
1226                        description: StyledString::Text(
1227                            "Next.js does not support having both App Router and Pages Router \
1228                             routes matching the same path. Please remove one of the conflicting \
1229                             routes."
1230                                .into(),
1231                        )
1232                        .resolved_cell(),
1233                        severity: IssueSeverity::Error,
1234                    }
1235                    .resolved_cell()
1236                    .emit();
1237                    *entry.get_mut() = Route::Conflict;
1238                }
1239                Entry::Vacant(entry) => {
1240                    entry.insert(page_route.clone());
1241                }
1242            }
1243        }
1244
1245        let pages_document_endpoint = self
1246            .pages_project()
1247            .document_endpoint()
1248            .to_resolved()
1249            .await?;
1250        let pages_app_endpoint = self.pages_project().app_endpoint().to_resolved().await?;
1251        let pages_error_endpoint = self.pages_project().error_endpoint().to_resolved().await?;
1252
1253        let middleware = self.find_middleware();
1254        let middleware = if let FindContextFileResult::Found(..) = *middleware.await? {
1255            Some(Middleware {
1256                endpoint: self.middleware_endpoint().to_resolved().await?,
1257            })
1258        } else {
1259            None
1260        };
1261
1262        let instrumentation = self.find_instrumentation();
1263        let instrumentation = if let FindContextFileResult::Found(..) = *instrumentation.await? {
1264            Some(Instrumentation {
1265                node_js: self.instrumentation_endpoint(false).to_resolved().await?,
1266                edge: self.instrumentation_endpoint(true).to_resolved().await?,
1267            })
1268        } else {
1269            None
1270        };
1271
1272        Ok(Entrypoints {
1273            routes,
1274            middleware,
1275            instrumentation,
1276            pages_document_endpoint,
1277            pages_app_endpoint,
1278            pages_error_endpoint,
1279        }
1280        .cell())
1281    }
1282
1283    #[turbo_tasks::function]
1284    async fn edge_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1285        let mut transitions = vec![];
1286
1287        let app_dir = find_app_dir(self.project_path().owned().await?)
1288            .owned()
1289            .await?;
1290        let app_project = *self.app_project().await?;
1291
1292        let ecmascript_client_reference_transition_name =
1293            app_project.map(|_| AppProject::client_transition_name());
1294
1295        if let Some(app_project) = app_project {
1296            transitions.push((
1297                AppProject::client_transition_name(),
1298                app_project
1299                    .edge_ecmascript_client_reference_transition()
1300                    .to_resolved()
1301                    .await?,
1302            ));
1303        }
1304
1305        Ok(Vc::upcast(ModuleAssetContext::new(
1306            TransitionOptions {
1307                named_transitions: transitions.clone().into_iter().collect(),
1308                ..Default::default()
1309            }
1310            .cell(),
1311            self.edge_compile_time_info(),
1312            get_server_module_options_context(
1313                self.project_path().owned().await?,
1314                self.execution_context(),
1315                ServerContextType::Middleware {
1316                    app_dir: app_dir.clone(),
1317                    ecmascript_client_reference_transition_name:
1318                        ecmascript_client_reference_transition_name.clone(),
1319                },
1320                self.next_mode(),
1321                self.next_config(),
1322                NextRuntime::Edge,
1323                self.encryption_key(),
1324                self.edge_compile_time_info().environment(),
1325            ),
1326            get_edge_resolve_options_context(
1327                self.project_path().owned().await?,
1328                ServerContextType::Middleware {
1329                    app_dir: app_dir.clone(),
1330                    ecmascript_client_reference_transition_name:
1331                        ecmascript_client_reference_transition_name.clone(),
1332                },
1333                self.next_mode(),
1334                self.next_config(),
1335                self.execution_context(),
1336                None, // root params can't be used in middleware
1337            ),
1338            Layer::new_with_user_friendly_name(
1339                rcstr!("middleware-edge"),
1340                rcstr!("Edge Middleware"),
1341            ),
1342        )))
1343    }
1344
1345    #[turbo_tasks::function]
1346    async fn node_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1347        let mut transitions = vec![];
1348
1349        let app_dir = find_app_dir(self.project_path().owned().await?)
1350            .owned()
1351            .await?;
1352        let app_project = *self.app_project().await?;
1353
1354        let ecmascript_client_reference_transition_name =
1355            app_project.map(|_| AppProject::client_transition_name());
1356
1357        if let Some(app_project) = app_project {
1358            transitions.push((
1359                AppProject::client_transition_name(),
1360                app_project
1361                    .edge_ecmascript_client_reference_transition()
1362                    .to_resolved()
1363                    .await?,
1364            ));
1365        }
1366
1367        Ok(Vc::upcast(ModuleAssetContext::new(
1368            TransitionOptions {
1369                named_transitions: transitions.clone().into_iter().collect(),
1370                ..Default::default()
1371            }
1372            .cell(),
1373            self.server_compile_time_info(),
1374            get_server_module_options_context(
1375                self.project_path().owned().await?,
1376                self.execution_context(),
1377                ServerContextType::Middleware {
1378                    app_dir: app_dir.clone(),
1379                    ecmascript_client_reference_transition_name:
1380                        ecmascript_client_reference_transition_name.clone(),
1381                },
1382                self.next_mode(),
1383                self.next_config(),
1384                NextRuntime::NodeJs,
1385                self.encryption_key(),
1386                self.server_compile_time_info().environment(),
1387            ),
1388            get_server_resolve_options_context(
1389                self.project_path().owned().await?,
1390                ServerContextType::Middleware {
1391                    app_dir: app_dir.clone(),
1392                    ecmascript_client_reference_transition_name,
1393                },
1394                self.next_mode(),
1395                self.next_config(),
1396                self.execution_context(),
1397                None, // root params can't be used in middleware
1398            ),
1399            Layer::new_with_user_friendly_name(rcstr!("middleware"), rcstr!("Middleware")),
1400        )))
1401    }
1402
1403    #[turbo_tasks::function]
1404    async fn middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1405        let edge_module_context = self.edge_middleware_context();
1406
1407        let middleware = self.find_middleware();
1408        let FindContextFileResult::Found(fs_path, _) = &*middleware.await? else {
1409            return Ok(Vc::upcast(edge_module_context));
1410        };
1411        let source = Vc::upcast(FileSource::new(fs_path.clone()));
1412
1413        let module = edge_module_context
1414            .process(
1415                source,
1416                ReferenceType::Entry(EntryReferenceSubType::Middleware),
1417            )
1418            .module();
1419
1420        let config = parse_config_from_source(source, module, NextRuntime::Edge).await?;
1421
1422        if matches!(config.runtime, NextRuntime::NodeJs) {
1423            Ok(self.node_middleware_context())
1424        } else {
1425            Ok(edge_module_context)
1426        }
1427    }
1428
1429    #[turbo_tasks::function]
1430    async fn find_middleware(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
1431        Ok(find_context_file(
1432            self.project_path().owned().await?,
1433            middleware_files(self.next_config().page_extensions()),
1434        ))
1435    }
1436
1437    #[turbo_tasks::function]
1438    async fn middleware_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
1439        let middleware = self.find_middleware();
1440        let FindContextFileResult::Found(fs_path, _) = &*middleware.await? else {
1441            return Ok(Vc::upcast(EmptyEndpoint::new()));
1442        };
1443        let source = Vc::upcast(FileSource::new(fs_path.clone()));
1444        let app_dir = find_app_dir(self.project_path().owned().await?)
1445            .owned()
1446            .await?;
1447        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
1448            .as_ref()
1449            .map(|_| AppProject::client_transition_name());
1450
1451        let middleware_asset_context = self.middleware_context();
1452
1453        Ok(Vc::upcast(MiddlewareEndpoint::new(
1454            self,
1455            middleware_asset_context,
1456            source,
1457            app_dir.clone(),
1458            ecmascript_client_reference_transition_name,
1459        )))
1460    }
1461
1462    #[turbo_tasks::function]
1463    async fn node_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1464        let mut transitions = vec![];
1465
1466        let app_dir = find_app_dir(self.project_path().owned().await?)
1467            .owned()
1468            .await?;
1469        let app_project = &*self.app_project().await?;
1470
1471        let ecmascript_client_reference_transition_name = app_project
1472            .as_ref()
1473            .map(|_| AppProject::client_transition_name());
1474
1475        if let Some(app_project) = app_project {
1476            transitions.push((
1477                AppProject::client_transition_name(),
1478                app_project
1479                    .ecmascript_client_reference_transition()
1480                    .to_resolved()
1481                    .await?,
1482            ));
1483        }
1484
1485        Ok(Vc::upcast(ModuleAssetContext::new(
1486            TransitionOptions {
1487                named_transitions: transitions.into_iter().collect(),
1488                ..Default::default()
1489            }
1490            .cell(),
1491            self.server_compile_time_info(),
1492            get_server_module_options_context(
1493                self.project_path().owned().await?,
1494                self.execution_context(),
1495                ServerContextType::Instrumentation {
1496                    app_dir: app_dir.clone(),
1497                    ecmascript_client_reference_transition_name:
1498                        ecmascript_client_reference_transition_name.clone(),
1499                },
1500                self.next_mode(),
1501                self.next_config(),
1502                NextRuntime::NodeJs,
1503                self.encryption_key(),
1504                self.server_compile_time_info().environment(),
1505            ),
1506            get_server_resolve_options_context(
1507                self.project_path().owned().await?,
1508                ServerContextType::Instrumentation {
1509                    app_dir: app_dir.clone(),
1510                    ecmascript_client_reference_transition_name,
1511                },
1512                self.next_mode(),
1513                self.next_config(),
1514                self.execution_context(),
1515                None, // root params can't be used in instrumentation
1516            ),
1517            Layer::new_with_user_friendly_name(
1518                rcstr!("instrumentation"),
1519                rcstr!("Instrumentation"),
1520            ),
1521        )))
1522    }
1523
1524    #[turbo_tasks::function]
1525    async fn edge_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1526        let mut transitions = vec![];
1527
1528        let app_dir = find_app_dir(self.project_path().owned().await?)
1529            .owned()
1530            .await?;
1531        let app_project = &*self.app_project().await?;
1532
1533        let ecmascript_client_reference_transition_name = app_project
1534            .as_ref()
1535            .map(|_| AppProject::client_transition_name());
1536
1537        if let Some(app_project) = app_project {
1538            transitions.push((
1539                AppProject::client_transition_name(),
1540                app_project
1541                    .edge_ecmascript_client_reference_transition()
1542                    .to_resolved()
1543                    .await?,
1544            ));
1545        }
1546
1547        Ok(Vc::upcast(ModuleAssetContext::new(
1548            TransitionOptions {
1549                named_transitions: transitions.into_iter().collect(),
1550                ..Default::default()
1551            }
1552            .cell(),
1553            self.edge_compile_time_info(),
1554            get_server_module_options_context(
1555                self.project_path().owned().await?,
1556                self.execution_context(),
1557                ServerContextType::Instrumentation {
1558                    app_dir: app_dir.clone(),
1559                    ecmascript_client_reference_transition_name:
1560                        ecmascript_client_reference_transition_name.clone(),
1561                },
1562                self.next_mode(),
1563                self.next_config(),
1564                NextRuntime::Edge,
1565                self.encryption_key(),
1566                self.edge_compile_time_info().environment(),
1567            ),
1568            get_edge_resolve_options_context(
1569                self.project_path().owned().await?,
1570                ServerContextType::Instrumentation {
1571                    app_dir: app_dir.clone(),
1572                    ecmascript_client_reference_transition_name,
1573                },
1574                self.next_mode(),
1575                self.next_config(),
1576                self.execution_context(),
1577                None, // root params can't be used in instrumentation
1578            ),
1579            Layer::new_with_user_friendly_name(
1580                rcstr!("instrumentation-edge"),
1581                rcstr!("Edge Instrumentation"),
1582            ),
1583        )))
1584    }
1585
1586    #[turbo_tasks::function]
1587    async fn find_instrumentation(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
1588        Ok(find_context_file(
1589            self.project_path().owned().await?,
1590            instrumentation_files(self.next_config().page_extensions()),
1591        ))
1592    }
1593
1594    #[turbo_tasks::function]
1595    async fn instrumentation_endpoint(
1596        self: Vc<Self>,
1597        is_edge: bool,
1598    ) -> Result<Vc<Box<dyn Endpoint>>> {
1599        let instrumentation = self.find_instrumentation();
1600        let FindContextFileResult::Found(fs_path, _) = &*instrumentation.await? else {
1601            return Ok(Vc::upcast(EmptyEndpoint::new()));
1602        };
1603        let source = Vc::upcast(FileSource::new(fs_path.clone()));
1604        let app_dir = find_app_dir(self.project_path().owned().await?)
1605            .owned()
1606            .await?;
1607        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
1608            .as_ref()
1609            .map(|_| AppProject::client_transition_name());
1610
1611        let instrumentation_asset_context = if is_edge {
1612            self.edge_instrumentation_context()
1613        } else {
1614            self.node_instrumentation_context()
1615        };
1616
1617        Ok(Vc::upcast(InstrumentationEndpoint::new(
1618            self,
1619            instrumentation_asset_context,
1620            source,
1621            is_edge,
1622            app_dir.clone(),
1623            ecmascript_client_reference_transition_name,
1624        )))
1625    }
1626
1627    #[turbo_tasks::function]
1628    pub async fn emit_all_output_assets(
1629        self: Vc<Self>,
1630        output_assets: OperationVc<OutputAssets>,
1631    ) -> Result<()> {
1632        let span = tracing::info_span!("emitting");
1633        async move {
1634            let all_output_assets = all_assets_from_entries_operation(output_assets);
1635
1636            let client_relative_path = self.client_relative_path().owned().await?;
1637            let node_root = self.node_root().owned().await?;
1638
1639            if let Some(map) = self.await?.versioned_content_map {
1640                map.insert_output_assets(
1641                    all_output_assets,
1642                    node_root.clone(),
1643                    client_relative_path.clone(),
1644                    node_root.clone(),
1645                )
1646                .as_side_effect()
1647                .await?;
1648
1649                Ok(())
1650            } else {
1651                emit_assets(
1652                    all_output_assets.connect(),
1653                    node_root.clone(),
1654                    client_relative_path.clone(),
1655                    node_root.clone(),
1656                )
1657                .as_side_effect()
1658                .await?;
1659
1660                Ok(())
1661            }
1662        }
1663        .instrument(span)
1664        .await
1665    }
1666
1667    #[turbo_tasks::function]
1668    async fn hmr_content(self: Vc<Self>, identifier: RcStr) -> Result<Vc<OptionVersionedContent>> {
1669        if let Some(map) = self.await?.versioned_content_map {
1670            let content = map.get(self.client_relative_path().await?.join(&identifier)?);
1671            Ok(content)
1672        } else {
1673            bail!("must be in dev mode to hmr")
1674        }
1675    }
1676
1677    #[turbo_tasks::function]
1678    async fn hmr_version(self: Vc<Self>, identifier: RcStr) -> Result<Vc<Box<dyn Version>>> {
1679        let content = self.hmr_content(identifier).await?;
1680        if let Some(content) = &*content {
1681            Ok(content.version())
1682        } else {
1683            Ok(Vc::upcast(NotFoundVersion::new()))
1684        }
1685    }
1686
1687    /// Get the version state for a session. Initialized with the first seen
1688    /// version in that session.
1689    #[turbo_tasks::function]
1690    pub async fn hmr_version_state(
1691        self: Vc<Self>,
1692        identifier: RcStr,
1693        session: TransientInstance<()>,
1694    ) -> Result<Vc<VersionState>> {
1695        let version = self.hmr_version(identifier);
1696
1697        // The session argument is important to avoid caching this function between
1698        // sessions.
1699        let _ = session;
1700
1701        // INVALIDATION: This is intentionally untracked to avoid invalidating this
1702        // function completely. We want to initialize the VersionState with the
1703        // first seen version of the session.
1704        let state = VersionState::new(
1705            version
1706                .into_trait_ref()
1707                .strongly_consistent()
1708                .untracked()
1709                .await?,
1710        )
1711        .await?;
1712        Ok(state)
1713    }
1714
1715    /// Emits opaque HMR events whenever a change is detected in the chunk group
1716    /// internally known as `identifier`.
1717    #[turbo_tasks::function]
1718    pub async fn hmr_update(
1719        self: Vc<Self>,
1720        identifier: RcStr,
1721        from: Vc<VersionState>,
1722    ) -> Result<Vc<Update>> {
1723        let from = from.get();
1724        let content = self.hmr_content(identifier).await?;
1725        if let Some(content) = *content {
1726            Ok(content.update(from))
1727        } else {
1728            Ok(Update::Missing.cell())
1729        }
1730    }
1731
1732    /// Gets a list of all HMR identifiers that can be subscribed to. This is
1733    /// only needed for testing purposes and isn't used in real apps.
1734    #[turbo_tasks::function]
1735    pub async fn hmr_identifiers(self: Vc<Self>) -> Result<Vc<Vec<RcStr>>> {
1736        if let Some(map) = self.await?.versioned_content_map {
1737            Ok(map.keys_in_path(self.client_relative_path().owned().await?))
1738        } else {
1739            bail!("must be in dev mode to hmr")
1740        }
1741    }
1742
1743    /// Completion when server side changes are detected in output assets
1744    /// referenced from the roots
1745    #[turbo_tasks::function]
1746    pub async fn server_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
1747        let path = self.node_root().owned().await?;
1748        Ok(any_output_changed(roots, path, true))
1749    }
1750
1751    /// Completion when client side changes are detected in output assets
1752    /// referenced from the roots
1753    #[turbo_tasks::function]
1754    pub async fn client_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
1755        let path = self.client_root().owned().await?;
1756        Ok(any_output_changed(roots, path, false))
1757    }
1758
1759    #[turbo_tasks::function]
1760    pub async fn client_main_modules(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
1761        let pages_project = self.pages_project();
1762        let mut modules = vec![ChunkGroupEntry::Entry(vec![
1763            pages_project.client_main_module().to_resolved().await?,
1764        ])];
1765
1766        if let Some(app_project) = *self.app_project().await? {
1767            modules.push(ChunkGroupEntry::Entry(vec![
1768                app_project.client_main_module().to_resolved().await?,
1769            ]));
1770        }
1771
1772        Ok(Vc::cell(modules))
1773    }
1774
1775    /// Gets the module id strategy for the project.
1776    #[turbo_tasks::function]
1777    pub async fn module_ids(self: Vc<Self>) -> Result<Vc<Box<dyn ModuleIdStrategy>>> {
1778        let module_id_strategy = *self.next_config().module_ids(self.next_mode()).await?;
1779        match module_id_strategy {
1780            ModuleIdStrategyConfig::Named => Ok(Vc::upcast(DevModuleIdStrategy::new())),
1781            ModuleIdStrategyConfig::Deterministic => {
1782                let module_graphs = self.whole_app_module_graphs().await?;
1783                Ok(Vc::upcast(get_global_module_id_strategy(
1784                    *module_graphs.full,
1785                )))
1786            }
1787        }
1788    }
1789
1790    /// Compute the used exports for each module.
1791    #[turbo_tasks::function]
1792    pub async fn export_usage(self: Vc<Self>) -> Result<Vc<OptionExportUsageInfo>> {
1793        if *self
1794            .next_config()
1795            .turbopack_remove_unused_exports(self.next_mode())
1796            .await?
1797        {
1798            let module_graphs = self.whole_app_module_graphs().await?;
1799            Ok(Vc::cell(Some(
1800                compute_export_usage_info(module_graphs.full)
1801                    // As a performance optimization, we resolve strongly consistently
1802                    .resolve_strongly_consistent()
1803                    .await?,
1804            )))
1805        } else {
1806            Ok(Vc::cell(None))
1807        }
1808    }
1809}
1810
1811// This is a performance optimization. This function is a root aggregation function that
1812// aggregates over the whole subgraph.
1813#[turbo_tasks::function(operation)]
1814async fn whole_app_module_graph_operation(
1815    project: ResolvedVc<Project>,
1816) -> Result<Vc<ModuleGraphs>> {
1817    mark_root();
1818
1819    let should_trace = project.next_mode().await?.is_production();
1820    let base_single_module_graph =
1821        SingleModuleGraph::new_with_entries(project.get_all_entries(), should_trace);
1822    let base_visited_modules = VisitedModules::from_graph(base_single_module_graph);
1823
1824    let base = ModuleGraph::from_single_graph(base_single_module_graph);
1825    let additional_entries = project.get_all_additional_entries(base);
1826
1827    let additional_module_graph = SingleModuleGraph::new_with_entries_visited(
1828        additional_entries,
1829        base_visited_modules,
1830        should_trace,
1831    );
1832
1833    let full = ModuleGraph::from_graphs(vec![base_single_module_graph, additional_module_graph]);
1834    Ok(ModuleGraphs {
1835        base: base.to_resolved().await?,
1836        full: full.to_resolved().await?,
1837    }
1838    .cell())
1839}
1840
1841#[turbo_tasks::value(shared)]
1842pub struct ModuleGraphs {
1843    pub base: ResolvedVc<ModuleGraph>,
1844    pub full: ResolvedVc<ModuleGraph>,
1845}
1846
1847#[turbo_tasks::function]
1848async fn any_output_changed(
1849    roots: Vc<OutputAssets>,
1850    path: FileSystemPath,
1851    server: bool,
1852) -> Result<Vc<Completion>> {
1853    let completions = AdjacencyMap::new()
1854        .skip_duplicates()
1855        .visit(roots.await?.iter().copied(), get_referenced_output_assets)
1856        .await
1857        .completed()?
1858        .into_inner()
1859        .into_postorder_topological()
1860        .map(|m| {
1861            let path = path.clone();
1862
1863            async move {
1864                let asset_path = m.path().await?;
1865                if !asset_path.path.ends_with(".map")
1866                    && (!server || !asset_path.path.ends_with(".css"))
1867                    && asset_path.is_inside_ref(&path)
1868                {
1869                    anyhow::Ok(Some(content_changed(*ResolvedVc::upcast(m))))
1870                } else {
1871                    Ok(None)
1872                }
1873            }
1874        })
1875        .map(|v| async move {
1876            Ok(match v.await? {
1877                Some(v) => Some(v.to_resolved().await?),
1878                None => None,
1879            })
1880        })
1881        .try_flat_join()
1882        .await?;
1883
1884    Ok(Vc::<Completions>::cell(completions).completed())
1885}
1886
1887async fn get_referenced_output_assets(
1888    parent: ResolvedVc<Box<dyn OutputAsset>>,
1889) -> Result<impl Iterator<Item = ResolvedVc<Box<dyn OutputAsset>>> + Send> {
1890    Ok(parent.references().owned().await?.into_iter())
1891}
1892
1893#[turbo_tasks::function(operation)]
1894fn all_assets_from_entries_operation(
1895    operation: OperationVc<OutputAssets>,
1896) -> Result<Vc<OutputAssets>> {
1897    let assets = operation.connect();
1898    Ok(all_assets_from_entries(assets))
1899}
1900
1901#[turbo_tasks::function]
1902fn stable_endpoint(endpoint: Vc<Box<dyn Endpoint>>) -> Vc<Box<dyn Endpoint>> {
1903    endpoint
1904}