next_core/next_dynamic/
dynamic_module.rs

1use std::collections::BTreeMap;
2
3use anyhow::{Result, bail};
4use indoc::formatdoc;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{ResolvedVc, Vc};
7use turbopack_core::{
8    asset::{Asset, AssetContent},
9    chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, ModuleChunkItemIdExt},
10    ident::AssetIdent,
11    module::Module,
12    module_graph::ModuleGraph,
13    reference::{ModuleReferences, SingleChunkableModuleReference},
14};
15use turbopack_ecmascript::{
16    chunk::{
17        EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkPlaceable,
18        EcmascriptChunkType, EcmascriptExports,
19    },
20    references::esm::{EsmExport, EsmExports},
21    runtime_functions::{TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_IMPORT},
22    utils::StringifyJs,
23};
24
25#[turbo_tasks::function]
26fn modifier() -> Vc<RcStr> {
27    Vc::cell("next/dynamic entry".into())
28}
29
30/// A [`NextDynamicEntryModule`] is a marker asset used to indicate which
31/// dynamic assets should appear in the dynamic manifest.
32#[turbo_tasks::value(shared)]
33pub struct NextDynamicEntryModule {
34    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
35}
36
37#[turbo_tasks::value_impl]
38impl NextDynamicEntryModule {
39    #[turbo_tasks::function]
40    pub fn new(module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>) -> Vc<Self> {
41        NextDynamicEntryModule { module }.cell()
42    }
43}
44
45#[turbo_tasks::function]
46fn dynamic_ref_description() -> Vc<RcStr> {
47    Vc::cell("next/dynamic reference".into())
48}
49
50#[turbo_tasks::value_impl]
51impl Module for NextDynamicEntryModule {
52    #[turbo_tasks::function]
53    fn ident(&self) -> Vc<AssetIdent> {
54        self.module.ident().with_modifier(modifier())
55    }
56
57    #[turbo_tasks::function]
58    async fn references(&self) -> Result<Vc<ModuleReferences>> {
59        Ok(Vc::cell(vec![ResolvedVc::upcast(
60            SingleChunkableModuleReference::new(
61                Vc::upcast(*self.module),
62                dynamic_ref_description(),
63            )
64            .to_resolved()
65            .await?,
66        )]))
67    }
68}
69
70#[turbo_tasks::value_impl]
71impl Asset for NextDynamicEntryModule {
72    #[turbo_tasks::function]
73    fn content(&self) -> Result<Vc<AssetContent>> {
74        bail!("Next.js Server Component module has no content")
75    }
76}
77
78#[turbo_tasks::value_impl]
79impl ChunkableModule for NextDynamicEntryModule {
80    #[turbo_tasks::function]
81    fn as_chunk_item(
82        self: ResolvedVc<Self>,
83        module_graph: ResolvedVc<ModuleGraph>,
84        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
85    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
86        Vc::upcast(
87            NextDynamicEntryChunkItem {
88                chunking_context,
89                module_graph,
90                inner: self,
91            }
92            .cell(),
93        )
94    }
95}
96
97#[turbo_tasks::value_impl]
98impl EcmascriptChunkPlaceable for NextDynamicEntryModule {
99    #[turbo_tasks::function]
100    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
101        let module_reference = ResolvedVc::upcast(
102            SingleChunkableModuleReference::new(
103                Vc::upcast(*self.module),
104                dynamic_ref_description(),
105            )
106            .to_resolved()
107            .await?,
108        );
109
110        let mut exports = BTreeMap::new();
111        exports.insert(
112            "default".into(),
113            EsmExport::ImportedBinding(module_reference, "default".into(), false),
114        );
115
116        Ok(EcmascriptExports::EsmExports(
117            EsmExports {
118                exports,
119                star_exports: vec![module_reference],
120            }
121            .resolved_cell(),
122        )
123        .cell())
124    }
125}
126
127#[turbo_tasks::value]
128struct NextDynamicEntryChunkItem {
129    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
130    module_graph: ResolvedVc<ModuleGraph>,
131    inner: ResolvedVc<NextDynamicEntryModule>,
132}
133
134#[turbo_tasks::value_impl]
135impl EcmascriptChunkItem for NextDynamicEntryChunkItem {
136    #[turbo_tasks::function]
137    async fn content(&self) -> Result<Vc<EcmascriptChunkItemContent>> {
138        let inner = self.inner.await?;
139
140        let module_id = inner
141            .module
142            .chunk_item_id(Vc::upcast(*self.chunking_context))
143            .await?;
144        Ok(EcmascriptChunkItemContent {
145            inner_code: formatdoc!(
146                r#"
147                    {TURBOPACK_EXPORT_NAMESPACE}({TURBOPACK_IMPORT}({}));
148                "#,
149                StringifyJs(&module_id),
150            )
151            .into(),
152            ..Default::default()
153        }
154        .cell())
155    }
156}
157
158#[turbo_tasks::value_impl]
159impl ChunkItem for NextDynamicEntryChunkItem {
160    #[turbo_tasks::function]
161    fn asset_ident(&self) -> Vc<AssetIdent> {
162        self.inner.ident()
163    }
164
165    #[turbo_tasks::function]
166    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
167        *self.chunking_context
168    }
169
170    #[turbo_tasks::function]
171    fn ty(&self) -> Vc<Box<dyn ChunkType>> {
172        Vc::upcast(Vc::<EcmascriptChunkType>::default())
173    }
174
175    #[turbo_tasks::function]
176    fn module(&self) -> Vc<Box<dyn Module>> {
177        Vc::upcast(*self.inner)
178    }
179}