next_api/
project.rs

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