Skip to main content

turbopack_ecmascript/references/
exports_info.rs

1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4    common::DUMMY_SP,
5    ecma::ast::{Expr, Ident, KeyValueProp, ObjectLit, PropName, PropOrSpread},
6    quote,
7};
8use turbo_rcstr::rcstr;
9use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, debug::ValueDebugFormat};
10use turbopack_core::chunk::ChunkingContext;
11
12use crate::{
13    ast_path_trie::{AstPathId, AstPathTrie},
14    chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
15    code_gen::{CodeGen, CodeGeneration},
16    create_visitor, magic_identifier,
17    references::esm::mangle::mangled_export_names,
18};
19
20/// Responsible for initializing the `ExportsInfoBinding` object binding, so that it may be
21/// referenced in the the file.
22///
23/// There can be many references, and they appear at any nesting in the file. But we must only
24/// initialize the binding a single time.
25///
26/// This singleton behavior must be enforced by the caller!
27#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
28pub struct ExportsInfoBinding {}
29
30impl ExportsInfoBinding {
31    #[allow(clippy::new_without_default)]
32    pub fn new() -> Self {
33        ExportsInfoBinding {}
34    }
35
36    pub async fn code_generation(
37        &self,
38        _trie: &AstPathTrie,
39        chunking_context: Vc<Box<dyn ChunkingContext>>,
40        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
41        exports: ResolvedVc<EcmascriptExports>,
42    ) -> Result<CodeGeneration> {
43        let export_usage_info = chunking_context
44            .module_export_usage(*ResolvedVc::upcast(module))
45            .await?;
46        let export_usage_info = export_usage_info.export_usage.await?;
47        // The keys of `__webpack_exports_info__` stay the *original* export names — user code
48        // looks them up by name. The emitted key is reported as `mangledName` instead, which is
49        // always present alongside `canMangle` (`null` when `canMangle` is false), regardless of
50        // whether export mangling is enabled at all — see the `map` closure below for exactly
51        // what each of the three fields means.
52        let exports = exports.await?;
53        let mangled_names = mangled_export_names(*module, chunking_context).await?;
54
55        let props = if let EcmascriptExports::EsmExports(exports) = &*exports {
56            exports
57                .await?
58                .exports
59                .keys()
60                .map(|e| {
61                    let is_used = export_usage_info.is_export_used(e);
62                    let used: Expr = is_used.into();
63                    // `canMangle` is true exactly when this export is a genuine candidate for
64                    // mangling: the module has to be eligible at all (which is what a `Some` map
65                    // means — see `mangled_export_names`) and the export itself has to be used, as
66                    // an unused export is never emitted and so was never a candidate.
67                    // `mangledName` is then always a string — the assigned key when mangling
68                    // actually renamed it, or the export's own name when it was considered but
69                    // kept itself (e.g. already short enough) — and only `null` when `canMangle`
70                    // is false.
71                    let can_mangle_names = mangled_names.as_ref().filter(|_| is_used);
72                    let can_mangle_expr: Expr = can_mangle_names.is_some().into();
73                    let mangled_name: Expr =
74                        match can_mangle_names {
75                            Some(names) => Expr::Lit(names.get(e).unwrap_or(e).as_str().into()),
76                            None => Expr::Lit(swc_core::ecma::ast::Lit::Null(
77                                swc_core::ecma::ast::Null { span: DUMMY_SP },
78                            )),
79                        };
80                    PropOrSpread::Prop(Box::new(swc_core::ecma::ast::Prop::KeyValue(
81                        KeyValueProp {
82                            key: PropName::Str(e.as_str().into()),
83                            value: quote!(
84                                "{ used: $v, canMangle: $c, mangledName: $m }" as Box<Expr>,
85                                v: Expr = used,
86                                c: Expr = can_mangle_expr,
87                                m: Expr = mangled_name
88                            ),
89                        },
90                    )))
91                })
92                .collect()
93        } else {
94            vec![]
95        };
96
97        let data = Expr::Object(ObjectLit {
98            props,
99            span: DUMMY_SP,
100        });
101
102        Ok(CodeGeneration::hoisted_stmt(
103            rcstr!("__webpack_exports_info__"),
104            quote!(
105                "var $name = $data;" as Stmt,
106                name = exports_ident(),
107                data: Expr = data
108            ),
109        ))
110    }
111}
112
113impl From<ExportsInfoBinding> for CodeGen {
114    fn from(val: ExportsInfoBinding) -> Self {
115        CodeGen::ExportsInfoBinding(val)
116    }
117}
118
119/// Handles rewriting `__webpack_exports_info__` references into the injected binding created by
120/// ExportsInfoBinding.
121///
122/// There can be many references, and they appear at any nesting in the file. But all references
123/// refer to the same mutable object.
124#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
125pub struct ExportsInfoRef {
126    ast_path: AstPathId,
127}
128
129impl ExportsInfoRef {
130    pub fn new(ast_path: AstPathId) -> Self {
131        ExportsInfoRef { ast_path }
132    }
133
134    pub async fn code_generation(
135        &self,
136        trie: &AstPathTrie,
137        _chunking_context: Vc<Box<dyn ChunkingContext>>,
138    ) -> Result<CodeGeneration> {
139        let visitor = create_visitor!(trie, self.ast_path, visit_mut_expr, |expr: &mut Expr| {
140            *expr = Expr::Ident(exports_ident());
141        });
142
143        Ok(CodeGeneration::visitors(vec![visitor]))
144    }
145}
146
147impl From<ExportsInfoRef> for CodeGen {
148    fn from(val: ExportsInfoRef) -> Self {
149        CodeGen::ExportsInfoRef(val)
150    }
151}
152
153fn exports_ident() -> Ident {
154    Ident::new(
155        magic_identifier::mangle("__webpack_exports_info__").into(),
156        DUMMY_SP,
157        Default::default(),
158    )
159}