Skip to main content

next_core/next_server_component/
server_component_module.rs

1use std::collections::BTreeMap;
2
3use anyhow::Result;
4use indoc::formatdoc;
5use turbo_rcstr::rcstr;
6use turbo_tasks::{ResolvedVc, Vc};
7use turbo_tasks_fs::FileSystemPath;
8use turbopack_core::{
9    chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext, ModuleChunkItemIdExt},
10    ident::AssetIdent,
11    module::{Module, ModuleSideEffects},
12    module_graph::ModuleGraph,
13    reference::ModuleReferences,
14    source::OptionSource,
15};
16use turbopack_ecmascript::{
17    chunk::{
18        EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
19        ecmascript_chunk_item,
20    },
21    references::esm::{EsmExport, EsmExports},
22    runtime_functions::{TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_IMPORT},
23    utils::StringifyJs,
24};
25
26use super::server_component_reference::NextServerComponentModuleReference;
27
28#[turbo_tasks::value(shared)]
29pub struct NextServerComponentModule {
30    pub module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
31    /// The original source path before any transformations (e.g., page.mdx before it becomes
32    /// page.mdx.tsx). This is used to generate consistent manifest keys that match what the
33    /// LoaderTree stores.
34    source_path: FileSystemPath,
35}
36
37#[turbo_tasks::value_impl]
38impl NextServerComponentModule {
39    #[turbo_tasks::function]
40    pub fn new(
41        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
42        source_path: FileSystemPath,
43    ) -> Vc<Self> {
44        NextServerComponentModule {
45            module,
46            source_path,
47        }
48        .cell()
49    }
50
51    /// Returns the original source path (before transformations like MDX -> MDX.tsx).
52    /// Use this for manifest key generation to match the LoaderTree paths.
53    #[turbo_tasks::function]
54    pub fn source_path(&self) -> Vc<FileSystemPath> {
55        self.source_path.clone().cell()
56    }
57
58    /// Returns the transformed module path (e.g., page.mdx.tsx for MDX files).
59    /// This is the path of the actual compiled module.
60    #[turbo_tasks::function]
61    pub fn server_path(&self) -> Vc<FileSystemPath> {
62        self.module.ident().path()
63    }
64}
65
66#[turbo_tasks::value_impl]
67impl Module for NextServerComponentModule {
68    #[turbo_tasks::function]
69    fn ident(&self) -> Vc<AssetIdent> {
70        self.module
71            .ident()
72            .with_modifier(rcstr!("Next.js Server Component"))
73    }
74
75    #[turbo_tasks::function]
76    fn source(&self) -> Vc<OptionSource> {
77        Vc::cell(None)
78    }
79
80    #[turbo_tasks::function]
81    async fn references(&self) -> Result<Vc<ModuleReferences>> {
82        Ok(Vc::cell(vec![ResolvedVc::upcast(
83            NextServerComponentModuleReference::new(Vc::upcast(*self.module))
84                .to_resolved()
85                .await?,
86        )]))
87    }
88    #[turbo_tasks::function]
89    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
90        // This just exports another import
91        ModuleSideEffects::ModuleEvaluationIsSideEffectFree.cell()
92    }
93}
94
95#[turbo_tasks::value_impl]
96impl ChunkableModule for NextServerComponentModule {
97    #[turbo_tasks::function]
98    fn as_chunk_item(
99        self: ResolvedVc<Self>,
100        module_graph: ResolvedVc<ModuleGraph>,
101        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
102    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
103        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
104    }
105}
106
107#[turbo_tasks::value_impl]
108impl EcmascriptChunkPlaceable for NextServerComponentModule {
109    #[turbo_tasks::function]
110    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
111        let module_reference = ResolvedVc::upcast(
112            NextServerComponentModuleReference::new(Vc::upcast(*self.module))
113                .to_resolved()
114                .await?,
115        );
116
117        let mut exports = BTreeMap::new();
118        let default = rcstr!("default");
119        exports.insert(
120            default.clone(),
121            EsmExport::ImportedBinding(module_reference, default, false),
122        );
123
124        Ok(EcmascriptExports::EsmExports(
125            EsmExports {
126                exports,
127                star_exports: vec![module_reference],
128            }
129            .resolved_cell(),
130        )
131        .cell())
132    }
133
134    #[turbo_tasks::function]
135    async fn chunk_item_content(
136        &self,
137        chunking_context: Vc<Box<dyn ChunkingContext>>,
138        _module_graph: Vc<ModuleGraph>,
139        _async_module_info: Option<Vc<AsyncModuleInfo>>,
140        _estimated: bool,
141    ) -> Result<Vc<EcmascriptChunkItemContent>> {
142        let module_id = self.module.chunk_item_id(chunking_context).await?;
143        Ok(EcmascriptChunkItemContent {
144            inner_code: formatdoc!(
145                r#"
146                    {TURBOPACK_EXPORT_NAMESPACE}({TURBOPACK_IMPORT}({}));
147                "#,
148                StringifyJs(&module_id),
149            )
150            .into(),
151            ..Default::default()
152        }
153        .cell())
154    }
155}