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 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 _ => {}
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#[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#[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}