Skip to main content

turbopack_ecmascript/references/
emit_collect.rs

1use anyhow::{Result, bail};
2use bincode::{Decode, Encode};
3use swc_core::{
4    common::DUMMY_SP,
5    ecma::ast::{Expr, IdentName, Invalid, MemberExpr, MemberProp},
6};
7use turbo_rcstr::{RcStr, rcstr};
8use turbo_tasks::{NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat};
9use turbopack_core::{
10    chunk::{ChunkingContext, ChunkingType},
11    compile_time_info::CompileTimeDefineValue,
12    emit_collect::EmittedModuleReference,
13    issue::IssueSource,
14    module::Module,
15    reference::ModuleReference,
16    reference_type::EcmaScriptModulesReferenceSubType,
17    resolve::{
18        BindingUsage, ExportUsage, ModuleResolveResult, ResolveErrorMode,
19        options::ResolveOptions,
20        origin::{ResolveOrigin, ResolveOriginExt},
21        parse::Request,
22    },
23};
24use turbopack_resolve::ecmascript::esm_resolve;
25
26use crate::{
27    analyzer::imports::ImportAnnotations,
28    ast_path_trie::{AstPathId, AstPathTrie, AstPathTrieBuilder},
29    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
30    collect_module::{COLLECT_LIST_EXPORT, EcmascriptCollectModule},
31    create_visitor,
32    references::{
33        esm::{base::ReferencedAsset, mangle::generated_export_key},
34        pattern_mapping::{PatternMapping, ResolveType},
35        removal::RemovalCodeGen,
36    },
37    utils::AstPathRange,
38};
39
40#[turbo_tasks::value]
41#[derive(Hash, Debug, ValueToString)]
42#[value_to_string("emit {request} {namespace}")]
43pub struct EmitReference {
44    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
45    request: ResolvedVc<Request>,
46    annotations: ImportAnnotations,
47    issue_source: IssueSource,
48    error_mode: ResolveErrorMode,
49    export_usage: ExportUsage,
50    namespace: RcStr,
51    data: Option<CompileTimeDefineValue>,
52    emit_to_all_entries: bool,
53}
54
55impl EmitReference {
56    pub fn new(
57        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
58        request: ResolvedVc<Request>,
59        issue_source: IssueSource,
60        annotations: ImportAnnotations,
61        error_mode: ResolveErrorMode,
62        export_usage: ExportUsage,
63        namespace: RcStr,
64        data: Option<CompileTimeDefineValue>,
65        emit_to_all_entries: bool,
66    ) -> Self {
67        EmitReference {
68            origin,
69            request,
70            issue_source,
71            annotations,
72            error_mode,
73            export_usage,
74            namespace,
75            data,
76            emit_to_all_entries,
77        }
78    }
79}
80
81#[turbo_tasks::value_impl]
82impl ModuleReference for EmitReference {
83    #[turbo_tasks::function]
84    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
85        let origin = if let Some(transition) = self.annotations.transition() {
86            self.origin
87                .with_transition(transition.into())
88                .await?
89                .to_resolved()
90                .await?
91        } else {
92            self.origin
93        };
94
95        if self.annotations.turbopack_loader().is_some()
96            || self.annotations.turbopack_rename_as().is_some()
97            || self.annotations.turbopack_module_type().is_some()
98        {
99            // TODO implement
100            bail!(
101                "turbopack_loader, turbopack_rename_as, and turbopack_module_type annotations are \
102                 not yet supported for emit references"
103            );
104        }
105
106        esm_resolve(
107            *origin,
108            *self.request,
109            EcmaScriptModulesReferenceSubType::Emit,
110            self.error_mode,
111            Some(self.issue_source),
112        )
113        .await
114    }
115
116    fn chunking_type(&self) -> Option<ChunkingType> {
117        Some(ChunkingType::Emitted {
118            namespace: self.namespace.clone(),
119            emit_to_all_entries: self.emit_to_all_entries,
120        })
121    }
122
123    fn binding_usage(&self) -> BindingUsage {
124        BindingUsage {
125            import: Default::default(),
126            export: self.export_usage.clone(),
127        }
128    }
129}
130
131#[turbo_tasks::value_impl]
132impl EmittedModuleReference for EmitReference {
133    #[turbo_tasks::function]
134    fn data(&self) -> Vc<CompileTimeDefineValue> {
135        self.data
136            .clone()
137            .unwrap_or(CompileTimeDefineValue::Undefined)
138            .cell()
139    }
140}
141
142impl IntoCodeGenReference for EmitReference {
143    fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
144        ResolvedVc::upcast(self.resolved_cell())
145    }
146
147    fn into_code_gen_reference(
148        self,
149        trie: &AstPathTrieBuilder,
150        path: AstPathId,
151    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
152        let reference = self.resolved_cell();
153        // The reference is on the import specifier; the statement to remove is its parent.
154        let path = trie.parent_or_root(path);
155        (
156            ResolvedVc::upcast(reference),
157            CodeGen::RemovalCodeGen(RemovalCodeGen::new(
158                rcstr!("TURBOPACK collect"),
159                AstPathRange::Exact(path),
160            )),
161        )
162    }
163}
164
165#[turbo_tasks::value]
166#[derive(Hash, Debug, ValueToString)]
167#[value_to_string("collect {namespace}")]
168pub struct CollectReference {
169    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
170    parent_module: ResolvedVc<Box<dyn Module>>,
171    namespace: RcStr,
172}
173
174impl CollectReference {
175    pub fn new(
176        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
177        parent_module: ResolvedVc<Box<dyn Module>>,
178        namespace: RcStr,
179    ) -> Self {
180        CollectReference {
181            origin,
182            parent_module,
183            namespace,
184        }
185    }
186}
187
188fn collect_request() -> Vc<Request> {
189    Request::parse_string(rcstr!("data:text/javascript,"))
190}
191#[turbo_tasks::function]
192async fn with_data_uri(options: Vc<ResolveOptions>) -> Result<Vc<ResolveOptions>> {
193    let mut options: ResolveOptions = options.owned().await?;
194    options.parse_data_uris = true;
195    Ok(options.cell())
196}
197
198#[turbo_tasks::value_impl]
199impl ModuleReference for CollectReference {
200    #[turbo_tasks::function]
201    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
202        let origin = self.origin.into_trait_ref().await?;
203
204        // TODO eventually, you might want to be able to use multiple different collect modules here
205        Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
206            EcmascriptCollectModule::new(
207                *self.parent_module,
208                self.namespace.clone(),
209                *origin.asset_context(),
210            )
211            .to_resolved()
212            .await?,
213        )))
214    }
215
216    fn chunking_type(&self) -> Option<ChunkingType> {
217        Some(ChunkingType::PerEntry)
218    }
219}
220
221impl IntoCodeGenReference for CollectReference {
222    fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
223        ResolvedVc::upcast(self.resolved_cell())
224    }
225
226    fn into_code_gen_reference(
227        self,
228        trie: &AstPathTrieBuilder,
229        path: AstPathId,
230    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
231        let reference = self.resolved_cell();
232        let path = trie.parent_or_root(path);
233        (
234            ResolvedVc::upcast(reference),
235            CodeGen::CollectReferenceCodeGen(CollectReferenceCodeGen { reference, path }),
236        )
237    }
238}
239
240#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
241pub struct CollectReferenceCodeGen {
242    reference: ResolvedVc<CollectReference>,
243    path: AstPathId,
244}
245
246impl CollectReferenceCodeGen {
247    pub async fn code_generation(
248        &self,
249        trie: &AstPathTrie,
250        chunking_context: Vc<Box<dyn ChunkingContext>>,
251    ) -> Result<CodeGeneration> {
252        let reference = self.reference.await?;
253
254        let pm = PatternMapping::resolve_request(
255            collect_request(),
256            *reference.origin,
257            chunking_context,
258            self.reference.resolve_reference(),
259            ResolveType::ChunkItem,
260            Some(Vc::upcast(*self.reference)),
261        )
262        .await?;
263        let mut visitors = Vec::new();
264
265        // The collect module's own `getList` export is mangled like any other, so resolve the key
266        // it is actually emitted under rather than hard-coding the source name. Both sides ask the
267        // same module, so they always agree; when that module keeps its original names this is
268        // just `getList` again.
269        let export =
270            match ReferencedAsset::from_resolve_result(self.reference.resolve_reference()).await? {
271                ReferencedAsset::Some(module) => {
272                    generated_export_key(module, chunking_context, &COLLECT_LIST_EXPORT).await?
273                }
274                _ => COLLECT_LIST_EXPORT,
275            };
276
277        visitors.push(create_visitor!(
278            trie,
279            self.path,
280            visit_mut_expr,
281            |expr: &mut Expr| {
282                *expr = Expr::Member(MemberExpr {
283                    span: DUMMY_SP,
284                    obj: Box::new(pm.create_require(Expr::Invalid(Invalid::default()))),
285                    prop: MemberProp::Ident(IdentName::new(export.as_str().into(), DUMMY_SP)),
286                });
287            }
288        ));
289
290        Ok(CodeGeneration::visitors(visitors))
291    }
292}