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