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 _ => {}
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#[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.into_trait_ref().await?.origin_path().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#[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
264 .into_trait_ref()
265 .await?
266 .origin_path()
267 .parent()
268 .join(&dir)?,
269 include_subdirs,
270 filter,
271 issue_source,
272 error_mode,
273 )
274 .to_resolved()
275 .await?;
276 let inner = RequireContextAsset {
277 source,
278 origin,
279 map,
280
281 dir: dir.clone(),
282 include_subdirs,
283 }
284 .resolved_cell();
285
286 Ok(RequireContextAssetReference {
287 inner,
288 dir,
289 include_subdirs,
290 issue_source,
291 error_mode,
292 })
293 }
294}
295
296#[turbo_tasks::value_impl]
297impl ModuleReference for RequireContextAssetReference {
298 #[turbo_tasks::function]
299 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
300 *ModuleResolveResult::module(ResolvedVc::upcast(self.inner))
301 }
302
303 fn chunking_type(&self) -> Option<ChunkingType> {
304 Some(ChunkingType::Parallel {
305 inherit_async: false,
306 hoisted: false,
307 })
308 }
309
310 fn source(&self) -> Option<IssueSource> {
311 self.issue_source
312 }
313}
314
315impl IntoCodeGenReference for RequireContextAssetReference {
316 fn into_code_gen_reference(
317 self,
318 path: AstPath,
319 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
320 let reference = self.resolved_cell();
321 (
322 ResolvedVc::upcast(reference),
323 CodeGen::RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen {
324 reference,
325 path,
326 }),
327 )
328 }
329}
330
331#[derive(
332 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
333)]
334pub struct RequireContextAssetReferenceCodeGen {
335 path: AstPath,
336 reference: ResolvedVc<RequireContextAssetReference>,
337}
338
339impl RequireContextAssetReferenceCodeGen {
340 pub async fn code_generation(
341 &self,
342 chunking_context: Vc<Box<dyn ChunkingContext>>,
343 ) -> Result<CodeGeneration> {
344 let module_id = self
345 .reference
346 .await?
347 .inner
348 .chunk_item_id(chunking_context)
349 .await?;
350
351 let mut visitors = Vec::new();
352
353 visitors.push(create_visitor!(
354 self.path,
355 visit_mut_expr,
356 |expr: &mut Expr| {
357 if let Expr::Call(_) = expr {
358 *expr = quote!(
359 "$turbopack_module_context($turbopack_require($id))" as Expr,
360 turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
361 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
362 id: Expr = module_id_to_lit(&module_id)
363 );
364 }
365 }
366 ));
367
368 Ok(CodeGeneration::visitors(visitors))
369 }
370}
371
372#[turbo_tasks::value(transparent)]
373#[derive(ValueToString)]
374#[value_to_string("resolved reference")]
375pub struct ResolvedModuleReference(ResolvedVc<ModuleResolveResult>);
376
377#[turbo_tasks::value_impl]
378impl ModuleReference for ResolvedModuleReference {
379 #[turbo_tasks::function]
380 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
381 *self.0
382 }
383
384 fn chunking_type(&self) -> Option<ChunkingType> {
385 Some(ChunkingType::Parallel {
386 inherit_async: false,
387 hoisted: false,
388 })
389 }
390}
391
392#[turbo_tasks::value]
393pub struct RequireContextAsset {
394 source: ResolvedVc<Box<dyn Source>>,
395
396 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
397 map: ResolvedVc<RequireContextMap>,
398
399 dir: RcStr,
400 include_subdirs: bool,
401}
402
403fn modifier(dir: &RcStr, include_subdirs: bool) -> RcStr {
404 format!(
405 "require.context {}/{}",
406 dir,
407 if include_subdirs { "**" } else { "*" },
408 )
409 .into()
410}
411
412#[turbo_tasks::value_impl]
413impl Module for RequireContextAsset {
414 #[turbo_tasks::function]
415 async fn ident(&self) -> Result<Vc<AssetIdent>> {
416 Ok(self
417 .source
418 .ident()
419 .owned()
420 .await?
421 .with_modifier(modifier(&self.dir, self.include_subdirs))
422 .into_vc())
423 }
424
425 #[turbo_tasks::function]
426 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
427 Vc::cell(Some(self.source))
428 }
429
430 #[turbo_tasks::function]
431 async fn references(&self) -> Result<Vc<ModuleReferences>> {
432 let map = &*self.map.await?;
433
434 Ok(Vc::cell(
435 map.iter()
436 .map(|(_, entry)| {
437 ResolvedVc::upcast(ResolvedVc::<ResolvedModuleReference>::cell(entry.result))
438 })
439 .collect(),
440 ))
441 }
442
443 #[turbo_tasks::function]
444 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
445 ModuleSideEffects::SideEffectFree.cell()
446 }
447}
448
449#[turbo_tasks::value_impl]
450impl ChunkableModule for RequireContextAsset {
451 #[turbo_tasks::function]
452 fn as_chunk_item(
453 self: ResolvedVc<Self>,
454 module_graph: ResolvedVc<ModuleGraph>,
455 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
456 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
457 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
458 }
459}
460
461#[turbo_tasks::value_impl]
462impl EcmascriptChunkPlaceable for RequireContextAsset {
463 #[turbo_tasks::function]
464 fn get_exports(&self) -> Vc<EcmascriptExports> {
465 EcmascriptExports::Value.cell()
466 }
467
468 #[turbo_tasks::function]
469 async fn chunk_item_content(
470 &self,
471 chunking_context: Vc<Box<dyn ChunkingContext>>,
472 _module_graph: Vc<ModuleGraph>,
473 _async_module_info: Option<Vc<AsyncModuleInfo>>,
474 _estimated: bool,
475 ) -> Result<Vc<EcmascriptChunkItemContent>> {
476 let map = &*self.map.await?;
477 let minify = chunking_context.minify_type().await?;
478
479 let mut context_map = ObjectLit {
480 span: DUMMY_SP,
481 props: vec![],
482 };
483
484 for (key, entry) in map {
485 let pm = PatternMapping::resolve_request(
486 *entry.request,
487 *self.origin,
488 chunking_context,
489 *entry.result,
490 ResolveType::ChunkItem,
491 )
492 .await?;
493
494 let PatternMapping::Single(pm) = &*pm else {
495 continue;
496 };
497
498 let key_expr = Expr::Lit(Lit::Str(entry.origin_relative.as_str().into()));
499
500 let prop = KeyValueProp {
501 key: PropName::Str(key.as_str().into()),
502 value: quote_expr!(
503 "{ id: () => $id, module: () => $module }",
504 id: Expr =
505 pm.create_id(Cow::Borrowed(&key_expr)),
506 module: Expr =
507 pm.create_require(Cow::Borrowed(&key_expr)),
508 ),
509 };
510
511 context_map
512 .props
513 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(prop))));
514 }
515
516 let expr = quote_expr!(
517 "$turbopack_export_value($obj);",
518 turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into(),
519 obj: Expr = Expr::Object(context_map),
520 );
521
522 let module = ast::Module {
523 span: DUMMY_SP,
524 body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
525 span: DUMMY_SP,
526 expr,
527 }))],
528 shebang: None,
529 };
530
531 let source_map: Arc<swc_core::common::SourceMap> = Default::default();
532
533 let mut bytes: Vec<u8> = vec![];
534 let mut wr: JsWriter<'_, &mut Vec<u8>> =
535 JsWriter::new(source_map.clone(), "\n", &mut bytes, None);
536 if matches!(*minify, MinifyType::Minify { .. }) {
537 wr.set_indent_str("");
538 }
539
540 let mut emitter = Emitter {
541 cfg: swc_core::ecma::codegen::Config::default(),
542 cm: source_map.clone(),
543 comments: None,
544 wr,
545 };
546
547 emitter.emit_module(&module)?;
548
549 Ok(EcmascriptChunkItemContent {
550 inner_code: bytes.into(),
551 ..Default::default()
552 }
553 .cell())
554 }
555}