1use std::{borrow::Cow, collections::VecDeque, sync::Arc};
2
3use anyhow::{Result, bail};
4use serde::{Deserialize, Serialize};
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 asset::{Asset, AssetContent},
25 chunk::{
26 ChunkItem, ChunkType, ChunkableModule, ChunkableModuleReference, ChunkingContext,
27 MinifyType, ModuleChunkItemIdExt,
28 },
29 ident::AssetIdent,
30 issue::IssueSource,
31 module::Module,
32 module_graph::ModuleGraph,
33 reference::{ModuleReference, ModuleReferences},
34 reference_type::CommonJsReferenceSubType,
35 resolve::{ModuleResolveResult, 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(FxIndexMap<RcStr, DirListEntry>);
64
65#[turbo_tasks::value_impl]
66impl DirList {
67 #[turbo_tasks::function]
68 pub(crate) fn read(dir: FileSystemPath, recursive: bool, filter: Vc<EsRegex>) -> Vc<Self> {
69 Self::read_internal(dir.clone(), dir, recursive, filter)
70 }
71
72 #[turbo_tasks::function]
73 pub(crate) async fn read_internal(
74 root: FileSystemPath,
75 dir: FileSystemPath,
76 recursive: bool,
77 filter: Vc<EsRegex>,
78 ) -> Result<Vc<Self>> {
79 let root_val = root.clone();
80 let dir_val = dir.clone();
81 let regex = &filter.await?;
82
83 let mut list = FxIndexMap::default();
84
85 let dir_content = dir.read_dir().await?;
86 let entries = match &*dir_content {
87 DirectoryContent::Entries(entries) => Some(entries),
88 DirectoryContent::NotFound => None,
89 };
90
91 for (_, entry) in entries.iter().flat_map(|m| m.iter()) {
92 match entry {
93 DirectoryEntry::File(path) => {
94 if let Some(relative_path) = root_val.get_relative_path_to(path)
95 && regex.is_match(&relative_path)
96 {
97 list.insert(relative_path, DirListEntry::File(path.clone()));
98 }
99 }
100 DirectoryEntry::Directory(path) if recursive => {
101 if let Some(relative_path) = dir_val.get_relative_path_to(path) {
102 list.insert(
103 relative_path,
104 DirListEntry::Dir(
105 DirList::read_internal(
106 root.clone(),
107 path.clone(),
108 recursive,
109 filter,
110 )
111 .to_resolved()
112 .await?,
113 ),
114 );
115 }
116 }
117 _ => {}
119 }
120 }
121
122 list.sort_keys();
123
124 Ok(Vc::cell(list))
125 }
126
127 #[turbo_tasks::function]
128 async fn flatten(self: Vc<Self>) -> Result<Vc<FlatDirList>> {
129 let this = self.await?;
130
131 let mut queue = VecDeque::from([this]);
132
133 let mut list = FxIndexMap::default();
134
135 while let Some(dir) = queue.pop_front() {
136 for (k, entry) in &*dir {
137 match entry {
138 DirListEntry::File(path) => {
139 list.insert(k.clone(), path.clone());
140 }
141 DirListEntry::Dir(d) => {
142 queue.push_back(d.await?);
143 }
144 }
145 }
146 }
147
148 Ok(Vc::cell(list))
149 }
150}
151
152#[turbo_tasks::value(transparent)]
153pub(crate) struct FlatDirList(FxIndexMap<RcStr, FileSystemPath>);
154
155#[turbo_tasks::value_impl]
156impl FlatDirList {
157 #[turbo_tasks::function]
158 pub(crate) fn read(dir: FileSystemPath, recursive: bool, filter: Vc<EsRegex>) -> Vc<Self> {
159 DirList::read(dir, recursive, filter).flatten()
160 }
161}
162
163#[turbo_tasks::value]
164#[derive(Debug)]
165pub struct RequireContextMapEntry {
166 pub origin_relative: RcStr,
167 pub request: ResolvedVc<Request>,
168 pub result: ResolvedVc<ModuleResolveResult>,
169}
170
171#[turbo_tasks::value(transparent)]
173pub struct RequireContextMap(FxIndexMap<RcStr, RequireContextMapEntry>);
174
175#[turbo_tasks::value_impl]
176impl RequireContextMap {
177 #[turbo_tasks::function]
178 pub(crate) async fn generate(
179 origin: Vc<Box<dyn ResolveOrigin>>,
180 dir: FileSystemPath,
181 recursive: bool,
182 filter: Vc<EsRegex>,
183 issue_source: Option<IssueSource>,
184 is_optional: bool,
185 ) -> Result<Vc<Self>> {
186 let origin_path = origin.origin_path().await?.parent();
187
188 let list = &*FlatDirList::read(dir, recursive, filter).await?;
189
190 let mut map = FxIndexMap::default();
191
192 for (context_relative, path) in list {
193 let Some(origin_relative) = origin_path.get_relative_path_to(path) else {
194 bail!("invariant error: this was already checked in `list_dir`");
195 };
196
197 let request = Request::parse(origin_relative.clone().into())
198 .to_resolved()
199 .await?;
200 let result = cjs_resolve(
201 origin,
202 *request,
203 CommonJsReferenceSubType::Undefined,
204 issue_source,
205 is_optional,
206 )
207 .to_resolved()
208 .await?;
209
210 map.insert(
211 context_relative.clone(),
212 RequireContextMapEntry {
213 origin_relative,
214 request,
215 result,
216 },
217 );
218 }
219
220 Ok(Vc::cell(map))
221 }
222}
223
224#[turbo_tasks::value]
227#[derive(Hash, Debug)]
228pub struct RequireContextAssetReference {
229 pub inner: ResolvedVc<RequireContextAsset>,
230 pub dir: RcStr,
231 pub include_subdirs: bool,
232
233 pub issue_source: Option<IssueSource>,
234 pub in_try: bool,
235}
236
237impl RequireContextAssetReference {
238 pub async fn new(
239 source: ResolvedVc<Box<dyn Source>>,
240 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
241 dir: RcStr,
242 include_subdirs: bool,
243 filter: Vc<EsRegex>,
244 issue_source: Option<IssueSource>,
245 in_try: bool,
246 ) -> Result<Self> {
247 let map = RequireContextMap::generate(
248 *origin,
249 origin.origin_path().await?.parent().join(&dir)?,
250 include_subdirs,
251 filter,
252 issue_source,
253 in_try,
254 )
255 .to_resolved()
256 .await?;
257 let inner = RequireContextAsset {
258 source,
259 origin,
260 map,
261
262 dir: dir.clone(),
263 include_subdirs,
264 }
265 .resolved_cell();
266
267 Ok(RequireContextAssetReference {
268 inner,
269 dir,
270 include_subdirs,
271 issue_source,
272 in_try,
273 })
274 }
275}
276
277#[turbo_tasks::value_impl]
278impl ModuleReference for RequireContextAssetReference {
279 #[turbo_tasks::function]
280 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
281 *ModuleResolveResult::module(ResolvedVc::upcast(self.inner))
282 }
283}
284
285#[turbo_tasks::value_impl]
286impl ValueToString for RequireContextAssetReference {
287 #[turbo_tasks::function]
288 fn to_string(&self) -> Vc<RcStr> {
289 Vc::cell(
290 format!(
291 "require.context {}/{}",
292 self.dir,
293 if self.include_subdirs { "**" } else { "*" },
294 )
295 .into(),
296 )
297 }
298}
299
300#[turbo_tasks::value_impl]
301impl ChunkableModuleReference for RequireContextAssetReference {}
302
303impl IntoCodeGenReference for RequireContextAssetReference {
304 fn into_code_gen_reference(
305 self,
306 path: AstPath,
307 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
308 let reference = self.resolved_cell();
309 (
310 ResolvedVc::upcast(reference),
311 CodeGen::RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen {
312 reference,
313 path,
314 }),
315 )
316 }
317}
318
319#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
320pub struct RequireContextAssetReferenceCodeGen {
321 path: AstPath,
322 reference: ResolvedVc<RequireContextAssetReference>,
323}
324
325impl RequireContextAssetReferenceCodeGen {
326 pub async fn code_generation(
327 &self,
328 chunking_context: Vc<Box<dyn ChunkingContext>>,
329 ) -> Result<CodeGeneration> {
330 let module_id = self
331 .reference
332 .await?
333 .inner
334 .chunk_item_id(Vc::upcast(chunking_context))
335 .await?;
336
337 let mut visitors = Vec::new();
338
339 visitors.push(create_visitor!(
340 self.path,
341 visit_mut_expr,
342 |expr: &mut Expr| {
343 if let Expr::Call(_) = expr {
344 *expr = quote!(
345 "$turbopack_module_context($turbopack_require($id))" as Expr,
346 turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
347 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
348 id: Expr = module_id_to_lit(&module_id)
349 );
350 }
351 }
352 ));
353
354 Ok(CodeGeneration::visitors(visitors))
355 }
356}
357
358#[turbo_tasks::value(transparent)]
359pub struct ResolvedModuleReference(ResolvedVc<ModuleResolveResult>);
360
361#[turbo_tasks::value_impl]
362impl ModuleReference for ResolvedModuleReference {
363 #[turbo_tasks::function]
364 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
365 *self.0
366 }
367}
368
369#[turbo_tasks::value_impl]
370impl ValueToString for ResolvedModuleReference {
371 #[turbo_tasks::function]
372 fn to_string(&self) -> Vc<RcStr> {
373 Vc::cell(rcstr!("resolved reference"))
374 }
375}
376
377#[turbo_tasks::value_impl]
378impl ChunkableModuleReference for ResolvedModuleReference {}
379
380#[turbo_tasks::value]
381pub struct RequireContextAsset {
382 source: ResolvedVc<Box<dyn Source>>,
383
384 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
385 map: ResolvedVc<RequireContextMap>,
386
387 dir: RcStr,
388 include_subdirs: bool,
389}
390
391fn modifier(dir: &RcStr, include_subdirs: bool) -> RcStr {
392 format!(
393 "require.context {}/{}",
394 dir,
395 if include_subdirs { "**" } else { "*" },
396 )
397 .into()
398}
399
400#[turbo_tasks::value_impl]
401impl Module for RequireContextAsset {
402 #[turbo_tasks::function]
403 fn ident(&self) -> Vc<AssetIdent> {
404 self.source
405 .ident()
406 .with_modifier(modifier(&self.dir, self.include_subdirs))
407 }
408
409 #[turbo_tasks::function]
410 async fn references(&self) -> Result<Vc<ModuleReferences>> {
411 let map = &*self.map.await?;
412
413 Ok(Vc::cell(
414 map.iter()
415 .map(|(_, entry)| {
416 ResolvedVc::upcast(ResolvedVc::<ResolvedModuleReference>::cell(entry.result))
417 })
418 .collect(),
419 ))
420 }
421}
422
423#[turbo_tasks::value_impl]
424impl Asset for RequireContextAsset {
425 #[turbo_tasks::function]
426 fn content(&self) -> Vc<AssetContent> {
427 unimplemented!()
428 }
429}
430
431#[turbo_tasks::value_impl]
432impl ChunkableModule for RequireContextAsset {
433 #[turbo_tasks::function]
434 async fn as_chunk_item(
435 self: ResolvedVc<Self>,
436 module_graph: ResolvedVc<ModuleGraph>,
437 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
438 ) -> Result<Vc<Box<dyn turbopack_core::chunk::ChunkItem>>> {
439 let this = self.await?;
440 Ok(Vc::upcast(
441 RequireContextChunkItem {
442 module_graph,
443 chunking_context,
444 inner: self,
445
446 origin: this.origin,
447 map: this.map,
448 }
449 .cell(),
450 ))
451 }
452}
453
454#[turbo_tasks::value_impl]
455impl EcmascriptChunkPlaceable for RequireContextAsset {
456 #[turbo_tasks::function]
457 fn get_exports(&self) -> Vc<EcmascriptExports> {
458 EcmascriptExports::Value.cell()
459 }
460}
461
462#[turbo_tasks::value]
463pub struct RequireContextChunkItem {
464 module_graph: ResolvedVc<ModuleGraph>,
465 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
466 inner: ResolvedVc<RequireContextAsset>,
467
468 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
469 map: ResolvedVc<RequireContextMap>,
470}
471
472#[turbo_tasks::value_impl]
473impl EcmascriptChunkItem for RequireContextChunkItem {
474 #[turbo_tasks::function]
475 async fn content(&self) -> Result<Vc<EcmascriptChunkItemContent>> {
476 let map = &*self.map.await?;
477 let minify = self.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 *ResolvedVc::upcast(self.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}
556
557#[turbo_tasks::value_impl]
558impl ChunkItem for RequireContextChunkItem {
559 #[turbo_tasks::function]
560 fn asset_ident(&self) -> Vc<AssetIdent> {
561 self.inner.ident()
562 }
563
564 #[turbo_tasks::function]
565 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
566 *ResolvedVc::upcast(self.chunking_context)
567 }
568
569 #[turbo_tasks::function]
570 async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
571 Ok(Vc::upcast(
572 Vc::<EcmascriptChunkType>::default().resolve().await?,
573 ))
574 }
575
576 #[turbo_tasks::function]
577 fn module(&self) -> Vc<Box<dyn Module>> {
578 *ResolvedVc::upcast(self.inner)
579 }
580}