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