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