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.origin_path().await?.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.origin_path().await?.parent().join(&dir)?,
264            include_subdirs,
265            filter,
266            issue_source,
267            error_mode,
268        )
269        .to_resolved()
270        .await?;
271        let inner = RequireContextAsset {
272            source,
273            origin,
274            map,
275
276            dir: dir.clone(),
277            include_subdirs,
278        }
279        .resolved_cell();
280
281        Ok(RequireContextAssetReference {
282            inner,
283            dir,
284            include_subdirs,
285            issue_source,
286            error_mode,
287        })
288    }
289}
290
291#[turbo_tasks::value_impl]
292impl ModuleReference for RequireContextAssetReference {
293    #[turbo_tasks::function]
294    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
295        *ModuleResolveResult::module(ResolvedVc::upcast(self.inner))
296    }
297
298    fn chunking_type(&self) -> Option<ChunkingType> {
299        Some(ChunkingType::Parallel {
300            inherit_async: false,
301            hoisted: false,
302        })
303    }
304}
305
306impl IntoCodeGenReference for RequireContextAssetReference {
307    fn into_code_gen_reference(
308        self,
309        path: AstPath,
310    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
311        let reference = self.resolved_cell();
312        (
313            ResolvedVc::upcast(reference),
314            CodeGen::RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen {
315                reference,
316                path,
317            }),
318        )
319    }
320}
321
322#[derive(
323    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
324)]
325pub struct RequireContextAssetReferenceCodeGen {
326    path: AstPath,
327    reference: ResolvedVc<RequireContextAssetReference>,
328}
329
330impl RequireContextAssetReferenceCodeGen {
331    pub async fn code_generation(
332        &self,
333        chunking_context: Vc<Box<dyn ChunkingContext>>,
334    ) -> Result<CodeGeneration> {
335        let module_id = self
336            .reference
337            .await?
338            .inner
339            .chunk_item_id(chunking_context)
340            .await?;
341
342        let mut visitors = Vec::new();
343
344        visitors.push(create_visitor!(
345            self.path,
346            visit_mut_expr,
347            |expr: &mut Expr| {
348                if let Expr::Call(_) = expr {
349                    *expr = quote!(
350                        "$turbopack_module_context($turbopack_require($id))" as Expr,
351                        turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
352                        turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
353                        id: Expr = module_id_to_lit(&module_id)
354                    );
355                }
356            }
357        ));
358
359        Ok(CodeGeneration::visitors(visitors))
360    }
361}
362
363#[turbo_tasks::value(transparent)]
364#[derive(ValueToString)]
365#[value_to_string("resolved reference")]
366pub struct ResolvedModuleReference(ResolvedVc<ModuleResolveResult>);
367
368#[turbo_tasks::value_impl]
369impl ModuleReference for ResolvedModuleReference {
370    #[turbo_tasks::function]
371    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
372        *self.0
373    }
374
375    fn chunking_type(&self) -> Option<ChunkingType> {
376        Some(ChunkingType::Parallel {
377            inherit_async: false,
378            hoisted: false,
379        })
380    }
381}
382
383#[turbo_tasks::value]
384pub struct RequireContextAsset {
385    source: ResolvedVc<Box<dyn Source>>,
386
387    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
388    map: ResolvedVc<RequireContextMap>,
389
390    dir: RcStr,
391    include_subdirs: bool,
392}
393
394fn modifier(dir: &RcStr, include_subdirs: bool) -> RcStr {
395    format!(
396        "require.context {}/{}",
397        dir,
398        if include_subdirs { "**" } else { "*" },
399    )
400    .into()
401}
402
403#[turbo_tasks::value_impl]
404impl Module for RequireContextAsset {
405    #[turbo_tasks::function]
406    fn ident(&self) -> Vc<AssetIdent> {
407        self.source
408            .ident()
409            .with_modifier(modifier(&self.dir, self.include_subdirs))
410    }
411
412    #[turbo_tasks::function]
413    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
414        Vc::cell(Some(self.source))
415    }
416
417    #[turbo_tasks::function]
418    async fn references(&self) -> Result<Vc<ModuleReferences>> {
419        let map = &*self.map.await?;
420
421        Ok(Vc::cell(
422            map.iter()
423                .map(|(_, entry)| {
424                    ResolvedVc::upcast(ResolvedVc::<ResolvedModuleReference>::cell(entry.result))
425                })
426                .collect(),
427        ))
428    }
429
430    #[turbo_tasks::function]
431    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
432        ModuleSideEffects::SideEffectFree.cell()
433    }
434}
435
436#[turbo_tasks::value_impl]
437impl ChunkableModule for RequireContextAsset {
438    #[turbo_tasks::function]
439    fn as_chunk_item(
440        self: ResolvedVc<Self>,
441        module_graph: ResolvedVc<ModuleGraph>,
442        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
443    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
444        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
445    }
446}
447
448#[turbo_tasks::value_impl]
449impl EcmascriptChunkPlaceable for RequireContextAsset {
450    #[turbo_tasks::function]
451    fn get_exports(&self) -> Vc<EcmascriptExports> {
452        EcmascriptExports::Value.cell()
453    }
454
455    #[turbo_tasks::function]
456    async fn chunk_item_content(
457        &self,
458        chunking_context: Vc<Box<dyn ChunkingContext>>,
459        _module_graph: Vc<ModuleGraph>,
460        _async_module_info: Option<Vc<AsyncModuleInfo>>,
461        _estimated: bool,
462    ) -> Result<Vc<EcmascriptChunkItemContent>> {
463        let map = &*self.map.await?;
464        let minify = chunking_context.minify_type().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                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
520        let mut bytes: Vec<u8> = vec![];
521        let mut wr: JsWriter<'_, &mut Vec<u8>> =
522            JsWriter::new(source_map.clone(), "\n", &mut bytes, None);
523        if matches!(*minify, MinifyType::Minify { .. }) {
524            wr.set_indent_str("");
525        }
526
527        let mut emitter = Emitter {
528            cfg: swc_core::ecma::codegen::Config::default(),
529            cm: source_map.clone(),
530            comments: None,
531            wr,
532        };
533
534        emitter.emit_module(&module)?;
535
536        Ok(EcmascriptChunkItemContent {
537            inner_code: bytes.into(),
538            ..Default::default()
539        }
540        .cell())
541    }
542}