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