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