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