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    async fn ident(&self) -> Result<Vc<AssetIdent>> {
407        Ok(self
408            .source
409            .ident()
410            .owned()
411            .await?
412            .with_modifier(modifier(&self.dir, self.include_subdirs))
413            .into_vc())
414    }
415
416    #[turbo_tasks::function]
417    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
418        Vc::cell(Some(self.source))
419    }
420
421    #[turbo_tasks::function]
422    async fn references(&self) -> Result<Vc<ModuleReferences>> {
423        let map = &*self.map.await?;
424
425        Ok(Vc::cell(
426            map.iter()
427                .map(|(_, entry)| {
428                    ResolvedVc::upcast(ResolvedVc::<ResolvedModuleReference>::cell(entry.result))
429                })
430                .collect(),
431        ))
432    }
433
434    #[turbo_tasks::function]
435    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
436        ModuleSideEffects::SideEffectFree.cell()
437    }
438}
439
440#[turbo_tasks::value_impl]
441impl ChunkableModule for RequireContextAsset {
442    #[turbo_tasks::function]
443    fn as_chunk_item(
444        self: ResolvedVc<Self>,
445        module_graph: ResolvedVc<ModuleGraph>,
446        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
447    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
448        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
449    }
450}
451
452#[turbo_tasks::value_impl]
453impl EcmascriptChunkPlaceable for RequireContextAsset {
454    #[turbo_tasks::function]
455    fn get_exports(&self) -> Vc<EcmascriptExports> {
456        EcmascriptExports::Value.cell()
457    }
458
459    #[turbo_tasks::function]
460    async fn chunk_item_content(
461        &self,
462        chunking_context: Vc<Box<dyn ChunkingContext>>,
463        _module_graph: Vc<ModuleGraph>,
464        _async_module_info: Option<Vc<AsyncModuleInfo>>,
465        _estimated: bool,
466    ) -> Result<Vc<EcmascriptChunkItemContent>> {
467        let map = &*self.map.await?;
468        let minify = chunking_context.minify_type().await?;
469
470        let mut context_map = ObjectLit {
471            span: DUMMY_SP,
472            props: vec![],
473        };
474
475        for (key, entry) in map {
476            let pm = PatternMapping::resolve_request(
477                *entry.request,
478                *self.origin,
479                chunking_context,
480                *entry.result,
481                ResolveType::ChunkItem,
482            )
483            .await?;
484
485            let PatternMapping::Single(pm) = &*pm else {
486                continue;
487            };
488
489            let key_expr = Expr::Lit(Lit::Str(entry.origin_relative.as_str().into()));
490
491            let prop = KeyValueProp {
492                key: PropName::Str(key.as_str().into()),
493                value: quote_expr!(
494                    "{ id: () => $id, module: () => $module }",
495                    id: Expr =
496                        pm.create_id(Cow::Borrowed(&key_expr)),
497                    module: Expr =
498                        pm.create_require(Cow::Borrowed(&key_expr)),
499                ),
500            };
501
502            context_map
503                .props
504                .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(prop))));
505        }
506
507        let expr = quote_expr!(
508            "$turbopack_export_value($obj);",
509            turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into(),
510            obj: Expr = Expr::Object(context_map),
511        );
512
513        let module = ast::Module {
514            span: DUMMY_SP,
515            body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
516                span: DUMMY_SP,
517                expr,
518            }))],
519            shebang: None,
520        };
521
522        let source_map: Arc<swc_core::common::SourceMap> = Default::default();
523
524        let mut bytes: Vec<u8> = vec![];
525        let mut wr: JsWriter<'_, &mut Vec<u8>> =
526            JsWriter::new(source_map.clone(), "\n", &mut bytes, None);
527        if matches!(*minify, MinifyType::Minify { .. }) {
528            wr.set_indent_str("");
529        }
530
531        let mut emitter = Emitter {
532            cfg: swc_core::ecma::codegen::Config::default(),
533            cm: source_map.clone(),
534            comments: None,
535            wr,
536        };
537
538        emitter.emit_module(&module)?;
539
540        Ok(EcmascriptChunkItemContent {
541            inner_code: bytes.into(),
542            ..Default::default()
543        }
544        .cell())
545    }
546}