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