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