Skip to main content

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;
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    chunk::{
25        AsyncModuleInfo, ChunkableModule, ChunkingContext, ChunkingType, MinifyType,
26        ModuleChunkItemIdExt,
27    },
28    ident::AssetIdent,
29    issue::IssueSource,
30    module::{Module, ModuleSideEffects},
31    module_graph::ModuleGraph,
32    reference::{ModuleReference, ModuleReferences},
33    reference_type::CommonJsReferenceSubType,
34    resolve::{ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin, parse::Request},
35    source::Source,
36};
37use turbopack_resolve::ecmascript::cjs_resolve;
38
39use crate::{
40    EcmascriptChunkPlaceable,
41    chunk::{EcmascriptChunkItemContent, EcmascriptExports, ecmascript_chunk_item},
42    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
43    create_visitor,
44    references::{
45        AstPath,
46        pattern_mapping::{PatternMapping, ResolveType},
47    },
48    runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_MODULE_CONTEXT, TURBOPACK_REQUIRE},
49    utils::module_id_to_lit,
50};
51
52#[turbo_tasks::value]
53#[derive(Debug)]
54pub(crate) enum DirListEntry {
55    File(FileSystemPath),
56    Dir(ResolvedVc<DirList>),
57}
58
59#[turbo_tasks::value(transparent)]
60pub(crate) struct DirList(
61    #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, DirListEntry>,
62);
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(
153    #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, FileSystemPath>,
154);
155
156#[turbo_tasks::value_impl]
157impl FlatDirList {
158    #[turbo_tasks::function]
159    pub(crate) fn read(dir: FileSystemPath, recursive: bool, filter: Vc<EsRegex>) -> Vc<Self> {
160        DirList::read(dir, recursive, filter).flatten()
161    }
162}
163
164#[turbo_tasks::value]
165#[derive(Debug)]
166pub struct RequireContextMapEntry {
167    pub origin_relative: RcStr,
168    pub request: ResolvedVc<Request>,
169    pub result: ResolvedVc<ModuleResolveResult>,
170}
171
172/// The resolved context map for a `require.context(..)` call.
173#[turbo_tasks::value(transparent)]
174pub struct RequireContextMap(
175    #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, RequireContextMapEntry>,
176);
177
178#[turbo_tasks::value_impl]
179impl RequireContextMap {
180    #[turbo_tasks::function]
181    pub(crate) async fn generate(
182        origin: Vc<Box<dyn ResolveOrigin>>,
183        dir: FileSystemPath,
184        recursive: bool,
185        filter: Vc<EsRegex>,
186        issue_source: Option<IssueSource>,
187        error_mode: ResolveErrorMode,
188    ) -> Result<Vc<Self>> {
189        let origin_path = origin.into_trait_ref().await?.origin_path().parent();
190
191        let list = &*FlatDirList::read(dir, recursive, filter).await?;
192
193        let mut map = FxIndexMap::default();
194
195        for (context_relative, path) in list {
196            let Some(origin_relative) = origin_path.get_relative_path_to(path) else {
197                bail!("invariant error: this was already checked in `list_dir`");
198            };
199
200            let request = Request::parse(origin_relative.clone().into())
201                .to_resolved()
202                .await?;
203            let result = cjs_resolve(
204                origin,
205                *request,
206                CommonJsReferenceSubType::Undefined,
207                issue_source,
208                error_mode,
209            )
210            .to_resolved()
211            .await?;
212
213            map.insert(
214                context_relative.clone(),
215                RequireContextMapEntry {
216                    origin_relative,
217                    request,
218                    result,
219                },
220            );
221        }
222
223        Ok(Vc::cell(map))
224    }
225}
226
227/// A reference for `require.context()`, will replace it with an inlined map
228/// wrapped in `__turbopack_module_context__`;
229#[turbo_tasks::value]
230#[derive(Hash, Debug, ValueToString)]
231pub struct RequireContextAssetReference {
232    pub inner: ResolvedVc<RequireContextAsset>,
233    pub dir: RcStr,
234    pub include_subdirs: bool,
235
236    pub issue_source: Option<IssueSource>,
237    pub error_mode: ResolveErrorMode,
238}
239
240impl std::fmt::Display for RequireContextAssetReference {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        write!(
243            f,
244            "require.context {}/{}",
245            self.dir,
246            if self.include_subdirs { "**" } else { "*" },
247        )
248    }
249}
250
251impl RequireContextAssetReference {
252    pub async fn new(
253        source: ResolvedVc<Box<dyn Source>>,
254        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
255        dir: RcStr,
256        include_subdirs: bool,
257        filter: Vc<EsRegex>,
258        issue_source: Option<IssueSource>,
259        error_mode: ResolveErrorMode,
260    ) -> Result<Self> {
261        let map = RequireContextMap::generate(
262            *origin,
263            origin
264                .into_trait_ref()
265                .await?
266                .origin_path()
267                .parent()
268                .join(&dir)?,
269            include_subdirs,
270            filter,
271            issue_source,
272            error_mode,
273        )
274        .to_resolved()
275        .await?;
276        let inner = RequireContextAsset {
277            source,
278            origin,
279            map,
280
281            dir: dir.clone(),
282            include_subdirs,
283        }
284        .resolved_cell();
285
286        Ok(RequireContextAssetReference {
287            inner,
288            dir,
289            include_subdirs,
290            issue_source,
291            error_mode,
292        })
293    }
294}
295
296#[turbo_tasks::value_impl]
297impl ModuleReference for RequireContextAssetReference {
298    #[turbo_tasks::function]
299    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
300        *ModuleResolveResult::module(ResolvedVc::upcast(self.inner))
301    }
302
303    fn chunking_type(&self) -> Option<ChunkingType> {
304        Some(ChunkingType::Parallel {
305            inherit_async: false,
306            hoisted: false,
307        })
308    }
309
310    fn source(&self) -> Option<IssueSource> {
311        self.issue_source
312    }
313}
314
315impl IntoCodeGenReference for RequireContextAssetReference {
316    fn into_code_gen_reference(
317        self,
318        path: AstPath,
319    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
320        let reference = self.resolved_cell();
321        (
322            ResolvedVc::upcast(reference),
323            CodeGen::RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen {
324                reference,
325                path,
326            }),
327        )
328    }
329}
330
331#[derive(
332    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
333)]
334pub struct RequireContextAssetReferenceCodeGen {
335    path: AstPath,
336    reference: ResolvedVc<RequireContextAssetReference>,
337}
338
339impl RequireContextAssetReferenceCodeGen {
340    pub async fn code_generation(
341        &self,
342        chunking_context: Vc<Box<dyn ChunkingContext>>,
343    ) -> Result<CodeGeneration> {
344        let module_id = self
345            .reference
346            .await?
347            .inner
348            .chunk_item_id(chunking_context)
349            .await?;
350
351        let mut visitors = Vec::new();
352
353        visitors.push(create_visitor!(
354            self.path,
355            visit_mut_expr,
356            |expr: &mut Expr| {
357                if let Expr::Call(_) = expr {
358                    *expr = quote!(
359                        "$turbopack_module_context($turbopack_require($id))" as Expr,
360                        turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
361                        turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
362                        id: Expr = module_id_to_lit(&module_id)
363                    );
364                }
365            }
366        ));
367
368        Ok(CodeGeneration::visitors(visitors))
369    }
370}
371
372#[turbo_tasks::value(transparent)]
373#[derive(ValueToString)]
374#[value_to_string("resolved reference")]
375pub struct ResolvedModuleReference(ResolvedVc<ModuleResolveResult>);
376
377#[turbo_tasks::value_impl]
378impl ModuleReference for ResolvedModuleReference {
379    #[turbo_tasks::function]
380    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
381        *self.0
382    }
383
384    fn chunking_type(&self) -> Option<ChunkingType> {
385        Some(ChunkingType::Parallel {
386            inherit_async: false,
387            hoisted: false,
388        })
389    }
390}
391
392#[turbo_tasks::value]
393pub struct RequireContextAsset {
394    source: ResolvedVc<Box<dyn Source>>,
395
396    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
397    map: ResolvedVc<RequireContextMap>,
398
399    dir: RcStr,
400    include_subdirs: bool,
401}
402
403fn modifier(dir: &RcStr, include_subdirs: bool) -> RcStr {
404    format!(
405        "require.context {}/{}",
406        dir,
407        if include_subdirs { "**" } else { "*" },
408    )
409    .into()
410}
411
412#[turbo_tasks::value_impl]
413impl Module for RequireContextAsset {
414    #[turbo_tasks::function]
415    async fn ident(&self) -> Result<Vc<AssetIdent>> {
416        Ok(self
417            .source
418            .ident()
419            .owned()
420            .await?
421            .with_modifier(modifier(&self.dir, self.include_subdirs))
422            .into_vc())
423    }
424
425    #[turbo_tasks::function]
426    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
427        Vc::cell(Some(self.source))
428    }
429
430    #[turbo_tasks::function]
431    async fn references(&self) -> Result<Vc<ModuleReferences>> {
432        let map = &*self.map.await?;
433
434        Ok(Vc::cell(
435            map.iter()
436                .map(|(_, entry)| {
437                    ResolvedVc::upcast(ResolvedVc::<ResolvedModuleReference>::cell(entry.result))
438                })
439                .collect(),
440        ))
441    }
442
443    #[turbo_tasks::function]
444    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
445        ModuleSideEffects::SideEffectFree.cell()
446    }
447}
448
449#[turbo_tasks::value_impl]
450impl ChunkableModule for RequireContextAsset {
451    #[turbo_tasks::function]
452    fn as_chunk_item(
453        self: ResolvedVc<Self>,
454        module_graph: ResolvedVc<ModuleGraph>,
455        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
456    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
457        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
458    }
459}
460
461#[turbo_tasks::value_impl]
462impl EcmascriptChunkPlaceable for RequireContextAsset {
463    #[turbo_tasks::function]
464    fn get_exports(&self) -> Vc<EcmascriptExports> {
465        EcmascriptExports::Value.cell()
466    }
467
468    #[turbo_tasks::function]
469    async fn chunk_item_content(
470        &self,
471        chunking_context: Vc<Box<dyn ChunkingContext>>,
472        _module_graph: Vc<ModuleGraph>,
473        _async_module_info: Option<Vc<AsyncModuleInfo>>,
474        _estimated: bool,
475    ) -> Result<Vc<EcmascriptChunkItemContent>> {
476        let map = &*self.map.await?;
477        let minify = 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                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}