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;
18use turbo_tasks::{
19 FxIndexMap, NonLocalValue, ResolvedVc, Value, 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 ModuleChunkItemIdExt,
28 },
29 ident::AssetIdent,
30 issue::IssueSource,
31 module::Module,
32 module_graph::ModuleGraph,
33 reference::{ModuleReference, ModuleReferences},
34 resolve::{ModuleResolveResult, origin::ResolveOrigin, parse::Request},
35 source::Source,
36};
37use turbopack_resolve::ecmascript::cjs_resolve;
38
39use crate::{
40 EcmascriptChunkPlaceable,
41 chunk::{
42 EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkType, EcmascriptExports,
43 },
44 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
45 create_visitor,
46 references::{
47 AstPath,
48 pattern_mapping::{PatternMapping, ResolveType},
49 },
50 runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_MODULE_CONTEXT, TURBOPACK_REQUIRE},
51 utils::module_id_to_lit,
52};
53
54#[turbo_tasks::value]
55#[derive(Debug)]
56pub(crate) enum DirListEntry {
57 File(ResolvedVc<FileSystemPath>),
58 Dir(ResolvedVc<DirList>),
59}
60
61#[turbo_tasks::value(transparent)]
62pub(crate) struct DirList(FxIndexMap<RcStr, DirListEntry>);
63
64#[turbo_tasks::value_impl]
65impl DirList {
66 #[turbo_tasks::function]
67 pub(crate) fn read(dir: Vc<FileSystemPath>, recursive: bool, filter: Vc<EsRegex>) -> Vc<Self> {
68 Self::read_internal(dir, dir, recursive, filter)
69 }
70
71 #[turbo_tasks::function]
72 pub(crate) async fn read_internal(
73 root: Vc<FileSystemPath>,
74 dir: Vc<FileSystemPath>,
75 recursive: bool,
76 filter: Vc<EsRegex>,
77 ) -> Result<Vc<Self>> {
78 let root_val = &root.await?;
79 let dir_val = &dir.await?;
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.await?) {
94 if regex.is_match(&relative_path) {
95 list.insert(relative_path, DirListEntry::File(*path));
96 }
97 }
98 }
99 DirectoryEntry::Directory(path) if recursive => {
100 if let Some(relative_path) = dir_val.get_relative_path_to(&*path.await?) {
101 list.insert(
102 relative_path,
103 DirListEntry::Dir(
104 DirList::read_internal(root, **path, recursive, filter)
105 .to_resolved()
106 .await?,
107 ),
108 );
109 }
110 }
111 _ => {}
113 }
114 }
115
116 list.sort_keys();
117
118 Ok(Vc::cell(list))
119 }
120
121 #[turbo_tasks::function]
122 async fn flatten(self: Vc<Self>) -> Result<Vc<FlatDirList>> {
123 let this = self.await?;
124
125 let mut queue = VecDeque::from([this]);
126
127 let mut list = FxIndexMap::default();
128
129 while let Some(dir) = queue.pop_front() {
130 for (k, entry) in &*dir {
131 match entry {
132 DirListEntry::File(path) => {
133 list.insert(k.clone(), *path);
134 }
135 DirListEntry::Dir(d) => {
136 queue.push_back(d.await?);
137 }
138 }
139 }
140 }
141
142 Ok(Vc::cell(list))
143 }
144}
145
146#[turbo_tasks::value(transparent)]
147pub(crate) struct FlatDirList(FxIndexMap<RcStr, ResolvedVc<FileSystemPath>>);
148
149#[turbo_tasks::value_impl]
150impl FlatDirList {
151 #[turbo_tasks::function]
152 pub(crate) fn read(dir: Vc<FileSystemPath>, recursive: bool, filter: Vc<EsRegex>) -> Vc<Self> {
153 DirList::read(dir, recursive, filter).flatten()
154 }
155}
156
157#[turbo_tasks::value]
158#[derive(Debug)]
159pub struct RequireContextMapEntry {
160 pub origin_relative: RcStr,
161 pub request: ResolvedVc<Request>,
162 pub result: ResolvedVc<ModuleResolveResult>,
163}
164
165#[turbo_tasks::value(transparent)]
167pub struct RequireContextMap(FxIndexMap<RcStr, RequireContextMapEntry>);
168
169#[turbo_tasks::value_impl]
170impl RequireContextMap {
171 #[turbo_tasks::function]
172 pub(crate) async fn generate(
173 origin: Vc<Box<dyn ResolveOrigin>>,
174 dir: Vc<FileSystemPath>,
175 recursive: bool,
176 filter: Vc<EsRegex>,
177 issue_source: Option<IssueSource>,
178 is_optional: bool,
179 ) -> Result<Vc<Self>> {
180 let origin_path = &*origin.origin_path().parent().await?;
181
182 let list = &*FlatDirList::read(dir, recursive, filter).await?;
183
184 let mut map = FxIndexMap::default();
185
186 for (context_relative, path) in list {
187 let Some(origin_relative) = origin_path.get_relative_path_to(&*path.await?) else {
188 bail!("invariant error: this was already checked in `list_dir`");
189 };
190
191 let request = Request::parse(Value::new(origin_relative.clone().into()))
192 .to_resolved()
193 .await?;
194 let result = cjs_resolve(origin, *request, issue_source.clone(), is_optional)
195 .to_resolved()
196 .await?;
197
198 map.insert(
199 context_relative.clone(),
200 RequireContextMapEntry {
201 origin_relative,
202 request,
203 result,
204 },
205 );
206 }
207
208 Ok(Vc::cell(map))
209 }
210}
211
212#[turbo_tasks::value]
215#[derive(Hash, Debug)]
216pub struct RequireContextAssetReference {
217 pub inner: ResolvedVc<RequireContextAsset>,
218 pub dir: RcStr,
219 pub include_subdirs: bool,
220
221 pub issue_source: Option<IssueSource>,
222 pub in_try: bool,
223}
224
225impl RequireContextAssetReference {
226 pub async fn new(
227 source: ResolvedVc<Box<dyn Source>>,
228 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
229 dir: RcStr,
230 include_subdirs: bool,
231 filter: Vc<EsRegex>,
232 issue_source: Option<IssueSource>,
233 in_try: bool,
234 ) -> Result<Self> {
235 let map = RequireContextMap::generate(
236 *origin,
237 origin.origin_path().parent().join(dir.clone()),
238 include_subdirs,
239 filter,
240 issue_source.clone(),
241 in_try,
242 )
243 .to_resolved()
244 .await?;
245 let inner = RequireContextAsset {
246 source,
247 origin,
248 map,
249
250 dir: dir.clone(),
251 include_subdirs,
252 }
253 .resolved_cell();
254
255 Ok(RequireContextAssetReference {
256 inner,
257 dir,
258 include_subdirs,
259 issue_source,
260 in_try,
261 })
262 }
263}
264
265#[turbo_tasks::value_impl]
266impl ModuleReference for RequireContextAssetReference {
267 #[turbo_tasks::function]
268 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
269 *ModuleResolveResult::module(ResolvedVc::upcast(self.inner))
270 }
271}
272
273#[turbo_tasks::value_impl]
274impl ValueToString for RequireContextAssetReference {
275 #[turbo_tasks::function]
276 async fn to_string(&self) -> Vc<RcStr> {
277 Vc::cell(
278 format!(
279 "require.context {}/{}",
280 self.dir,
281 if self.include_subdirs { "**" } else { "*" },
282 )
283 .into(),
284 )
285 }
286}
287
288#[turbo_tasks::value_impl]
289impl ChunkableModuleReference for RequireContextAssetReference {}
290
291impl IntoCodeGenReference for RequireContextAssetReference {
292 fn into_code_gen_reference(
293 self,
294 path: AstPath,
295 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
296 let reference = self.resolved_cell();
297 (
298 ResolvedVc::upcast(reference),
299 CodeGen::RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen {
300 reference,
301 path,
302 }),
303 )
304 }
305}
306
307#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
308pub struct RequireContextAssetReferenceCodeGen {
309 path: AstPath,
310 reference: ResolvedVc<RequireContextAssetReference>,
311}
312
313impl RequireContextAssetReferenceCodeGen {
314 pub async fn code_generation(
315 &self,
316 _module_graph: Vc<ModuleGraph>,
317 chunking_context: Vc<Box<dyn ChunkingContext>>,
318 ) -> Result<CodeGeneration> {
319 let module_id = self
320 .reference
321 .await?
322 .inner
323 .chunk_item_id(Vc::upcast(chunking_context))
324 .await?;
325
326 let mut visitors = Vec::new();
327
328 visitors.push(create_visitor!(self.path, visit_mut_expr(expr: &mut Expr) {
329 if let Expr::Call(_) = expr {
330 *expr = quote!(
331 "$turbopack_module_context($turbopack_require($id))" as Expr,
332 turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
333 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
334 id: Expr = module_id_to_lit(&module_id)
335 );
336 }
337 }));
338
339 Ok(CodeGeneration::visitors(visitors))
340 }
341}
342
343#[turbo_tasks::value(transparent)]
344pub struct ResolvedModuleReference(ResolvedVc<ModuleResolveResult>);
345
346#[turbo_tasks::value_impl]
347impl ModuleReference for ResolvedModuleReference {
348 #[turbo_tasks::function]
349 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
350 *self.0
351 }
352}
353
354#[turbo_tasks::value_impl]
355impl ValueToString for ResolvedModuleReference {
356 #[turbo_tasks::function]
357 fn to_string(&self) -> Vc<RcStr> {
358 Vc::cell("resolved reference".into())
359 }
360}
361
362#[turbo_tasks::value_impl]
363impl ChunkableModuleReference for ResolvedModuleReference {}
364
365#[turbo_tasks::value]
366pub struct RequireContextAsset {
367 source: ResolvedVc<Box<dyn Source>>,
368
369 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
370 map: ResolvedVc<RequireContextMap>,
371
372 dir: RcStr,
373 include_subdirs: bool,
374}
375
376#[turbo_tasks::function]
377fn modifier(dir: RcStr, include_subdirs: bool) -> Vc<RcStr> {
378 Vc::cell(
379 format!(
380 "require.context {}/{}",
381 dir,
382 if include_subdirs { "**" } else { "*" },
383 )
384 .into(),
385 )
386}
387
388#[turbo_tasks::value_impl]
389impl Module for RequireContextAsset {
390 #[turbo_tasks::function]
391 fn ident(&self) -> Vc<AssetIdent> {
392 self.source
393 .ident()
394 .with_modifier(modifier(self.dir.clone(), self.include_subdirs))
395 }
396
397 #[turbo_tasks::function]
398 async fn references(&self) -> Result<Vc<ModuleReferences>> {
399 let map = &*self.map.await?;
400
401 Ok(Vc::cell(
402 map.iter()
403 .map(|(_, entry)| {
404 ResolvedVc::upcast(ResolvedVc::<ResolvedModuleReference>::cell(entry.result))
405 })
406 .collect(),
407 ))
408 }
409}
410
411#[turbo_tasks::value_impl]
412impl Asset for RequireContextAsset {
413 #[turbo_tasks::function]
414 fn content(&self) -> Vc<AssetContent> {
415 unimplemented!()
416 }
417}
418
419#[turbo_tasks::value_impl]
420impl ChunkableModule for RequireContextAsset {
421 #[turbo_tasks::function]
422 async fn as_chunk_item(
423 self: ResolvedVc<Self>,
424 module_graph: ResolvedVc<ModuleGraph>,
425 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
426 ) -> Result<Vc<Box<dyn turbopack_core::chunk::ChunkItem>>> {
427 let this = self.await?;
428 Ok(Vc::upcast(
429 RequireContextChunkItem {
430 module_graph,
431 chunking_context,
432 inner: self,
433
434 origin: this.origin,
435 map: this.map,
436 }
437 .cell(),
438 ))
439 }
440}
441
442#[turbo_tasks::value_impl]
443impl EcmascriptChunkPlaceable for RequireContextAsset {
444 #[turbo_tasks::function]
445 fn get_exports(&self) -> Vc<EcmascriptExports> {
446 EcmascriptExports::Value.cell()
447 }
448}
449
450#[turbo_tasks::value]
451pub struct RequireContextChunkItem {
452 module_graph: ResolvedVc<ModuleGraph>,
453 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
454 inner: ResolvedVc<RequireContextAsset>,
455
456 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
457 map: ResolvedVc<RequireContextMap>,
458}
459
460#[turbo_tasks::value_impl]
461impl EcmascriptChunkItem for RequireContextChunkItem {
462 #[turbo_tasks::function]
463 async fn content(&self) -> Result<Vc<EcmascriptChunkItemContent>> {
464 let map = &*self.map.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 *ResolvedVc::upcast(self.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 let mut bytes: Vec<u8> = vec![];
520 let mut emitter = Emitter {
521 cfg: swc_core::ecma::codegen::Config::default(),
522 cm: source_map.clone(),
523 comments: None,
524 wr: JsWriter::new(source_map, "\n", &mut bytes, None),
525 };
526
527 emitter.emit_module(&module)?;
528
529 Ok(EcmascriptChunkItemContent {
530 inner_code: bytes.into(),
531 ..Default::default()
532 }
533 .cell())
534 }
535}
536
537#[turbo_tasks::value_impl]
538impl ChunkItem for RequireContextChunkItem {
539 #[turbo_tasks::function]
540 fn asset_ident(&self) -> Vc<AssetIdent> {
541 self.inner.ident()
542 }
543
544 #[turbo_tasks::function]
545 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
546 *ResolvedVc::upcast(self.chunking_context)
547 }
548
549 #[turbo_tasks::function]
550 async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
551 Ok(Vc::upcast(
552 Vc::<EcmascriptChunkType>::default().resolve().await?,
553 ))
554 }
555
556 #[turbo_tasks::function]
557 fn module(&self) -> Vc<Box<dyn Module>> {
558 *ResolvedVc::upcast(self.inner)
559 }
560}