Skip to main content

turbopack_ecmascript/tree_shake/side_effects/
module.rs

1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
4use turbo_tasks_fs::rope::RopeBuilder;
5use turbopack_core::{
6    chunk::{
7        AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset, ModuleChunkItemIdExt,
8    },
9    ident::AssetIdent,
10    module::{Module, ModuleSideEffects},
11    module_graph::ModuleGraph,
12    reference::{ModuleReferences, SingleChunkableModuleReference},
13    resolve::{ExportUsage, ModulePart},
14};
15
16use crate::{
17    EcmascriptModuleAsset,
18    chunk::{
19        EcmascriptChunkItemContent, EcmascriptChunkItemOptions, EcmascriptChunkPlaceable,
20        EcmascriptExports, ecmascript_chunk_item, item::RewriteSourcePath,
21    },
22    references::async_module::AsyncModuleOptions,
23    runtime_functions::{TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_IMPORT},
24    utils::StringifyModuleId,
25};
26
27#[turbo_tasks::value]
28pub struct SideEffectsModule {
29    /// Original module
30    pub module: ResolvedVc<EcmascriptModuleAsset>,
31    /// The part of the original module that is the binding
32    pub part: ModulePart,
33    /// The module that is the binding
34    pub resolved_as: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
35    /// Side effects from the original module to the binding.
36    pub side_effects: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
37}
38
39#[turbo_tasks::value_impl]
40impl SideEffectsModule {
41    #[turbo_tasks::function]
42    pub fn new(
43        module: ResolvedVc<EcmascriptModuleAsset>,
44        part: ModulePart,
45        resolved_as: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
46        side_effects: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
47    ) -> Vc<Self> {
48        SideEffectsModule {
49            module,
50            part,
51            resolved_as,
52            side_effects,
53        }
54        .cell()
55    }
56}
57
58#[turbo_tasks::value_impl]
59impl Module for SideEffectsModule {
60    #[turbo_tasks::function]
61    async fn ident(&self) -> Result<Vc<AssetIdent>> {
62        let mut ident = self
63            .module
64            .ident()
65            .owned()
66            .await?
67            .with_part(self.part.clone())
68            .with_asset(
69                rcstr!("resolved"),
70                self.resolved_as.ident().to_resolved().await?,
71            )
72            .with_modifier(rcstr!("side effects"));
73
74        for (i, side_effect) in self.side_effects.iter().enumerate() {
75            ident = ident.with_asset(
76                RcStr::from(format!("side effect {i}")),
77                side_effect.ident().to_resolved().await?,
78            );
79        }
80
81        Ok(ident.into_vc())
82    }
83
84    #[turbo_tasks::function]
85    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
86        Vc::cell(None)
87    }
88
89    #[turbo_tasks::function]
90    async fn references(&self) -> Result<Vc<ModuleReferences>> {
91        let mut references = vec![];
92
93        references.extend(
94            self.side_effects
95                .iter()
96                .map(|side_effect| async move {
97                    Ok(ResolvedVc::upcast(
98                        SingleChunkableModuleReference::new(
99                            *ResolvedVc::upcast(*side_effect),
100                            rcstr!("side effect"),
101                            ExportUsage::evaluation(),
102                        )
103                        .to_resolved()
104                        .await?,
105                    ))
106                })
107                .try_join()
108                .await?,
109        );
110
111        references.push(ResolvedVc::upcast(
112            SingleChunkableModuleReference::new(
113                *ResolvedVc::upcast(self.resolved_as),
114                rcstr!("resolved as"),
115                ExportUsage::all(),
116            )
117            .to_resolved()
118            .await?,
119        ));
120
121        Ok(Vc::cell(references))
122    }
123
124    #[turbo_tasks::function]
125    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
126        // This module exists to collect side effects from references.  So it isn't side effectful
127        // but it may depend on side effectful modules.  use this mode to allow inner graph tree
128        // shaking to still potentially trim this module and its dependencies.
129        ModuleSideEffects::ModuleEvaluationIsSideEffectFree.cell()
130    }
131}
132
133#[turbo_tasks::value_impl]
134impl EcmascriptChunkPlaceable for SideEffectsModule {
135    #[turbo_tasks::function]
136    fn get_exports(&self) -> Vc<EcmascriptExports> {
137        self.resolved_as.get_exports()
138    }
139
140    #[turbo_tasks::function]
141    async fn chunk_item_content(
142        self: Vc<Self>,
143        chunking_context: Vc<Box<dyn ChunkingContext>>,
144        _module_graph: Vc<ModuleGraph>,
145        _async_module_info: Option<Vc<AsyncModuleInfo>>,
146        _estimated: bool,
147    ) -> Result<Vc<EcmascriptChunkItemContent>> {
148        let module = self.await?;
149        let mut code = RopeBuilder::default();
150        let mut has_top_level_await = false;
151
152        for &side_effect in module.side_effects.iter() {
153            let need_await = 'need_await: {
154                let async_module = *side_effect.get_async_module().await?;
155                if let Some(async_module) = async_module
156                    && async_module.await?.has_top_level_await
157                {
158                    break 'need_await true;
159                }
160                false
161            };
162
163            if !has_top_level_await && need_await {
164                has_top_level_await = true;
165            }
166
167            code.push_bytes(
168                format!(
169                    "{}{TURBOPACK_IMPORT}({});\n",
170                    if need_await { "await " } else { "" },
171                    StringifyModuleId(&side_effect.chunk_item_id(chunking_context).await?)
172                )
173                .as_bytes(),
174            );
175        }
176
177        code.push_bytes(
178            format!(
179                "{TURBOPACK_EXPORT_NAMESPACE}({TURBOPACK_IMPORT}({}));\n",
180                StringifyModuleId(&module.resolved_as.chunk_item_id(chunking_context).await?)
181            )
182            .as_bytes(),
183        );
184
185        let code = code.build();
186
187        Ok(EcmascriptChunkItemContent {
188            inner_code: code,
189            source_map: None,
190            rewrite_source_path: RewriteSourcePath::None,
191            options: EcmascriptChunkItemOptions {
192                strict: true,
193                async_module: if has_top_level_await {
194                    Some(AsyncModuleOptions {
195                        has_top_level_await: true,
196                    })
197                } else {
198                    None
199                },
200                supports_arrow_functions: *chunking_context
201                    .environment()
202                    .runtime_versions()
203                    .supports_arrow_functions()
204                    .await?,
205                ..Default::default()
206            },
207            additional_ids: Default::default(),
208            placeholder_for_future_extensions: (),
209        }
210        .cell())
211    }
212}
213
214#[turbo_tasks::value_impl]
215impl ChunkableModule for SideEffectsModule {
216    #[turbo_tasks::function]
217    fn as_chunk_item(
218        self: ResolvedVc<Self>,
219        module_graph: ResolvedVc<ModuleGraph>,
220        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
221    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
222        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
223    }
224}
225
226#[turbo_tasks::value_impl]
227impl EvaluatableAsset for SideEffectsModule {}