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