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.module.ident().owned().await?;
63        ident.parts.push(self.part.clone());
64
65        ident.add_asset(
66            rcstr!("resolved"),
67            self.resolved_as.ident().to_resolved().await?,
68        );
69
70        ident.add_modifier(rcstr!("side effects"));
71
72        for (i, side_effect) in self.side_effects.iter().enumerate() {
73            ident.add_asset(
74                RcStr::from(format!("side effect {i}")),
75                side_effect.ident().to_resolved().await?,
76            );
77        }
78
79        Ok(AssetIdent::new(ident))
80    }
81
82    #[turbo_tasks::function]
83    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
84        Vc::cell(None)
85    }
86
87    #[turbo_tasks::function]
88    async fn references(&self) -> Result<Vc<ModuleReferences>> {
89        let mut references = vec![];
90
91        references.extend(
92            self.side_effects
93                .iter()
94                .map(|side_effect| async move {
95                    Ok(ResolvedVc::upcast(
96                        SingleChunkableModuleReference::new(
97                            *ResolvedVc::upcast(*side_effect),
98                            rcstr!("side effect"),
99                            ExportUsage::evaluation(),
100                        )
101                        .to_resolved()
102                        .await?,
103                    ))
104                })
105                .try_join()
106                .await?,
107        );
108
109        references.push(ResolvedVc::upcast(
110            SingleChunkableModuleReference::new(
111                *ResolvedVc::upcast(self.resolved_as),
112                rcstr!("resolved as"),
113                ExportUsage::all(),
114            )
115            .to_resolved()
116            .await?,
117        ));
118
119        Ok(Vc::cell(references))
120    }
121
122    #[turbo_tasks::function]
123    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
124        // This module exists to collect side effects from references.  So it isn't side effectful
125        // but it may depend on side effectful modules.  use this mode to allow inner graph tree
126        // shaking to still potentially trim this module and its dependencies.
127        ModuleSideEffects::ModuleEvaluationIsSideEffectFree.cell()
128    }
129}
130
131#[turbo_tasks::value_impl]
132impl EcmascriptChunkPlaceable for SideEffectsModule {
133    #[turbo_tasks::function]
134    fn get_exports(&self) -> Vc<EcmascriptExports> {
135        self.resolved_as.get_exports()
136    }
137
138    #[turbo_tasks::function]
139    async fn chunk_item_content(
140        self: Vc<Self>,
141        chunking_context: Vc<Box<dyn ChunkingContext>>,
142        _module_graph: Vc<ModuleGraph>,
143        _async_module_info: Option<Vc<AsyncModuleInfo>>,
144        _estimated: bool,
145    ) -> Result<Vc<EcmascriptChunkItemContent>> {
146        let module = self.await?;
147        let mut code = RopeBuilder::default();
148        let mut has_top_level_await = false;
149
150        for &side_effect in module.side_effects.iter() {
151            let need_await = 'need_await: {
152                let async_module = *side_effect.get_async_module().await?;
153                if let Some(async_module) = async_module
154                    && async_module.await?.has_top_level_await
155                {
156                    break 'need_await true;
157                }
158                false
159            };
160
161            if !has_top_level_await && need_await {
162                has_top_level_await = true;
163            }
164
165            code.push_bytes(
166                format!(
167                    "{}{TURBOPACK_IMPORT}({});\n",
168                    if need_await { "await " } else { "" },
169                    StringifyModuleId(&side_effect.chunk_item_id(chunking_context).await?)
170                )
171                .as_bytes(),
172            );
173        }
174
175        code.push_bytes(
176            format!(
177                "{TURBOPACK_EXPORT_NAMESPACE}({TURBOPACK_IMPORT}({}));\n",
178                StringifyModuleId(&module.resolved_as.chunk_item_id(chunking_context).await?)
179            )
180            .as_bytes(),
181        );
182
183        let code = code.build();
184
185        Ok(EcmascriptChunkItemContent {
186            inner_code: code,
187            source_map: None,
188            rewrite_source_path: RewriteSourcePath::None,
189            options: EcmascriptChunkItemOptions {
190                strict: true,
191                async_module: if has_top_level_await {
192                    Some(AsyncModuleOptions {
193                        has_top_level_await: true,
194                    })
195                } else {
196                    None
197                },
198                ..Default::default()
199            },
200            additional_ids: Default::default(),
201            placeholder_for_future_extensions: (),
202        }
203        .cell())
204    }
205}
206
207#[turbo_tasks::value_impl]
208impl ChunkableModule for SideEffectsModule {
209    #[turbo_tasks::function]
210    fn as_chunk_item(
211        self: ResolvedVc<Self>,
212        module_graph: ResolvedVc<ModuleGraph>,
213        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
214    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
215        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
216    }
217}
218
219#[turbo_tasks::value_impl]
220impl EvaluatableAsset for SideEffectsModule {}