1use std::{
2 borrow::Cow,
3 collections::BTreeMap,
4 fmt::{Display, Formatter, Write},
5 future::Future,
6 iter::{empty, once},
7 sync::LazyLock,
8};
9
10use anyhow::{Result, bail};
11use bincode::{Decode, Encode};
12use either::Either;
13use rustc_hash::{FxHashMap, FxHashSet};
14use serde::{Deserialize, Serialize};
15use smallvec::SmallVec;
16use tracing::{Instrument, Level};
17use turbo_frozenmap::{FrozenMap, FrozenSet};
18use turbo_rcstr::{RcStr, rcstr};
19use turbo_tasks::{
20 FxIndexMap, JoinIterExt, NonLocalValue, ReadRef, ResolvedVc, TryFlatJoinIterExt,
21 TryJoinIterExt, ValueToString, ValueToStringRef, Vc,
22};
23use turbo_tasks_fs::{FileSystemEntryType, FileSystemPath};
24use turbo_unix_path::normalize_request;
25
26use crate::{
27 context::AssetContext,
28 data_uri_source::DataUriSource,
29 file_source::FileSource,
30 issue::{
31 Issue, IssueExt, IssueSource, module::emit_unknown_module_type_error,
32 resolve::ResolvingIssue,
33 },
34 module::Module,
35 package_json::{PackageJsonIssue, read_package_json},
36 raw_module::RawModule,
37 reference_type::ReferenceType,
38 resolve::{
39 error::{handle_resolve_error, resolve_error_severity},
40 node::{node_cjs_resolve_options, node_esm_resolve_options},
41 options::{
42 ConditionValue, ImportMapResult, ResolveInPackage, ResolveIntoPackage, ResolveModules,
43 ResolveModulesOptions, ResolveOptions, resolve_modules_options,
44 },
45 origin::ResolveOrigin,
46 parse::{Request, stringify_data_uri},
47 pattern::{Pattern, PatternMatch, read_matches},
48 plugin::{AfterResolvePlugin, AfterResolvePluginCondition, BeforeResolvePlugin},
49 remap::{ExportImport, ExportsField, ImportsField},
50 },
51 source::Source,
52};
53
54mod alias_map;
55pub mod error;
56pub mod node;
57pub mod options;
58pub mod origin;
59pub mod parse;
60pub mod pattern;
61pub mod plugin;
62pub(crate) mod remap;
63
64pub use alias_map::{
65 AliasKey, AliasMap, AliasMapIntoIter, AliasMapLookupIterator, AliasMatch, AliasPattern,
66 AliasTemplate,
67};
68pub use remap::{
69 ReplacedSubpathValue, ReplacedSubpathValueResult, ReplacedSubpathValueResultType,
70 ResolveAliasMap, SubpathValue, TerminalState,
71};
72
73#[turbo_tasks::value(shared, task_input)]
75#[derive(Debug, Clone, Copy, Default, Hash)]
76pub enum ResolveErrorMode {
77 #[default]
79 Error,
80 Warn,
82 Ignore,
84}
85
86type AfterResolvePluginWithCondition = (
88 ResolvedVc<Box<dyn AfterResolvePlugin>>,
89 ReadRef<AfterResolvePluginCondition>,
90);
91
92#[turbo_tasks::value(shared)]
93#[derive(Clone, Debug)]
94pub enum ModuleResolveResultItem {
95 Module(ResolvedVc<Box<dyn Module>>),
96 External {
97 name: RcStr,
99 ty: ExternalType,
100 },
101 Unknown(ResolvedVc<Box<dyn Source>>),
103 Ignore,
105 Error(ResolvedVc<Box<dyn Issue>>),
107 Empty,
109 Custom(u8),
110 Duplicate(usize),
117}
118
119impl ModuleResolveResultItem {
120 async fn as_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
124 Ok(match *self {
125 ModuleResolveResultItem::Module(module) => Some(module),
126 ModuleResolveResultItem::Unknown(source) => {
127 emit_unknown_module_type_error(*source).await?;
128 None
129 }
130 ModuleResolveResultItem::Error(_err) => {
131 None
133 }
134 _ => None,
135 })
136 }
137}
138
139#[turbo_tasks::value(shared)]
140#[derive(Clone, Debug, Hash, Default, Serialize, Deserialize)]
141pub struct BindingUsage {
142 pub import: ImportUsage,
143 pub export: ExportUsage,
144}
145
146#[turbo_tasks::value_impl]
147impl BindingUsage {
148 #[turbo_tasks::function]
149 pub fn all() -> Vc<Self> {
150 Self::default().cell()
151 }
152}
153
154#[turbo_tasks::value(shared)]
156#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
157pub enum ImportUsage {
158 #[default]
161 TopLevel,
162 Exports(FrozenSet<RcStr>),
168}
169
170#[turbo_tasks::value]
172#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
173pub enum ExportUsage {
174 Named(RcStr),
175 PartialNamespaceObject(SmallVec<[RcStr; 1]>),
177 #[default]
179 All,
180 Evaluation,
182 Passthrough {
188 namespace_object_may_escape: bool,
191 },
192}
193
194impl Display for ExportUsage {
195 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
196 match self {
197 ExportUsage::Named(name) => write!(f, "export {name}"),
198 ExportUsage::PartialNamespaceObject(names) => {
199 write!(f, "exports ")?;
200 for (i, name) in names.iter().enumerate() {
201 if i > 0 {
202 write!(f, ", ")?;
203 }
204 write!(f, "{name}")?;
205 }
206 Ok(())
207 }
208 ExportUsage::All => write!(f, "all"),
209 ExportUsage::Evaluation => write!(f, "evaluation"),
210 ExportUsage::Passthrough {
211 namespace_object_may_escape,
212 ..
213 } => write!(
214 f,
215 "passthrough{}",
216 if *namespace_object_may_escape {
217 " namespace"
218 } else {
219 ""
220 }
221 ),
222 }
223 }
224}
225
226#[turbo_tasks::value_impl]
227impl ExportUsage {
228 #[turbo_tasks::function]
229 pub fn all() -> Vc<Self> {
230 Self::All.cell()
231 }
232
233 #[turbo_tasks::function]
234 pub fn evaluation() -> Vc<Self> {
235 Self::Evaluation.cell()
236 }
237
238 #[turbo_tasks::function]
239 pub fn named(name: RcStr) -> Vc<Self> {
240 Self::Named(name).cell()
241 }
242
243 #[turbo_tasks::function]
244 pub fn partial_namespace_object(names: Vec<RcStr>) -> Vc<Self> {
245 Self::PartialNamespaceObject(names.into_iter().collect()).cell()
246 }
247
248 #[turbo_tasks::function]
249 pub fn passthrough(namespace_object_may_escape: bool) -> Vc<Self> {
250 Self::Passthrough {
251 namespace_object_may_escape,
252 }
253 .cell()
254 }
255}
256
257#[turbo_tasks::value(shared)]
258#[derive(Clone, Debug)]
259pub struct ModuleResolveResult {
260 pub primary: Box<[(RequestKey, ModuleResolveResultItem)]>,
261 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
264}
265
266impl ModuleResolveResult {
267 pub fn unresolvable() -> ResolvedVc<Self> {
268 ModuleResolveResult {
269 primary: Default::default(),
270 affecting_sources: Default::default(),
271 }
272 .resolved_cell()
273 }
274
275 pub fn module(module: ResolvedVc<Box<dyn Module>>) -> ResolvedVc<Self> {
276 Self::module_with_key(RequestKey::default(), module)
277 }
278
279 pub fn module_with_key(
280 request_key: RequestKey,
281 module: ResolvedVc<Box<dyn Module>>,
282 ) -> ResolvedVc<Self> {
283 ModuleResolveResult {
284 primary: vec![(request_key, ModuleResolveResultItem::Module(module))]
285 .into_boxed_slice(),
286 affecting_sources: Default::default(),
287 }
288 .resolved_cell()
289 }
290
291 pub fn modules(
292 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
293 ) -> ResolvedVc<Self> {
294 let mut primary: Vec<_> = modules
295 .into_iter()
296 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
297 .collect();
298 Self::mark_duplicates(&mut primary);
299 ModuleResolveResult {
300 primary: primary.into_boxed_slice(),
301 affecting_sources: Default::default(),
302 }
303 .resolved_cell()
304 }
305
306 pub fn modules_with_affecting_sources(
307 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
308 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
309 ) -> ResolvedVc<Self> {
310 let mut primary: Vec<_> = modules
311 .into_iter()
312 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
313 .collect();
314 Self::mark_duplicates(&mut primary);
315 ModuleResolveResult {
316 primary: primary.into_boxed_slice(),
317 affecting_sources: affecting_sources.into_boxed_slice(),
318 }
319 .resolved_cell()
320 }
321}
322
323impl ModuleResolveResult {
324 fn mark_duplicates(primary: &mut [(RequestKey, ModuleResolveResultItem)]) {
328 if primary.len() <= 1 {
329 return;
330 }
331 let mut seen_modules = FxHashMap::default();
333 for (i, (_, item)) in primary.iter_mut().enumerate() {
334 if let ModuleResolveResultItem::Module(m) = *item {
335 if let Some(&first) = seen_modules.get(&m) {
336 *item = ModuleResolveResultItem::Duplicate(first);
337 } else {
338 seen_modules.insert(m, i);
339 }
340 }
341 }
342 }
343
344 pub fn primary_modules_raw_iter(
346 &self,
347 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
348 self.primary.iter().filter_map(|(_, item)| match *item {
349 ModuleResolveResultItem::Module(a) => Some(a),
350 _ => None,
351 })
352 }
353
354 pub async fn primary_modules(&self) -> Result<SmallVec<[ResolvedVc<Box<dyn Module>>; 2]>> {
358 self.primary
359 .iter()
360 .map(async |(_, item)| item.as_module().await)
361 .join()
362 .await
363 .into_iter()
364 .filter_map(Result::transpose)
365 .collect()
366 }
367
368 pub async fn first_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
370 for (_, item) in self.primary.iter() {
371 if let Some(module) = item.as_module().await? {
372 return Ok(Some(module));
373 }
374 }
375 Ok(None)
376 }
377
378 pub fn affecting_sources_iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
379 self.affecting_sources.iter().copied()
380 }
381
382 pub fn is_unresolvable(&self) -> bool {
383 self.primary.is_empty()
384 }
385
386 pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
387 self.primary.iter().filter_map(|i| match &i.1 {
388 ModuleResolveResultItem::Error(e) => Some(*e),
389 _ => None,
390 })
391 }
392}
393
394pub struct ModuleResolveResultBuilder {
395 pub primary: FxIndexMap<RequestKey, ModuleResolveResultItem>,
396 pub affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
397}
398
399impl From<ModuleResolveResultBuilder> for ModuleResolveResult {
400 fn from(v: ModuleResolveResultBuilder) -> Self {
401 let mut primary: Vec<_> = v.primary.into_iter().collect();
402 Self::mark_duplicates(&mut primary);
403 ModuleResolveResult {
404 primary: primary.into_boxed_slice(),
405 affecting_sources: v.affecting_sources.into_boxed_slice(),
406 }
407 }
408}
409
410fn expand_duplicate<'a>(
414 source: &'a [(RequestKey, ModuleResolveResultItem)],
415 item: &'a ModuleResolveResultItem,
416) -> &'a ModuleResolveResultItem {
417 if let ModuleResolveResultItem::Duplicate(i) = *item {
418 &source[i].1
419 } else {
420 item
421 }
422}
423
424impl From<ModuleResolveResult> for ModuleResolveResultBuilder {
425 fn from(v: ModuleResolveResult) -> Self {
426 let primary = v
432 .primary
433 .iter()
434 .map(|(k, item)| (k.clone(), expand_duplicate(&v.primary, item).clone()))
435 .collect();
436 ModuleResolveResultBuilder {
437 primary,
438 affecting_sources: v.affecting_sources.into_vec(),
439 }
440 }
441}
442impl ModuleResolveResultBuilder {
443 pub fn merge_alternatives(&mut self, other: &ModuleResolveResult) {
444 for (k, v) in other.primary.iter() {
448 if !self.primary.contains_key(k) {
449 self.primary
450 .insert(k.clone(), expand_duplicate(&other.primary, v).clone());
451 }
452 }
453 let set = self
454 .affecting_sources
455 .iter()
456 .copied()
457 .collect::<FxHashSet<_>>();
458 self.affecting_sources.extend(
459 other
460 .affecting_sources
461 .iter()
462 .filter(|source| !set.contains(source))
463 .copied(),
464 );
465 }
466}
467
468#[turbo_tasks::value_impl]
469impl ModuleResolveResult {
470 #[turbo_tasks::function]
471 pub async fn alternatives(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
472 if results.len() == 1 {
473 return Ok(results.into_iter().next().unwrap());
474 }
475 let mut iter = results.into_iter().try_join().await?.into_iter();
476 if let Some(current) = iter.next() {
477 let mut current: ModuleResolveResultBuilder = ReadRef::into_owned(current).into();
478 for result in iter {
479 let other = &*result;
481 current.merge_alternatives(other);
482 }
483 Ok(Self::cell(current.into()))
484 } else {
485 Ok(*ModuleResolveResult::unresolvable())
486 }
487 }
488
489 #[turbo_tasks::function]
492 pub async fn concat(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
493 if results.len() == 1 {
494 return Ok(results.into_iter().next().unwrap());
495 }
496 let mut primary = Vec::new();
497 let mut affecting_sources = Vec::new();
498 let mut seen_sources = FxHashSet::default();
499 for result in results.into_iter().try_join().await? {
500 primary.extend(
501 result.primary.iter().map(|(key, item)| {
502 (key.clone(), expand_duplicate(&result.primary, item).clone())
503 }),
504 );
505 for source in result.affecting_sources.iter().copied() {
506 if seen_sources.insert(source) {
507 affecting_sources.push(source);
508 }
509 }
510 }
511 Self::mark_duplicates(&mut primary);
512 Ok(Self::cell(Self {
513 primary: primary.into_boxed_slice(),
514 affecting_sources: affecting_sources.into_boxed_slice(),
515 }))
516 }
517}
518
519#[turbo_tasks::task_input]
520#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
521pub enum ExternalTraced {
522 Untraced,
523 Traced,
524}
525
526impl Display for ExternalTraced {
527 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
528 match self {
529 ExternalTraced::Untraced => write!(f, "untraced"),
530 ExternalTraced::Traced => write!(f, "traced"),
531 }
532 }
533}
534
535#[turbo_tasks::task_input]
536#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encode, Decode)]
537pub enum ExternalType {
538 Url,
539 CommonJs,
540 EcmaScriptModule,
541 Global,
542 Script,
543}
544
545impl Display for ExternalType {
546 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
547 match self {
548 ExternalType::CommonJs => write!(f, "commonjs"),
549 ExternalType::EcmaScriptModule => write!(f, "esm"),
550 ExternalType::Url => write!(f, "url"),
551 ExternalType::Global => write!(f, "global"),
552 ExternalType::Script => write!(f, "script"),
553 }
554 }
555}
556
557#[turbo_tasks::value(shared)]
558#[derive(Debug, Clone)]
559pub enum ResolveResultItem {
560 Source(ResolvedVc<Box<dyn Source>>),
561 External {
562 name: RcStr,
564 ty: ExternalType,
565 traced: ExternalTraced,
566 target: Option<FileSystemPath>,
569 },
570 Ignore,
572 Error(ResolvedVc<Box<dyn Issue>>),
574 Empty,
576 Custom(u8),
577}
578
579#[derive(Clone, Debug, Default, Hash)]
586#[turbo_tasks::value(task_input)]
587pub struct RequestKey {
588 pub request: Option<RcStr>,
589 pub conditions: FrozenMap<RcStr, bool>,
590}
591
592impl Display for RequestKey {
593 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
594 if let Some(request) = &self.request {
595 write!(f, "{request}")?;
596 } else {
597 write!(f, "<default>")?;
598 }
599 if !self.conditions.is_empty() {
600 write!(f, " (")?;
601 for (i, (k, v)) in self.conditions.iter().enumerate() {
602 if i > 0 {
603 write!(f, ", ")?;
604 }
605 write!(f, "{k}={v}")?;
606 }
607 write!(f, ")")?;
608 }
609 Ok(())
610 }
611}
612
613impl RequestKey {
614 pub fn new(request: RcStr) -> Self {
615 RequestKey {
616 request: Some(request),
617 ..Default::default()
618 }
619 }
620}
621
622#[turbo_tasks::value(shared)]
623#[derive(Clone)]
624pub struct ResolveResult {
625 pub primary: Box<[(RequestKey, ResolveResultItem)]>,
626 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
629}
630
631#[turbo_tasks::value_impl]
632impl ValueToString for ResolveResult {
633 #[turbo_tasks::function]
634 async fn to_string(&self) -> Result<Vc<RcStr>> {
635 let mut result = String::new();
636 if self.is_unresolvable() {
637 result.push_str("unresolvable");
638 }
639 for (i, (request, item)) in self.primary.iter().enumerate() {
640 if i > 0 {
641 result.push_str(", ");
642 }
643 write!(result, "{request} -> ").unwrap();
644 match item {
645 ResolveResultItem::Source(a) => {
646 result.push_str(&a.ident().to_string().await?);
647 }
648 ResolveResultItem::External {
649 name: s,
650 ty,
651 traced,
652 target,
653 } => {
654 result.push_str("external ");
655 result.push_str(s);
656 write!(
657 result,
658 " ({ty}, {traced}, {:?})",
659 if let Some(target) = target {
660 Some(target.to_string_ref().await?)
661 } else {
662 None
663 }
664 )?;
665 }
666 ResolveResultItem::Ignore => {
667 result.push_str("ignore");
668 }
669 ResolveResultItem::Empty => {
670 result.push_str("empty");
671 }
672 ResolveResultItem::Error(_) => {
673 result.push_str("error");
674 }
675 ResolveResultItem::Custom(_) => {
676 result.push_str("custom");
677 }
678 }
679 result.push('\n');
680 }
681 if !self.affecting_sources.is_empty() {
682 result.push_str(" (affecting sources: ");
683 for (i, source) in self.affecting_sources.iter().enumerate() {
684 if i > 0 {
685 result.push_str(", ");
686 }
687 result.push_str(&source.ident().to_string().await?);
688 }
689 result.push(')');
690 }
691 Ok(Vc::cell(result.into()))
692 }
693}
694
695impl ResolveResult {
696 pub fn unresolvable() -> Self {
697 ResolveResult {
698 primary: Default::default(),
699 affecting_sources: Default::default(),
700 }
701 }
702
703 pub fn unresolvable_with_affecting_sources(
704 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
705 ) -> Self {
706 ResolveResult {
707 primary: Default::default(),
708 affecting_sources: affecting_sources.into_boxed_slice(),
709 }
710 }
711
712 pub fn primary(result: ResolveResultItem) -> Self {
713 Self::primary_with_key(RequestKey::default(), result)
714 }
715
716 pub fn primary_with_key(request_key: RequestKey, result: ResolveResultItem) -> Self {
717 ResolveResult {
718 primary: vec![(request_key, result)].into_boxed_slice(),
719 affecting_sources: Default::default(),
720 }
721 }
722
723 pub fn primary_with_affecting_sources(
724 request_key: RequestKey,
725 result: ResolveResultItem,
726 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
727 ) -> Self {
728 ResolveResult {
729 primary: vec![(request_key, result)].into_boxed_slice(),
730 affecting_sources: affecting_sources.into_boxed_slice(),
731 }
732 }
733
734 pub fn source(source: ResolvedVc<Box<dyn Source>>) -> Self {
735 Self::source_with_key(RequestKey::default(), source)
736 }
737
738 fn source_with_key(request_key: RequestKey, source: ResolvedVc<Box<dyn Source>>) -> Self {
739 ResolveResult {
740 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
741 affecting_sources: Default::default(),
742 }
743 }
744
745 fn source_with_affecting_sources(
746 request_key: RequestKey,
747 source: ResolvedVc<Box<dyn Source>>,
748 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
749 ) -> Self {
750 ResolveResult {
751 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
752 affecting_sources: affecting_sources.into_boxed_slice(),
753 }
754 }
755
756 pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
757 self.primary.iter().filter_map(|i| match &i.1 {
758 ResolveResultItem::Error(e) => Some(*e),
759 _ => None,
760 })
761 }
762}
763
764impl ResolveResult {
765 pub fn get_affecting_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
768 self.affecting_sources.iter().copied()
769 }
770
771 pub fn is_unresolvable(&self) -> bool {
772 self.primary.is_empty()
773 }
774
775 pub fn first_source(&self) -> Option<ResolvedVc<Box<dyn Source>>> {
776 self.primary.iter().find_map(|(_, item)| {
777 if let &ResolveResultItem::Source(a) = item {
778 Some(a)
779 } else {
780 None
781 }
782 })
783 }
784
785 pub fn primary_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> {
786 self.primary.iter().filter_map(|(_, item)| {
787 if let &ResolveResultItem::Source(a) = item {
788 Some(a)
789 } else {
790 None
791 }
792 })
793 }
794
795 pub async fn map_module<A, AF>(&self, source_fn: A) -> Result<ModuleResolveResult>
796 where
797 A: Fn(ResolvedVc<Box<dyn Source>>) -> AF,
798 AF: Future<Output = Result<ModuleResolveResultItem>>,
799 {
800 Ok(ModuleResolveResult {
801 primary: self
802 .primary
803 .iter()
804 .map(|(request, item)| {
805 let asset_fn = &source_fn;
806 let request = request.clone();
807 let item = item.clone();
808 async move {
809 Ok((
810 request,
811 match item {
812 ResolveResultItem::Source(source) => asset_fn(source).await?,
813 ResolveResultItem::External {
814 name,
815 ty,
816 traced,
817 target,
818 } => {
819 if traced == ExternalTraced::Traced || target.is_some() {
820 bail!("map_module doesn't handle traced externals");
822 }
823 ModuleResolveResultItem::External { name, ty }
824 }
825 ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
826 ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
827 ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
828 ResolveResultItem::Custom(u8) => {
829 ModuleResolveResultItem::Custom(u8)
830 }
831 },
832 ))
833 }
834 })
835 .try_join()
836 .await?
837 .into_iter()
838 .collect(),
839 affecting_sources: self.affecting_sources.clone(),
840 })
841 }
842
843 pub async fn map_primary_items<A, AF>(&self, item_fn: A) -> Result<ModuleResolveResult>
844 where
845 A: Fn(ResolveResultItem) -> AF,
846 AF: Future<Output = Result<ModuleResolveResultItem>>,
847 {
848 Ok(ModuleResolveResult {
849 primary: self
850 .primary
851 .iter()
852 .map(|(request, item)| {
853 let asset_fn = &item_fn;
854 let request = request.clone();
855 let item = item.clone();
856 async move { Ok((request, asset_fn(item).await?)) }
857 })
858 .try_join()
859 .await?
860 .into_iter()
861 .collect(),
862 affecting_sources: self.affecting_sources.clone(),
863 })
864 }
865
866 fn with_request_ref(&self, request: RcStr) -> Self {
869 let new_primary = self
870 .primary
871 .iter()
872 .map(|(k, v)| {
873 (
874 RequestKey {
875 request: Some(request.clone()),
876 conditions: k.conditions.clone(),
877 },
878 v.clone(),
879 )
880 })
881 .collect();
882 ResolveResult {
883 primary: new_primary,
884 affecting_sources: self.affecting_sources.clone(),
885 }
886 }
887
888 pub fn with_conditions(&self, new_conditions: &[(RcStr, bool)]) -> Self {
889 let primary = self
890 .primary
891 .iter()
892 .map(|(k, v)| {
893 (
894 RequestKey {
895 request: k.request.clone(),
896 conditions: k.conditions.extend(new_conditions.iter().cloned()),
897 },
898 v.clone(),
899 )
900 })
901 .collect::<FxIndexMap<_, _>>() .into_iter()
903 .collect();
904 ResolveResult {
905 primary,
906 affecting_sources: self.affecting_sources.clone(),
907 }
908 }
909}
910
911struct ResolveResultBuilder {
912 primary: FxIndexMap<RequestKey, ResolveResultItem>,
913 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
914}
915
916impl From<ResolveResultBuilder> for ResolveResult {
917 fn from(v: ResolveResultBuilder) -> Self {
918 ResolveResult {
919 primary: v.primary.into_iter().collect(),
920 affecting_sources: v.affecting_sources.into_boxed_slice(),
921 }
922 }
923}
924impl From<ResolveResult> for ResolveResultBuilder {
925 fn from(v: ResolveResult) -> Self {
926 ResolveResultBuilder {
927 primary: IntoIterator::into_iter(v.primary).collect(),
928 affecting_sources: v.affecting_sources.into_vec(),
929 }
930 }
931}
932impl ResolveResultBuilder {
933 pub fn merge_alternatives(&mut self, other: &ResolveResult) {
934 for (k, v) in other.primary.iter() {
935 if !self.primary.contains_key(k) {
936 self.primary.insert(k.clone(), v.clone());
937 }
938 }
939 let set = self
940 .affecting_sources
941 .iter()
942 .copied()
943 .collect::<FxHashSet<_>>();
944 self.affecting_sources.extend(
945 other
946 .affecting_sources
947 .iter()
948 .filter(|source| !set.contains(source))
949 .copied(),
950 );
951 }
952}
953
954#[turbo_tasks::value_impl]
955impl ResolveResult {
956 #[turbo_tasks::function]
957 pub async fn as_raw_module_result(&self) -> Result<Vc<ModuleResolveResult>> {
958 Ok(self
959 .map_module(async |asset| {
960 Ok(ModuleResolveResultItem::Module(ResolvedVc::upcast(
961 RawModule::new(*asset).to_resolved().await?,
962 )))
963 })
964 .await?
965 .cell())
966 }
967
968 #[turbo_tasks::function]
969 fn with_affecting_sources(
970 &self,
971 sources: Vec<ResolvedVc<Box<dyn Source>>>,
972 ) -> Result<Vc<Self>> {
973 Ok(Self {
974 primary: self.primary.clone(),
975 affecting_sources: self
976 .affecting_sources
977 .iter()
978 .copied()
979 .chain(sources)
980 .collect(),
981 }
982 .cell())
983 }
984
985 #[turbo_tasks::function]
986 async fn alternatives(results: Vec<Vc<ResolveResult>>) -> Result<Vc<Self>> {
987 if results.len() == 1 {
988 return Ok(results.into_iter().next().unwrap());
989 }
990 let mut iter = results.into_iter().try_join().await?.into_iter();
991 if let Some(current) = iter.next() {
992 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
993 for result in iter {
994 let other = &*result;
996 current.merge_alternatives(other);
997 }
998 Ok(Self::cell(current.into()))
999 } else {
1000 Ok(ResolveResult::unresolvable().cell())
1001 }
1002 }
1003
1004 #[turbo_tasks::function]
1005 async fn alternatives_with_affecting_sources(
1006 results: Vec<Vc<ResolveResult>>,
1007 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1008 ) -> Result<Vc<Self>> {
1009 debug_assert!(
1010 !affecting_sources.is_empty(),
1011 "Caller should not call this function if there are no affecting sources"
1012 );
1013 if results.len() == 1 {
1014 return Ok(results
1015 .into_iter()
1016 .next()
1017 .unwrap()
1018 .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()));
1019 }
1020 let mut iter = results.into_iter().try_join().await?.into_iter();
1021 if let Some(current) = iter.next() {
1022 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
1023 for result in iter {
1024 let other = &*result;
1026 current.merge_alternatives(other);
1027 }
1028 current.affecting_sources.extend(affecting_sources);
1029 Ok(Self::cell(current.into()))
1030 } else {
1031 Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell())
1032 }
1033 }
1034
1035 #[turbo_tasks::function]
1040 fn with_replaced_request_key(
1041 &self,
1042 old_request_key: RcStr,
1043 new_request_key: RcStr,
1044 ) -> Result<Vc<Self>> {
1045 let new_primary = self
1046 .primary
1047 .iter()
1048 .filter_map(|(k, v)| {
1049 let remaining = k.request.as_ref()?.strip_prefix(&*old_request_key)?;
1050 Some((
1051 RequestKey {
1052 request: Some(format!("{new_request_key}{remaining}").into()),
1053 conditions: k.conditions.clone(),
1054 },
1055 v.clone(),
1056 ))
1057 })
1058 .collect();
1059 Ok(ResolveResult {
1060 primary: new_primary,
1061 affecting_sources: self.affecting_sources.clone(),
1062 }
1063 .cell())
1064 }
1065
1066 #[turbo_tasks::function]
1070 fn with_stripped_request_key_prefix(&self, prefix: RcStr) -> Result<Vc<Self>> {
1071 let new_primary = self
1072 .primary
1073 .iter()
1074 .filter_map(|(k, v)| {
1075 let remaining = k.request.as_ref()?.strip_prefix(&*prefix)?;
1076 Some((
1077 RequestKey {
1078 request: Some(remaining.into()),
1079 conditions: k.conditions.clone(),
1080 },
1081 v.clone(),
1082 ))
1083 })
1084 .collect();
1085 Ok(ResolveResult {
1086 primary: new_primary,
1087 affecting_sources: self.affecting_sources.clone(),
1088 }
1089 .cell())
1090 }
1091
1092 #[turbo_tasks::function]
1097 async fn with_replaced_request_key_pattern(
1098 &self,
1099 old_request_key: Vc<Pattern>,
1100 request_key: Vc<Pattern>,
1101 ) -> Result<Vc<Self>> {
1102 let old_request_key = &*old_request_key.await?;
1103 let request_key = &*request_key.await?;
1104
1105 let new_primary = self
1106 .primary
1107 .iter()
1108 .map(|(k, v)| {
1109 (
1110 RequestKey {
1111 request: k
1112 .request
1113 .as_ref()
1114 .and_then(|r| old_request_key.match_apply_template(r, request_key))
1115 .map(Into::into),
1116 conditions: k.conditions.clone(),
1117 },
1118 v.clone(),
1119 )
1120 })
1121 .collect();
1122 Ok(ResolveResult {
1123 primary: new_primary,
1124 affecting_sources: self.affecting_sources.clone(),
1125 }
1126 .cell())
1127 }
1128
1129 #[turbo_tasks::function]
1132 fn with_request(&self, request: RcStr) -> Vc<Self> {
1133 let new_primary = self
1134 .primary
1135 .iter()
1136 .map(|(k, v)| {
1137 (
1138 RequestKey {
1139 request: Some(request.clone()),
1140 conditions: k.conditions.clone(),
1141 },
1142 v.clone(),
1143 )
1144 })
1145 .collect();
1146 ResolveResult {
1147 primary: new_primary,
1148 affecting_sources: self.affecting_sources.clone(),
1149 }
1150 .cell()
1151 }
1152}
1153
1154#[turbo_tasks::value(transparent)]
1155pub struct ResolveResultOption(Option<ResolvedVc<ResolveResult>>);
1156
1157#[turbo_tasks::value_impl]
1158impl ResolveResultOption {
1159 #[turbo_tasks::function]
1160 pub fn some(result: ResolvedVc<ResolveResult>) -> Vc<Self> {
1161 ResolveResultOption(Some(result)).cell()
1162 }
1163
1164 #[turbo_tasks::function]
1165 pub fn none() -> Vc<Self> {
1166 ResolveResultOption(None).cell()
1167 }
1168}
1169
1170async fn exists(
1171 fs_path: &FileSystemPath,
1172 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1173) -> Result<Option<FileSystemPath>> {
1174 type_exists(fs_path, FileSystemEntryType::File, refs).await
1175}
1176
1177async fn dir_exists(
1178 fs_path: &FileSystemPath,
1179 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1180) -> Result<Option<FileSystemPath>> {
1181 type_exists(fs_path, FileSystemEntryType::Directory, refs).await
1182}
1183
1184async fn type_exists(
1185 fs_path: &FileSystemPath,
1186 ty: FileSystemEntryType,
1187 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1188) -> Result<Option<FileSystemPath>> {
1189 let Some(path) = realpath_if_exists(fs_path, refs).await? else {
1190 return Ok(None);
1191 };
1192 Ok(if *path.get_type().await? == ty {
1193 Some(path)
1194 } else {
1195 None
1196 })
1197}
1198
1199async fn realpath_if_exists(
1200 fs_path: &FileSystemPath,
1201 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1202) -> Result<Option<FileSystemPath>> {
1203 let result = fs_path.realpath_with_links().await?;
1204 if let Some(refs) = refs {
1205 refs.extend(
1206 result
1207 .symlinks
1208 .iter()
1209 .map(async |path| {
1210 Ok(ResolvedVc::upcast(
1211 FileSource::new(path.clone()).to_resolved().await?,
1212 ))
1213 })
1214 .try_join()
1215 .await?,
1216 );
1217 }
1218 match &result.path_result {
1219 Ok(path) => Ok(Some(path.clone())),
1220 Err(_) => Ok(None),
1223 }
1224}
1225
1226#[turbo_tasks::value(shared)]
1227enum ExportsFieldResult {
1228 Some(#[turbo_tasks(debug_ignore, unsafe_ignore)] ExportsField),
1229 None,
1230}
1231
1232#[turbo_tasks::function]
1235async fn exports_field(
1236 package_json_path: ResolvedVc<Box<dyn Source>>,
1237) -> Result<Vc<ExportsFieldResult>> {
1238 let read = read_package_json(*package_json_path).await?;
1239 let package_json = match &*read {
1240 Some(json) => json,
1241 None => return Ok(ExportsFieldResult::None.cell()),
1242 };
1243
1244 let Some(exports) = package_json.get("exports") else {
1245 return Ok(ExportsFieldResult::None.cell());
1246 };
1247 match exports.try_into() {
1248 Ok(exports) => Ok(ExportsFieldResult::Some(exports).cell()),
1249 Err(err) => {
1250 PackageJsonIssue {
1251 error_message: err.to_string().into(),
1252 source: IssueSource::from_source_only(package_json_path),
1254 }
1255 .resolved_cell()
1256 .emit();
1257 Ok(ExportsFieldResult::None.cell())
1258 }
1259 }
1260}
1261
1262#[turbo_tasks::value(shared)]
1263enum ImportsFieldResult {
1264 Some(
1265 #[turbo_tasks(debug_ignore, unsafe_ignore)] ImportsField,
1266 FileSystemPath,
1267 ),
1268 None,
1269}
1270
1271#[turbo_tasks::function]
1274async fn imports_field(lookup_path: FileSystemPath) -> Result<Vc<ImportsFieldResult>> {
1275 let package_json_context =
1277 find_context_file(lookup_path, *package_json().to_resolved().await?, false).await?;
1278 let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context else {
1279 return Ok(ImportsFieldResult::None.cell());
1280 };
1281 let source = Vc::upcast::<Box<dyn Source>>(FileSource::new(package_json_path.clone()))
1282 .to_resolved()
1283 .await?;
1284
1285 let read = read_package_json(*source).await?;
1286 let package_json = match &*read {
1287 Some(json) => json,
1288 None => return Ok(ImportsFieldResult::None.cell()),
1289 };
1290
1291 let Some(imports) = package_json.get("imports") else {
1292 return Ok(ImportsFieldResult::None.cell());
1293 };
1294 match imports.try_into() {
1295 Ok(imports) => Ok(ImportsFieldResult::Some(imports, package_json_path.clone()).cell()),
1296 Err(err) => {
1297 PackageJsonIssue {
1298 error_message: err.to_string().into(),
1299 source: IssueSource::from_source_only(source),
1301 }
1302 .resolved_cell()
1303 .emit();
1304 Ok(ImportsFieldResult::None.cell())
1305 }
1306 }
1307}
1308
1309#[turbo_tasks::function]
1310pub fn package_json() -> Vc<Vec<RcStr>> {
1311 Vc::cell(vec![rcstr!("package.json")])
1312}
1313
1314#[turbo_tasks::value(shared)]
1315pub enum FindContextFileResult {
1316 Found(FileSystemPath, Vec<ResolvedVc<Box<dyn Source>>>),
1317 NotFound(Vec<ResolvedVc<Box<dyn Source>>>),
1318}
1319
1320#[turbo_tasks::function]
1321pub async fn find_context_file(
1322 lookup_path: FileSystemPath,
1323 names: Vc<Vec<RcStr>>,
1324 collect_affecting_sources: bool,
1325) -> Result<Vc<FindContextFileResult>> {
1326 let mut refs = Vec::new();
1327 for name in &*names.await? {
1328 let fs_path = lookup_path.join(name)?;
1329 if let Some(fs_path) = exists(
1330 &fs_path,
1331 if collect_affecting_sources {
1332 Some(&mut refs)
1333 } else {
1334 None
1335 },
1336 )
1337 .await?
1338 {
1339 return Ok(FindContextFileResult::Found(fs_path, refs).cell());
1340 }
1341 }
1342 if lookup_path.is_root() {
1343 return Ok(FindContextFileResult::NotFound(refs).cell());
1344 }
1345 if refs.is_empty() {
1346 Ok(find_context_file(
1348 lookup_path.parent(),
1349 names,
1350 collect_affecting_sources,
1351 ))
1352 } else {
1353 let parent_result =
1354 find_context_file(lookup_path.parent(), names, collect_affecting_sources).await?;
1355 Ok(match &*parent_result {
1356 FindContextFileResult::Found(p, r) => {
1357 refs.extend(r.iter().copied());
1358 FindContextFileResult::Found(p.clone(), refs)
1359 }
1360 FindContextFileResult::NotFound(r) => {
1361 refs.extend(r.iter().copied());
1362 FindContextFileResult::NotFound(refs)
1363 }
1364 }
1365 .cell())
1366 }
1367}
1368
1369#[turbo_tasks::function]
1372pub async fn find_context_file_or_package_key(
1373 lookup_path: FileSystemPath,
1374 names: Vc<Vec<RcStr>>,
1375 package_key: RcStr,
1376) -> Result<Vc<FindContextFileResult>> {
1377 let package_json_path = lookup_path.join("package.json")?;
1378 if let Some(package_json_path) = exists(&package_json_path, None).await?
1379 && let Some(json) =
1380 &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
1381 && json.get(&*package_key).is_some()
1382 {
1383 return Ok(FindContextFileResult::Found(package_json_path, Vec::new()).cell());
1384 }
1385 for name in &*names.await? {
1386 let fs_path = lookup_path.join(name)?;
1387 if let Some(fs_path) = exists(&fs_path, None).await? {
1388 return Ok(FindContextFileResult::Found(fs_path, Vec::new()).cell());
1389 }
1390 }
1391 if lookup_path.is_root() {
1392 return Ok(FindContextFileResult::NotFound(Vec::new()).cell());
1393 }
1394
1395 Ok(find_context_file(lookup_path.parent(), names, false))
1396}
1397
1398#[derive(Clone, PartialEq, Eq, Debug, NonLocalValue, Encode, Decode)]
1399enum FindPackageItem {
1400 PackageDirectory { name: RcStr, dir: FileSystemPath },
1401 PackageFile { name: RcStr, file: FileSystemPath },
1402}
1403
1404#[turbo_tasks::value]
1405#[derive(Debug)]
1406struct FindPackageResult {
1407 packages: Vec<FindPackageItem>,
1408 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1410}
1411
1412#[turbo_tasks::function]
1413async fn find_package(
1414 lookup_path: FileSystemPath,
1415 package_name: Pattern,
1416 options: Vc<ResolveModulesOptions>,
1417 collect_affecting_sources: bool,
1418) -> Result<Vc<FindPackageResult>> {
1419 let mut packages = vec![];
1420 let mut affecting_sources = vec![];
1421 let options = options.await?;
1422 let package_name_cell = Pattern::new(package_name.clone());
1423
1424 fn get_package_name(basepath: &FileSystemPath, package_dir: &FileSystemPath) -> Result<RcStr> {
1425 if let Some(name) = basepath.get_path_to(package_dir) {
1426 Ok(name.into())
1427 } else {
1428 bail!("Package directory {package_dir} is not inside the lookup path {basepath}",);
1429 }
1430 }
1431
1432 for resolve_modules in &options.modules {
1433 match resolve_modules {
1434 ResolveModules::Nested(names) => {
1435 let mut lookup_path = lookup_path.clone();
1436 loop {
1437 for name in names.iter() {
1438 let fs_path = lookup_path.join(name)?;
1439 if let Some(fs_path) = dir_exists(
1440 &fs_path,
1441 collect_affecting_sources.then_some(&mut affecting_sources),
1442 )
1443 .await?
1444 {
1445 let matches =
1446 read_matches(fs_path.clone(), rcstr!(""), true, package_name_cell)
1447 .await?;
1448 for m in &*matches {
1449 if let PatternMatch::Directory(_, package_dir) = m {
1450 let Some(dir) = realpath_if_exists(
1451 package_dir,
1452 collect_affecting_sources.then_some(&mut affecting_sources),
1453 )
1454 .await?
1455 else {
1456 continue;
1457 };
1458 packages.push(FindPackageItem::PackageDirectory {
1459 name: get_package_name(&fs_path, package_dir)?,
1460 dir,
1461 });
1462 }
1463 }
1464 }
1465 }
1466 if lookup_path.is_root() {
1467 break;
1468 }
1469 lookup_path = lookup_path.parent();
1470 }
1471 }
1472 ResolveModules::Path {
1473 dir,
1474 excluded_extensions,
1475 } => {
1476 let matches =
1477 read_matches(dir.clone(), rcstr!(""), true, package_name_cell).await?;
1478 for m in &*matches {
1479 match m {
1480 PatternMatch::Directory(_, package_dir) => {
1481 let Some(resolved_dir) = realpath_if_exists(
1482 package_dir,
1483 collect_affecting_sources.then_some(&mut affecting_sources),
1484 )
1485 .await?
1486 else {
1487 continue;
1488 };
1489 packages.push(FindPackageItem::PackageDirectory {
1490 name: get_package_name(dir, package_dir)?,
1491 dir: resolved_dir,
1492 });
1493 }
1494 PatternMatch::File(_, package_file) => {
1495 let Some(file) = realpath_if_exists(
1496 package_file,
1497 collect_affecting_sources.then_some(&mut affecting_sources),
1498 )
1499 .await?
1500 else {
1501 continue;
1502 };
1503 packages.push(FindPackageItem::PackageFile {
1504 name: get_package_name(dir, package_file)?,
1505 file,
1506 });
1507 }
1508 }
1509 }
1510
1511 let excluded_extensions = excluded_extensions.await?;
1512 let mut package_name_with_extensions = package_name.clone();
1513 package_name_with_extensions.push(Pattern::alternatives(
1514 options
1515 .extensions
1516 .iter()
1517 .filter(|ext| !excluded_extensions.contains(*ext))
1518 .cloned()
1519 .map(Pattern::from),
1520 ));
1521 let package_name_with_extensions = Pattern::new(package_name_with_extensions);
1522
1523 let matches =
1524 read_matches(dir.clone(), rcstr!(""), true, package_name_with_extensions)
1525 .await?;
1526 for m in &matches {
1527 if let PatternMatch::File(_, package_file) = m {
1528 let Some(file) = realpath_if_exists(
1529 package_file,
1530 collect_affecting_sources.then_some(&mut affecting_sources),
1531 )
1532 .await?
1533 else {
1534 continue;
1535 };
1536 packages.push(FindPackageItem::PackageFile {
1537 name: get_package_name(dir, package_file)?,
1538 file,
1539 });
1540 }
1541 }
1542 }
1543 }
1544 }
1545 Ok(FindPackageResult::cell(FindPackageResult {
1546 packages,
1547 affecting_sources,
1548 }))
1549}
1550
1551fn merge_results(results: Vec<Vc<ResolveResult>>) -> Vc<ResolveResult> {
1552 match results.len() {
1553 0 => ResolveResult::unresolvable().cell(),
1554 1 => results.into_iter().next().unwrap(),
1555 _ => ResolveResult::alternatives(results),
1556 }
1557}
1558
1559fn merge_results_with_affecting_sources(
1560 results: Vec<Vc<ResolveResult>>,
1561 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1562) -> Vc<ResolveResult> {
1563 if affecting_sources.is_empty() {
1564 return merge_results(results);
1565 }
1566 match results.len() {
1567 0 => ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell(),
1568 1 => results
1569 .into_iter()
1570 .next()
1571 .unwrap()
1572 .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()),
1573 _ => ResolveResult::alternatives_with_affecting_sources(
1574 results,
1575 affecting_sources.into_iter().map(|src| *src).collect(),
1576 ),
1577 }
1578}
1579
1580#[turbo_tasks::function]
1582pub async fn resolve_raw(
1583 lookup_dir: FileSystemPath,
1584 path: Vc<Pattern>,
1585 collect_affecting_sources: bool,
1586 force_in_lookup_dir: bool,
1587) -> Result<Vc<ResolveResult>> {
1588 async fn to_result(
1589 request: RcStr,
1590 path: &FileSystemPath,
1591 collect_affecting_sources: bool,
1592 ) -> Result<ResolveResult> {
1593 let result = &*path.realpath_with_links().await?;
1594 let path = match &result.path_result {
1595 Ok(path) => path,
1596 Err(error) => bail!(error.clone()),
1597 };
1598 let request_key = RequestKey::new(request);
1599 let source = ResolvedVc::upcast(FileSource::new(path.clone()).to_resolved().await?);
1600 Ok(if collect_affecting_sources {
1601 ResolveResult::source_with_affecting_sources(
1602 request_key,
1603 source,
1604 result
1605 .symlinks
1606 .iter()
1607 .map(|symlink| {
1608 Vc::upcast::<Box<dyn Source>>(FileSource::new(symlink.clone()))
1609 .to_resolved()
1610 })
1611 .try_join()
1612 .await?,
1613 )
1614 } else {
1615 ResolveResult::source_with_key(request_key, source)
1616 })
1617 }
1618
1619 async fn collect_matches(
1620 matches: &[PatternMatch],
1621 collect_affecting_sources: bool,
1622 ) -> Result<Vec<Vc<ResolveResult>>> {
1623 Ok(matches
1624 .iter()
1625 .map(async |m| {
1626 Ok(if let PatternMatch::File(request, path) = m {
1627 Some(to_result(request.clone(), path, collect_affecting_sources).await?)
1628 } else {
1629 None
1630 })
1631 })
1632 .try_flat_join()
1633 .await?
1634 .into_iter()
1637 .map(|res| res.cell())
1638 .collect())
1639 }
1640
1641 let mut results = Vec::new();
1642
1643 let pat = path.await?;
1644 if let Some(pat) = pat
1645 .filter_could_match("/ROOT/")
1646 .and_then(|pat| pat.filter_could_not_match("/ROOT/fsd8nz8og54z"))
1649 {
1650 let path = Pattern::new(pat);
1651 let matches = read_matches(
1652 lookup_dir.root().owned().await?,
1653 rcstr!("/ROOT/"),
1654 true,
1655 path,
1656 )
1657 .await?;
1658 results.extend(collect_matches(&matches, collect_affecting_sources).await?);
1659 }
1660
1661 {
1662 let matches =
1663 read_matches(lookup_dir.clone(), rcstr!(""), force_in_lookup_dir, path).await?;
1664
1665 results.extend(collect_matches(&matches, collect_affecting_sources).await?);
1666 }
1667
1668 Ok(merge_results(results))
1669}
1670
1671#[turbo_tasks::function]
1672pub async fn resolve(
1673 lookup_path: FileSystemPath,
1674 reference_type: ReferenceType,
1675 request: Vc<Request>,
1676 options: Vc<ResolveOptions>,
1677) -> Result<Vc<ResolveResult>> {
1678 resolve_inline(lookup_path, reference_type, request, options).await
1679}
1680
1681pub async fn resolve_inline(
1682 lookup_path: FileSystemPath,
1683 reference_type: ReferenceType,
1684 request: Vc<Request>,
1685 options: Vc<ResolveOptions>,
1686) -> Result<Vc<ResolveResult>> {
1687 let span = tracing::info_span!(
1688 "resolving",
1689 lookup_path = display(lookup_path.to_string_ref().await?),
1690 name = tracing::field::Empty,
1691 reference_type = display(&reference_type),
1692 );
1693 if !span.is_disabled() {
1694 span.record("name", request.to_string().await?.as_str());
1696 }
1697
1698 async {
1699 let options_value = options.await?;
1701
1702 let has_before_plugins = !options_value.before_resolve_plugins.is_empty();
1704 let has_after_plugins = !options_value.after_resolve_plugins.is_empty();
1705
1706 let before_plugins_result = if has_before_plugins {
1707 handle_before_resolve_plugins(
1708 lookup_path.clone(),
1709 reference_type.clone(),
1710 request,
1711 options,
1712 )
1713 .await?
1714 } else {
1715 None
1716 };
1717
1718 let raw_result = match before_plugins_result {
1719 Some(result) => result,
1720 None => {
1721 *resolve_internal(lookup_path.clone(), request, options)
1722 .to_resolved()
1723 .await?
1724 }
1725 };
1726
1727 let result = if has_after_plugins {
1728 handle_after_resolve_plugins(lookup_path, reference_type, request, options, raw_result)
1729 .await?
1730 } else {
1731 raw_result
1732 };
1733
1734 Ok(result)
1735 }
1736 .instrument(span)
1737 .await
1738}
1739
1740#[turbo_tasks::function]
1741pub async fn url_resolve(
1742 origin: Vc<Box<dyn ResolveOrigin>>,
1743 request: ResolvedVc<Request>,
1744 reference_type: ReferenceType,
1745 issue_source: Option<IssueSource>,
1746 error_mode: ResolveErrorMode,
1747) -> Result<Vc<ModuleResolveResult>> {
1748 let origin_ref = origin.into_trait_ref().await?;
1749 let resolve_options = origin_ref.resolve_options();
1750 let rel_request = request.as_relative();
1751 let origin_path = origin_ref.origin_path();
1752 let origin_path_parent = origin_path.parent();
1753 let rel_result = resolve(
1754 origin_path_parent.clone(),
1755 reference_type.clone(),
1756 rel_request,
1757 resolve_options,
1758 );
1759 let result =
1760 if rel_result.await?.is_unresolvable() && rel_request.to_resolved().await? != request {
1761 let result = resolve(
1762 origin_path_parent,
1763 reference_type.clone(),
1764 *request,
1765 resolve_options,
1766 );
1767 if resolve_options.await?.collect_affecting_sources {
1768 result.with_affecting_sources(
1769 rel_result
1770 .await?
1771 .get_affecting_sources()
1772 .map(|src| *src)
1773 .collect(),
1774 )
1775 } else {
1776 result
1777 }
1778 } else {
1779 rel_result
1780 };
1781 let result = origin_ref
1782 .asset_context()
1783 .process_resolve_result(result, reference_type.clone());
1784 handle_resolve_error(
1785 result,
1786 reference_type,
1787 origin_path,
1788 *request,
1789 resolve_options,
1790 error_mode,
1791 issue_source,
1792 )
1793 .await
1794}
1795
1796#[turbo_tasks::value(transparent)]
1797struct MatchingBeforeResolvePlugins(Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>);
1798
1799#[turbo_tasks::function]
1800async fn get_matching_before_resolve_plugins(
1801 options: Vc<ResolveOptions>,
1802 request: Vc<Request>,
1803) -> Result<Vc<MatchingBeforeResolvePlugins>> {
1804 let request_ref = request.await?;
1805 let matching_plugins = options
1806 .await?
1807 .before_resolve_plugins
1808 .iter()
1809 .map(async |plugin| {
1810 Ok(
1811 if plugin
1812 .into_trait_ref()
1813 .await?
1814 .before_resolve_condition()
1815 .await?
1816 .matches(&request_ref)
1817 {
1818 Some(*plugin)
1819 } else {
1820 None
1821 },
1822 )
1823 })
1824 .try_flat_join()
1825 .await?;
1826 Ok(Vc::cell(matching_plugins))
1827}
1828
1829#[tracing::instrument(level = "trace", skip_all)]
1830async fn handle_before_resolve_plugins(
1831 lookup_path: FileSystemPath,
1832 reference_type: ReferenceType,
1833 request: Vc<Request>,
1834 options: Vc<ResolveOptions>,
1835) -> Result<Option<Vc<ResolveResult>>> {
1836 for plugin in get_matching_before_resolve_plugins(options, request).await? {
1837 if let Some(result) = *plugin
1838 .before_resolve(lookup_path.clone(), reference_type.clone(), request)
1839 .await?
1840 {
1841 return Ok(Some(*result));
1842 }
1843 }
1844 Ok(None)
1845}
1846
1847#[tracing::instrument(level = "trace", skip_all)]
1848async fn handle_after_resolve_plugins(
1849 lookup_path: FileSystemPath,
1850 reference_type: ReferenceType,
1851 request: Vc<Request>,
1852 options: Vc<ResolveOptions>,
1853 result: Vc<ResolveResult>,
1854) -> Result<Vc<ResolveResult>> {
1855 let options_value = options.await?;
1857
1858 let resolved_conditions = options_value
1860 .after_resolve_plugins
1861 .iter()
1862 .map(async |p| {
1863 let condition = p.into_trait_ref().await?.after_resolve_condition().await?;
1864 Ok((*p, condition))
1865 })
1866 .try_join()
1867 .await?;
1868
1869 async fn apply_plugins_to_path(
1870 path: FileSystemPath,
1871 lookup_path: FileSystemPath,
1872 reference_type: ReferenceType,
1873 request: Vc<Request>,
1874 plugins_with_conditions: &[AfterResolvePluginWithCondition],
1875 ) -> Result<Option<Vc<ResolveResult>>> {
1876 for (plugin, after_resolve_condition) in plugins_with_conditions {
1877 if after_resolve_condition.matches(&path)
1878 && let Some(result) = *plugin
1879 .after_resolve(
1880 path.clone(),
1881 lookup_path.clone(),
1882 reference_type.clone(),
1883 request,
1884 )
1885 .await?
1886 {
1887 return Ok(Some(*result));
1888 }
1889 }
1890 Ok(None)
1891 }
1892
1893 let mut changed = false;
1894 let result_value = result.await?;
1895
1896 let mut new_primary = FxIndexMap::default();
1897 let mut new_affecting_sources = Vec::new();
1898
1899 for (key, primary) in result_value.primary.iter() {
1900 if let &ResolveResultItem::Source(source) = primary {
1901 let path = source.ident().await?.path.clone();
1902 if let Some(new_result) = apply_plugins_to_path(
1903 path,
1904 lookup_path.clone(),
1905 reference_type.clone(),
1906 request,
1907 &resolved_conditions,
1908 )
1909 .await?
1910 {
1911 let new_result = new_result.await?;
1912 changed = true;
1913 new_primary.extend(
1914 new_result
1915 .primary
1916 .iter()
1917 .map(|(_, item)| (key.clone(), item.clone())),
1918 );
1919 new_affecting_sources.extend(new_result.affecting_sources.iter().copied());
1920 } else {
1921 new_primary.insert(key.clone(), primary.clone());
1922 }
1923 } else {
1924 new_primary.insert(key.clone(), primary.clone());
1925 }
1926 }
1927
1928 if !changed {
1929 return Ok(result);
1930 }
1931
1932 let mut affecting_sources = result_value.affecting_sources.to_vec();
1933 affecting_sources.append(&mut new_affecting_sources);
1934
1935 Ok(ResolveResult {
1936 primary: new_primary.into_iter().collect(),
1937 affecting_sources: affecting_sources.into_boxed_slice(),
1938 }
1939 .cell())
1940}
1941
1942#[turbo_tasks::function]
1943async fn resolve_internal(
1944 lookup_path: FileSystemPath,
1945 request: ResolvedVc<Request>,
1946 options: ResolvedVc<ResolveOptions>,
1947) -> Result<Vc<ResolveResult>> {
1948 resolve_internal_inline(lookup_path.clone(), *request, *options).await
1949}
1950
1951async fn resolve_internal_inline(
1952 lookup_path: FileSystemPath,
1953 request: Vc<Request>,
1954 options: Vc<ResolveOptions>,
1955) -> Result<Vc<ResolveResult>> {
1956 let span = tracing::info_span!(
1957 "internal resolving",
1958 lookup_path = display(lookup_path.to_string_ref().await?),
1959 name = tracing::field::Empty
1960 );
1961 if !span.is_disabled() {
1962 span.record("name", request.to_string().await?.as_str());
1964 }
1965
1966 async move {
1967 let options_value: &ResolveOptions = &*options.await?;
1968
1969 let request_value = request.await?;
1970
1971 let mut has_alias = false;
1973 if let Some(import_map) = &options_value.import_map {
1974 let request_parts = match &*request_value {
1975 Request::Alternatives { requests } => requests.as_slice(),
1976 _ => &[request.to_resolved().await?],
1977 };
1978 for &request in request_parts {
1979 let result = import_map
1980 .await?
1981 .lookup(lookup_path.clone(), *request)
1982 .await?;
1983 if !matches!(result, ImportMapResult::NoEntry) {
1984 has_alias = true;
1985 let resolved_result = resolve_import_map_result(
1986 &result,
1987 lookup_path.clone(),
1988 lookup_path.clone(),
1989 *request,
1990 options,
1991 request.query().owned().await?,
1992 )
1993 .await?;
1994 if let Some(resolved_result) = resolved_result {
2001 let resolved_result = resolved_result.into_cell_if_resolvable().await?;
2002 if let Some(result) = resolved_result {
2003 return Ok(result);
2004 }
2005 }
2006 }
2007 }
2008 }
2009
2010 let result = match &*request_value {
2011 Request::Dynamic => ResolveResult::unresolvable().cell(),
2012 Request::Alternatives { requests } => {
2013 let results = requests
2014 .iter()
2015 .map(|req| resolve_internal_inline(lookup_path.clone(), **req, options))
2016 .try_join()
2017 .await?;
2018
2019 merge_results(results)
2020 }
2021 Request::Raw {
2022 path,
2023 query,
2024 force_in_lookup_dir,
2025 fragment,
2026 } => {
2027 let mut results = Vec::new();
2028 let matches = read_matches(
2029 lookup_path.clone(),
2030 rcstr!(""),
2031 *force_in_lookup_dir,
2032 *Pattern::new(path.clone()).to_resolved().await?,
2033 )
2034 .await?;
2035
2036 for m in matches.iter() {
2037 match m {
2038 PatternMatch::File(matched_pattern, path) => {
2039 results.push(
2040 resolved(
2041 RequestKey::new(matched_pattern.clone()),
2042 path.clone(),
2043 lookup_path.clone(),
2044 request,
2045 options_value,
2046 options,
2047 query.clone(),
2048 fragment.clone(),
2049 )
2050 .await?
2051 .into_cell(),
2052 );
2053 }
2054 PatternMatch::Directory(matched_pattern, path) => {
2055 results.push(
2056 resolve_into_folder(path.clone(), options)
2057 .with_request(matched_pattern.clone()),
2058 );
2059 }
2060 }
2061 }
2062
2063 merge_results(results)
2064 }
2065 Request::Relative {
2066 path,
2067 query,
2068 force_in_lookup_dir,
2069 fragment,
2070 } => {
2071 resolve_relative_request(
2072 lookup_path.clone(),
2073 request,
2074 options,
2075 options_value,
2076 path,
2077 query.clone(),
2078 *force_in_lookup_dir,
2079 fragment.clone(),
2080 )
2081 .await?
2082 }
2083 Request::Module {
2084 module,
2085 path,
2086 query,
2087 fragment,
2088 } => {
2089 resolve_module_request(
2090 lookup_path.clone(),
2091 request,
2092 options,
2093 options_value,
2094 module,
2095 path,
2096 query.clone(),
2097 fragment.clone(),
2098 )
2099 .await?
2100 }
2101 Request::ServerRelative {
2102 path,
2103 query,
2104 fragment,
2105 } => {
2106 let mut new_pat = path.clone();
2107 new_pat.push_front(rcstr!(".").into());
2108 let relative = Request::relative(new_pat, query.clone(), fragment.clone(), true);
2109
2110 if let Some(root) = &options_value.server_relative_root {
2114 Box::pin(resolve_internal_inline(root.clone(), relative, options)).await?
2115 } else {
2116 if !has_alias {
2120 ResolvingIssue {
2121 severity: resolve_error_severity(options).await?,
2122 request_type: "server relative import: not implemented yet".to_string(),
2123 request: relative.to_resolved().await?,
2124 file_path: lookup_path.clone(),
2125 resolve_options: options.to_resolved().await?,
2126 error_message: Some(
2127 "server relative imports are not implemented yet. Please try an \
2128 import relative to the file you are importing from."
2129 .to_string(),
2130 ),
2131 source: None,
2132 }
2133 .resolved_cell()
2134 .emit();
2135 }
2136
2137 ResolveResult::unresolvable().cell()
2138 }
2139 }
2140 Request::Windows {
2141 path: _,
2142 query: _,
2143 fragment: _,
2144 } => {
2145 if !has_alias {
2146 ResolvingIssue {
2147 severity: resolve_error_severity(options).await?,
2148 request_type: "windows import: not implemented yet".to_string(),
2149 request: request.to_resolved().await?,
2150 file_path: lookup_path.clone(),
2151 resolve_options: options.to_resolved().await?,
2152 error_message: Some("windows imports are not implemented yet".to_string()),
2153 source: None,
2154 }
2155 .resolved_cell()
2156 .emit();
2157 }
2158
2159 ResolveResult::unresolvable().cell()
2160 }
2161 Request::Empty => ResolveResult::unresolvable().cell(),
2162 Request::PackageInternal { path } => {
2163 let (conditions, unspecified_conditions) = options_value
2164 .in_package
2165 .iter()
2166 .find_map(|item| match item {
2167 ResolveInPackage::ImportsField {
2168 conditions,
2169 unspecified_conditions,
2170 } => Some((Cow::Borrowed(conditions), *unspecified_conditions)),
2171 _ => None,
2172 })
2173 .unwrap_or_else(|| (Default::default(), ConditionValue::Unset));
2174 resolve_package_internal_with_imports_field(
2175 lookup_path.clone(),
2176 request,
2177 options,
2178 path,
2179 &conditions,
2180 &unspecified_conditions,
2181 )
2182 .await?
2183 }
2184 Request::DataUri {
2185 media_type,
2186 encoding,
2187 data,
2188 } => {
2189 let uri: RcStr = stringify_data_uri(media_type, encoding, *data)
2191 .await?
2192 .into();
2193 if options_value.parse_data_uris {
2194 ResolveResult::primary_with_key(
2195 RequestKey::new(uri.clone()),
2196 ResolveResultItem::Source(ResolvedVc::upcast(
2197 DataUriSource::new(
2198 media_type.clone(),
2199 encoding.clone(),
2200 **data,
2201 lookup_path.clone(),
2202 )
2203 .to_resolved()
2204 .await?,
2205 )),
2206 )
2207 .cell()
2208 } else {
2209 ResolveResult::primary_with_key(
2210 RequestKey::new(uri.clone()),
2211 ResolveResultItem::External {
2212 name: uri,
2213 ty: ExternalType::Url,
2214 traced: ExternalTraced::Untraced,
2215 target: None,
2216 },
2217 )
2218 .cell()
2219 }
2220 }
2221 Request::Uri {
2222 protocol,
2223 remainder,
2224 query: _,
2225 fragment: _,
2226 } => {
2227 let uri: RcStr = format!("{protocol}{remainder}").into();
2228 ResolveResult::primary_with_key(
2229 RequestKey::new(uri.clone()),
2230 ResolveResultItem::External {
2231 name: uri,
2232 ty: ExternalType::Url,
2233 traced: ExternalTraced::Untraced,
2234 target: None,
2235 },
2236 )
2237 .cell()
2238 }
2239 Request::Unknown { path } => {
2240 if !has_alias {
2241 ResolvingIssue {
2242 severity: resolve_error_severity(options).await?,
2243 request_type: format!("unknown import: `{}`", path.describe_as_string()),
2244 request: request.to_resolved().await?,
2245 file_path: lookup_path.clone(),
2246 resolve_options: options.to_resolved().await?,
2247 error_message: None,
2248 source: None,
2249 }
2250 .resolved_cell()
2251 .emit();
2252 }
2253 ResolveResult::unresolvable().cell()
2254 }
2255 };
2256
2257 if !matches!(*request_value, Request::Alternatives { .. }) {
2260 if let Some(import_map) = &options_value.fallback_import_map
2262 && result.await?.is_unresolvable()
2263 {
2264 let result = import_map
2265 .await?
2266 .lookup(lookup_path.clone(), request)
2267 .await?;
2268 let resolved_result = resolve_import_map_result(
2269 &result,
2270 lookup_path.clone(),
2271 lookup_path.clone(),
2272 request,
2273 options,
2274 request.query().owned().await?,
2275 )
2276 .await?;
2277 if let Some(resolved_result) = resolved_result {
2278 let resolved_result = resolved_result.into_cell_if_resolvable().await?;
2279 if let Some(result) = resolved_result {
2280 return Ok(result);
2281 }
2282 }
2283 }
2284 }
2285
2286 Ok(result)
2287 }
2288 .instrument(span)
2289 .await
2290}
2291
2292#[turbo_tasks::function]
2293async fn resolve_into_folder(
2294 package_path: FileSystemPath,
2295 options: Vc<ResolveOptions>,
2296) -> Result<Vc<ResolveResult>> {
2297 let options_value = options.await?;
2298
2299 let mut affecting_sources = vec![];
2300 if let Some(package_json_path) = exists(
2301 &package_path.join("package.json")?,
2302 if options_value.collect_affecting_sources {
2303 Some(&mut affecting_sources)
2304 } else {
2305 None
2306 },
2307 )
2308 .await?
2309 {
2310 for resolve_into_package in options_value.into_package.iter() {
2311 match resolve_into_package {
2312 ResolveIntoPackage::MainField { field: name } => {
2313 if let Some(package_json) =
2314 &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone())))
2315 .await?
2316 && let Some(field_value) = package_json[name.as_str()].as_str()
2317 {
2318 let normalized_request = RcStr::from(normalize_request(field_value));
2319 if normalized_request.is_empty()
2320 || &*normalized_request == "."
2321 || &*normalized_request == "./"
2322 {
2323 continue;
2324 }
2325 let request = Request::parse_string(normalized_request);
2326
2327 let options = if options_value.fully_specified {
2329 *options.with_fully_specified(false).to_resolved().await?
2330 } else {
2331 options
2332 };
2333 let result =
2334 &*resolve_internal_inline(package_path.clone(), request, options)
2335 .await?
2336 .await?;
2337 if !result.is_unresolvable() {
2340 let mut result: ResolveResultBuilder =
2341 result.with_request_ref(rcstr!(".")).into();
2342 if options_value.collect_affecting_sources {
2343 result.affecting_sources.push(ResolvedVc::upcast(
2344 FileSource::new(package_json_path).to_resolved().await?,
2345 ));
2346 result.affecting_sources.extend(affecting_sources);
2347 }
2348 return Ok(ResolveResult::from(result).cell());
2349 }
2350 };
2351 }
2352 ResolveIntoPackage::ExportsField { .. } => {}
2353 }
2354 }
2355 }
2356
2357 if options_value.fully_specified {
2358 return Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell());
2359 }
2360
2361 let pattern = match &options_value.default_files[..] {
2363 [] => {
2364 return Ok(
2365 ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell(),
2366 );
2367 }
2368 [file] => Pattern::Constant(format!("./{file}").into()),
2369 files => Pattern::Alternatives(
2370 files
2371 .iter()
2372 .map(|file| Pattern::Constant(format!("./{file}").into()))
2373 .collect(),
2374 ),
2375 };
2376
2377 let request = Request::parse(pattern);
2378 let result = resolve_internal_inline(package_path.clone(), request, options)
2379 .await?
2380 .with_request(rcstr!("."));
2381
2382 Ok(if !affecting_sources.is_empty() {
2383 result.with_affecting_sources(ResolvedVc::deref_vec(affecting_sources))
2384 } else {
2385 result
2386 })
2387}
2388
2389#[tracing::instrument(level = Level::TRACE, skip_all)]
2390async fn resolve_relative_request(
2391 lookup_path: FileSystemPath,
2392 request: Vc<Request>,
2393 options: Vc<ResolveOptions>,
2394 options_value: &ResolveOptions,
2395 path_pattern: &Pattern,
2396 query: RcStr,
2397 force_in_lookup_dir: bool,
2398 fragment: RcStr,
2399) -> Result<Vc<ResolveResult>> {
2400 debug_assert!(query.is_empty() || query.starts_with("?"));
2401 debug_assert!(fragment.is_empty() || fragment.starts_with("#"));
2402 let lookup_path_ref = lookup_path.clone();
2404 if let Some(result) = apply_in_package(
2405 lookup_path.clone(),
2406 options,
2407 options_value,
2408 |package_path| {
2409 let request = path_pattern.as_constant_string()?;
2410 let prefix_path = package_path.get_path_to(&lookup_path_ref)?;
2411 let request = normalize_request(&format!("./{prefix_path}/{request}"));
2412 Some(request.into())
2413 },
2414 query.clone(),
2415 fragment.clone(),
2416 )
2417 .await?
2418 {
2419 return Ok(result.into_cell());
2420 }
2421
2422 let mut new_path = path_pattern.clone();
2423
2424 #[derive(Eq, PartialEq, Clone, Hash, Debug)]
2427 enum RequestKeyTransform {
2428 None,
2430 AddedFragment,
2432 AddedExtension {
2435 ext: RcStr,
2437 next: Vec<RequestKeyTransform>,
2440 },
2441 ReplacedExtension {
2442 ext: RcStr,
2445 next: Vec<RequestKeyTransform>,
2448 },
2449 }
2450
2451 impl RequestKeyTransform {
2452 fn undo(
2455 &self,
2456 matched_pattern: &RcStr,
2457 fragment: &RcStr,
2458 pattern: &Pattern,
2459 ) -> impl Iterator<Item = (RcStr, RcStr)> {
2460 let mut result = SmallVec::new();
2461 self.apply_internal(matched_pattern, fragment, pattern, &mut result);
2462 result.into_iter()
2463 }
2464
2465 fn apply_internal(
2466 &self,
2467 matched_pattern: &RcStr,
2468 fragment: &RcStr,
2469 pattern: &Pattern,
2470 result: &mut SmallVec<[(RcStr, RcStr); 2]>,
2471 ) {
2472 match self {
2473 RequestKeyTransform::None => {
2474 if pattern.is_match(matched_pattern.as_str()) {
2475 result.push((matched_pattern.clone(), fragment.clone()));
2476 }
2477 }
2478 RequestKeyTransform::AddedFragment => {
2479 debug_assert!(
2480 !fragment.is_empty(),
2481 "can only have an AddedFragment modification if there was a fragment"
2482 );
2483 if let Some(stripped_pattern) = matched_pattern.strip_suffix(fragment.as_str())
2484 && pattern.is_match(stripped_pattern)
2485 {
2486 result.push((stripped_pattern.into(), RcStr::default()));
2487 }
2488 }
2489 RequestKeyTransform::AddedExtension { ext, next } => {
2490 if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
2491 let stripped_pattern: RcStr = stripped_pattern.into();
2492 Self::apply_all(next, &stripped_pattern, fragment, pattern, result);
2493 }
2494 }
2495 RequestKeyTransform::ReplacedExtension { ext, next } => {
2496 if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
2497 let replaced_pattern: RcStr = format!(
2498 "{stripped_pattern}{old_ext}",
2499 old_ext = TS_EXTENSION_REPLACEMENTS.reverse.get(ext).unwrap()
2500 )
2501 .into();
2502 Self::apply_all(next, &replaced_pattern, fragment, pattern, result);
2503 }
2504 }
2505 }
2506 }
2507
2508 fn apply_all(
2509 list: &[RequestKeyTransform],
2510 matched_pattern: &RcStr,
2511 fragment: &RcStr,
2512 pattern: &Pattern,
2513 result: &mut SmallVec<[(RcStr, RcStr); 2]>,
2514 ) {
2515 list.iter()
2516 .for_each(|pm| pm.apply_internal(matched_pattern, fragment, pattern, result));
2517 }
2518 }
2519
2520 let mut modifications = Vec::new();
2521 modifications.push(RequestKeyTransform::None);
2522
2523 if !fragment.is_empty() {
2527 modifications.push(RequestKeyTransform::AddedFragment);
2528 new_path.push(Pattern::Alternatives(vec![
2529 Pattern::Constant(RcStr::default()),
2530 Pattern::Constant(fragment.clone()),
2531 ]));
2532 }
2533
2534 if !options_value.fully_specified {
2535 modifications =
2537 modifications
2538 .iter()
2539 .cloned()
2540 .chain(options_value.extensions.iter().map(|ext| {
2541 RequestKeyTransform::AddedExtension {
2542 ext: ext.clone(),
2543 next: modifications.clone(),
2544 }
2545 }))
2546 .collect();
2547 new_path.push(Pattern::Alternatives(
2552 once(Pattern::Constant(RcStr::default()))
2553 .chain(
2554 options_value
2555 .extensions
2556 .iter()
2557 .map(|ext| Pattern::Constant(ext.clone())),
2558 )
2559 .collect(),
2560 ));
2561 new_path.normalize();
2562 };
2563
2564 struct ExtensionReplacements {
2565 forward: FxHashMap<RcStr, SmallVec<[RcStr; 3]>>,
2566 reverse: FxHashMap<RcStr, RcStr>,
2567 }
2568 static TS_EXTENSION_REPLACEMENTS: LazyLock<ExtensionReplacements> = LazyLock::new(|| {
2569 let mut forward = FxHashMap::default();
2570 forward.insert(
2571 rcstr!(".js"),
2572 SmallVec::from_vec(vec![rcstr!(".ts"), rcstr!(".tsx"), rcstr!(".js")]),
2573 );
2574
2575 forward.insert(
2576 rcstr!(".mjs"),
2577 SmallVec::from_vec(vec![rcstr!(".mts"), rcstr!(".mjs")]),
2578 );
2579
2580 forward.insert(
2581 rcstr!(".cjs"),
2582 SmallVec::from_vec(vec![rcstr!(".cts"), rcstr!(".cjs")]),
2583 );
2584 let reverse = forward
2585 .iter()
2586 .flat_map(|(k, v)| v.iter().map(|v: &RcStr| (v.clone(), k.clone())))
2587 .collect::<FxHashMap<_, _>>();
2588 ExtensionReplacements { forward, reverse }
2589 });
2590
2591 if options_value.enable_typescript_with_output_extension {
2592 let mut replaced_extensions = SmallVec::<[RcStr; 4]>::new();
2594 let replaced = new_path.replace_final_constants(&mut |c: &RcStr| -> Option<Pattern> {
2595 let (base, ext) = c.split_at(c.rfind('.')?);
2596
2597 let (ext, replacements) = TS_EXTENSION_REPLACEMENTS.forward.get_key_value(ext)?;
2598 for replacement in replacements {
2599 if replacement != ext && !replaced_extensions.contains(replacement) {
2600 replaced_extensions.push(replacement.clone());
2601 debug_assert!(replaced_extensions.len() <= replaced_extensions.inline_size());
2602 }
2603 }
2604
2605 let replacements = replacements
2606 .iter()
2607 .cloned()
2608 .map(Pattern::Constant)
2609 .collect();
2610
2611 if base.is_empty() {
2612 Some(Pattern::Alternatives(replacements))
2613 } else {
2614 Some(Pattern::Concatenation(vec![
2615 Pattern::Constant(base.into()),
2616 Pattern::Alternatives(replacements),
2617 ]))
2618 }
2619 });
2620 if replaced {
2621 modifications = modifications
2623 .iter()
2624 .cloned()
2625 .chain(replaced_extensions.iter().map(|ext| {
2626 RequestKeyTransform::ReplacedExtension {
2627 ext: ext.clone(),
2628 next: modifications.clone(),
2629 }
2630 }))
2631 .collect();
2632 new_path.normalize();
2633 }
2634 }
2635
2636 let matches = read_matches(
2637 lookup_path.clone(),
2638 rcstr!(""),
2639 force_in_lookup_dir,
2640 *Pattern::new(new_path.clone()).to_resolved().await?,
2641 )
2642 .await?;
2643
2644 let mut keys = FxHashSet::default();
2648 let results = matches
2649 .iter()
2650 .flat_map(|m| {
2651 if let PatternMatch::File(matched_pattern, path) = m {
2652 Either::Left(
2653 modifications
2654 .iter()
2655 .flat_map(|m| m.undo(matched_pattern, &fragment, path_pattern))
2656 .map(move |result| (result, path)),
2657 )
2658 } else {
2659 Either::Right(empty())
2660 }
2661 })
2662 .filter(move |((matched_pattern, _), _)| keys.insert(matched_pattern.clone()))
2664 .map(|((matched_pattern, fragment), path)| {
2665 resolved(
2666 RequestKey::new(matched_pattern),
2667 path.clone(),
2668 lookup_path.clone(),
2669 request,
2670 options_value,
2671 options,
2672 query.clone(),
2673 fragment,
2674 )
2675 })
2676 .try_join()
2677 .await?;
2678
2679 let mut results: Vec<Vc<ResolveResult>> = results.into_iter().map(|r| r.into_cell()).collect();
2681
2682 for m in matches.iter() {
2684 if let PatternMatch::Directory(matched_pattern, path) = m {
2685 results.push(
2686 resolve_into_folder(path.clone(), options).with_request(matched_pattern.clone()),
2687 );
2688 }
2689 }
2690
2691 Ok(merge_results(results))
2692}
2693
2694#[tracing::instrument(level = Level::TRACE, skip_all)]
2695async fn apply_in_package(
2696 lookup_path: FileSystemPath,
2697 options: Vc<ResolveOptions>,
2698 options_value: &ResolveOptions,
2699 get_request: impl Fn(&FileSystemPath) -> Option<RcStr>,
2700 query: RcStr,
2701 fragment: RcStr,
2702) -> Result<Option<ResolveResultOrCell>> {
2703 for in_package in options_value.in_package.iter() {
2705 let ResolveInPackage::AliasField(field) = in_package else {
2709 continue;
2710 };
2711
2712 let FindContextFileResult::Found(package_json_path, refs) = &*find_context_file(
2713 lookup_path.clone(),
2714 *package_json().to_resolved().await?,
2715 options_value.collect_affecting_sources,
2716 )
2717 .await?
2718 else {
2719 continue;
2720 };
2721
2722 let read =
2723 read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
2724 let Some(package_json) = &*read else {
2725 continue;
2726 };
2727
2728 let Some(field_value) = package_json[field.as_str()].as_object() else {
2729 continue;
2730 };
2731
2732 let package_path = package_json_path.parent();
2733
2734 let Some(request) = get_request(&package_path) else {
2735 continue;
2736 };
2737
2738 let value = if let Some(value) = field_value.get(&*request) {
2739 value
2740 } else if let Some(request) = request.strip_prefix("./") {
2741 let Some(value) = field_value.get(request) else {
2742 continue;
2743 };
2744 value
2745 } else {
2746 continue;
2747 };
2748
2749 let refs = refs.clone();
2750
2751 if value.as_bool() == Some(false) {
2752 return Ok(Some(ResolveResultOrCell::Value(
2753 ResolveResult::primary_with_affecting_sources(
2754 RequestKey::new(request.clone()),
2755 ResolveResultItem::Ignore,
2756 refs,
2757 ),
2758 )));
2759 }
2760
2761 if let Some(value) = value.as_str() {
2762 if value == &*request {
2763 return Ok(None);
2765 }
2766 let mut result = resolve_internal(
2767 package_path,
2768 Request::parse(Pattern::Constant(value.into()))
2769 .with_query(query.clone())
2770 .with_fragment(fragment.clone()),
2771 options,
2772 )
2773 .with_replaced_request_key(value.into(), request.clone());
2774 if options_value.collect_affecting_sources && !refs.is_empty() {
2775 result = result.with_affecting_sources(refs.into_iter().map(|src| *src).collect());
2776 }
2777 return Ok(Some(ResolveResultOrCell::Cell(result)));
2778 }
2779
2780 ResolvingIssue {
2781 severity: resolve_error_severity(options).await?,
2782 file_path: package_json_path.clone(),
2783 request_type: format!("alias field ({field})"),
2784 request: Request::parse(Pattern::Constant(request))
2785 .to_resolved()
2786 .await?,
2787 resolve_options: options.to_resolved().await?,
2788 error_message: Some(format!("invalid alias field value: {value}")),
2789 source: None,
2790 }
2791 .resolved_cell()
2792 .emit();
2793
2794 return Ok(Some(ResolveResultOrCell::Value(
2795 ResolveResult::unresolvable_with_affecting_sources(refs),
2796 )));
2797 }
2798 Ok(None)
2799}
2800
2801#[turbo_tasks::value]
2802enum FindSelfReferencePackageResult {
2803 Found {
2804 name: String,
2805 package_path: FileSystemPath,
2806 },
2807 NotFound,
2808}
2809
2810#[turbo_tasks::function]
2811async fn find_self_reference(
2814 lookup_path: FileSystemPath,
2815) -> Result<Vc<FindSelfReferencePackageResult>> {
2816 let package_json_context =
2817 find_context_file(lookup_path, *package_json().to_resolved().await?, false).await?;
2818 if let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context {
2819 let read =
2820 read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
2821 if let Some(json) = &*read
2822 && json.get("exports").is_some()
2823 && let Some(name) = json["name"].as_str()
2824 {
2825 return Ok(FindSelfReferencePackageResult::Found {
2826 name: name.to_string(),
2827 package_path: package_json_path.parent(),
2828 }
2829 .cell());
2830 }
2831 }
2832 Ok(FindSelfReferencePackageResult::NotFound.cell())
2833}
2834
2835#[tracing::instrument(level = Level::TRACE, skip_all)]
2836async fn resolve_module_request(
2837 lookup_path: FileSystemPath,
2838 request: Vc<Request>,
2839 options: Vc<ResolveOptions>,
2840 options_value: &ResolveOptions,
2841 module: &Pattern,
2842 path: &Pattern,
2843 query: RcStr,
2844 fragment: RcStr,
2845) -> Result<Vc<ResolveResult>> {
2846 if let Some(result) = apply_in_package(
2848 lookup_path.clone(),
2849 options,
2850 options_value,
2851 |_| {
2852 let full_pattern = Pattern::concat([module.clone(), path.clone()]);
2853 full_pattern.as_constant_string().cloned()
2854 },
2855 query.clone(),
2856 fragment.clone(),
2857 )
2858 .await?
2859 {
2860 return Ok(result.into_cell());
2861 }
2862
2863 let mut results = vec![];
2864
2865 if let FindSelfReferencePackageResult::Found { name, package_path } =
2869 &*find_self_reference(lookup_path.clone()).await?
2870 && module.is_match(name)
2871 {
2872 let result = resolve_into_package(
2873 path.clone(),
2874 package_path.clone(),
2875 query.clone(),
2876 fragment.clone(),
2877 options,
2878 );
2879 if !result.await?.is_unresolvable() {
2880 return Ok(result);
2881 }
2882 }
2883
2884 let result = find_package(
2885 lookup_path.clone(),
2886 module.clone(),
2887 *resolve_modules_options(options).to_resolved().await?,
2888 options_value.collect_affecting_sources,
2889 )
2890 .await?;
2891
2892 if result.packages.is_empty() {
2893 return Ok(ResolveResult::unresolvable_with_affecting_sources(
2894 result.affecting_sources.clone(),
2895 )
2896 .cell());
2897 }
2898
2899 for item in &result.packages {
2905 match item {
2906 FindPackageItem::PackageDirectory { name, dir } => {
2907 results.push(
2908 resolve_into_package(
2909 path.clone(),
2910 dir.clone(),
2911 query.clone(),
2912 fragment.clone(),
2913 options,
2914 )
2915 .with_replaced_request_key(rcstr!("."), name.clone()),
2916 );
2917 }
2918 FindPackageItem::PackageFile { name, file } => {
2919 if path.is_match("") {
2920 let resolved_result = resolved(
2921 RequestKey::new(rcstr!(".")),
2922 file.clone(),
2923 lookup_path.clone(),
2924 request,
2925 options_value,
2926 options,
2927 query.clone(),
2928 fragment.clone(),
2929 )
2930 .await?
2931 .into_cell()
2932 .with_replaced_request_key(rcstr!("."), name.clone());
2933 results.push(resolved_result)
2934 }
2935 }
2936 }
2937 }
2938
2939 let module_result =
2940 merge_results_with_affecting_sources(results, result.affecting_sources.clone());
2941
2942 if options_value.prefer_relative {
2943 let mut module_prefixed = module.clone();
2944 module_prefixed.push_front(rcstr!("./").into());
2945 let pattern = Pattern::concat([module_prefixed.clone(), rcstr!("/").into(), path.clone()]);
2946 let relative = Request::relative(pattern, query, fragment, true)
2947 .to_resolved()
2948 .await?;
2949 let relative_result = Box::pin(resolve_internal_inline(
2950 lookup_path.clone(),
2951 *relative,
2952 options,
2953 ))
2954 .await?;
2955 let relative_result = relative_result.with_stripped_request_key_prefix(rcstr!("./"));
2956
2957 Ok(merge_results(vec![relative_result, module_result]))
2958 } else {
2959 Ok(module_result)
2960 }
2961}
2962
2963#[turbo_tasks::function]
2964async fn resolve_into_package(
2965 path: Pattern,
2966 package_path: FileSystemPath,
2967 query: RcStr,
2968 fragment: RcStr,
2969 options: ResolvedVc<ResolveOptions>,
2970) -> Result<Vc<ResolveResult>> {
2971 let options_value = options.await?;
2972 let mut results = Vec::new();
2973
2974 let is_root_match = path.is_match("") || path.is_match("/");
2975 let could_match_others = path.could_match_others("");
2976
2977 let mut export_path_request = path.clone();
2978 export_path_request.push_front(rcstr!(".").into());
2979 for resolve_into_package in options_value.into_package.iter() {
2980 match resolve_into_package {
2981 ResolveIntoPackage::MainField { .. } => {}
2983 ResolveIntoPackage::ExportsField {
2984 conditions,
2985 unspecified_conditions,
2986 } => {
2987 let package_json_path = package_path.join("package.json")?;
2988 let ExportsFieldResult::Some(exports_field) =
2989 &*exports_field(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
2990 else {
2991 continue;
2992 };
2993
2994 results.push(
2995 handle_exports_imports_field(
2996 package_path.clone(),
2997 package_json_path,
2998 *options,
2999 exports_field,
3000 export_path_request.clone(),
3001 conditions,
3002 unspecified_conditions,
3003 query,
3004 ExportImport::Export,
3005 )
3006 .await?,
3007 );
3008
3009 return Ok(merge_results(results));
3012 }
3013 }
3014 }
3015
3016 if is_root_match {
3018 results.push(resolve_into_folder(
3019 package_path.clone(),
3020 options.with_fully_specified(false),
3021 ));
3022 }
3023
3024 if could_match_others {
3025 let mut new_pat = path.clone();
3026 new_pat.push_front(rcstr!(".").into());
3027
3028 let relative = Request::relative(new_pat, query, fragment, true)
3029 .to_resolved()
3030 .await?;
3031 results.push(resolve_internal_inline(package_path.clone(), *relative, *options).await?);
3032 }
3033
3034 Ok(merge_results(results))
3035}
3036
3037#[tracing::instrument(level = Level::TRACE, skip_all)]
3038async fn resolve_import_map_result(
3039 result: &ImportMapResult,
3040 lookup_path: FileSystemPath,
3041 original_lookup_path: FileSystemPath,
3042 original_request: Vc<Request>,
3043 options: Vc<ResolveOptions>,
3044 query: RcStr,
3045) -> Result<Option<ResolveResultOrCell>> {
3046 Ok(match result {
3047 ImportMapResult::Result(result) => Some(ResolveResultOrCell::Cell(**result)),
3048 ImportMapResult::Alias(request, alias_lookup_path) => {
3049 let request_vc: Vc<Request> = **request;
3050 let request = if request_vc.query().await?.is_empty() && !query.is_empty() {
3052 request_vc.with_query(query.clone())
3053 } else {
3054 request_vc
3055 };
3056 let lookup_path = alias_lookup_path.clone().unwrap_or(lookup_path);
3057
3058 let request_pattern = request.request_pattern();
3060 let original_pattern = original_request.request_pattern();
3061
3062 if *request_pattern.await? == *original_pattern.await?
3063 && lookup_path == original_lookup_path
3064 {
3065 None
3066 } else {
3067 Some(ResolveResultOrCell::Cell(
3068 resolve_internal(lookup_path, request, options)
3069 .with_replaced_request_key_pattern(request_pattern, original_pattern),
3070 ))
3071 }
3072 }
3073 ImportMapResult::External {
3074 name,
3075 ty,
3076 traced,
3077 target,
3078 } => Some(ResolveResultOrCell::Value(ResolveResult::primary(
3079 ResolveResultItem::External {
3080 name: name.clone(),
3081 ty: *ty,
3082 traced: *traced,
3083 target: target.clone(),
3084 },
3085 ))),
3086 ImportMapResult::AliasExternal {
3087 name,
3088 ty,
3089 traced,
3090 lookup_dir: alias_lookup_path,
3091 } => {
3092 let request = Request::parse_string(name.clone());
3093
3094 if *request.to_resolved().await? == original_request
3096 && *alias_lookup_path == original_lookup_path
3097 {
3098 None
3099 } else {
3100 let is_external_resolvable = !resolve_internal(
3101 alias_lookup_path.clone(),
3102 request,
3103 match ty {
3104 ExternalType::CommonJs => node_cjs_resolve_options(),
3105 ExternalType::EcmaScriptModule => node_esm_resolve_options(),
3106 ExternalType::Script | ExternalType::Url | ExternalType::Global => options,
3107 },
3108 )
3109 .await?
3110 .is_unresolvable();
3111 if is_external_resolvable {
3112 Some(ResolveResultOrCell::Value(ResolveResult::primary(
3113 ResolveResultItem::External {
3114 name: name.clone(),
3115 ty: *ty,
3116 traced: *traced,
3117 target: None,
3118 },
3119 )))
3120 } else {
3121 None
3122 }
3123 }
3124 }
3125 ImportMapResult::Alternatives(list) => {
3126 let results = list
3127 .iter()
3128 .map(|result| {
3129 resolve_import_map_result(
3130 result,
3131 lookup_path.clone(),
3132 original_lookup_path.clone(),
3133 original_request,
3134 options,
3135 query.clone(),
3136 )
3137 })
3138 .try_join()
3139 .await?;
3140
3141 let cells: Vec<Vc<ResolveResult>> = results
3143 .into_iter()
3144 .flatten()
3145 .map(|r| r.into_cell())
3146 .collect();
3147 Some(ResolveResultOrCell::Cell(merge_results(cells)))
3148 }
3149 ImportMapResult::NoEntry => None,
3150 ImportMapResult::Error(issue) => Some(ResolveResultOrCell::Value(ResolveResult::primary(
3151 ResolveResultItem::Error(*issue),
3152 ))),
3153 })
3154}
3155
3156enum ResolveResultOrCell {
3159 Cell(Vc<ResolveResult>),
3160 Value(ResolveResult),
3161}
3162
3163impl ResolveResultOrCell {
3164 fn into_cell(self) -> Vc<ResolveResult> {
3165 match self {
3166 ResolveResultOrCell::Cell(vc) => vc,
3167 ResolveResultOrCell::Value(value) => value.cell(),
3168 }
3169 }
3170
3171 async fn into_cell_if_resolvable(self) -> Result<Option<Vc<ResolveResult>>> {
3172 match self {
3173 ResolveResultOrCell::Cell(resolved_result) => {
3174 if !resolved_result.await?.is_unresolvable() {
3175 return Ok(Some(resolved_result));
3176 }
3177 }
3178 ResolveResultOrCell::Value(resolve_result) => {
3179 if !resolve_result.is_unresolvable() {
3180 return Ok(Some(resolve_result.cell()));
3181 }
3182 }
3183 }
3184 Ok(None)
3185 }
3186}
3187
3188#[tracing::instrument(level = Level::TRACE, skip_all)]
3189async fn resolved(
3190 request_key: RequestKey,
3191 fs_path: FileSystemPath,
3192 original_context: FileSystemPath,
3193 original_request: Vc<Request>,
3194 options_value: &ResolveOptions,
3195 options: Vc<ResolveOptions>,
3196 query: RcStr,
3197 fragment: RcStr,
3198) -> Result<ResolveResultOrCell> {
3199 let result = &*fs_path.realpath_with_links().await?;
3200 let path = match &result.path_result {
3201 Ok(path) => path,
3202 Err(error) => bail!(error.clone()),
3203 };
3204
3205 let path_ref = path.clone();
3206 if let Some(result) = apply_in_package(
3208 path.parent(),
3209 options,
3210 options_value,
3211 |package_path| package_path.get_relative_request_to(&path_ref),
3212 query.clone(),
3213 fragment.clone(),
3214 )
3215 .await?
3216 {
3217 return Ok(result);
3218 }
3219
3220 if let Some(resolved_map) = options_value.resolved_map {
3221 let result = resolved_map
3222 .lookup(path.clone(), original_context.clone(), original_request)
3223 .await?;
3224
3225 let resolved_result = resolve_import_map_result(
3226 &result,
3227 path.parent(),
3228 original_context.clone(),
3229 original_request,
3230 options,
3231 query.clone(),
3232 )
3233 .await?;
3234
3235 if let Some(result) = resolved_result {
3236 return Ok(result);
3237 }
3238 }
3239 let source = ResolvedVc::upcast(
3240 FileSource::new_with_query_and_fragment(path.clone(), query, fragment)
3241 .to_resolved()
3242 .await?,
3243 );
3244 Ok(ResolveResultOrCell::Value(
3245 if options_value.collect_affecting_sources {
3246 ResolveResult::source_with_affecting_sources(
3247 request_key,
3248 source,
3249 result
3250 .symlinks
3251 .iter()
3252 .map(async |symlink| {
3253 anyhow::Ok(ResolvedVc::upcast(
3254 FileSource::new(symlink.clone()).to_resolved().await?,
3255 ))
3256 })
3257 .try_join()
3258 .await?,
3259 )
3260 } else {
3261 ResolveResult::source_with_key(request_key, source)
3262 },
3263 ))
3264}
3265
3266async fn apply_conditions(
3271 resolve_result: Vc<ResolveResult>,
3272 conditions: &[(RcStr, bool)],
3273) -> Result<Vc<ResolveResult>> {
3274 if conditions.is_empty() {
3275 Ok(resolve_result)
3276 } else {
3277 Ok(resolve_result.await?.with_conditions(conditions).cell())
3278 }
3279}
3280
3281async fn handle_exports_imports_field(
3282 package_path: FileSystemPath,
3283 package_json_path: FileSystemPath,
3284 options: Vc<ResolveOptions>,
3285 exports_imports_field: &AliasMap<SubpathValue>,
3286 mut path: Pattern,
3287 conditions: &BTreeMap<RcStr, ConditionValue>,
3288 unspecified_conditions: &ConditionValue,
3289 query: RcStr,
3290 ty: ExportImport,
3291) -> Result<Vc<ResolveResult>> {
3292 let mut results = Vec::new();
3293 let mut conditions_state = FxHashMap::default();
3294
3295 if !query.is_empty() {
3296 path.push(query.into());
3297 }
3298 let req = path;
3299
3300 let values = exports_imports_field.lookup(&req);
3301 for value in values {
3302 let value = value?;
3303 if value.output.add_results(
3304 value.prefix,
3305 value.key,
3306 conditions,
3307 unspecified_conditions,
3308 &mut conditions_state,
3309 &mut results,
3310 ) != TerminalState::Unset
3311 {
3312 break;
3315 }
3316 }
3317
3318 let mut resolved_results = Vec::new();
3319 for ReplacedSubpathValueResult {
3320 ty: result_ty,
3321 conditions,
3322 map_prefix,
3323 map_key,
3324 } in results
3325 {
3326 match result_ty {
3327 ReplacedSubpathValueResultType::Path(result_path) => {
3328 let request = match ty {
3329 ExportImport::Export => {
3330 Pattern::Concatenation(vec![
3332 Pattern::Constant(rcstr!("./")),
3333 result_path.clone(),
3334 ])
3335 }
3336 ExportImport::Import => result_path.clone(),
3337 };
3338 let request = *Request::parse(request).to_resolved().await?;
3339
3340 let resolve_result = Box::pin(resolve_internal_inline(
3341 package_path.clone(),
3342 request,
3343 options,
3344 ))
3345 .await?;
3346
3347 let resolve_result = if let Some(req) = req.as_constant_string() {
3348 resolve_result.with_request(req.clone())
3349 } else {
3350 match map_key {
3351 AliasKey::Exact => resolve_result.with_request(map_prefix.clone().into()),
3352 AliasKey::Wildcard { .. } => {
3353 let mut old_request_key = result_path;
3363 if matches!(ty, ExportImport::Export) {
3364 old_request_key.push_front(rcstr!("./").into());
3366 }
3367 let new_request_key = req.clone();
3368
3369 resolve_result.with_replaced_request_key_pattern(
3370 Pattern::new(old_request_key),
3371 Pattern::new(new_request_key),
3372 )
3373 }
3374 }
3375 };
3376
3377 let resolve_result = apply_conditions(resolve_result, &conditions).await?;
3378 resolved_results.push(resolve_result);
3379 }
3380 ReplacedSubpathValueResultType::Empty => {
3381 let resolve_result = ResolveResult::primary(ResolveResultItem::Empty).cell();
3383 let resolve_result = apply_conditions(resolve_result, &conditions).await?;
3384 resolved_results.push(resolve_result);
3385 }
3386 }
3387 }
3388
3389 Ok(merge_results_with_affecting_sources(
3391 resolved_results,
3392 vec![ResolvedVc::upcast(
3393 FileSource::new(package_json_path).to_resolved().await?,
3394 )],
3395 ))
3396}
3397
3398async fn resolve_package_internal_with_imports_field(
3403 file_path: FileSystemPath,
3404 request: Vc<Request>,
3405 resolve_options: Vc<ResolveOptions>,
3406 pattern: &Pattern,
3407 conditions: &BTreeMap<RcStr, ConditionValue>,
3408 unspecified_conditions: &ConditionValue,
3409) -> Result<Vc<ResolveResult>> {
3410 let Pattern::Constant(specifier) = pattern else {
3411 bail!("PackageInternal requests can only be Constant strings");
3412 };
3413 if specifier == "#" || specifier.ends_with('/') {
3414 ResolvingIssue {
3415 severity: resolve_error_severity(resolve_options).await?,
3416 file_path: file_path.clone(),
3417 request_type: format!("package imports request: `{specifier}`"),
3418 request: request.to_resolved().await?,
3419 resolve_options: resolve_options.to_resolved().await?,
3420 error_message: None,
3421 source: None,
3422 }
3423 .resolved_cell()
3424 .emit();
3425 return Ok(ResolveResult::unresolvable().cell());
3426 }
3427
3428 let imports_result = imports_field(file_path).await?;
3429 let (imports, package_json_path) = match &*imports_result {
3430 ImportsFieldResult::Some(i, p) => (i, p.clone()),
3431 ImportsFieldResult::None => return Ok(ResolveResult::unresolvable().cell()),
3432 };
3433
3434 handle_exports_imports_field(
3435 package_json_path.parent(),
3436 package_json_path.clone(),
3437 resolve_options,
3438 imports,
3439 Pattern::Constant(specifier.clone()),
3440 conditions,
3441 unspecified_conditions,
3442 RcStr::default(),
3443 ExportImport::Import,
3444 )
3445 .await
3446}
3447
3448#[turbo_tasks::task_input]
3452#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Encode, Decode)]
3453pub enum ModulePart {
3454 Evaluation,
3457 Export(RcStr),
3459 PartialExport { export: RcStr, member: RcStr },
3466 RenamedExport {
3469 original_export: RcStr,
3470 export: RcStr,
3471 },
3472 RenamedNamespace { export: RcStr },
3475 RenamedPartialNamespace { export: RcStr, member: RcStr },
3481 Internal(u32),
3484 Locals,
3486 Exports,
3488 Facade,
3491}
3492
3493impl ModulePart {
3494 pub fn evaluation() -> Self {
3495 ModulePart::Evaluation
3496 }
3497
3498 pub fn export(export: RcStr) -> Self {
3499 ModulePart::Export(export)
3500 }
3501
3502 pub fn partial_export(export: RcStr, member: RcStr) -> Self {
3503 ModulePart::PartialExport { export, member }
3504 }
3505
3506 pub fn renamed_export(original_export: RcStr, export: RcStr) -> Self {
3507 ModulePart::RenamedExport {
3508 original_export,
3509 export,
3510 }
3511 }
3512
3513 pub fn renamed_namespace(export: RcStr) -> Self {
3514 ModulePart::RenamedNamespace { export }
3515 }
3516
3517 pub fn renamed_partial_namespace(export: RcStr, member: RcStr) -> Self {
3518 ModulePart::RenamedPartialNamespace { export, member }
3519 }
3520
3521 pub fn get_export(&self) -> Option<&RcStr> {
3523 match self {
3524 ModulePart::Export(export)
3525 | ModulePart::PartialExport { export, .. }
3526 | ModulePart::RenamedExport { export, .. }
3527 | ModulePart::RenamedNamespace { export }
3528 | ModulePart::RenamedPartialNamespace { export, .. } => Some(export),
3529 _ => None,
3530 }
3531 }
3532
3533 pub fn internal(id: u32) -> Self {
3534 ModulePart::Internal(id)
3535 }
3536
3537 pub fn locals() -> Self {
3538 ModulePart::Locals
3539 }
3540
3541 pub fn exports() -> Self {
3542 ModulePart::Exports
3543 }
3544
3545 pub fn facade() -> Self {
3546 ModulePart::Facade
3547 }
3548}
3549
3550impl Display for ModulePart {
3551 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3552 match self {
3553 ModulePart::Evaluation => f.write_str("module evaluation"),
3554 ModulePart::Export(export) => write!(f, "export {export}"),
3555 ModulePart::PartialExport { export, member } => {
3556 write!(f, "export {export} .{member}")
3557 }
3558 ModulePart::RenamedExport {
3559 original_export,
3560 export,
3561 } => write!(f, "export {original_export} as {export}"),
3562 ModulePart::RenamedNamespace { export } => write!(f, "export * as {export}"),
3563 ModulePart::RenamedPartialNamespace { export, member } => {
3564 write!(f, "export * as {export} .{member}")
3565 }
3566 ModulePart::Internal(id) => write!(f, "internal part {id}"),
3567 ModulePart::Locals => f.write_str("locals"),
3568 ModulePart::Exports => f.write_str("exports"),
3569 ModulePart::Facade => f.write_str("facade"),
3570 }
3571 }
3572}
3573#[cfg(test)]
3574mod tests {
3575 use std::{
3576 fs::{File, create_dir_all},
3577 io::Write,
3578 };
3579
3580 use anyhow::Result;
3581 use turbo_rcstr::{RcStr, rcstr};
3582 use turbo_tasks::{TryJoinIterExt, Vc};
3583 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
3584 use turbo_tasks_fs::{DiskFileSystem, FileContent, FileSystem, FileSystemPath};
3585
3586 use super::*;
3587 use crate::{
3588 asset::AssetContent, module::Module, raw_module::RawModule, source::Source,
3589 virtual_source::VirtualSource,
3590 };
3591
3592 #[test]
3593 fn module_part_export_names_and_display() {
3594 let export = rcstr!("value");
3595 let member = rcstr!("member");
3596 let original = rcstr!("original");
3597
3598 let with_exports = [
3599 ModulePart::export(export.clone()),
3600 ModulePart::partial_export(export.clone(), member.clone()),
3601 ModulePart::renamed_export(original, export.clone()),
3602 ModulePart::renamed_namespace(export.clone()),
3603 ModulePart::renamed_partial_namespace(export.clone(), member.clone()),
3604 ];
3605 for part in with_exports {
3606 assert_eq!(part.get_export(), Some(&export));
3607 }
3608
3609 let without_exports = [
3610 ModulePart::evaluation(),
3611 ModulePart::internal(0),
3612 ModulePart::locals(),
3613 ModulePart::exports(),
3614 ModulePart::facade(),
3615 ];
3616 for part in without_exports {
3617 assert_eq!(part.get_export(), None);
3618 }
3619
3620 assert_eq!(
3621 ModulePart::partial_export(export.clone(), member.clone()).to_string(),
3622 "export value .member"
3623 );
3624 assert_eq!(
3625 ModulePart::renamed_partial_namespace(export, member).to_string(),
3626 "export * as value .member"
3627 );
3628 }
3629
3630 #[cfg(unix)]
3631 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3632 async fn test_symlink_resolution_failures_are_missing_candidates() {
3633 use std::os::unix::fs::symlink;
3634
3635 #[turbo_tasks::value]
3636 struct MissingPathsResult {
3637 missing_file: bool,
3638 dangling: bool,
3639 self_cycle: bool,
3640 }
3641
3642 let scratch = tempfile::tempdir().unwrap();
3643 create_dir_all(scratch.path().join("package")).unwrap();
3644 symlink("package", scratch.path().join("linked-package")).unwrap();
3645 symlink("missing", scratch.path().join("dangling")).unwrap();
3646 symlink("self-cycle", scratch.path().join("self-cycle")).unwrap();
3647
3648 let path = RcStr::from(scratch.path().to_str().unwrap());
3649 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3650 BackendOptions::default(),
3651 noop_backing_storage(),
3652 ));
3653
3654 #[turbo_tasks::function(operation, root)]
3655 async fn missing_paths_through_symlinks_operation(
3656 path: RcStr,
3657 ) -> Result<Vc<MissingPathsResult>> {
3658 let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
3659 let root = fs.root().owned().await?;
3660 Ok(MissingPathsResult {
3661 missing_file: realpath_if_exists(&root.join("linked-package/package.json")?, None)
3662 .await?
3663 .is_none(),
3664 dangling: realpath_if_exists(&root.join("dangling")?, None)
3665 .await?
3666 .is_none(),
3667 self_cycle: realpath_if_exists(&root.join("self-cycle")?, None)
3668 .await?
3669 .is_none(),
3670 }
3671 .cell())
3672 }
3673
3674 tt.run_once(async move {
3675 let missing = missing_paths_through_symlinks_operation(path)
3676 .read_strongly_consistent()
3677 .await?;
3678 assert!(missing.missing_file);
3679 assert!(missing.dangling);
3680 assert!(missing.self_cycle);
3681
3682 anyhow::Ok(())
3683 })
3684 .await
3685 .unwrap();
3686 }
3687
3688 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3689 async fn test_explicit_js_resolves_to_ts() {
3690 resolve_relative_request_test(TestParams {
3691 files: vec!["foo.js", "foo.ts"],
3692 pattern: rcstr!("./foo.js").into(),
3693 enable_typescript_with_output_extension: true,
3694 fully_specified: false,
3695 custom_extensions: None,
3696 expected: vec![("./foo.js", "foo.ts")],
3697 })
3698 .await;
3699 }
3700
3701 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3702 async fn test_implicit_request_ts_priority() {
3703 resolve_relative_request_test(TestParams {
3704 files: vec!["foo.js", "foo.ts"],
3705 pattern: rcstr!("./foo").into(),
3706 enable_typescript_with_output_extension: true,
3707 fully_specified: false,
3708 custom_extensions: None,
3709 expected: vec![("./foo", "foo.ts")],
3710 })
3711 .await;
3712 }
3713
3714 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3715 async fn test_ts_priority_over_json() {
3716 resolve_relative_request_test(TestParams {
3717 files: vec!["posts.json", "posts.ts"],
3718 pattern: rcstr!("./posts").into(),
3719 enable_typescript_with_output_extension: true,
3720 fully_specified: false,
3721 custom_extensions: None,
3722 expected: vec![("./posts", "posts.ts")],
3723 })
3724 .await;
3725 }
3726
3727 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3728 async fn test_only_js_file_no_ts() {
3729 resolve_relative_request_test(TestParams {
3730 files: vec!["bar.js"],
3731 pattern: rcstr!("./bar.js").into(),
3732 enable_typescript_with_output_extension: true,
3733 fully_specified: false,
3734 custom_extensions: None,
3735 expected: vec![("./bar.js", "bar.js")],
3736 })
3737 .await;
3738 }
3739
3740 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3741 async fn test_explicit_ts_request() {
3742 resolve_relative_request_test(TestParams {
3743 files: vec!["foo.js", "foo.ts"],
3744 pattern: rcstr!("./foo.ts").into(),
3745 enable_typescript_with_output_extension: true,
3746 fully_specified: false,
3747 custom_extensions: None,
3748 expected: vec![("./foo.ts", "foo.ts")],
3749 })
3750 .await;
3751 }
3752
3753 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3755 async fn test_fragment() {
3756 resolve_relative_request_test(TestParams {
3757 files: vec!["client.ts"],
3758 pattern: rcstr!("./client#frag").into(),
3759 enable_typescript_with_output_extension: true,
3760 fully_specified: false,
3761 custom_extensions: None,
3762 expected: vec![("./client", "client.ts")],
3763 })
3764 .await;
3765 }
3766
3767 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3768 async fn test_fragment_as_part_of_filename() {
3769 resolve_relative_request_test(TestParams {
3771 files: vec!["client#component.js", "client#component.ts"],
3772 pattern: rcstr!("./client#component.js").into(),
3773 enable_typescript_with_output_extension: true,
3774 fully_specified: false,
3775 custom_extensions: None,
3776 expected: vec![("./client", "client#component.ts")],
3779 })
3780 .await;
3781 }
3782
3783 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3784 async fn test_fragment_with_ts_priority() {
3785 resolve_relative_request_test(TestParams {
3787 files: vec!["page#section.js", "page#section.ts"],
3788 pattern: rcstr!("./page#section").into(),
3789 enable_typescript_with_output_extension: true,
3790 fully_specified: false,
3791 custom_extensions: None,
3792 expected: vec![("./page", "page#section.ts")],
3793 })
3794 .await;
3795 }
3796
3797 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3798 async fn test_query() {
3799 resolve_relative_request_test(TestParams {
3800 files: vec!["client.ts", "client.js"],
3801 pattern: rcstr!("./client?q=s").into(),
3802 enable_typescript_with_output_extension: true,
3803 fully_specified: false,
3804 custom_extensions: None,
3805 expected: vec![("./client", "client.ts")],
3806 })
3807 .await;
3808 }
3809
3810 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3812 async fn test_dynamic_pattern_with_js_extension() {
3813 resolve_relative_request_test(TestParams {
3817 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3818 pattern: Pattern::Concatenation(vec![
3819 Pattern::Constant(rcstr!("./src/")),
3820 Pattern::Dynamic,
3821 Pattern::Constant(rcstr!(".js")),
3822 ]),
3823 enable_typescript_with_output_extension: true,
3824 fully_specified: false,
3825 custom_extensions: None,
3826 expected: vec![
3827 ("./src/foo.js", "src/foo.ts"),
3828 ("./src/bar.js", "src/bar.js"),
3829 ],
3830 })
3831 .await;
3832 }
3833
3834 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3835 async fn test_dynamic_pattern_without_extension() {
3836 resolve_relative_request_test(TestParams {
3840 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3841 pattern: Pattern::Concatenation(vec![
3842 Pattern::Constant(rcstr!("./src/")),
3843 Pattern::Dynamic,
3844 ]),
3845 enable_typescript_with_output_extension: true,
3846 fully_specified: false,
3847 custom_extensions: None,
3848 expected: vec![
3849 ("./src/bar.js", "src/bar.js"),
3850 ("./src/bar", "src/bar.js"),
3851 ("./src/foo.js", "src/foo.js"),
3857 ("./src/foo", "src/foo.js"),
3858 ("./src/foo.ts", "src/foo.ts"),
3859 ],
3860 })
3861 .await;
3862 }
3863
3864 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3867 async fn test_custom_extensions_web_before_default() {
3868 resolve_relative_request_test(TestParams {
3869 files: vec!["Component.web.tsx", "Component.tsx"],
3870 pattern: rcstr!("./Component").into(),
3871 enable_typescript_with_output_extension: false,
3872 fully_specified: false,
3873 custom_extensions: Some(vec![
3874 rcstr!(".web.tsx"),
3875 rcstr!(".web.ts"),
3876 rcstr!(".web.jsx"),
3877 rcstr!(".web.js"),
3878 rcstr!(".tsx"),
3879 rcstr!(".ts"),
3880 rcstr!(".jsx"),
3881 rcstr!(".js"),
3882 ]),
3883 expected: vec![("./Component", "Component.web.tsx")],
3884 })
3885 .await;
3886 }
3887
3888 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3890 async fn test_custom_extensions_fallback_when_web_missing() {
3891 resolve_relative_request_test(TestParams {
3892 files: vec!["Component.tsx"],
3893 pattern: rcstr!("./Component").into(),
3894 enable_typescript_with_output_extension: false,
3895 fully_specified: false,
3896 custom_extensions: Some(vec![
3897 rcstr!(".web.tsx"),
3898 rcstr!(".web.ts"),
3899 rcstr!(".web.jsx"),
3900 rcstr!(".web.js"),
3901 rcstr!(".tsx"),
3902 rcstr!(".ts"),
3903 rcstr!(".jsx"),
3904 rcstr!(".js"),
3905 ]),
3906 expected: vec![("./Component", "Component.tsx")],
3907 })
3908 .await;
3909 }
3910
3911 struct TestParams<'a> {
3913 files: Vec<&'a str>,
3914 pattern: Pattern,
3915 enable_typescript_with_output_extension: bool,
3916 fully_specified: bool,
3917 custom_extensions: Option<Vec<RcStr>>,
3919 expected: Vec<(&'a str, &'a str)>,
3920 }
3921
3922 async fn resolve_relative_request_test(
3924 TestParams {
3925 files,
3926 pattern,
3927 enable_typescript_with_output_extension,
3928 fully_specified,
3929 custom_extensions,
3930 expected,
3931 }: TestParams<'_>,
3932 ) {
3933 let scratch = tempfile::tempdir().unwrap();
3934 {
3935 let path = scratch.path();
3936
3937 for file_name in &files {
3938 let file_path = path.join(file_name);
3939 if let Some(parent) = file_path.parent() {
3940 create_dir_all(parent).unwrap();
3941 }
3942 File::create_new(&file_path)
3943 .unwrap()
3944 .write_all(format!("export default '{file_name}'").as_bytes())
3945 .unwrap();
3946 }
3947 }
3948
3949 let path: RcStr = scratch.path().to_str().unwrap().into();
3950 let expected_owned: Vec<(String, String)> = expected
3951 .iter()
3952 .map(|(k, v)| (k.to_string(), v.to_string()))
3953 .collect();
3954
3955 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3956 BackendOptions::default(),
3957 noop_backing_storage(),
3958 ));
3959
3960 let custom_extensions_owned = custom_extensions;
3961
3962 tt.run_once(async move {
3963 #[turbo_tasks::value(transparent)]
3964 struct ResolveRelativeRequestOutput(Vec<(String, String)>);
3965
3966 #[turbo_tasks::function(operation, root)]
3967 async fn resolve_relative_request_operation(
3968 path: RcStr,
3969 pattern: Pattern,
3970 enable_typescript_with_output_extension: bool,
3971 fully_specified: bool,
3972 custom_extensions: Option<Vec<RcStr>>,
3973 ) -> Result<Vc<ResolveRelativeRequestOutput>> {
3974 let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
3975 let lookup_path = fs.root().owned().await?;
3976
3977 let result = resolve_relative_helper(
3978 lookup_path,
3979 pattern,
3980 enable_typescript_with_output_extension,
3981 fully_specified,
3982 custom_extensions,
3983 )
3984 .await?;
3985
3986 let results: Vec<(String, String)> = result
3987 .primary
3988 .iter()
3989 .map(async |(k, v)| {
3990 Ok((
3991 k.to_string(),
3992 if let ResolveResultItem::Source(source) = v {
3993 source.ident().await?.path.path.to_string()
3994 } else {
3995 unreachable!()
3996 },
3997 ))
3998 })
3999 .try_join()
4000 .await?;
4001
4002 Ok(Vc::cell(results))
4003 }
4004
4005 let results = resolve_relative_request_operation(
4006 path,
4007 pattern,
4008 enable_typescript_with_output_extension,
4009 fully_specified,
4010 custom_extensions_owned,
4011 )
4012 .read_strongly_consistent()
4013 .await?;
4014
4015 assert_eq!(&*results, &expected_owned);
4016
4017 Ok(())
4018 })
4019 .await
4020 .unwrap();
4021 }
4022
4023 #[turbo_tasks::function]
4024 async fn resolve_relative_helper(
4025 lookup_path: FileSystemPath,
4026 pattern: Pattern,
4027 enable_typescript_with_output_extension: bool,
4028 fully_specified: bool,
4029 custom_extensions: Option<Vec<RcStr>>,
4030 ) -> Result<Vc<ResolveResult>> {
4031 let request = Request::parse(pattern.clone());
4032
4033 let extensions = custom_extensions
4034 .unwrap_or_else(|| vec![rcstr!(".ts"), rcstr!(".js"), rcstr!(".json")]);
4035 let mut options_value = node_esm_resolve_options()
4036 .with_fully_specified(fully_specified)
4037 .with_extensions(extensions)
4038 .owned()
4039 .await?;
4040 options_value.enable_typescript_with_output_extension =
4041 enable_typescript_with_output_extension;
4042 let options = options_value.clone().cell();
4043 match &*request.await? {
4044 Request::Relative {
4045 path,
4046 query,
4047 force_in_lookup_dir,
4048 fragment,
4049 } => {
4050 resolve_relative_request(
4051 lookup_path,
4052 request,
4053 options,
4054 &options_value,
4055 path,
4056 query.clone(),
4057 *force_in_lookup_dir,
4058 fragment.clone(),
4059 )
4060 .await
4061 }
4062 r => panic!("request should be relative, got {r:?}"),
4063 }
4064 }
4065
4066 #[turbo_tasks::value(transparent)]
4073 pub struct DupCheckResult(Vec<String>);
4074
4075 async fn snapshot_primary(result: &ModuleResolveResult) -> Result<Vec<String>> {
4076 let mut out = Vec::with_capacity(result.primary.len());
4077 for (_, item) in result.primary.iter() {
4078 out.push(match *item {
4079 ModuleResolveResultItem::Module(m) => {
4080 let ident = m.ident().await?;
4081 format!("module:{}", ident.path.path)
4082 }
4083 ModuleResolveResultItem::Duplicate(i) => format!("dup:{i}"),
4084 _ => "other".to_string(),
4085 });
4086 }
4087 Ok(out)
4088 }
4089
4090 #[turbo_tasks::function]
4091 fn fs() -> Vc<Box<dyn FileSystem>> {
4092 Vc::upcast(DiskFileSystem::new(rcstr!("temp"), Vc::cell(fs_path())))
4093 }
4094
4095 #[turbo_tasks::function]
4096 async fn make_module(name: RcStr) -> Result<Vc<Box<dyn Module>>> {
4097 let path = fs().root().await?.join(&name)?;
4098 let file_content =
4099 FileContent::Content(turbo_tasks_fs::File::from(format!("// {name}"))).resolved_cell();
4100 let content = AssetContent::file(*file_content).to_resolved().await?;
4101 let source = VirtualSource::new(path, *content);
4102 let module = RawModule::new(Vc::upcast(source)).to_resolved().await?;
4103 Ok(Vc::upcast(*module))
4104 }
4105
4106 fn fs_path() -> RcStr {
4107 rcstr!("/tmp/_mdt")
4108 }
4109
4110 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4111 async fn modules_constructor_marks_module_duplicates() {
4112 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4113 BackendOptions::default(),
4114 noop_backing_storage(),
4115 ));
4116 #[turbo_tasks::function(operation, root)]
4117 async fn run_test() -> Result<Vc<DupCheckResult>> {
4118 let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
4119 let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
4120
4121 let result = ModuleResolveResult::modules([
4122 (RequestKey::new(rcstr!("a")), m_a),
4123 (RequestKey::new(rcstr!("b")), m_b),
4124 (RequestKey::new(rcstr!("a-again")), m_a),
4125 (RequestKey::new(rcstr!("b-again")), m_b),
4126 ])
4127 .await?;
4128
4129 let modules = result.primary_modules().await?;
4131 assert_eq!(modules.as_slice(), [m_a, m_b]);
4132
4133 Ok(Vc::cell(snapshot_primary(&result).await?))
4134 }
4135 tt.run_once(async move {
4136 let snap = run_test().read_strongly_consistent().await?;
4137 assert_eq!(
4138 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4139 vec!["module:a.js", "module:b.js", "dup:0", "dup:1"]
4140 );
4141 Ok(())
4142 })
4143 .await
4144 .unwrap();
4145 }
4146
4147 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4148 async fn first_module_returns_first_when_duplicates_follow() {
4149 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4150 BackendOptions::default(),
4151 noop_backing_storage(),
4152 ));
4153 #[turbo_tasks::function(operation, root)]
4154 async fn run_test() -> Result<Vc<DupCheckResult>> {
4155 let m = make_module(rcstr!("a.js")).to_resolved().await?;
4156
4157 let result = ModuleResolveResult::modules([
4158 (RequestKey::default(), m),
4159 (RequestKey::new(rcstr!("again")), m),
4160 (RequestKey::new(rcstr!("once-more")), m),
4161 ])
4162 .await?;
4163
4164 assert_eq!(result.first_module().await?, Some(m));
4165 assert_eq!(result.primary_modules().await?.as_slice(), [m]);
4166 Ok(Vc::cell(snapshot_primary(&result).await?))
4167 }
4168 tt.run_once(async move {
4169 let snap = run_test().read_strongly_consistent().await?;
4170 assert_eq!(
4171 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4172 vec!["module:a.js", "dup:0", "dup:0"]
4173 );
4174 Ok(())
4175 })
4176 .await
4177 .unwrap();
4178 }
4179
4180 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4181 async fn builder_marks_module_duplicates_skipping_non_dedup_items() {
4182 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4183 BackendOptions::default(),
4184 noop_backing_storage(),
4185 ));
4186 #[turbo_tasks::function(operation, root)]
4187 async fn run_test() -> Result<Vc<DupCheckResult>> {
4188 let m = make_module(rcstr!("a.js")).to_resolved().await?;
4189
4190 let mut builder = ModuleResolveResultBuilder {
4191 primary: Default::default(),
4192 affecting_sources: Vec::new(),
4193 };
4194 builder.primary.insert(
4195 RequestKey::new(rcstr!("k0")),
4196 ModuleResolveResultItem::Module(m),
4197 );
4198 builder.primary.insert(
4199 RequestKey::new(rcstr!("k1")),
4200 ModuleResolveResultItem::Empty,
4201 );
4202 builder.primary.insert(
4203 RequestKey::new(rcstr!("k2")),
4204 ModuleResolveResultItem::Module(m),
4205 );
4206 let result: ModuleResolveResult = builder.into();
4207 assert_eq!(result.primary_modules().await?.as_slice(), [m]);
4208 Ok(Vc::cell(snapshot_primary(&result).await?))
4209 }
4210 tt.run_once(async move {
4211 let snap = run_test().read_strongly_consistent().await?;
4212
4213 assert_eq!(
4214 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4215 vec!["module:a.js", "other", "dup:0"]
4216 );
4217 Ok(())
4218 })
4219 .await
4220 .unwrap();
4221 }
4222
4223 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4224 async fn alternatives_preserves_unique_module_set() {
4225 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4226 BackendOptions::default(),
4227 noop_backing_storage(),
4228 ));
4229 #[turbo_tasks::function(operation, root)]
4230 async fn run_test() -> Result<Vc<DupCheckResult>> {
4231 let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
4232 let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
4233
4234 let r1 = *ModuleResolveResult::modules([
4236 (RequestKey::new(rcstr!("k1")), m_a),
4237 (RequestKey::new(rcstr!("k2")), m_a),
4238 ]);
4239 let r2 = *ModuleResolveResult::module(m_b);
4242
4243 let merged = ModuleResolveResult::alternatives(vec![r1, r2]).await?;
4244 assert_eq!(merged.primary_modules().await?.as_slice(), [m_a, m_b]);
4245
4246 for (i, (_, item)) in merged.primary.iter().enumerate() {
4248 if let ModuleResolveResultItem::Duplicate(first) = *item {
4249 assert!(
4250 first < i,
4251 "Duplicate index {first} at position {i} must point backwards"
4252 );
4253 let pointed = &merged.primary[first].1;
4254 let ModuleResolveResultItem::Module(pointed_module) = *pointed else {
4255 panic!(
4256 "Duplicate({first}) at {i} points at {pointed:?}, expected a concrete \
4257 Module"
4258 );
4259 };
4260 assert_eq!(
4263 pointed_module, m_a,
4264 "Duplicate({first}) at position {i} points at the wrong module"
4265 );
4266 }
4267 }
4268 Ok(Vc::cell(snapshot_primary(&merged).await?))
4269 }
4270 tt.run_once(async move {
4271 let snap = run_test().read_strongly_consistent().await?;
4272
4273 assert_eq!(
4274 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4275 vec!["module:a.js", "dup:0", "module:b.js"]
4276 );
4277 Ok(())
4278 })
4279 .await
4280 .unwrap();
4281 }
4282
4283 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4284 async fn concat_preserves_distinct_modules_with_same_request_key() {
4285 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4286 BackendOptions::default(),
4287 noop_backing_storage(),
4288 ));
4289 #[turbo_tasks::function(operation, root)]
4290 async fn run_test() -> Result<Vc<DupCheckResult>> {
4291 let app = make_module(rcstr!("app.js")).to_resolved().await?;
4292 let package = make_module(rcstr!("package.js")).to_resolved().await?;
4293 let key = RequestKey::new(rcstr!("./config.js"));
4294 let app_result = *ModuleResolveResult::module_with_key(key.clone(), app);
4295 let package_result = *ModuleResolveResult::modules([
4296 (key, package),
4297 (RequestKey::new(rcstr!("also-app")), app),
4298 ]);
4299 let combined = ModuleResolveResult::concat(vec![app_result, package_result]).await?;
4300 assert_eq!(combined.primary_modules().await?.as_slice(), [app, package]);
4301 Ok(Vc::cell(snapshot_primary(&combined).await?))
4302 }
4303 tt.run_once(async move {
4304 let snap = run_test().read_strongly_consistent().await?;
4305 assert_eq!(
4306 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4307 vec!["module:app.js", "module:package.js", "dup:0"]
4308 );
4309 Ok(())
4310 })
4311 .await
4312 .unwrap();
4313 }
4314}