Skip to main content

next_core/next_server_component/
server_component_module.rs

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