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::{
9    NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
10};
11use turbopack_core::{
12    chunk::{ChunkingContext, ChunkingType},
13    compile_time_info::CompileTimeDefineValue,
14    emit_collect::EmittedModuleReference,
15    issue::IssueSource,
16    module::Module,
17    reference::ModuleReference,
18    reference_type::EcmaScriptModulesReferenceSubType,
19    resolve::{
20        BindingUsage, ExportUsage, ModuleResolveResult, ResolveErrorMode,
21        options::ResolveOptions,
22        origin::{ResolveOrigin, ResolveOriginExt},
23        parse::Request,
24    },
25};
26use turbopack_resolve::ecmascript::esm_resolve;
27
28use crate::{
29    analyzer::imports::ImportAnnotations,
30    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
31    collect_module::{COLLECT_LIST_EXPORT, EcmascriptCollectModule},
32    create_visitor,
33    references::{
34        AstPath,
35        esm::{base::ReferencedAsset, mangle::generated_export_key},
36        pattern_mapping::{PatternMapping, ResolveType},
37        removal::RemovalCodeGen,
38    },
39    utils::AstPathRange,
40};
41
42#[turbo_tasks::value]
43#[derive(Hash, Debug, ValueToString)]
44#[value_to_string("emit {request} {namespace}")]
45pub struct EmitReference {
46    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
47    request: ResolvedVc<Request>,
48    annotations: ImportAnnotations,
49    issue_source: IssueSource,
50    error_mode: ResolveErrorMode,
51    export_usage: ExportUsage,
52    namespace: RcStr,
53    data: Option<CompileTimeDefineValue>,
54    emit_to_all_entries: bool,
55}
56
57impl EmitReference {
58    pub fn new(
59        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
60        request: ResolvedVc<Request>,
61        issue_source: IssueSource,
62        annotations: ImportAnnotations,
63        error_mode: ResolveErrorMode,
64        export_usage: ExportUsage,
65        namespace: RcStr,
66        data: Option<CompileTimeDefineValue>,
67        emit_to_all_entries: bool,
68    ) -> Self {
69        EmitReference {
70            origin,
71            request,
72            issue_source,
73            annotations,
74            error_mode,
75            export_usage,
76            namespace,
77            data,
78            emit_to_all_entries,
79        }
80    }
81}
82
83#[turbo_tasks::value_impl]
84impl ModuleReference for EmitReference {
85    #[turbo_tasks::function]
86    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
87        let origin = if let Some(transition) = self.annotations.transition() {
88            self.origin
89                .with_transition(transition.into())
90                .await?
91                .to_resolved()
92                .await?
93        } else {
94            self.origin
95        };
96
97        if self.annotations.turbopack_loader().is_some()
98            || self.annotations.turbopack_rename_as().is_some()
99            || self.annotations.turbopack_module_type().is_some()
100        {
101            // TODO implement
102            bail!(
103                "turbopack_loader, turbopack_rename_as, and turbopack_module_type annotations are \
104                 not yet supported for emit references"
105            );
106        }
107
108        esm_resolve(
109            *origin,
110            *self.request,
111            EcmaScriptModulesReferenceSubType::Emit,
112            self.error_mode,
113            Some(self.issue_source),
114        )
115        .await
116    }
117
118    fn chunking_type(&self) -> Option<ChunkingType> {
119        Some(ChunkingType::Emitted {
120            namespace: self.namespace.clone(),
121            emit_to_all_entries: self.emit_to_all_entries,
122        })
123    }
124
125    fn binding_usage(&self) -> BindingUsage {
126        BindingUsage {
127            import: Default::default(),
128            export: self.export_usage.clone(),
129        }
130    }
131}
132
133#[turbo_tasks::value_impl]
134impl EmittedModuleReference for EmitReference {
135    #[turbo_tasks::function]
136    fn data(&self) -> Vc<CompileTimeDefineValue> {
137        self.data
138            .clone()
139            .unwrap_or(CompileTimeDefineValue::Undefined)
140            .cell()
141    }
142}
143
144impl IntoCodeGenReference for EmitReference {
145    fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
146        ResolvedVc::upcast(self.resolved_cell())
147    }
148
149    fn into_code_gen_reference(
150        self,
151        mut path: AstPath,
152    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
153        let reference = self.resolved_cell();
154        path.0.pop();
155        (
156            ResolvedVc::upcast(reference),
157            CodeGen::RemovalCodeGen(RemovalCodeGen::new(
158                rcstr!("TURBOPACK collect"),
159                AstPathRange::Exact(path.0),
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        mut path: AstPath,
229    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
230        let reference = self.resolved_cell();
231        path.0.pop();
232        (
233            ResolvedVc::upcast(reference),
234            CodeGen::CollectReferenceCodeGen(CollectReferenceCodeGen { reference, path }),
235        )
236    }
237}
238
239#[derive(
240    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
241)]
242pub struct CollectReferenceCodeGen {
243    reference: ResolvedVc<CollectReference>,
244    path: AstPath,
245}
246
247impl CollectReferenceCodeGen {
248    pub async fn code_generation(
249        &self,
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            self.path,
279            visit_mut_expr,
280            |expr: &mut Expr| {
281                *expr = Expr::Member(MemberExpr {
282                    span: DUMMY_SP,
283                    obj: Box::new(pm.create_require(Expr::Invalid(Invalid::default()))),
284                    prop: MemberProp::Ident(IdentName::new(export.as_str().into(), DUMMY_SP)),
285                });
286            }
287        ));
288
289        Ok(CodeGeneration::visitors(visitors))
290    }
291}