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, RealPathErrorType};
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};
68use remap::TerminalState;
69pub use remap::{
70 ReplacedSubpathValue, ReplacedSubpathValueResult, ReplacedSubpathValueResultType,
71 ResolveAliasMap, SubpathValue,
72};
73
74#[turbo_tasks::value(shared, task_input)]
76#[derive(Debug, Clone, Copy, Default, Hash)]
77pub enum ResolveErrorMode {
78 #[default]
80 Error,
81 Warn,
83 Ignore,
85}
86
87type AfterResolvePluginWithCondition = (
89 ResolvedVc<Box<dyn AfterResolvePlugin>>,
90 ReadRef<AfterResolvePluginCondition>,
91);
92
93#[turbo_tasks::value(shared)]
94#[derive(Clone, Debug)]
95pub enum ModuleResolveResultItem {
96 Module(ResolvedVc<Box<dyn Module>>),
97 External {
98 name: RcStr,
100 ty: ExternalType,
101 },
102 Unknown(ResolvedVc<Box<dyn Source>>),
104 Ignore,
106 Error(ResolvedVc<Box<dyn Issue>>),
108 Empty,
110 Custom(u8),
111 Duplicate(usize),
118}
119
120impl ModuleResolveResultItem {
121 async fn as_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
125 Ok(match *self {
126 ModuleResolveResultItem::Module(module) => Some(module),
127 ModuleResolveResultItem::Unknown(source) => {
128 emit_unknown_module_type_error(*source).await?;
129 None
130 }
131 ModuleResolveResultItem::Error(_err) => {
132 None
134 }
135 _ => None,
136 })
137 }
138}
139
140#[turbo_tasks::value(shared)]
141#[derive(Clone, Debug, Hash, Default, Serialize, Deserialize)]
142pub struct BindingUsage {
143 pub import: ImportUsage,
144 pub export: ExportUsage,
145}
146
147#[turbo_tasks::value_impl]
148impl BindingUsage {
149 #[turbo_tasks::function]
150 pub fn all() -> Vc<Self> {
151 Self::default().cell()
152 }
153}
154
155#[turbo_tasks::value(shared)]
157#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
158pub enum ImportUsage {
159 #[default]
162 TopLevel,
163 Exports(FrozenSet<RcStr>),
169}
170
171#[turbo_tasks::value]
173#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
174pub enum ExportUsage {
175 Named(RcStr),
176 PartialNamespaceObject(SmallVec<[RcStr; 1]>),
178 #[default]
180 All,
181 Evaluation,
183 Passthrough {
189 namespace_object_may_escape: bool,
192 },
193}
194
195impl Display for ExportUsage {
196 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
197 match self {
198 ExportUsage::Named(name) => write!(f, "export {name}"),
199 ExportUsage::PartialNamespaceObject(names) => {
200 write!(f, "exports ")?;
201 for (i, name) in names.iter().enumerate() {
202 if i > 0 {
203 write!(f, ", ")?;
204 }
205 write!(f, "{name}")?;
206 }
207 Ok(())
208 }
209 ExportUsage::All => write!(f, "all"),
210 ExportUsage::Evaluation => write!(f, "evaluation"),
211 ExportUsage::Passthrough {
212 namespace_object_may_escape,
213 ..
214 } => write!(
215 f,
216 "passthrough{}",
217 if *namespace_object_may_escape {
218 " namespace"
219 } else {
220 ""
221 }
222 ),
223 }
224 }
225}
226
227#[turbo_tasks::value_impl]
228impl ExportUsage {
229 #[turbo_tasks::function]
230 pub fn all() -> Vc<Self> {
231 Self::All.cell()
232 }
233
234 #[turbo_tasks::function]
235 pub fn evaluation() -> Vc<Self> {
236 Self::Evaluation.cell()
237 }
238
239 #[turbo_tasks::function]
240 pub fn named(name: RcStr) -> Vc<Self> {
241 Self::Named(name).cell()
242 }
243
244 #[turbo_tasks::function]
245 pub fn partial_namespace_object(names: Vec<RcStr>) -> Vc<Self> {
246 Self::PartialNamespaceObject(names.into_iter().collect()).cell()
247 }
248
249 #[turbo_tasks::function]
250 pub fn passthrough(namespace_object_may_escape: bool) -> Vc<Self> {
251 Self::Passthrough {
252 namespace_object_may_escape,
253 }
254 .cell()
255 }
256}
257
258#[turbo_tasks::value(shared)]
259#[derive(Clone, Debug)]
260pub struct ModuleResolveResult {
261 pub primary: Box<[(RequestKey, ModuleResolveResultItem)]>,
262 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
265}
266
267impl ModuleResolveResult {
268 pub fn unresolvable() -> ResolvedVc<Self> {
269 ModuleResolveResult {
270 primary: Default::default(),
271 affecting_sources: Default::default(),
272 }
273 .resolved_cell()
274 }
275
276 pub fn module(module: ResolvedVc<Box<dyn Module>>) -> ResolvedVc<Self> {
277 Self::module_with_key(RequestKey::default(), module)
278 }
279
280 pub fn module_with_key(
281 request_key: RequestKey,
282 module: ResolvedVc<Box<dyn Module>>,
283 ) -> ResolvedVc<Self> {
284 ModuleResolveResult {
285 primary: vec![(request_key, ModuleResolveResultItem::Module(module))]
286 .into_boxed_slice(),
287 affecting_sources: Default::default(),
288 }
289 .resolved_cell()
290 }
291
292 pub fn modules(
293 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
294 ) -> ResolvedVc<Self> {
295 let mut primary: Vec<_> = modules
296 .into_iter()
297 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
298 .collect();
299 Self::mark_duplicates(&mut primary);
300 ModuleResolveResult {
301 primary: primary.into_boxed_slice(),
302 affecting_sources: Default::default(),
303 }
304 .resolved_cell()
305 }
306
307 pub fn modules_with_affecting_sources(
308 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
309 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
310 ) -> ResolvedVc<Self> {
311 let mut primary: Vec<_> = modules
312 .into_iter()
313 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
314 .collect();
315 Self::mark_duplicates(&mut primary);
316 ModuleResolveResult {
317 primary: primary.into_boxed_slice(),
318 affecting_sources: affecting_sources.into_boxed_slice(),
319 }
320 .resolved_cell()
321 }
322}
323
324impl ModuleResolveResult {
325 fn mark_duplicates(primary: &mut [(RequestKey, ModuleResolveResultItem)]) {
329 if primary.len() <= 1 {
330 return;
331 }
332 let mut seen_modules = FxHashMap::default();
334 for (i, (_, item)) in primary.iter_mut().enumerate() {
335 if let ModuleResolveResultItem::Module(m) = *item {
336 if let Some(&first) = seen_modules.get(&m) {
337 *item = ModuleResolveResultItem::Duplicate(first);
338 } else {
339 seen_modules.insert(m, i);
340 }
341 }
342 }
343 }
344
345 pub fn primary_modules_raw_iter(
347 &self,
348 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
349 self.primary.iter().filter_map(|(_, item)| match *item {
350 ModuleResolveResultItem::Module(a) => Some(a),
351 _ => None,
352 })
353 }
354
355 pub async fn primary_modules(&self) -> Result<SmallVec<[ResolvedVc<Box<dyn Module>>; 2]>> {
359 self.primary
360 .iter()
361 .map(async |(_, item)| item.as_module().await)
362 .join()
363 .await
364 .into_iter()
365 .filter_map(Result::transpose)
366 .collect()
367 }
368
369 pub async fn first_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
371 for (_, item) in self.primary.iter() {
372 if let Some(module) = item.as_module().await? {
373 return Ok(Some(module));
374 }
375 }
376 Ok(None)
377 }
378
379 pub fn affecting_sources_iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
380 self.affecting_sources.iter().copied()
381 }
382
383 pub fn is_unresolvable(&self) -> bool {
384 self.primary.is_empty()
385 }
386
387 pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
388 self.primary.iter().filter_map(|i| match &i.1 {
389 ModuleResolveResultItem::Error(e) => Some(*e),
390 _ => None,
391 })
392 }
393}
394
395pub struct ModuleResolveResultBuilder {
396 pub primary: FxIndexMap<RequestKey, ModuleResolveResultItem>,
397 pub affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
398}
399
400impl From<ModuleResolveResultBuilder> for ModuleResolveResult {
401 fn from(v: ModuleResolveResultBuilder) -> Self {
402 let mut primary: Vec<_> = v.primary.into_iter().collect();
403 Self::mark_duplicates(&mut primary);
404 ModuleResolveResult {
405 primary: primary.into_boxed_slice(),
406 affecting_sources: v.affecting_sources.into_boxed_slice(),
407 }
408 }
409}
410
411fn expand_duplicate<'a>(
415 source: &'a [(RequestKey, ModuleResolveResultItem)],
416 item: &'a ModuleResolveResultItem,
417) -> &'a ModuleResolveResultItem {
418 if let ModuleResolveResultItem::Duplicate(i) = *item {
419 &source[i].1
420 } else {
421 item
422 }
423}
424
425impl From<ModuleResolveResult> for ModuleResolveResultBuilder {
426 fn from(v: ModuleResolveResult) -> Self {
427 let primary = v
433 .primary
434 .iter()
435 .map(|(k, item)| (k.clone(), expand_duplicate(&v.primary, item).clone()))
436 .collect();
437 ModuleResolveResultBuilder {
438 primary,
439 affecting_sources: v.affecting_sources.into_vec(),
440 }
441 }
442}
443impl ModuleResolveResultBuilder {
444 pub fn merge_alternatives(&mut self, other: &ModuleResolveResult) {
445 for (k, v) in other.primary.iter() {
449 if !self.primary.contains_key(k) {
450 self.primary
451 .insert(k.clone(), expand_duplicate(&other.primary, v).clone());
452 }
453 }
454 let set = self
455 .affecting_sources
456 .iter()
457 .copied()
458 .collect::<FxHashSet<_>>();
459 self.affecting_sources.extend(
460 other
461 .affecting_sources
462 .iter()
463 .filter(|source| !set.contains(source))
464 .copied(),
465 );
466 }
467}
468
469#[turbo_tasks::value_impl]
470impl ModuleResolveResult {
471 #[turbo_tasks::function]
472 pub async fn alternatives(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
473 if results.len() == 1 {
474 return Ok(results.into_iter().next().unwrap());
475 }
476 let mut iter = results.into_iter().try_join().await?.into_iter();
477 if let Some(current) = iter.next() {
478 let mut current: ModuleResolveResultBuilder = ReadRef::into_owned(current).into();
479 for result in iter {
480 let other = &*result;
482 current.merge_alternatives(other);
483 }
484 Ok(Self::cell(current.into()))
485 } else {
486 Ok(*ModuleResolveResult::unresolvable())
487 }
488 }
489
490 #[turbo_tasks::function]
493 pub async fn concat(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
494 if results.len() == 1 {
495 return Ok(results.into_iter().next().unwrap());
496 }
497 let mut primary = Vec::new();
498 let mut affecting_sources = Vec::new();
499 let mut seen_sources = FxHashSet::default();
500 for result in results.into_iter().try_join().await? {
501 primary.extend(
502 result.primary.iter().map(|(key, item)| {
503 (key.clone(), expand_duplicate(&result.primary, item).clone())
504 }),
505 );
506 for source in result.affecting_sources.iter().copied() {
507 if seen_sources.insert(source) {
508 affecting_sources.push(source);
509 }
510 }
511 }
512 Self::mark_duplicates(&mut primary);
513 Ok(Self::cell(Self {
514 primary: primary.into_boxed_slice(),
515 affecting_sources: affecting_sources.into_boxed_slice(),
516 }))
517 }
518}
519
520#[turbo_tasks::task_input]
521#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
522pub enum ExternalTraced {
523 Untraced,
524 Traced,
525}
526
527impl Display for ExternalTraced {
528 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
529 match self {
530 ExternalTraced::Untraced => write!(f, "untraced"),
531 ExternalTraced::Traced => write!(f, "traced"),
532 }
533 }
534}
535
536#[turbo_tasks::task_input]
537#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encode, Decode)]
538pub enum ExternalType {
539 Url,
540 CommonJs,
541 EcmaScriptModule,
542 Global,
543 Script,
544}
545
546impl Display for ExternalType {
547 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
548 match self {
549 ExternalType::CommonJs => write!(f, "commonjs"),
550 ExternalType::EcmaScriptModule => write!(f, "esm"),
551 ExternalType::Url => write!(f, "url"),
552 ExternalType::Global => write!(f, "global"),
553 ExternalType::Script => write!(f, "script"),
554 }
555 }
556}
557
558#[turbo_tasks::value(shared)]
559#[derive(Debug, Clone)]
560pub enum ResolveResultItem {
561 Source(ResolvedVc<Box<dyn Source>>),
562 External {
563 name: RcStr,
565 ty: ExternalType,
566 traced: ExternalTraced,
567 target: Option<FileSystemPath>,
570 },
571 Ignore,
573 Error(ResolvedVc<Box<dyn Issue>>),
575 Empty,
577 Custom(u8),
578}
579
580#[derive(Clone, Debug, Default, Hash)]
587#[turbo_tasks::value(task_input)]
588pub struct RequestKey {
589 pub request: Option<RcStr>,
590 pub conditions: FrozenMap<RcStr, bool>,
591}
592
593impl Display for RequestKey {
594 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
595 if let Some(request) = &self.request {
596 write!(f, "{request}")?;
597 } else {
598 write!(f, "<default>")?;
599 }
600 if !self.conditions.is_empty() {
601 write!(f, " (")?;
602 for (i, (k, v)) in self.conditions.iter().enumerate() {
603 if i > 0 {
604 write!(f, ", ")?;
605 }
606 write!(f, "{k}={v}")?;
607 }
608 write!(f, ")")?;
609 }
610 Ok(())
611 }
612}
613
614impl RequestKey {
615 pub fn new(request: RcStr) -> Self {
616 RequestKey {
617 request: Some(request),
618 ..Default::default()
619 }
620 }
621}
622
623#[turbo_tasks::value(shared)]
624#[derive(Clone)]
625pub struct ResolveResult {
626 pub primary: Box<[(RequestKey, ResolveResultItem)]>,
627 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
630}
631
632#[turbo_tasks::value_impl]
633impl ValueToString for ResolveResult {
634 #[turbo_tasks::function]
635 async fn to_string(&self) -> Result<Vc<RcStr>> {
636 let mut result = String::new();
637 if self.is_unresolvable() {
638 result.push_str("unresolvable");
639 }
640 for (i, (request, item)) in self.primary.iter().enumerate() {
641 if i > 0 {
642 result.push_str(", ");
643 }
644 write!(result, "{request} -> ").unwrap();
645 match item {
646 ResolveResultItem::Source(a) => {
647 result.push_str(&a.ident().to_string().await?);
648 }
649 ResolveResultItem::External {
650 name: s,
651 ty,
652 traced,
653 target,
654 } => {
655 result.push_str("external ");
656 result.push_str(s);
657 write!(
658 result,
659 " ({ty}, {traced}, {:?})",
660 if let Some(target) = target {
661 Some(target.to_string_ref().await?)
662 } else {
663 None
664 }
665 )?;
666 }
667 ResolveResultItem::Ignore => {
668 result.push_str("ignore");
669 }
670 ResolveResultItem::Empty => {
671 result.push_str("empty");
672 }
673 ResolveResultItem::Error(_) => {
674 result.push_str("error");
675 }
676 ResolveResultItem::Custom(_) => {
677 result.push_str("custom");
678 }
679 }
680 result.push('\n');
681 }
682 if !self.affecting_sources.is_empty() {
683 result.push_str(" (affecting sources: ");
684 for (i, source) in self.affecting_sources.iter().enumerate() {
685 if i > 0 {
686 result.push_str(", ");
687 }
688 result.push_str(&source.ident().to_string().await?);
689 }
690 result.push(')');
691 }
692 Ok(Vc::cell(result.into()))
693 }
694}
695
696impl ResolveResult {
697 pub fn unresolvable() -> Self {
698 ResolveResult {
699 primary: Default::default(),
700 affecting_sources: Default::default(),
701 }
702 }
703
704 pub fn unresolvable_with_affecting_sources(
705 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
706 ) -> Self {
707 ResolveResult {
708 primary: Default::default(),
709 affecting_sources: affecting_sources.into_boxed_slice(),
710 }
711 }
712
713 pub fn primary(result: ResolveResultItem) -> Self {
714 Self::primary_with_key(RequestKey::default(), result)
715 }
716
717 pub fn primary_with_key(request_key: RequestKey, result: ResolveResultItem) -> Self {
718 ResolveResult {
719 primary: vec![(request_key, result)].into_boxed_slice(),
720 affecting_sources: Default::default(),
721 }
722 }
723
724 pub fn primary_with_affecting_sources(
725 request_key: RequestKey,
726 result: ResolveResultItem,
727 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
728 ) -> Self {
729 ResolveResult {
730 primary: vec![(request_key, result)].into_boxed_slice(),
731 affecting_sources: affecting_sources.into_boxed_slice(),
732 }
733 }
734
735 pub fn source(source: ResolvedVc<Box<dyn Source>>) -> Self {
736 Self::source_with_key(RequestKey::default(), source)
737 }
738
739 fn source_with_key(request_key: RequestKey, source: ResolvedVc<Box<dyn Source>>) -> Self {
740 ResolveResult {
741 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
742 affecting_sources: Default::default(),
743 }
744 }
745
746 fn source_with_affecting_sources(
747 request_key: RequestKey,
748 source: ResolvedVc<Box<dyn Source>>,
749 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
750 ) -> Self {
751 ResolveResult {
752 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
753 affecting_sources: affecting_sources.into_boxed_slice(),
754 }
755 }
756
757 pub fn errors(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Issue>>> + '_ {
758 self.primary.iter().filter_map(|i| match &i.1 {
759 ResolveResultItem::Error(e) => Some(*e),
760 _ => None,
761 })
762 }
763}
764
765impl ResolveResult {
766 pub fn get_affecting_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
769 self.affecting_sources.iter().copied()
770 }
771
772 pub fn is_unresolvable(&self) -> bool {
773 self.primary.is_empty()
774 }
775
776 pub fn first_source(&self) -> Option<ResolvedVc<Box<dyn Source>>> {
777 self.primary.iter().find_map(|(_, item)| {
778 if let &ResolveResultItem::Source(a) = item {
779 Some(a)
780 } else {
781 None
782 }
783 })
784 }
785
786 pub fn primary_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> {
787 self.primary.iter().filter_map(|(_, item)| {
788 if let &ResolveResultItem::Source(a) = item {
789 Some(a)
790 } else {
791 None
792 }
793 })
794 }
795
796 pub async fn map_module<A, AF>(&self, source_fn: A) -> Result<ModuleResolveResult>
797 where
798 A: Fn(ResolvedVc<Box<dyn Source>>) -> AF,
799 AF: Future<Output = Result<ModuleResolveResultItem>>,
800 {
801 Ok(ModuleResolveResult {
802 primary: self
803 .primary
804 .iter()
805 .map(|(request, item)| {
806 let asset_fn = &source_fn;
807 let request = request.clone();
808 let item = item.clone();
809 async move {
810 Ok((
811 request,
812 match item {
813 ResolveResultItem::Source(source) => asset_fn(source).await?,
814 ResolveResultItem::External {
815 name,
816 ty,
817 traced,
818 target,
819 } => {
820 if traced == ExternalTraced::Traced || target.is_some() {
821 bail!("map_module doesn't handle traced externals");
823 }
824 ModuleResolveResultItem::External { name, ty }
825 }
826 ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
827 ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
828 ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
829 ResolveResultItem::Custom(u8) => {
830 ModuleResolveResultItem::Custom(u8)
831 }
832 },
833 ))
834 }
835 })
836 .try_join()
837 .await?
838 .into_iter()
839 .collect(),
840 affecting_sources: self.affecting_sources.clone(),
841 })
842 }
843
844 pub async fn map_primary_items<A, AF>(&self, item_fn: A) -> Result<ModuleResolveResult>
845 where
846 A: Fn(ResolveResultItem) -> AF,
847 AF: Future<Output = Result<ModuleResolveResultItem>>,
848 {
849 Ok(ModuleResolveResult {
850 primary: self
851 .primary
852 .iter()
853 .map(|(request, item)| {
854 let asset_fn = &item_fn;
855 let request = request.clone();
856 let item = item.clone();
857 async move { Ok((request, asset_fn(item).await?)) }
858 })
859 .try_join()
860 .await?
861 .into_iter()
862 .collect(),
863 affecting_sources: self.affecting_sources.clone(),
864 })
865 }
866
867 fn with_request_ref(&self, request: RcStr) -> Self {
870 let new_primary = self
871 .primary
872 .iter()
873 .map(|(k, v)| {
874 (
875 RequestKey {
876 request: Some(request.clone()),
877 conditions: k.conditions.clone(),
878 },
879 v.clone(),
880 )
881 })
882 .collect();
883 ResolveResult {
884 primary: new_primary,
885 affecting_sources: self.affecting_sources.clone(),
886 }
887 }
888
889 pub fn with_conditions(&self, new_conditions: &[(RcStr, bool)]) -> Self {
890 let primary = self
891 .primary
892 .iter()
893 .map(|(k, v)| {
894 (
895 RequestKey {
896 request: k.request.clone(),
897 conditions: k.conditions.extend(new_conditions.iter().cloned()),
898 },
899 v.clone(),
900 )
901 })
902 .collect::<FxIndexMap<_, _>>() .into_iter()
904 .collect();
905 ResolveResult {
906 primary,
907 affecting_sources: self.affecting_sources.clone(),
908 }
909 }
910}
911
912struct ResolveResultBuilder {
913 primary: FxIndexMap<RequestKey, ResolveResultItem>,
914 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
915}
916
917impl From<ResolveResultBuilder> for ResolveResult {
918 fn from(v: ResolveResultBuilder) -> Self {
919 ResolveResult {
920 primary: v.primary.into_iter().collect(),
921 affecting_sources: v.affecting_sources.into_boxed_slice(),
922 }
923 }
924}
925impl From<ResolveResult> for ResolveResultBuilder {
926 fn from(v: ResolveResult) -> Self {
927 ResolveResultBuilder {
928 primary: IntoIterator::into_iter(v.primary).collect(),
929 affecting_sources: v.affecting_sources.into_vec(),
930 }
931 }
932}
933impl ResolveResultBuilder {
934 pub fn merge_alternatives(&mut self, other: &ResolveResult) {
935 for (k, v) in other.primary.iter() {
936 if !self.primary.contains_key(k) {
937 self.primary.insert(k.clone(), v.clone());
938 }
939 }
940 let set = self
941 .affecting_sources
942 .iter()
943 .copied()
944 .collect::<FxHashSet<_>>();
945 self.affecting_sources.extend(
946 other
947 .affecting_sources
948 .iter()
949 .filter(|source| !set.contains(source))
950 .copied(),
951 );
952 }
953}
954
955#[turbo_tasks::value_impl]
956impl ResolveResult {
957 #[turbo_tasks::function]
958 pub async fn as_raw_module_result(&self) -> Result<Vc<ModuleResolveResult>> {
959 Ok(self
960 .map_module(async |asset| {
961 Ok(ModuleResolveResultItem::Module(ResolvedVc::upcast(
962 RawModule::new(*asset).to_resolved().await?,
963 )))
964 })
965 .await?
966 .cell())
967 }
968
969 #[turbo_tasks::function]
970 fn with_affecting_sources(
971 &self,
972 sources: Vec<ResolvedVc<Box<dyn Source>>>,
973 ) -> Result<Vc<Self>> {
974 Ok(Self {
975 primary: self.primary.clone(),
976 affecting_sources: self
977 .affecting_sources
978 .iter()
979 .copied()
980 .chain(sources)
981 .collect(),
982 }
983 .cell())
984 }
985
986 #[turbo_tasks::function]
987 async fn alternatives(results: Vec<Vc<ResolveResult>>) -> Result<Vc<Self>> {
988 if results.len() == 1 {
989 return Ok(results.into_iter().next().unwrap());
990 }
991 let mut iter = results.into_iter().try_join().await?.into_iter();
992 if let Some(current) = iter.next() {
993 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
994 for result in iter {
995 let other = &*result;
997 current.merge_alternatives(other);
998 }
999 Ok(Self::cell(current.into()))
1000 } else {
1001 Ok(ResolveResult::unresolvable().cell())
1002 }
1003 }
1004
1005 #[turbo_tasks::function]
1006 async fn alternatives_with_affecting_sources(
1007 results: Vec<Vc<ResolveResult>>,
1008 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1009 ) -> Result<Vc<Self>> {
1010 debug_assert!(
1011 !affecting_sources.is_empty(),
1012 "Caller should not call this function if there are no affecting sources"
1013 );
1014 if results.len() == 1 {
1015 return Ok(results
1016 .into_iter()
1017 .next()
1018 .unwrap()
1019 .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()));
1020 }
1021 let mut iter = results.into_iter().try_join().await?.into_iter();
1022 if let Some(current) = iter.next() {
1023 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
1024 for result in iter {
1025 let other = &*result;
1027 current.merge_alternatives(other);
1028 }
1029 current.affecting_sources.extend(affecting_sources);
1030 Ok(Self::cell(current.into()))
1031 } else {
1032 Ok(ResolveResult::unresolvable_with_affecting_sources(affecting_sources).cell())
1033 }
1034 }
1035
1036 #[turbo_tasks::function]
1041 fn with_replaced_request_key(
1042 &self,
1043 old_request_key: RcStr,
1044 new_request_key: RcStr,
1045 ) -> Result<Vc<Self>> {
1046 let new_primary = self
1047 .primary
1048 .iter()
1049 .filter_map(|(k, v)| {
1050 let remaining = k.request.as_ref()?.strip_prefix(&*old_request_key)?;
1051 Some((
1052 RequestKey {
1053 request: Some(format!("{new_request_key}{remaining}").into()),
1054 conditions: k.conditions.clone(),
1055 },
1056 v.clone(),
1057 ))
1058 })
1059 .collect();
1060 Ok(ResolveResult {
1061 primary: new_primary,
1062 affecting_sources: self.affecting_sources.clone(),
1063 }
1064 .cell())
1065 }
1066
1067 #[turbo_tasks::function]
1071 fn with_stripped_request_key_prefix(&self, prefix: RcStr) -> Result<Vc<Self>> {
1072 let new_primary = self
1073 .primary
1074 .iter()
1075 .filter_map(|(k, v)| {
1076 let remaining = k.request.as_ref()?.strip_prefix(&*prefix)?;
1077 Some((
1078 RequestKey {
1079 request: Some(remaining.into()),
1080 conditions: k.conditions.clone(),
1081 },
1082 v.clone(),
1083 ))
1084 })
1085 .collect();
1086 Ok(ResolveResult {
1087 primary: new_primary,
1088 affecting_sources: self.affecting_sources.clone(),
1089 }
1090 .cell())
1091 }
1092
1093 #[turbo_tasks::function]
1098 async fn with_replaced_request_key_pattern(
1099 &self,
1100 old_request_key: Vc<Pattern>,
1101 request_key: Vc<Pattern>,
1102 ) -> Result<Vc<Self>> {
1103 let old_request_key = &*old_request_key.await?;
1104 let request_key = &*request_key.await?;
1105
1106 let new_primary = self
1107 .primary
1108 .iter()
1109 .map(|(k, v)| {
1110 (
1111 RequestKey {
1112 request: k
1113 .request
1114 .as_ref()
1115 .and_then(|r| old_request_key.match_apply_template(r, request_key))
1116 .map(Into::into),
1117 conditions: k.conditions.clone(),
1118 },
1119 v.clone(),
1120 )
1121 })
1122 .collect();
1123 Ok(ResolveResult {
1124 primary: new_primary,
1125 affecting_sources: self.affecting_sources.clone(),
1126 }
1127 .cell())
1128 }
1129
1130 #[turbo_tasks::function]
1133 fn with_request(&self, request: RcStr) -> Vc<Self> {
1134 let new_primary = self
1135 .primary
1136 .iter()
1137 .map(|(k, v)| {
1138 (
1139 RequestKey {
1140 request: Some(request.clone()),
1141 conditions: k.conditions.clone(),
1142 },
1143 v.clone(),
1144 )
1145 })
1146 .collect();
1147 ResolveResult {
1148 primary: new_primary,
1149 affecting_sources: self.affecting_sources.clone(),
1150 }
1151 .cell()
1152 }
1153}
1154
1155#[turbo_tasks::value(transparent)]
1156pub struct ResolveResultOption(Option<ResolvedVc<ResolveResult>>);
1157
1158#[turbo_tasks::value_impl]
1159impl ResolveResultOption {
1160 #[turbo_tasks::function]
1161 pub fn some(result: ResolvedVc<ResolveResult>) -> Vc<Self> {
1162 ResolveResultOption(Some(result)).cell()
1163 }
1164
1165 #[turbo_tasks::function]
1166 pub fn none() -> Vc<Self> {
1167 ResolveResultOption(None).cell()
1168 }
1169}
1170
1171async fn exists(
1172 fs_path: &FileSystemPath,
1173 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1174) -> Result<Option<FileSystemPath>> {
1175 type_exists(fs_path, FileSystemEntryType::File, refs).await
1176}
1177
1178async fn dir_exists(
1179 fs_path: &FileSystemPath,
1180 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1181) -> Result<Option<FileSystemPath>> {
1182 type_exists(fs_path, FileSystemEntryType::Directory, refs).await
1183}
1184
1185async fn type_exists(
1186 fs_path: &FileSystemPath,
1187 ty: FileSystemEntryType,
1188 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1189) -> Result<Option<FileSystemPath>> {
1190 let Some(path) = realpath_if_exists(fs_path, refs).await? else {
1191 return Ok(None);
1192 };
1193 Ok(if *path.get_type().await? == ty {
1194 Some(path)
1195 } else {
1196 None
1197 })
1198}
1199
1200async fn realpath_if_exists(
1201 fs_path: &FileSystemPath,
1202 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1203) -> Result<Option<FileSystemPath>> {
1204 let result = fs_path.realpath_with_links().await?;
1205 if let Some(refs) = refs {
1206 refs.extend(
1207 result
1208 .symlinks
1209 .iter()
1210 .map(async |path| {
1211 Ok(ResolvedVc::upcast(
1212 FileSource::new(path.clone()).to_resolved().await?,
1213 ))
1214 })
1215 .try_join()
1216 .await?,
1217 );
1218 }
1219 match &result.path_result {
1220 Ok(path) => Ok(Some(path.clone())),
1221 Err(error) if matches!(error.kind(), RealPathErrorType::NotFound) => Ok(None),
1222 Err(error) => bail!(error.clone()),
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_missing_paths_through_symlinks_do_not_error() {
3633 use std::os::unix::fs::symlink;
3634
3635 #[turbo_tasks::value]
3636 struct MissingPathsResult {
3637 missing_file: bool,
3638 dangling_package: bool,
3639 }
3640
3641 let scratch = tempfile::tempdir().unwrap();
3642 create_dir_all(scratch.path().join("package")).unwrap();
3643 symlink("package", scratch.path().join("linked-package")).unwrap();
3644 symlink("missing-package", scratch.path().join("dangling-package")).unwrap();
3645
3646 let path = RcStr::from(scratch.path().to_str().unwrap());
3647 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3648 BackendOptions::default(),
3649 noop_backing_storage(),
3650 ));
3651
3652 #[turbo_tasks::function(operation, root)]
3653 async fn missing_paths_through_symlinks_operation(
3654 path: RcStr,
3655 ) -> Result<Vc<MissingPathsResult>> {
3656 let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
3657 let root = fs.root().owned().await?;
3658 let missing_file = root.join("linked-package/package.json")?;
3659 let dangling_package = root.join("dangling-package")?;
3660
3661 Ok(MissingPathsResult {
3662 missing_file: realpath_if_exists(&missing_file, None).await?.is_none(),
3663 dangling_package: realpath_if_exists(&dangling_package, None).await?.is_none(),
3664 }
3665 .cell())
3666 }
3667
3668 tt.run_once(async move {
3669 let missing = missing_paths_through_symlinks_operation(path)
3670 .read_strongly_consistent()
3671 .await?;
3672 assert!(missing.missing_file);
3673 assert!(missing.dangling_package);
3674
3675 anyhow::Ok(())
3676 })
3677 .await
3678 .unwrap();
3679 }
3680
3681 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3682 async fn test_explicit_js_resolves_to_ts() {
3683 resolve_relative_request_test(TestParams {
3684 files: vec!["foo.js", "foo.ts"],
3685 pattern: rcstr!("./foo.js").into(),
3686 enable_typescript_with_output_extension: true,
3687 fully_specified: false,
3688 custom_extensions: None,
3689 expected: vec![("./foo.js", "foo.ts")],
3690 })
3691 .await;
3692 }
3693
3694 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3695 async fn test_implicit_request_ts_priority() {
3696 resolve_relative_request_test(TestParams {
3697 files: vec!["foo.js", "foo.ts"],
3698 pattern: rcstr!("./foo").into(),
3699 enable_typescript_with_output_extension: true,
3700 fully_specified: false,
3701 custom_extensions: None,
3702 expected: vec![("./foo", "foo.ts")],
3703 })
3704 .await;
3705 }
3706
3707 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3708 async fn test_ts_priority_over_json() {
3709 resolve_relative_request_test(TestParams {
3710 files: vec!["posts.json", "posts.ts"],
3711 pattern: rcstr!("./posts").into(),
3712 enable_typescript_with_output_extension: true,
3713 fully_specified: false,
3714 custom_extensions: None,
3715 expected: vec![("./posts", "posts.ts")],
3716 })
3717 .await;
3718 }
3719
3720 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3721 async fn test_only_js_file_no_ts() {
3722 resolve_relative_request_test(TestParams {
3723 files: vec!["bar.js"],
3724 pattern: rcstr!("./bar.js").into(),
3725 enable_typescript_with_output_extension: true,
3726 fully_specified: false,
3727 custom_extensions: None,
3728 expected: vec![("./bar.js", "bar.js")],
3729 })
3730 .await;
3731 }
3732
3733 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3734 async fn test_explicit_ts_request() {
3735 resolve_relative_request_test(TestParams {
3736 files: vec!["foo.js", "foo.ts"],
3737 pattern: rcstr!("./foo.ts").into(),
3738 enable_typescript_with_output_extension: true,
3739 fully_specified: false,
3740 custom_extensions: None,
3741 expected: vec![("./foo.ts", "foo.ts")],
3742 })
3743 .await;
3744 }
3745
3746 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3748 async fn test_fragment() {
3749 resolve_relative_request_test(TestParams {
3750 files: vec!["client.ts"],
3751 pattern: rcstr!("./client#frag").into(),
3752 enable_typescript_with_output_extension: true,
3753 fully_specified: false,
3754 custom_extensions: None,
3755 expected: vec![("./client", "client.ts")],
3756 })
3757 .await;
3758 }
3759
3760 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3761 async fn test_fragment_as_part_of_filename() {
3762 resolve_relative_request_test(TestParams {
3764 files: vec!["client#component.js", "client#component.ts"],
3765 pattern: rcstr!("./client#component.js").into(),
3766 enable_typescript_with_output_extension: true,
3767 fully_specified: false,
3768 custom_extensions: None,
3769 expected: vec![("./client", "client#component.ts")],
3772 })
3773 .await;
3774 }
3775
3776 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3777 async fn test_fragment_with_ts_priority() {
3778 resolve_relative_request_test(TestParams {
3780 files: vec!["page#section.js", "page#section.ts"],
3781 pattern: rcstr!("./page#section").into(),
3782 enable_typescript_with_output_extension: true,
3783 fully_specified: false,
3784 custom_extensions: None,
3785 expected: vec![("./page", "page#section.ts")],
3786 })
3787 .await;
3788 }
3789
3790 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3791 async fn test_query() {
3792 resolve_relative_request_test(TestParams {
3793 files: vec!["client.ts", "client.js"],
3794 pattern: rcstr!("./client?q=s").into(),
3795 enable_typescript_with_output_extension: true,
3796 fully_specified: false,
3797 custom_extensions: None,
3798 expected: vec![("./client", "client.ts")],
3799 })
3800 .await;
3801 }
3802
3803 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3805 async fn test_dynamic_pattern_with_js_extension() {
3806 resolve_relative_request_test(TestParams {
3810 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3811 pattern: Pattern::Concatenation(vec![
3812 Pattern::Constant(rcstr!("./src/")),
3813 Pattern::Dynamic,
3814 Pattern::Constant(rcstr!(".js")),
3815 ]),
3816 enable_typescript_with_output_extension: true,
3817 fully_specified: false,
3818 custom_extensions: None,
3819 expected: vec![
3820 ("./src/foo.js", "src/foo.ts"),
3821 ("./src/bar.js", "src/bar.js"),
3822 ],
3823 })
3824 .await;
3825 }
3826
3827 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3828 async fn test_dynamic_pattern_without_extension() {
3829 resolve_relative_request_test(TestParams {
3833 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3834 pattern: Pattern::Concatenation(vec![
3835 Pattern::Constant(rcstr!("./src/")),
3836 Pattern::Dynamic,
3837 ]),
3838 enable_typescript_with_output_extension: true,
3839 fully_specified: false,
3840 custom_extensions: None,
3841 expected: vec![
3842 ("./src/bar.js", "src/bar.js"),
3843 ("./src/bar", "src/bar.js"),
3844 ("./src/foo.js", "src/foo.js"),
3850 ("./src/foo", "src/foo.js"),
3851 ("./src/foo.ts", "src/foo.ts"),
3852 ],
3853 })
3854 .await;
3855 }
3856
3857 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3860 async fn test_custom_extensions_web_before_default() {
3861 resolve_relative_request_test(TestParams {
3862 files: vec!["Component.web.tsx", "Component.tsx"],
3863 pattern: rcstr!("./Component").into(),
3864 enable_typescript_with_output_extension: false,
3865 fully_specified: false,
3866 custom_extensions: Some(vec![
3867 rcstr!(".web.tsx"),
3868 rcstr!(".web.ts"),
3869 rcstr!(".web.jsx"),
3870 rcstr!(".web.js"),
3871 rcstr!(".tsx"),
3872 rcstr!(".ts"),
3873 rcstr!(".jsx"),
3874 rcstr!(".js"),
3875 ]),
3876 expected: vec![("./Component", "Component.web.tsx")],
3877 })
3878 .await;
3879 }
3880
3881 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3883 async fn test_custom_extensions_fallback_when_web_missing() {
3884 resolve_relative_request_test(TestParams {
3885 files: vec!["Component.tsx"],
3886 pattern: rcstr!("./Component").into(),
3887 enable_typescript_with_output_extension: false,
3888 fully_specified: false,
3889 custom_extensions: Some(vec![
3890 rcstr!(".web.tsx"),
3891 rcstr!(".web.ts"),
3892 rcstr!(".web.jsx"),
3893 rcstr!(".web.js"),
3894 rcstr!(".tsx"),
3895 rcstr!(".ts"),
3896 rcstr!(".jsx"),
3897 rcstr!(".js"),
3898 ]),
3899 expected: vec![("./Component", "Component.tsx")],
3900 })
3901 .await;
3902 }
3903
3904 struct TestParams<'a> {
3906 files: Vec<&'a str>,
3907 pattern: Pattern,
3908 enable_typescript_with_output_extension: bool,
3909 fully_specified: bool,
3910 custom_extensions: Option<Vec<RcStr>>,
3912 expected: Vec<(&'a str, &'a str)>,
3913 }
3914
3915 async fn resolve_relative_request_test(
3917 TestParams {
3918 files,
3919 pattern,
3920 enable_typescript_with_output_extension,
3921 fully_specified,
3922 custom_extensions,
3923 expected,
3924 }: TestParams<'_>,
3925 ) {
3926 let scratch = tempfile::tempdir().unwrap();
3927 {
3928 let path = scratch.path();
3929
3930 for file_name in &files {
3931 let file_path = path.join(file_name);
3932 if let Some(parent) = file_path.parent() {
3933 create_dir_all(parent).unwrap();
3934 }
3935 File::create_new(&file_path)
3936 .unwrap()
3937 .write_all(format!("export default '{file_name}'").as_bytes())
3938 .unwrap();
3939 }
3940 }
3941
3942 let path: RcStr = scratch.path().to_str().unwrap().into();
3943 let expected_owned: Vec<(String, String)> = expected
3944 .iter()
3945 .map(|(k, v)| (k.to_string(), v.to_string()))
3946 .collect();
3947
3948 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3949 BackendOptions::default(),
3950 noop_backing_storage(),
3951 ));
3952
3953 let custom_extensions_owned = custom_extensions;
3954
3955 tt.run_once(async move {
3956 #[turbo_tasks::value(transparent)]
3957 struct ResolveRelativeRequestOutput(Vec<(String, String)>);
3958
3959 #[turbo_tasks::function(operation, root)]
3960 async fn resolve_relative_request_operation(
3961 path: RcStr,
3962 pattern: Pattern,
3963 enable_typescript_with_output_extension: bool,
3964 fully_specified: bool,
3965 custom_extensions: Option<Vec<RcStr>>,
3966 ) -> Result<Vc<ResolveRelativeRequestOutput>> {
3967 let fs = DiskFileSystem::new(rcstr!("temp"), Vc::cell(path));
3968 let lookup_path = fs.root().owned().await?;
3969
3970 let result = resolve_relative_helper(
3971 lookup_path,
3972 pattern,
3973 enable_typescript_with_output_extension,
3974 fully_specified,
3975 custom_extensions,
3976 )
3977 .await?;
3978
3979 let results: Vec<(String, String)> = result
3980 .primary
3981 .iter()
3982 .map(async |(k, v)| {
3983 Ok((
3984 k.to_string(),
3985 if let ResolveResultItem::Source(source) = v {
3986 source.ident().await?.path.path.to_string()
3987 } else {
3988 unreachable!()
3989 },
3990 ))
3991 })
3992 .try_join()
3993 .await?;
3994
3995 Ok(Vc::cell(results))
3996 }
3997
3998 let results = resolve_relative_request_operation(
3999 path,
4000 pattern,
4001 enable_typescript_with_output_extension,
4002 fully_specified,
4003 custom_extensions_owned,
4004 )
4005 .read_strongly_consistent()
4006 .await?;
4007
4008 assert_eq!(&*results, &expected_owned);
4009
4010 Ok(())
4011 })
4012 .await
4013 .unwrap();
4014 }
4015
4016 #[turbo_tasks::function]
4017 async fn resolve_relative_helper(
4018 lookup_path: FileSystemPath,
4019 pattern: Pattern,
4020 enable_typescript_with_output_extension: bool,
4021 fully_specified: bool,
4022 custom_extensions: Option<Vec<RcStr>>,
4023 ) -> Result<Vc<ResolveResult>> {
4024 let request = Request::parse(pattern.clone());
4025
4026 let extensions = custom_extensions
4027 .unwrap_or_else(|| vec![rcstr!(".ts"), rcstr!(".js"), rcstr!(".json")]);
4028 let mut options_value = node_esm_resolve_options()
4029 .with_fully_specified(fully_specified)
4030 .with_extensions(extensions)
4031 .owned()
4032 .await?;
4033 options_value.enable_typescript_with_output_extension =
4034 enable_typescript_with_output_extension;
4035 let options = options_value.clone().cell();
4036 match &*request.await? {
4037 Request::Relative {
4038 path,
4039 query,
4040 force_in_lookup_dir,
4041 fragment,
4042 } => {
4043 resolve_relative_request(
4044 lookup_path,
4045 request,
4046 options,
4047 &options_value,
4048 path,
4049 query.clone(),
4050 *force_in_lookup_dir,
4051 fragment.clone(),
4052 )
4053 .await
4054 }
4055 r => panic!("request should be relative, got {r:?}"),
4056 }
4057 }
4058
4059 #[turbo_tasks::value(transparent)]
4066 pub struct DupCheckResult(Vec<String>);
4067
4068 async fn snapshot_primary(result: &ModuleResolveResult) -> Result<Vec<String>> {
4069 let mut out = Vec::with_capacity(result.primary.len());
4070 for (_, item) in result.primary.iter() {
4071 out.push(match *item {
4072 ModuleResolveResultItem::Module(m) => {
4073 let ident = m.ident().await?;
4074 format!("module:{}", ident.path.path)
4075 }
4076 ModuleResolveResultItem::Duplicate(i) => format!("dup:{i}"),
4077 _ => "other".to_string(),
4078 });
4079 }
4080 Ok(out)
4081 }
4082
4083 #[turbo_tasks::function]
4084 fn fs() -> Vc<Box<dyn FileSystem>> {
4085 Vc::upcast(DiskFileSystem::new(rcstr!("temp"), Vc::cell(fs_path())))
4086 }
4087
4088 #[turbo_tasks::function]
4089 async fn make_module(name: RcStr) -> Result<Vc<Box<dyn Module>>> {
4090 let path = fs().root().await?.join(&name)?;
4091 let file_content =
4092 FileContent::Content(turbo_tasks_fs::File::from(format!("// {name}"))).resolved_cell();
4093 let content = AssetContent::file(*file_content).to_resolved().await?;
4094 let source = VirtualSource::new(path, *content);
4095 let module = RawModule::new(Vc::upcast(source)).to_resolved().await?;
4096 Ok(Vc::upcast(*module))
4097 }
4098
4099 fn fs_path() -> RcStr {
4100 rcstr!("/tmp/_mdt")
4101 }
4102
4103 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4104 async fn modules_constructor_marks_module_duplicates() {
4105 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4106 BackendOptions::default(),
4107 noop_backing_storage(),
4108 ));
4109 #[turbo_tasks::function(operation, root)]
4110 async fn run_test() -> Result<Vc<DupCheckResult>> {
4111 let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
4112 let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
4113
4114 let result = ModuleResolveResult::modules([
4115 (RequestKey::new(rcstr!("a")), m_a),
4116 (RequestKey::new(rcstr!("b")), m_b),
4117 (RequestKey::new(rcstr!("a-again")), m_a),
4118 (RequestKey::new(rcstr!("b-again")), m_b),
4119 ])
4120 .await?;
4121
4122 let modules = result.primary_modules().await?;
4124 assert_eq!(modules.as_slice(), [m_a, m_b]);
4125
4126 Ok(Vc::cell(snapshot_primary(&result).await?))
4127 }
4128 tt.run_once(async move {
4129 let snap = run_test().read_strongly_consistent().await?;
4130 assert_eq!(
4131 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4132 vec!["module:a.js", "module:b.js", "dup:0", "dup:1"]
4133 );
4134 Ok(())
4135 })
4136 .await
4137 .unwrap();
4138 }
4139
4140 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4141 async fn first_module_returns_first_when_duplicates_follow() {
4142 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4143 BackendOptions::default(),
4144 noop_backing_storage(),
4145 ));
4146 #[turbo_tasks::function(operation, root)]
4147 async fn run_test() -> Result<Vc<DupCheckResult>> {
4148 let m = make_module(rcstr!("a.js")).to_resolved().await?;
4149
4150 let result = ModuleResolveResult::modules([
4151 (RequestKey::default(), m),
4152 (RequestKey::new(rcstr!("again")), m),
4153 (RequestKey::new(rcstr!("once-more")), m),
4154 ])
4155 .await?;
4156
4157 assert_eq!(result.first_module().await?, Some(m));
4158 assert_eq!(result.primary_modules().await?.as_slice(), [m]);
4159 Ok(Vc::cell(snapshot_primary(&result).await?))
4160 }
4161 tt.run_once(async move {
4162 let snap = run_test().read_strongly_consistent().await?;
4163 assert_eq!(
4164 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4165 vec!["module:a.js", "dup:0", "dup:0"]
4166 );
4167 Ok(())
4168 })
4169 .await
4170 .unwrap();
4171 }
4172
4173 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4174 async fn builder_marks_module_duplicates_skipping_non_dedup_items() {
4175 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4176 BackendOptions::default(),
4177 noop_backing_storage(),
4178 ));
4179 #[turbo_tasks::function(operation, root)]
4180 async fn run_test() -> Result<Vc<DupCheckResult>> {
4181 let m = make_module(rcstr!("a.js")).to_resolved().await?;
4182
4183 let mut builder = ModuleResolveResultBuilder {
4184 primary: Default::default(),
4185 affecting_sources: Vec::new(),
4186 };
4187 builder.primary.insert(
4188 RequestKey::new(rcstr!("k0")),
4189 ModuleResolveResultItem::Module(m),
4190 );
4191 builder.primary.insert(
4192 RequestKey::new(rcstr!("k1")),
4193 ModuleResolveResultItem::Empty,
4194 );
4195 builder.primary.insert(
4196 RequestKey::new(rcstr!("k2")),
4197 ModuleResolveResultItem::Module(m),
4198 );
4199 let result: ModuleResolveResult = builder.into();
4200 assert_eq!(result.primary_modules().await?.as_slice(), [m]);
4201 Ok(Vc::cell(snapshot_primary(&result).await?))
4202 }
4203 tt.run_once(async move {
4204 let snap = run_test().read_strongly_consistent().await?;
4205
4206 assert_eq!(
4207 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4208 vec!["module:a.js", "other", "dup:0"]
4209 );
4210 Ok(())
4211 })
4212 .await
4213 .unwrap();
4214 }
4215
4216 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4217 async fn alternatives_preserves_unique_module_set() {
4218 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4219 BackendOptions::default(),
4220 noop_backing_storage(),
4221 ));
4222 #[turbo_tasks::function(operation, root)]
4223 async fn run_test() -> Result<Vc<DupCheckResult>> {
4224 let m_a = make_module(rcstr!("a.js")).to_resolved().await?;
4225 let m_b = make_module(rcstr!("b.js")).to_resolved().await?;
4226
4227 let r1 = *ModuleResolveResult::modules([
4229 (RequestKey::new(rcstr!("k1")), m_a),
4230 (RequestKey::new(rcstr!("k2")), m_a),
4231 ]);
4232 let r2 = *ModuleResolveResult::module(m_b);
4235
4236 let merged = ModuleResolveResult::alternatives(vec![r1, r2]).await?;
4237 assert_eq!(merged.primary_modules().await?.as_slice(), [m_a, m_b]);
4238
4239 for (i, (_, item)) in merged.primary.iter().enumerate() {
4241 if let ModuleResolveResultItem::Duplicate(first) = *item {
4242 assert!(
4243 first < i,
4244 "Duplicate index {first} at position {i} must point backwards"
4245 );
4246 let pointed = &merged.primary[first].1;
4247 let ModuleResolveResultItem::Module(pointed_module) = *pointed else {
4248 panic!(
4249 "Duplicate({first}) at {i} points at {pointed:?}, expected a concrete \
4250 Module"
4251 );
4252 };
4253 assert_eq!(
4256 pointed_module, m_a,
4257 "Duplicate({first}) at position {i} points at the wrong module"
4258 );
4259 }
4260 }
4261 Ok(Vc::cell(snapshot_primary(&merged).await?))
4262 }
4263 tt.run_once(async move {
4264 let snap = run_test().read_strongly_consistent().await?;
4265
4266 assert_eq!(
4267 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4268 vec!["module:a.js", "dup:0", "module:b.js"]
4269 );
4270 Ok(())
4271 })
4272 .await
4273 .unwrap();
4274 }
4275
4276 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4277 async fn concat_preserves_distinct_modules_with_same_request_key() {
4278 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
4279 BackendOptions::default(),
4280 noop_backing_storage(),
4281 ));
4282 #[turbo_tasks::function(operation, root)]
4283 async fn run_test() -> Result<Vc<DupCheckResult>> {
4284 let app = make_module(rcstr!("app.js")).to_resolved().await?;
4285 let package = make_module(rcstr!("package.js")).to_resolved().await?;
4286 let key = RequestKey::new(rcstr!("./config.js"));
4287 let app_result = *ModuleResolveResult::module_with_key(key.clone(), app);
4288 let package_result = *ModuleResolveResult::modules([
4289 (key, package),
4290 (RequestKey::new(rcstr!("also-app")), app),
4291 ]);
4292 let combined = ModuleResolveResult::concat(vec![app_result, package_result]).await?;
4293 assert_eq!(combined.primary_modules().await?.as_slice(), [app, package]);
4294 Ok(Vc::cell(snapshot_primary(&combined).await?))
4295 }
4296 tt.run_once(async move {
4297 let snap = run_test().read_strongly_consistent().await?;
4298 assert_eq!(
4299 snap.iter().map(String::as_str).collect::<Vec<_>>(),
4300 vec!["module:app.js", "module:package.js", "dup:0"]
4301 );
4302 Ok(())
4303 })
4304 .await
4305 .unwrap();
4306 }
4307}