turbopack_ecmascript/references/
require_context.rs

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