turbopack_ecmascript/references/
require_context.rs

1use std::{borrow::Cow, collections::VecDeque, sync::Arc};
2
3use anyhow::{Result, bail};
4use bincode::{Decode, Encode};
5use swc_core::{
6    common::DUMMY_SP,
7    ecma::{
8        ast::{
9            Expr, ExprStmt, KeyValueProp, Lit, ModuleItem, ObjectLit, Prop, PropName, PropOrSpread,
10            Stmt, {self},
11        },
12        codegen::{Emitter, text_writer::JsWriter},
13    },
14    quote, quote_expr,
15};
16use turbo_esregex::EsRegex;
17use turbo_rcstr::{RcStr, rcstr};
18use turbo_tasks::{
19    FxIndexMap, NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat,
20    trace::TraceRawVcs,
21};
22use turbo_tasks_fs::{DirectoryContent, DirectoryEntry, FileSystemPath};
23use turbopack_core::{
24    asset::{Asset, AssetContent},
25    chunk::{
26        ChunkItem, ChunkType, ChunkableModule, ChunkableModuleReference, ChunkingContext,
27        MinifyType, ModuleChunkItemIdExt,
28    },
29    ident::AssetIdent,
30    issue::IssueSource,
31    module::{Module, ModuleSideEffects},
32    module_graph::ModuleGraph,
33    output::OutputAssetsReference,
34    reference::{ModuleReference, ModuleReferences},
35    reference_type::CommonJsReferenceSubType,
36    resolve::{ModuleResolveResult, origin::ResolveOrigin, parse::Request},
37    source::Source,
38};
39use turbopack_resolve::ecmascript::cjs_resolve;
40
41use crate::{
42    EcmascriptChunkPlaceable,
43    chunk::{
44        EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkType, EcmascriptExports,
45    },
46    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
47    create_visitor,
48    references::{
49        AstPath,
50        pattern_mapping::{PatternMapping, ResolveType},
51    },
52    runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_MODULE_CONTEXT, TURBOPACK_REQUIRE},
53    utils::module_id_to_lit,
54};
55
56#[turbo_tasks::value]
57#[derive(Debug)]
58pub(crate) enum DirListEntry {
59    File(FileSystemPath),
60    Dir(ResolvedVc<DirList>),
61}
62
63#[turbo_tasks::value(transparent)]
64pub(crate) struct DirList(
65    #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, DirListEntry>,
66);
67
68#[turbo_tasks::value_impl]
69impl DirList {
70    #[turbo_tasks::function]
71    pub(crate) fn read(dir: FileSystemPath, recursive: bool, filter: Vc<EsRegex>) -> Vc<Self> {
72        Self::read_internal(dir.clone(), dir, recursive, filter)
73    }
74
75    #[turbo_tasks::function]
76    pub(crate) async fn read_internal(
77        root: FileSystemPath,
78        dir: FileSystemPath,
79        recursive: bool,
80        filter: Vc<EsRegex>,
81    ) -> Result<Vc<Self>> {
82        let root_val = root.clone();
83        let dir_val = dir.clone();
84        let regex = &filter.await?;
85
86        let mut list = FxIndexMap::default();
87
88        let dir_content = dir.read_dir().await?;
89        let entries = match &*dir_content {
90            DirectoryContent::Entries(entries) => Some(entries),
91            DirectoryContent::NotFound => None,
92        };
93
94        for (_, entry) in entries.iter().flat_map(|m| m.iter()) {
95            match entry {
96                DirectoryEntry::File(path) => {
97                    if let Some(relative_path) = root_val.get_relative_path_to(path)
98                        && regex.is_match(&relative_path)
99                    {
100                        list.insert(relative_path, DirListEntry::File(path.clone()));
101                    }
102                }
103                DirectoryEntry::Directory(path) if recursive => {
104                    if let Some(relative_path) = dir_val.get_relative_path_to(path) {
105                        list.insert(
106                            relative_path,
107                            DirListEntry::Dir(
108                                DirList::read_internal(
109                                    root.clone(),
110                                    path.clone(),
111                                    recursive,
112                                    filter,
113                                )
114                                .to_resolved()
115                                .await?,
116                            ),
117                        );
118                    }
119                }
120                // ignore everything else
121                _ => {}
122            }
123        }
124
125        list.sort_keys();
126
127        Ok(Vc::cell(list))
128    }
129
130    #[turbo_tasks::function]
131    async fn flatten(self: Vc<Self>) -> Result<Vc<FlatDirList>> {
132        let this = self.await?;
133
134        let mut queue = VecDeque::from([this]);
135
136        let mut list = FxIndexMap::default();
137
138        while let Some(dir) = queue.pop_front() {
139            for (k, entry) in &*dir {
140                match entry {
141                    DirListEntry::File(path) => {
142                        list.insert(k.clone(), path.clone());
143                    }
144                    DirListEntry::Dir(d) => {
145                        queue.push_back(d.await?);
146                    }
147                }
148            }
149        }
150
151        Ok(Vc::cell(list))
152    }
153}
154
155#[turbo_tasks::value(transparent)]
156pub(crate) struct FlatDirList(
157    #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, FileSystemPath>,
158);
159
160#[turbo_tasks::value_impl]
161impl FlatDirList {
162    #[turbo_tasks::function]
163    pub(crate) fn read(dir: FileSystemPath, recursive: bool, filter: Vc<EsRegex>) -> Vc<Self> {
164        DirList::read(dir, recursive, filter).flatten()
165    }
166}
167
168#[turbo_tasks::value]
169#[derive(Debug)]
170pub struct RequireContextMapEntry {
171    pub origin_relative: RcStr,
172    pub request: ResolvedVc<Request>,
173    pub result: ResolvedVc<ModuleResolveResult>,
174}
175
176/// The resolved context map for a `require.context(..)` call.
177#[turbo_tasks::value(transparent)]
178pub struct RequireContextMap(
179    #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, RequireContextMapEntry>,
180);
181
182#[turbo_tasks::value_impl]
183impl RequireContextMap {
184    #[turbo_tasks::function]
185    pub(crate) async fn generate(
186        origin: Vc<Box<dyn ResolveOrigin>>,
187        dir: FileSystemPath,
188        recursive: bool,
189        filter: Vc<EsRegex>,
190        issue_source: Option<IssueSource>,
191        is_optional: bool,
192    ) -> Result<Vc<Self>> {
193        let origin_path = origin.origin_path().await?.parent();
194
195        let list = &*FlatDirList::read(dir, recursive, filter).await?;
196
197        let mut map = FxIndexMap::default();
198
199        for (context_relative, path) in list {
200            let Some(origin_relative) = origin_path.get_relative_path_to(path) else {
201                bail!("invariant error: this was already checked in `list_dir`");
202            };
203
204            let request = Request::parse(origin_relative.clone().into())
205                .to_resolved()
206                .await?;
207            let result = cjs_resolve(
208                origin,
209                *request,
210                CommonJsReferenceSubType::Undefined,
211                issue_source,
212                is_optional,
213            )
214            .to_resolved()
215            .await?;
216
217            map.insert(
218                context_relative.clone(),
219                RequireContextMapEntry {
220                    origin_relative,
221                    request,
222                    result,
223                },
224            );
225        }
226
227        Ok(Vc::cell(map))
228    }
229}
230
231/// A reference for `require.context()`, will replace it with an inlined map
232/// wrapped in `__turbopack_module_context__`;
233#[turbo_tasks::value]
234#[derive(Hash, Debug)]
235pub struct RequireContextAssetReference {
236    pub inner: ResolvedVc<RequireContextAsset>,
237    pub dir: RcStr,
238    pub include_subdirs: bool,
239
240    pub issue_source: Option<IssueSource>,
241    pub in_try: bool,
242}
243
244impl RequireContextAssetReference {
245    pub async fn new(
246        source: ResolvedVc<Box<dyn Source>>,
247        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
248        dir: RcStr,
249        include_subdirs: bool,
250        filter: Vc<EsRegex>,
251        issue_source: Option<IssueSource>,
252        in_try: bool,
253    ) -> Result<Self> {
254        let map = RequireContextMap::generate(
255            *origin,
256            origin.origin_path().await?.parent().join(&dir)?,
257            include_subdirs,
258            filter,
259            issue_source,
260            in_try,
261        )
262        .to_resolved()
263        .await?;
264        let inner = RequireContextAsset {
265            source,
266            origin,
267            map,
268
269            dir: dir.clone(),
270            include_subdirs,
271        }
272        .resolved_cell();
273
274        Ok(RequireContextAssetReference {
275            inner,
276            dir,
277            include_subdirs,
278            issue_source,
279            in_try,
280        })
281    }
282}
283
284#[turbo_tasks::value_impl]
285impl ModuleReference for RequireContextAssetReference {
286    #[turbo_tasks::function]
287    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
288        *ModuleResolveResult::module(ResolvedVc::upcast(self.inner))
289    }
290}
291
292#[turbo_tasks::value_impl]
293impl ValueToString for RequireContextAssetReference {
294    #[turbo_tasks::function]
295    fn to_string(&self) -> Vc<RcStr> {
296        Vc::cell(
297            format!(
298                "require.context {}/{}",
299                self.dir,
300                if self.include_subdirs { "**" } else { "*" },
301            )
302            .into(),
303        )
304    }
305}
306
307#[turbo_tasks::value_impl]
308impl ChunkableModuleReference for RequireContextAssetReference {}
309
310impl IntoCodeGenReference for RequireContextAssetReference {
311    fn into_code_gen_reference(
312        self,
313        path: AstPath,
314    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
315        let reference = self.resolved_cell();
316        (
317            ResolvedVc::upcast(reference),
318            CodeGen::RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen {
319                reference,
320                path,
321            }),
322        )
323    }
324}
325
326#[derive(
327    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
328)]
329pub struct RequireContextAssetReferenceCodeGen {
330    path: AstPath,
331    reference: ResolvedVc<RequireContextAssetReference>,
332}
333
334impl RequireContextAssetReferenceCodeGen {
335    pub async fn code_generation(
336        &self,
337        chunking_context: Vc<Box<dyn ChunkingContext>>,
338    ) -> Result<CodeGeneration> {
339        let module_id = self
340            .reference
341            .await?
342            .inner
343            .chunk_item_id(chunking_context)
344            .await?;
345
346        let mut visitors = Vec::new();
347
348        visitors.push(create_visitor!(
349            self.path,
350            visit_mut_expr,
351            |expr: &mut Expr| {
352                if let Expr::Call(_) = expr {
353                    *expr = quote!(
354                        "$turbopack_module_context($turbopack_require($id))" as Expr,
355                        turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
356                        turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
357                        id: Expr = module_id_to_lit(&module_id)
358                    );
359                }
360            }
361        ));
362
363        Ok(CodeGeneration::visitors(visitors))
364    }
365}
366
367#[turbo_tasks::value(transparent)]
368pub struct ResolvedModuleReference(ResolvedVc<ModuleResolveResult>);
369
370#[turbo_tasks::value_impl]
371impl ModuleReference for ResolvedModuleReference {
372    #[turbo_tasks::function]
373    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
374        *self.0
375    }
376}
377
378#[turbo_tasks::value_impl]
379impl ValueToString for ResolvedModuleReference {
380    #[turbo_tasks::function]
381    fn to_string(&self) -> Vc<RcStr> {
382        Vc::cell(rcstr!("resolved reference"))
383    }
384}
385
386#[turbo_tasks::value_impl]
387impl ChunkableModuleReference for ResolvedModuleReference {}
388
389#[turbo_tasks::value]
390pub struct RequireContextAsset {
391    source: ResolvedVc<Box<dyn Source>>,
392
393    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
394    map: ResolvedVc<RequireContextMap>,
395
396    dir: RcStr,
397    include_subdirs: bool,
398}
399
400fn modifier(dir: &RcStr, include_subdirs: bool) -> RcStr {
401    format!(
402        "require.context {}/{}",
403        dir,
404        if include_subdirs { "**" } else { "*" },
405    )
406    .into()
407}
408
409#[turbo_tasks::value_impl]
410impl Module for RequireContextAsset {
411    #[turbo_tasks::function]
412    fn ident(&self) -> Vc<AssetIdent> {
413        self.source
414            .ident()
415            .with_modifier(modifier(&self.dir, self.include_subdirs))
416    }
417
418    #[turbo_tasks::function]
419    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
420        Vc::cell(Some(self.source))
421    }
422
423    #[turbo_tasks::function]
424    async fn references(&self) -> Result<Vc<ModuleReferences>> {
425        let map = &*self.map.await?;
426
427        Ok(Vc::cell(
428            map.iter()
429                .map(|(_, entry)| {
430                    ResolvedVc::upcast(ResolvedVc::<ResolvedModuleReference>::cell(entry.result))
431                })
432                .collect(),
433        ))
434    }
435
436    #[turbo_tasks::function]
437    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
438        ModuleSideEffects::SideEffectFree.cell()
439    }
440}
441
442#[turbo_tasks::value_impl]
443impl Asset for RequireContextAsset {
444    #[turbo_tasks::function]
445    fn content(&self) -> Vc<AssetContent> {
446        unimplemented!()
447    }
448}
449
450#[turbo_tasks::value_impl]
451impl ChunkableModule for RequireContextAsset {
452    #[turbo_tasks::function]
453    async fn as_chunk_item(
454        self: ResolvedVc<Self>,
455        module_graph: ResolvedVc<ModuleGraph>,
456        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
457    ) -> Result<Vc<Box<dyn turbopack_core::chunk::ChunkItem>>> {
458        let this = self.await?;
459        Ok(Vc::upcast(
460            RequireContextChunkItem {
461                module_graph,
462                chunking_context,
463                inner: self,
464
465                origin: this.origin,
466                map: this.map,
467            }
468            .cell(),
469        ))
470    }
471}
472
473#[turbo_tasks::value_impl]
474impl EcmascriptChunkPlaceable for RequireContextAsset {
475    #[turbo_tasks::function]
476    fn get_exports(&self) -> Vc<EcmascriptExports> {
477        EcmascriptExports::Value.cell()
478    }
479}
480
481#[turbo_tasks::value]
482pub struct RequireContextChunkItem {
483    module_graph: ResolvedVc<ModuleGraph>,
484    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
485    inner: ResolvedVc<RequireContextAsset>,
486
487    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
488    map: ResolvedVc<RequireContextMap>,
489}
490
491#[turbo_tasks::value_impl]
492impl OutputAssetsReference for RequireContextChunkItem {}
493
494#[turbo_tasks::value_impl]
495impl EcmascriptChunkItem for RequireContextChunkItem {
496    #[turbo_tasks::function]
497    async fn content(&self) -> Result<Vc<EcmascriptChunkItemContent>> {
498        let map = &*self.map.await?;
499        let minify = self.chunking_context.minify_type().await?;
500
501        let mut context_map = ObjectLit {
502            span: DUMMY_SP,
503            props: vec![],
504        };
505
506        for (key, entry) in map {
507            let pm = PatternMapping::resolve_request(
508                *entry.request,
509                *self.origin,
510                *self.chunking_context,
511                *entry.result,
512                ResolveType::ChunkItem,
513            )
514            .await?;
515
516            let PatternMapping::Single(pm) = &*pm else {
517                continue;
518            };
519
520            let key_expr = Expr::Lit(Lit::Str(entry.origin_relative.as_str().into()));
521
522            let prop = KeyValueProp {
523                key: PropName::Str(key.as_str().into()),
524                value: quote_expr!(
525                    "{ id: () => $id, module: () => $module }",
526                    id: Expr =
527                        pm.create_id(Cow::Borrowed(&key_expr)),
528                    module: Expr =
529                        pm.create_require(Cow::Borrowed(&key_expr)),
530                ),
531            };
532
533            context_map
534                .props
535                .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(prop))));
536        }
537
538        let expr = quote_expr!(
539            "$turbopack_export_value($obj);",
540            turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into(),
541            obj: Expr = Expr::Object(context_map),
542        );
543
544        let module = ast::Module {
545            span: DUMMY_SP,
546            body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
547                span: DUMMY_SP,
548                expr,
549            }))],
550            shebang: None,
551        };
552
553        let source_map: Arc<swc_core::common::SourceMap> = Default::default();
554
555        let mut bytes: Vec<u8> = vec![];
556        let mut wr: JsWriter<'_, &mut Vec<u8>> =
557            JsWriter::new(source_map.clone(), "\n", &mut bytes, None);
558        if matches!(*minify, MinifyType::Minify { .. }) {
559            wr.set_indent_str("");
560        }
561
562        let mut emitter = Emitter {
563            cfg: swc_core::ecma::codegen::Config::default(),
564            cm: source_map.clone(),
565            comments: None,
566            wr,
567        };
568
569        emitter.emit_module(&module)?;
570
571        Ok(EcmascriptChunkItemContent {
572            inner_code: bytes.into(),
573            ..Default::default()
574        }
575        .cell())
576    }
577}
578
579#[turbo_tasks::value_impl]
580impl ChunkItem for RequireContextChunkItem {
581    #[turbo_tasks::function]
582    fn asset_ident(&self) -> Vc<AssetIdent> {
583        self.inner.ident()
584    }
585
586    #[turbo_tasks::function]
587    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
588        *self.chunking_context
589    }
590
591    #[turbo_tasks::function]
592    async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
593        Ok(Vc::upcast(
594            Vc::<EcmascriptChunkType>::default().resolve().await?,
595        ))
596    }
597
598    #[turbo_tasks::function]
599    fn module(&self) -> Vc<Box<dyn Module>> {
600        *ResolvedVc::upcast(self.inner)
601    }
602}