1use std::{
2 borrow::Cow,
3 collections::BTreeMap,
4 fmt::{Display, Formatter, Write},
5 future::Future,
6 iter::{empty, once},
7};
8
9use anyhow::{Result, bail};
10use bincode::{Decode, Encode};
11use either::Either;
12use once_cell::sync::Lazy;
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, FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryFlatJoinIterExt,
21 TryJoinIterExt, ValueToString, Vc, trace::TraceRawVcs,
22};
23use turbo_tasks_fs::{FileSystemEntryType, FileSystemPath};
24use turbo_unix_path::normalize_request;
25
26use crate::{
27 context::AssetContext,
28 data_uri_source::DataUriSource,
29 file_source::FileSource,
30 issue::{
31 IssueExt, IssueSource, module::emit_unknown_module_type_error, resolve::ResolvingIssue,
32 },
33 module::{Module, Modules, OptionModule},
34 output::{OutputAsset, OutputAssets},
35 package_json::{PackageJsonIssue, read_package_json},
36 raw_module::RawModule,
37 reference_type::ReferenceType,
38 resolve::{
39 alias_map::AliasKey,
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::{ExportsField, ImportsField, ReplacedSubpathValueResult},
50 },
51 source::{OptionSource, Source, Sources},
52};
53
54mod alias_map;
55pub mod node;
56pub mod options;
57pub mod origin;
58pub mod parse;
59pub mod pattern;
60pub mod plugin;
61pub(crate) mod remap;
62
63pub use alias_map::{
64 AliasMap, AliasMapIntoIter, AliasMapLookupIterator, AliasMatch, AliasPattern, AliasTemplate,
65};
66pub use remap::{ResolveAliasMap, SubpathValue};
67
68use crate::{error::PrettyPrintError, issue::IssueSeverity};
69
70type AfterResolvePluginWithCondition = (
72 ResolvedVc<Box<dyn AfterResolvePlugin>>,
73 Vc<AfterResolvePluginCondition>,
74);
75
76#[turbo_tasks::value(shared)]
77#[derive(Clone, Debug)]
78pub enum ModuleResolveResultItem {
79 Module(ResolvedVc<Box<dyn Module>>),
80 OutputAsset(ResolvedVc<Box<dyn OutputAsset>>),
81 External {
82 name: RcStr,
84 ty: ExternalType,
85 },
86 Unknown(ResolvedVc<Box<dyn Source>>),
88 Ignore,
89 Error(ResolvedVc<RcStr>),
90 Empty,
91 Custom(u8),
92}
93
94impl ModuleResolveResultItem {
95 async fn as_module(&self) -> Result<Option<ResolvedVc<Box<dyn Module>>>> {
96 Ok(match *self {
97 ModuleResolveResultItem::Module(module) => Some(module),
98 ModuleResolveResultItem::Unknown(source) => {
99 emit_unknown_module_type_error(*source).await?;
100 None
101 }
102 ModuleResolveResultItem::Error(_err) => {
103 None
105 }
106 _ => None,
107 })
108 }
109}
110
111#[turbo_tasks::value(shared)]
112#[derive(Clone, Debug, Hash, Default, Serialize, Deserialize)]
113pub struct BindingUsage {
114 pub import: ImportUsage,
115 pub export: ExportUsage,
116}
117
118#[turbo_tasks::value_impl]
119impl BindingUsage {
120 #[turbo_tasks::function]
121 pub fn all() -> Vc<Self> {
122 Self::default().cell()
123 }
124}
125
126#[turbo_tasks::value(shared)]
128#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
129pub enum ImportUsage {
130 #[default]
133 TopLevel,
134 Exports(FrozenSet<RcStr>),
140}
141
142#[turbo_tasks::value]
144#[derive(Debug, Clone, Default, Hash, Serialize, Deserialize)]
145pub enum ExportUsage {
146 Named(RcStr),
147 #[default]
149 All,
150 Evaluation,
152}
153
154impl Display for ExportUsage {
155 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
156 match self {
157 ExportUsage::Named(name) => write!(f, "export {name}"),
158 ExportUsage::All => write!(f, "all"),
159 ExportUsage::Evaluation => write!(f, "evaluation"),
160 }
161 }
162}
163
164#[turbo_tasks::value_impl]
165impl ExportUsage {
166 #[turbo_tasks::function]
167 pub fn all() -> Vc<Self> {
168 Self::All.cell()
169 }
170
171 #[turbo_tasks::function]
172 pub fn evaluation() -> Vc<Self> {
173 Self::Evaluation.cell()
174 }
175
176 #[turbo_tasks::function]
177 pub fn named(name: RcStr) -> Vc<Self> {
178 Self::Named(name).cell()
179 }
180}
181
182#[turbo_tasks::value(shared)]
183#[derive(Clone, Debug)]
184pub struct ModuleResolveResult {
185 pub primary: Box<[(RequestKey, ModuleResolveResultItem)]>,
186 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
189}
190
191impl ModuleResolveResult {
192 pub fn unresolvable() -> ResolvedVc<Self> {
193 ModuleResolveResult {
194 primary: Default::default(),
195 affecting_sources: Default::default(),
196 }
197 .resolved_cell()
198 }
199
200 pub fn module(module: ResolvedVc<Box<dyn Module>>) -> ResolvedVc<Self> {
201 Self::module_with_key(RequestKey::default(), module)
202 }
203
204 pub fn module_with_key(
205 request_key: RequestKey,
206 module: ResolvedVc<Box<dyn Module>>,
207 ) -> ResolvedVc<Self> {
208 ModuleResolveResult {
209 primary: vec![(request_key, ModuleResolveResultItem::Module(module))]
210 .into_boxed_slice(),
211 affecting_sources: Default::default(),
212 }
213 .resolved_cell()
214 }
215
216 pub fn output_asset(
217 request_key: RequestKey,
218 output_asset: ResolvedVc<Box<dyn OutputAsset>>,
219 ) -> ResolvedVc<Self> {
220 ModuleResolveResult {
221 primary: vec![(
222 request_key,
223 ModuleResolveResultItem::OutputAsset(output_asset),
224 )]
225 .into_boxed_slice(),
226 affecting_sources: Default::default(),
227 }
228 .resolved_cell()
229 }
230
231 pub fn modules(
232 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
233 ) -> ResolvedVc<Self> {
234 ModuleResolveResult {
235 primary: modules
236 .into_iter()
237 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
238 .collect(),
239 affecting_sources: Default::default(),
240 }
241 .resolved_cell()
242 }
243
244 pub fn modules_with_affecting_sources(
245 modules: impl IntoIterator<Item = (RequestKey, ResolvedVc<Box<dyn Module>>)>,
246 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
247 ) -> ResolvedVc<Self> {
248 ModuleResolveResult {
249 primary: modules
250 .into_iter()
251 .map(|(k, v)| (k, ModuleResolveResultItem::Module(v)))
252 .collect(),
253 affecting_sources: affecting_sources.into_boxed_slice(),
254 }
255 .resolved_cell()
256 }
257}
258
259impl ModuleResolveResult {
260 pub fn primary_modules_raw_iter(
262 &self,
263 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
264 self.primary.iter().filter_map(|(_, item)| match *item {
265 ModuleResolveResultItem::Module(a) => Some(a),
266 _ => None,
267 })
268 }
269
270 pub async fn primary_modules_ref(&self) -> Result<Vec<ResolvedVc<Box<dyn Module>>>> {
272 let mut set = FxIndexSet::default();
273 for (_, item) in self.primary.iter() {
274 if let Some(module) = item.as_module().await? {
275 set.insert(module);
276 }
277 }
278 Ok(set.into_iter().collect())
279 }
280
281 pub fn affecting_sources_iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
282 self.affecting_sources.iter().copied()
283 }
284
285 pub fn is_unresolvable_ref(&self) -> bool {
286 self.primary.is_empty()
287 }
288}
289
290pub struct ModuleResolveResultBuilder {
291 pub primary: FxIndexMap<RequestKey, ModuleResolveResultItem>,
292 pub affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
293}
294
295impl From<ModuleResolveResultBuilder> for ModuleResolveResult {
296 fn from(v: ModuleResolveResultBuilder) -> Self {
297 ModuleResolveResult {
298 primary: v.primary.into_iter().collect(),
299 affecting_sources: v.affecting_sources.into_boxed_slice(),
300 }
301 }
302}
303impl From<ModuleResolveResult> for ModuleResolveResultBuilder {
304 fn from(v: ModuleResolveResult) -> Self {
305 ModuleResolveResultBuilder {
306 primary: IntoIterator::into_iter(v.primary).collect(),
307 affecting_sources: v.affecting_sources.into_vec(),
308 }
309 }
310}
311impl ModuleResolveResultBuilder {
312 pub fn merge_alternatives(&mut self, other: &ModuleResolveResult) {
313 for (k, v) in other.primary.iter() {
314 if !self.primary.contains_key(k) {
315 self.primary.insert(k.clone(), v.clone());
316 }
317 }
318 let set = self
319 .affecting_sources
320 .iter()
321 .copied()
322 .collect::<FxHashSet<_>>();
323 self.affecting_sources.extend(
324 other
325 .affecting_sources
326 .iter()
327 .filter(|source| !set.contains(source))
328 .copied(),
329 );
330 }
331}
332
333#[turbo_tasks::value_impl]
334impl ModuleResolveResult {
335 #[turbo_tasks::function]
336 pub async fn alternatives(results: Vec<Vc<ModuleResolveResult>>) -> Result<Vc<Self>> {
337 if results.len() == 1 {
338 return Ok(results.into_iter().next().unwrap());
339 }
340 let mut iter = results.into_iter().try_join().await?.into_iter();
341 if let Some(current) = iter.next() {
342 let mut current: ModuleResolveResultBuilder = ReadRef::into_owned(current).into();
343 for result in iter {
344 let other = &*result;
346 current.merge_alternatives(other);
347 }
348 Ok(Self::cell(current.into()))
349 } else {
350 Ok(*ModuleResolveResult::unresolvable())
351 }
352 }
353
354 #[turbo_tasks::function]
355 pub fn is_unresolvable(&self) -> Vc<bool> {
356 Vc::cell(self.is_unresolvable_ref())
357 }
358
359 #[turbo_tasks::function]
360 pub async fn first_module(&self) -> Result<Vc<OptionModule>> {
361 for (_, item) in self.primary.iter() {
362 if let Some(module) = item.as_module().await? {
363 return Ok(Vc::cell(Some(module)));
364 }
365 }
366 Ok(Vc::cell(None))
367 }
368
369 #[turbo_tasks::function]
372 pub async fn primary_modules(&self) -> Result<Vc<Modules>> {
373 let mut set = FxIndexSet::default();
374 for (_, item) in self.primary.iter() {
375 if let Some(module) = item.as_module().await? {
376 set.insert(module);
377 }
378 }
379 Ok(Vc::cell(set.into_iter().collect()))
380 }
381
382 #[turbo_tasks::function]
383 pub fn primary_output_assets(&self) -> Vc<OutputAssets> {
384 Vc::cell(
385 self.primary
386 .iter()
387 .filter_map(|(_, item)| match item {
388 &ModuleResolveResultItem::OutputAsset(a) => Some(a),
389 _ => None,
390 })
391 .collect(),
392 )
393 }
394}
395
396#[derive(
397 Copy,
398 Clone,
399 Debug,
400 PartialEq,
401 Eq,
402 TaskInput,
403 Hash,
404 NonLocalValue,
405 TraceRawVcs,
406 Serialize,
407 Deserialize,
408 Encode,
409 Decode,
410)]
411pub enum ExternalTraced {
412 Untraced,
413 Traced,
414}
415
416impl Display for ExternalTraced {
417 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
418 match self {
419 ExternalTraced::Untraced => write!(f, "untraced"),
420 ExternalTraced::Traced => write!(f, "traced"),
421 }
422 }
423}
424
425#[derive(
426 Copy,
427 Clone,
428 Debug,
429 Eq,
430 PartialEq,
431 Hash,
432 Serialize,
433 Deserialize,
434 TraceRawVcs,
435 TaskInput,
436 NonLocalValue,
437 Encode,
438 Decode,
439)]
440pub enum ExternalType {
441 Url,
442 CommonJs,
443 EcmaScriptModule,
444 Global,
445 Script,
446}
447
448impl Display for ExternalType {
449 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
450 match self {
451 ExternalType::CommonJs => write!(f, "commonjs"),
452 ExternalType::EcmaScriptModule => write!(f, "esm"),
453 ExternalType::Url => write!(f, "url"),
454 ExternalType::Global => write!(f, "global"),
455 ExternalType::Script => write!(f, "script"),
456 }
457 }
458}
459
460#[turbo_tasks::value(shared)]
461#[derive(Debug, Clone)]
462pub enum ResolveResultItem {
463 Source(ResolvedVc<Box<dyn Source>>),
464 External {
465 name: RcStr,
467 ty: ExternalType,
468 traced: ExternalTraced,
469 target: Option<FileSystemPath>,
472 },
473 Ignore,
474 Error(ResolvedVc<RcStr>),
475 Empty,
476 Custom(u8),
477}
478
479#[derive(Clone, Debug, Default, Hash, TaskInput)]
486#[turbo_tasks::value]
487pub struct RequestKey {
488 pub request: Option<RcStr>,
489 pub conditions: FrozenMap<RcStr, bool>,
490}
491
492impl Display for RequestKey {
493 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
494 if let Some(request) = &self.request {
495 write!(f, "{request}")?;
496 } else {
497 write!(f, "<default>")?;
498 }
499 if !self.conditions.is_empty() {
500 write!(f, " (")?;
501 for (i, (k, v)) in self.conditions.iter().enumerate() {
502 if i > 0 {
503 write!(f, ", ")?;
504 }
505 write!(f, "{k}={v}")?;
506 }
507 write!(f, ")")?;
508 }
509 Ok(())
510 }
511}
512
513impl RequestKey {
514 pub fn new(request: RcStr) -> Self {
515 RequestKey {
516 request: Some(request),
517 ..Default::default()
518 }
519 }
520}
521
522#[turbo_tasks::value(shared)]
523#[derive(Clone)]
524pub struct ResolveResult {
525 pub primary: Box<[(RequestKey, ResolveResultItem)]>,
526 pub affecting_sources: Box<[ResolvedVc<Box<dyn Source>>]>,
529}
530
531#[turbo_tasks::value_impl]
532impl ValueToString for ResolveResult {
533 #[turbo_tasks::function]
534 async fn to_string(&self) -> Result<Vc<RcStr>> {
535 let mut result = String::new();
536 if self.is_unresolvable_ref() {
537 result.push_str("unresolvable");
538 }
539 for (i, (request, item)) in self.primary.iter().enumerate() {
540 if i > 0 {
541 result.push_str(", ");
542 }
543 write!(result, "{request} -> ").unwrap();
544 match item {
545 ResolveResultItem::Source(a) => {
546 result.push_str(&a.ident().to_string().await?);
547 }
548 ResolveResultItem::External {
549 name: s,
550 ty,
551 traced,
552 target,
553 } => {
554 result.push_str("external ");
555 result.push_str(s);
556 write!(
557 result,
558 " ({ty}, {traced}, {:?})",
559 if let Some(target) = target {
560 Some(target.value_to_string().await?)
561 } else {
562 None
563 }
564 )?;
565 }
566 ResolveResultItem::Ignore => {
567 result.push_str("ignore");
568 }
569 ResolveResultItem::Empty => {
570 result.push_str("empty");
571 }
572 ResolveResultItem::Error(_) => {
573 result.push_str("error");
574 }
575 ResolveResultItem::Custom(_) => {
576 result.push_str("custom");
577 }
578 }
579 result.push('\n');
580 }
581 if !self.affecting_sources.is_empty() {
582 result.push_str(" (affecting sources: ");
583 for (i, source) in self.affecting_sources.iter().enumerate() {
584 if i > 0 {
585 result.push_str(", ");
586 }
587 result.push_str(&source.ident().to_string().await?);
588 }
589 result.push(')');
590 }
591 Ok(Vc::cell(result.into()))
592 }
593}
594
595impl ResolveResult {
596 pub fn unresolvable() -> ResolvedVc<Self> {
597 ResolveResult {
598 primary: Default::default(),
599 affecting_sources: Default::default(),
600 }
601 .resolved_cell()
602 }
603
604 pub fn unresolvable_with_affecting_sources(
605 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
606 ) -> ResolvedVc<Self> {
607 ResolveResult {
608 primary: Default::default(),
609 affecting_sources: affecting_sources.into_boxed_slice(),
610 }
611 .resolved_cell()
612 }
613
614 pub fn primary(result: ResolveResultItem) -> ResolvedVc<Self> {
615 Self::primary_with_key(RequestKey::default(), result)
616 }
617
618 pub fn primary_with_key(
619 request_key: RequestKey,
620 result: ResolveResultItem,
621 ) -> ResolvedVc<Self> {
622 ResolveResult {
623 primary: vec![(request_key, result)].into_boxed_slice(),
624 affecting_sources: Default::default(),
625 }
626 .resolved_cell()
627 }
628
629 pub fn primary_with_affecting_sources(
630 request_key: RequestKey,
631 result: ResolveResultItem,
632 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
633 ) -> ResolvedVc<Self> {
634 ResolveResult {
635 primary: vec![(request_key, result)].into_boxed_slice(),
636 affecting_sources: affecting_sources.into_boxed_slice(),
637 }
638 .resolved_cell()
639 }
640
641 pub fn source(source: ResolvedVc<Box<dyn Source>>) -> ResolvedVc<Self> {
642 Self::source_with_key(RequestKey::default(), source).resolved_cell()
643 }
644
645 fn source_with_key(request_key: RequestKey, source: ResolvedVc<Box<dyn Source>>) -> Self {
646 ResolveResult {
647 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
648 affecting_sources: Default::default(),
649 }
650 }
651
652 fn source_with_affecting_sources(
653 request_key: RequestKey,
654 source: ResolvedVc<Box<dyn Source>>,
655 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
656 ) -> Self {
657 ResolveResult {
658 primary: vec![(request_key, ResolveResultItem::Source(source))].into_boxed_slice(),
659 affecting_sources: affecting_sources.into_boxed_slice(),
660 }
661 }
662}
663
664impl ResolveResult {
665 pub fn get_affecting_sources(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Source>>> + '_ {
668 self.affecting_sources.iter().copied()
669 }
670
671 pub fn is_unresolvable_ref(&self) -> bool {
672 self.primary.is_empty()
673 }
674
675 pub async fn map_module<A, AF>(&self, source_fn: A) -> Result<ModuleResolveResult>
676 where
677 A: Fn(ResolvedVc<Box<dyn Source>>) -> AF,
678 AF: Future<Output = Result<ModuleResolveResultItem>>,
679 {
680 Ok(ModuleResolveResult {
681 primary: self
682 .primary
683 .iter()
684 .map(|(request, item)| {
685 let asset_fn = &source_fn;
686 let request = request.clone();
687 let item = item.clone();
688 async move {
689 Ok((
690 request,
691 match item {
692 ResolveResultItem::Source(source) => asset_fn(source).await?,
693 ResolveResultItem::External {
694 name,
695 ty,
696 traced,
697 target,
698 } => {
699 if traced == ExternalTraced::Traced || target.is_some() {
700 bail!("map_module doesn't handle traced externals");
702 }
703 ModuleResolveResultItem::External { name, ty }
704 }
705 ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
706 ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
707 ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
708 ResolveResultItem::Custom(u8) => {
709 ModuleResolveResultItem::Custom(u8)
710 }
711 },
712 ))
713 }
714 })
715 .try_join()
716 .await?
717 .into_iter()
718 .collect(),
719 affecting_sources: self.affecting_sources.clone(),
720 })
721 }
722
723 pub async fn map_primary_items<A, AF>(&self, item_fn: A) -> Result<ModuleResolveResult>
724 where
725 A: Fn(ResolveResultItem) -> AF,
726 AF: Future<Output = Result<ModuleResolveResultItem>>,
727 {
728 Ok(ModuleResolveResult {
729 primary: self
730 .primary
731 .iter()
732 .map(|(request, item)| {
733 let asset_fn = &item_fn;
734 let request = request.clone();
735 let item = item.clone();
736 async move { Ok((request, asset_fn(item).await?)) }
737 })
738 .try_join()
739 .await?
740 .into_iter()
741 .collect(),
742 affecting_sources: self.affecting_sources.clone(),
743 })
744 }
745
746 fn with_request_ref(&self, request: RcStr) -> Self {
749 let new_primary = self
750 .primary
751 .iter()
752 .map(|(k, v)| {
753 (
754 RequestKey {
755 request: Some(request.clone()),
756 conditions: k.conditions.clone(),
757 },
758 v.clone(),
759 )
760 })
761 .collect();
762 ResolveResult {
763 primary: new_primary,
764 affecting_sources: self.affecting_sources.clone(),
765 }
766 }
767
768 pub fn with_conditions(&self, new_conditions: &[(RcStr, bool)]) -> Self {
769 let primary = self
770 .primary
771 .iter()
772 .map(|(k, v)| {
773 (
774 RequestKey {
775 request: k.request.clone(),
776 conditions: k.conditions.extend(new_conditions.iter().cloned()),
777 },
778 v.clone(),
779 )
780 })
781 .collect::<FxIndexMap<_, _>>() .into_iter()
783 .collect();
784 ResolveResult {
785 primary,
786 affecting_sources: self.affecting_sources.clone(),
787 }
788 }
789}
790
791struct ResolveResultBuilder {
792 primary: FxIndexMap<RequestKey, ResolveResultItem>,
793 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
794}
795
796impl From<ResolveResultBuilder> for ResolveResult {
797 fn from(v: ResolveResultBuilder) -> Self {
798 ResolveResult {
799 primary: v.primary.into_iter().collect(),
800 affecting_sources: v.affecting_sources.into_boxed_slice(),
801 }
802 }
803}
804impl From<ResolveResult> for ResolveResultBuilder {
805 fn from(v: ResolveResult) -> Self {
806 ResolveResultBuilder {
807 primary: IntoIterator::into_iter(v.primary).collect(),
808 affecting_sources: v.affecting_sources.into_vec(),
809 }
810 }
811}
812impl ResolveResultBuilder {
813 pub fn merge_alternatives(&mut self, other: &ResolveResult) {
814 for (k, v) in other.primary.iter() {
815 if !self.primary.contains_key(k) {
816 self.primary.insert(k.clone(), v.clone());
817 }
818 }
819 let set = self
820 .affecting_sources
821 .iter()
822 .copied()
823 .collect::<FxHashSet<_>>();
824 self.affecting_sources.extend(
825 other
826 .affecting_sources
827 .iter()
828 .filter(|source| !set.contains(source))
829 .copied(),
830 );
831 }
832}
833
834#[turbo_tasks::value_impl]
835impl ResolveResult {
836 #[turbo_tasks::function]
837 pub async fn as_raw_module_result(&self) -> Result<Vc<ModuleResolveResult>> {
838 Ok(self
839 .map_module(|asset| async move {
840 Ok(ModuleResolveResultItem::Module(ResolvedVc::upcast(
841 RawModule::new(*asset).to_resolved().await?,
842 )))
843 })
844 .await?
845 .cell())
846 }
847
848 #[turbo_tasks::function]
849 fn with_affecting_sources(
850 &self,
851 sources: Vec<ResolvedVc<Box<dyn Source>>>,
852 ) -> Result<Vc<Self>> {
853 Ok(Self {
854 primary: self.primary.clone(),
855 affecting_sources: self
856 .affecting_sources
857 .iter()
858 .copied()
859 .chain(sources)
860 .collect(),
861 }
862 .cell())
863 }
864
865 #[turbo_tasks::function]
866 async fn alternatives(results: Vec<Vc<ResolveResult>>) -> Result<Vc<Self>> {
867 if results.len() == 1 {
868 return Ok(results.into_iter().next().unwrap());
869 }
870 let mut iter = results.into_iter().try_join().await?.into_iter();
871 if let Some(current) = iter.next() {
872 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
873 for result in iter {
874 let other = &*result;
876 current.merge_alternatives(other);
877 }
878 Ok(Self::cell(current.into()))
879 } else {
880 Ok(*ResolveResult::unresolvable())
881 }
882 }
883
884 #[turbo_tasks::function]
885 async fn alternatives_with_affecting_sources(
886 results: Vec<Vc<ResolveResult>>,
887 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
888 ) -> Result<Vc<Self>> {
889 debug_assert!(
890 !affecting_sources.is_empty(),
891 "Caller should not call this function if there are no affecting sources"
892 );
893 if results.len() == 1 {
894 return Ok(results
895 .into_iter()
896 .next()
897 .unwrap()
898 .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()));
899 }
900 let mut iter = results.into_iter().try_join().await?.into_iter();
901 if let Some(current) = iter.next() {
902 let mut current: ResolveResultBuilder = ReadRef::into_owned(current).into();
903 for result in iter {
904 let other = &*result;
906 current.merge_alternatives(other);
907 }
908 current.affecting_sources.extend(affecting_sources);
909 Ok(Self::cell(current.into()))
910 } else {
911 Ok(*ResolveResult::unresolvable_with_affecting_sources(
912 affecting_sources,
913 ))
914 }
915 }
916
917 #[turbo_tasks::function]
918 pub fn is_unresolvable(&self) -> Vc<bool> {
919 Vc::cell(self.is_unresolvable_ref())
920 }
921
922 #[turbo_tasks::function]
923 pub fn first_source(&self) -> Vc<OptionSource> {
924 Vc::cell(self.primary.iter().find_map(|(_, item)| {
925 if let &ResolveResultItem::Source(a) = item {
926 Some(a)
927 } else {
928 None
929 }
930 }))
931 }
932
933 #[turbo_tasks::function]
934 pub fn primary_sources(&self) -> Vc<Sources> {
935 Vc::cell(
936 self.primary
937 .iter()
938 .filter_map(|(_, item)| {
939 if let &ResolveResultItem::Source(a) = item {
940 Some(a)
941 } else {
942 None
943 }
944 })
945 .collect(),
946 )
947 }
948
949 #[turbo_tasks::function]
954 fn with_replaced_request_key(
955 &self,
956 old_request_key: RcStr,
957 request_key: RequestKey,
958 ) -> Result<Vc<Self>> {
959 let new_primary = self
960 .primary
961 .iter()
962 .filter_map(|(k, v)| {
963 let remaining = k.request.as_ref()?.strip_prefix(&*old_request_key)?;
964 Some((
965 RequestKey {
966 request: request_key
967 .request
968 .as_ref()
969 .map(|r| format!("{r}{remaining}").into()),
970 conditions: request_key.conditions.clone(),
971 },
972 v.clone(),
973 ))
974 })
975 .collect();
976 Ok(ResolveResult {
977 primary: new_primary,
978 affecting_sources: self.affecting_sources.clone(),
979 }
980 .cell())
981 }
982
983 #[turbo_tasks::function]
987 fn with_stripped_request_key_prefix(&self, prefix: RcStr) -> Result<Vc<Self>> {
988 let new_primary = self
989 .primary
990 .iter()
991 .filter_map(|(k, v)| {
992 let remaining = k.request.as_ref()?.strip_prefix(&*prefix)?;
993 Some((
994 RequestKey {
995 request: Some(remaining.into()),
996 conditions: k.conditions.clone(),
997 },
998 v.clone(),
999 ))
1000 })
1001 .collect();
1002 Ok(ResolveResult {
1003 primary: new_primary,
1004 affecting_sources: self.affecting_sources.clone(),
1005 }
1006 .cell())
1007 }
1008
1009 #[turbo_tasks::function]
1014 async fn with_replaced_request_key_pattern(
1015 &self,
1016 old_request_key: Vc<Pattern>,
1017 request_key: Vc<Pattern>,
1018 ) -> Result<Vc<Self>> {
1019 let old_request_key = &*old_request_key.await?;
1020 let request_key = &*request_key.await?;
1021
1022 let new_primary = self
1023 .primary
1024 .iter()
1025 .map(|(k, v)| {
1026 (
1027 RequestKey {
1028 request: k
1029 .request
1030 .as_ref()
1031 .and_then(|r| old_request_key.match_apply_template(r, request_key))
1032 .map(Into::into),
1033 conditions: k.conditions.clone(),
1034 },
1035 v.clone(),
1036 )
1037 })
1038 .collect();
1039 Ok(ResolveResult {
1040 primary: new_primary,
1041 affecting_sources: self.affecting_sources.clone(),
1042 }
1043 .cell())
1044 }
1045
1046 #[turbo_tasks::function]
1049 fn with_request(&self, request: RcStr) -> Vc<Self> {
1050 let new_primary = self
1051 .primary
1052 .iter()
1053 .map(|(k, v)| {
1054 (
1055 RequestKey {
1056 request: Some(request.clone()),
1057 conditions: k.conditions.clone(),
1058 },
1059 v.clone(),
1060 )
1061 })
1062 .collect();
1063 ResolveResult {
1064 primary: new_primary,
1065 affecting_sources: self.affecting_sources.clone(),
1066 }
1067 .cell()
1068 }
1069}
1070
1071#[turbo_tasks::value(transparent)]
1072pub struct ResolveResultOption(Option<ResolvedVc<ResolveResult>>);
1073
1074#[turbo_tasks::value_impl]
1075impl ResolveResultOption {
1076 #[turbo_tasks::function]
1077 pub fn some(result: ResolvedVc<ResolveResult>) -> Vc<Self> {
1078 ResolveResultOption(Some(result)).cell()
1079 }
1080
1081 #[turbo_tasks::function]
1082 pub fn none() -> Vc<Self> {
1083 ResolveResultOption(None).cell()
1084 }
1085}
1086
1087async fn exists(
1088 fs_path: &FileSystemPath,
1089 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1090) -> Result<Option<FileSystemPath>> {
1091 type_exists(fs_path, FileSystemEntryType::File, refs).await
1092}
1093
1094async fn dir_exists(
1095 fs_path: &FileSystemPath,
1096 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1097) -> Result<Option<FileSystemPath>> {
1098 type_exists(fs_path, FileSystemEntryType::Directory, refs).await
1099}
1100
1101async fn type_exists(
1102 fs_path: &FileSystemPath,
1103 ty: FileSystemEntryType,
1104 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1105) -> Result<Option<FileSystemPath>> {
1106 let path = realpath(fs_path, refs).await?;
1107 Ok(if *path.get_type().await? == ty {
1108 Some(path)
1109 } else {
1110 None
1111 })
1112}
1113
1114async fn realpath(
1115 fs_path: &FileSystemPath,
1116 refs: Option<&mut Vec<ResolvedVc<Box<dyn Source>>>>,
1117) -> Result<FileSystemPath> {
1118 let result = fs_path.realpath_with_links().await?;
1119 if let Some(refs) = refs {
1120 refs.extend(
1121 result
1122 .symlinks
1123 .iter()
1124 .map(|path| async move {
1125 Ok(ResolvedVc::upcast(
1126 FileSource::new(path.clone()).to_resolved().await?,
1127 ))
1128 })
1129 .try_join()
1130 .await?,
1131 );
1132 }
1133 match &result.path_result {
1134 Ok(path) => Ok(path.clone()),
1135 Err(e) => bail!(e.as_error_message(fs_path, &result)),
1136 }
1137}
1138
1139#[turbo_tasks::value(shared)]
1140enum ExportsFieldResult {
1141 Some(#[turbo_tasks(debug_ignore, trace_ignore)] ExportsField),
1142 None,
1143}
1144
1145#[turbo_tasks::function]
1148async fn exports_field(
1149 package_json_path: ResolvedVc<Box<dyn Source>>,
1150) -> Result<Vc<ExportsFieldResult>> {
1151 let read = read_package_json(*package_json_path).await?;
1152 let package_json = match &*read {
1153 Some(json) => json,
1154 None => return Ok(ExportsFieldResult::None.cell()),
1155 };
1156
1157 let Some(exports) = package_json.get("exports") else {
1158 return Ok(ExportsFieldResult::None.cell());
1159 };
1160 match exports.try_into() {
1161 Ok(exports) => Ok(ExportsFieldResult::Some(exports).cell()),
1162 Err(err) => {
1163 PackageJsonIssue {
1164 error_message: err.to_string().into(),
1165 source: IssueSource::from_source_only(package_json_path),
1167 }
1168 .resolved_cell()
1169 .emit();
1170 Ok(ExportsFieldResult::None.cell())
1171 }
1172 }
1173}
1174
1175#[turbo_tasks::value(shared)]
1176enum ImportsFieldResult {
1177 Some(
1178 #[turbo_tasks(debug_ignore, trace_ignore)] ImportsField,
1179 FileSystemPath,
1180 ),
1181 None,
1182}
1183
1184#[turbo_tasks::function]
1187async fn imports_field(lookup_path: FileSystemPath) -> Result<Vc<ImportsFieldResult>> {
1188 let package_json_context =
1190 find_context_file(lookup_path, package_json().resolve().await?, false).await?;
1191 let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context else {
1192 return Ok(ImportsFieldResult::None.cell());
1193 };
1194 let source = Vc::upcast::<Box<dyn Source>>(FileSource::new(package_json_path.clone()))
1195 .to_resolved()
1196 .await?;
1197
1198 let read = read_package_json(*source).await?;
1199 let package_json = match &*read {
1200 Some(json) => json,
1201 None => return Ok(ImportsFieldResult::None.cell()),
1202 };
1203
1204 let Some(imports) = package_json.get("imports") else {
1205 return Ok(ImportsFieldResult::None.cell());
1206 };
1207 match imports.try_into() {
1208 Ok(imports) => Ok(ImportsFieldResult::Some(imports, package_json_path.clone()).cell()),
1209 Err(err) => {
1210 PackageJsonIssue {
1211 error_message: err.to_string().into(),
1212 source: IssueSource::from_source_only(source),
1214 }
1215 .resolved_cell()
1216 .emit();
1217 Ok(ImportsFieldResult::None.cell())
1218 }
1219 }
1220}
1221
1222#[turbo_tasks::function]
1223pub fn package_json() -> Vc<Vec<RcStr>> {
1224 Vc::cell(vec![rcstr!("package.json")])
1225}
1226
1227#[turbo_tasks::value(shared)]
1228pub enum FindContextFileResult {
1229 Found(FileSystemPath, Vec<ResolvedVc<Box<dyn Source>>>),
1230 NotFound(Vec<ResolvedVc<Box<dyn Source>>>),
1231}
1232
1233#[turbo_tasks::function]
1234pub async fn find_context_file(
1235 lookup_path: FileSystemPath,
1236 names: Vc<Vec<RcStr>>,
1237 collect_affecting_sources: bool,
1238) -> Result<Vc<FindContextFileResult>> {
1239 let mut refs = Vec::new();
1240 for name in &*names.await? {
1241 let fs_path = lookup_path.join(name)?;
1242 if let Some(fs_path) = exists(
1243 &fs_path,
1244 if collect_affecting_sources {
1245 Some(&mut refs)
1246 } else {
1247 None
1248 },
1249 )
1250 .await?
1251 {
1252 return Ok(FindContextFileResult::Found(fs_path, refs).cell());
1253 }
1254 }
1255 if lookup_path.is_root() {
1256 return Ok(FindContextFileResult::NotFound(refs).cell());
1257 }
1258 if refs.is_empty() {
1259 Ok(find_context_file(
1261 lookup_path.parent(),
1262 names,
1263 collect_affecting_sources,
1264 ))
1265 } else {
1266 let parent_result =
1267 find_context_file(lookup_path.parent(), names, collect_affecting_sources).await?;
1268 Ok(match &*parent_result {
1269 FindContextFileResult::Found(p, r) => {
1270 refs.extend(r.iter().copied());
1271 FindContextFileResult::Found(p.clone(), refs)
1272 }
1273 FindContextFileResult::NotFound(r) => {
1274 refs.extend(r.iter().copied());
1275 FindContextFileResult::NotFound(refs)
1276 }
1277 }
1278 .cell())
1279 }
1280}
1281
1282#[turbo_tasks::function]
1285pub async fn find_context_file_or_package_key(
1286 lookup_path: FileSystemPath,
1287 names: Vc<Vec<RcStr>>,
1288 package_key: RcStr,
1289) -> Result<Vc<FindContextFileResult>> {
1290 let package_json_path = lookup_path.join("package.json")?;
1291 if let Some(package_json_path) = exists(&package_json_path, None).await?
1292 && let Some(json) =
1293 &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
1294 && json.get(&*package_key).is_some()
1295 {
1296 return Ok(FindContextFileResult::Found(package_json_path, Vec::new()).cell());
1297 }
1298 for name in &*names.await? {
1299 let fs_path = lookup_path.join(name)?;
1300 if let Some(fs_path) = exists(&fs_path, None).await? {
1301 return Ok(FindContextFileResult::Found(fs_path, Vec::new()).cell());
1302 }
1303 }
1304 if lookup_path.is_root() {
1305 return Ok(FindContextFileResult::NotFound(Vec::new()).cell());
1306 }
1307
1308 Ok(find_context_file(lookup_path.parent(), names, false))
1309}
1310
1311#[derive(Clone, PartialEq, Eq, TraceRawVcs, Debug, NonLocalValue, Encode, Decode)]
1312enum FindPackageItem {
1313 PackageDirectory { name: RcStr, dir: FileSystemPath },
1314 PackageFile { name: RcStr, file: FileSystemPath },
1315}
1316
1317#[turbo_tasks::value]
1318#[derive(Debug)]
1319struct FindPackageResult {
1320 packages: Vec<FindPackageItem>,
1321 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1323}
1324
1325#[turbo_tasks::function]
1326async fn find_package(
1327 lookup_path: FileSystemPath,
1328 package_name: Pattern,
1329 options: Vc<ResolveModulesOptions>,
1330 collect_affecting_sources: bool,
1331) -> Result<Vc<FindPackageResult>> {
1332 let mut packages = vec![];
1333 let mut affecting_sources = vec![];
1334 let options = options.await?;
1335 let package_name_cell = Pattern::new(package_name.clone());
1336
1337 fn get_package_name(basepath: &FileSystemPath, package_dir: &FileSystemPath) -> Result<RcStr> {
1338 if let Some(name) = basepath.get_path_to(package_dir) {
1339 Ok(name.into())
1340 } else {
1341 bail!("Package directory {package_dir} is not inside the lookup path {basepath}");
1342 }
1343 }
1344
1345 for resolve_modules in &options.modules {
1346 match resolve_modules {
1347 ResolveModules::Nested(root, names) => {
1348 let mut lookup_path = lookup_path.clone();
1349 let mut lookup_path_value = lookup_path.clone();
1350 while lookup_path_value.is_inside_ref(root) {
1351 for name in names.iter() {
1352 let fs_path = lookup_path.join(name)?;
1353 if let Some(fs_path) = dir_exists(
1354 &fs_path,
1355 collect_affecting_sources.then_some(&mut affecting_sources),
1356 )
1357 .await?
1358 {
1359 let matches =
1360 read_matches(fs_path.clone(), rcstr!(""), true, package_name_cell)
1361 .await?;
1362 for m in &*matches {
1363 if let PatternMatch::Directory(_, package_dir) = m {
1364 packages.push(FindPackageItem::PackageDirectory {
1365 name: get_package_name(&fs_path, package_dir)?,
1366 dir: realpath(
1367 package_dir,
1368 collect_affecting_sources
1369 .then_some(&mut affecting_sources),
1370 )
1371 .await?,
1372 });
1373 }
1374 }
1375 }
1376 }
1377 lookup_path = lookup_path.parent();
1378 let new_context_value = lookup_path.clone();
1379 if new_context_value == lookup_path_value {
1380 break;
1381 }
1382 lookup_path_value = new_context_value;
1383 }
1384 }
1385 ResolveModules::Path {
1386 dir,
1387 excluded_extensions,
1388 } => {
1389 let matches =
1390 read_matches(dir.clone(), rcstr!(""), true, package_name_cell).await?;
1391 for m in &*matches {
1392 match m {
1393 PatternMatch::Directory(_, package_dir) => {
1394 packages.push(FindPackageItem::PackageDirectory {
1395 name: get_package_name(dir, package_dir)?,
1396 dir: realpath(
1397 package_dir,
1398 collect_affecting_sources.then_some(&mut affecting_sources),
1399 )
1400 .await?,
1401 });
1402 }
1403 PatternMatch::File(_, package_file) => {
1404 packages.push(FindPackageItem::PackageFile {
1405 name: get_package_name(dir, package_file)?,
1406 file: realpath(
1407 package_file,
1408 collect_affecting_sources.then_some(&mut affecting_sources),
1409 )
1410 .await?,
1411 });
1412 }
1413 }
1414 }
1415
1416 let excluded_extensions = excluded_extensions.await?;
1417 let mut package_name_with_extensions = package_name.clone();
1418 package_name_with_extensions.push(Pattern::alternatives(
1419 options
1420 .extensions
1421 .iter()
1422 .filter(|ext| !excluded_extensions.contains(*ext))
1423 .cloned()
1424 .map(Pattern::from),
1425 ));
1426 let package_name_with_extensions = Pattern::new(package_name_with_extensions);
1427
1428 let matches =
1429 read_matches(dir.clone(), rcstr!(""), true, package_name_with_extensions)
1430 .await?;
1431 for m in matches {
1432 if let PatternMatch::File(_, package_file) = m {
1433 packages.push(FindPackageItem::PackageFile {
1434 name: get_package_name(dir, package_file)?,
1435 file: realpath(
1436 package_file,
1437 collect_affecting_sources.then_some(&mut affecting_sources),
1438 )
1439 .await?,
1440 });
1441 }
1442 }
1443 }
1444 }
1445 }
1446 Ok(FindPackageResult::cell(FindPackageResult {
1447 packages,
1448 affecting_sources,
1449 }))
1450}
1451
1452fn merge_results(results: Vec<Vc<ResolveResult>>) -> Vc<ResolveResult> {
1453 match results.len() {
1454 0 => *ResolveResult::unresolvable(),
1455 1 => results.into_iter().next().unwrap(),
1456 _ => ResolveResult::alternatives(results),
1457 }
1458}
1459
1460fn merge_results_with_affecting_sources(
1461 results: Vec<Vc<ResolveResult>>,
1462 affecting_sources: Vec<ResolvedVc<Box<dyn Source>>>,
1463) -> Vc<ResolveResult> {
1464 if affecting_sources.is_empty() {
1465 return merge_results(results);
1466 }
1467 match results.len() {
1468 0 => *ResolveResult::unresolvable_with_affecting_sources(affecting_sources),
1469 1 => results
1470 .into_iter()
1471 .next()
1472 .unwrap()
1473 .with_affecting_sources(affecting_sources.into_iter().map(|src| *src).collect()),
1474 _ => ResolveResult::alternatives_with_affecting_sources(
1475 results,
1476 affecting_sources.into_iter().map(|src| *src).collect(),
1477 ),
1478 }
1479}
1480
1481#[turbo_tasks::function]
1483pub async fn resolve_raw(
1484 lookup_dir: FileSystemPath,
1485 path: Vc<Pattern>,
1486 collect_affecting_sources: bool,
1487 force_in_lookup_dir: bool,
1488) -> Result<Vc<ResolveResult>> {
1489 async fn to_result(
1490 request: RcStr,
1491 path: &FileSystemPath,
1492 collect_affecting_sources: bool,
1493 ) -> Result<ResolveResult> {
1494 let result = &*path.realpath_with_links().await?;
1495 let path = match &result.path_result {
1496 Ok(path) => path,
1497 Err(e) => bail!(e.as_error_message(path, result)),
1498 };
1499 let request_key = RequestKey::new(request);
1500 let source = ResolvedVc::upcast(FileSource::new(path.clone()).to_resolved().await?);
1501 Ok(if collect_affecting_sources {
1502 ResolveResult::source_with_affecting_sources(
1503 request_key,
1504 source,
1505 result
1506 .symlinks
1507 .iter()
1508 .map(|symlink| {
1509 Vc::upcast::<Box<dyn Source>>(FileSource::new(symlink.clone()))
1510 .to_resolved()
1511 })
1512 .try_join()
1513 .await?,
1514 )
1515 } else {
1516 ResolveResult::source_with_key(request_key, source)
1517 })
1518 }
1519
1520 async fn collect_matches(
1521 matches: &[PatternMatch],
1522 collect_affecting_sources: bool,
1523 ) -> Result<Vec<Vc<ResolveResult>>> {
1524 Ok(matches
1525 .iter()
1526 .map(|m| async move {
1527 Ok(if let PatternMatch::File(request, path) = m {
1528 Some(to_result(request.clone(), path, collect_affecting_sources).await?)
1529 } else {
1530 None
1531 })
1532 })
1533 .try_flat_join()
1534 .await?
1535 .into_iter()
1538 .map(|res| res.cell())
1539 .collect())
1540 }
1541
1542 let mut results = Vec::new();
1543
1544 let pat = path.await?;
1545 if let Some(pat) = pat
1546 .filter_could_match("/ROOT/")
1547 .and_then(|pat| pat.filter_could_not_match("/ROOT/fsd8nz8og54z"))
1550 {
1551 let path = Pattern::new(pat);
1552 let matches = read_matches(
1553 lookup_dir.root().owned().await?,
1554 rcstr!("/ROOT/"),
1555 true,
1556 path,
1557 )
1558 .await?;
1559 results.extend(
1560 collect_matches(&matches, collect_affecting_sources)
1561 .await?
1562 .into_iter(),
1563 );
1564 }
1565
1566 {
1567 let matches =
1568 read_matches(lookup_dir.clone(), rcstr!(""), force_in_lookup_dir, path).await?;
1569
1570 results.extend(
1571 collect_matches(&matches, collect_affecting_sources)
1572 .await?
1573 .into_iter(),
1574 );
1575 }
1576
1577 Ok(merge_results(results))
1578}
1579
1580#[turbo_tasks::function]
1581pub async fn resolve(
1582 lookup_path: FileSystemPath,
1583 reference_type: ReferenceType,
1584 request: Vc<Request>,
1585 options: Vc<ResolveOptions>,
1586) -> Result<Vc<ResolveResult>> {
1587 resolve_inline(lookup_path, reference_type, request, options).await
1588}
1589
1590pub async fn resolve_inline(
1591 lookup_path: FileSystemPath,
1592 reference_type: ReferenceType,
1593 request: Vc<Request>,
1594 options: Vc<ResolveOptions>,
1595) -> Result<Vc<ResolveResult>> {
1596 let span = tracing::info_span!(
1597 "resolving",
1598 lookup_path = display(lookup_path.value_to_string().await?),
1599 name = tracing::field::Empty,
1600 reference_type = display(&reference_type),
1601 );
1602 if !span.is_disabled() {
1603 span.record("name", request.to_string().await?.as_str());
1605 }
1606
1607 async {
1608 let options_value = options.await?;
1610
1611 let has_before_plugins = !options_value.before_resolve_plugins.is_empty();
1613 let has_after_plugins = !options_value.after_resolve_plugins.is_empty();
1614
1615 let before_plugins_result = if has_before_plugins {
1616 handle_before_resolve_plugins(
1617 lookup_path.clone(),
1618 reference_type.clone(),
1619 request,
1620 options,
1621 )
1622 .await?
1623 } else {
1624 None
1625 };
1626
1627 let raw_result = match before_plugins_result {
1628 Some(result) => result,
1629 None => {
1630 resolve_internal(lookup_path.clone(), request, options)
1631 .resolve()
1632 .await?
1633 }
1634 };
1635
1636 let result = if has_after_plugins {
1637 handle_after_resolve_plugins(lookup_path, reference_type, request, options, raw_result)
1638 .await?
1639 } else {
1640 raw_result
1641 };
1642
1643 Ok(result)
1644 }
1645 .instrument(span)
1646 .await
1647}
1648
1649#[turbo_tasks::function]
1650pub async fn url_resolve(
1651 origin: Vc<Box<dyn ResolveOrigin>>,
1652 request: Vc<Request>,
1653 reference_type: ReferenceType,
1654 issue_source: Option<IssueSource>,
1655 is_optional: bool,
1656) -> Result<Vc<ModuleResolveResult>> {
1657 let resolve_options = origin.resolve_options();
1658 let rel_request = request.as_relative();
1659 let origin_path_parent = origin.origin_path().await?.parent();
1660 let rel_result = resolve(
1661 origin_path_parent.clone(),
1662 reference_type.clone(),
1663 rel_request,
1664 resolve_options,
1665 );
1666 let result = if *rel_result.is_unresolvable().await? && rel_request.resolve().await? != request
1667 {
1668 let result = resolve(
1669 origin_path_parent,
1670 reference_type.clone(),
1671 request,
1672 resolve_options,
1673 );
1674 if resolve_options.await?.collect_affecting_sources {
1675 result.with_affecting_sources(
1676 rel_result
1677 .await?
1678 .get_affecting_sources()
1679 .map(|src| *src)
1680 .collect(),
1681 )
1682 } else {
1683 result
1684 }
1685 } else {
1686 rel_result
1687 };
1688 let result = origin
1689 .asset_context()
1690 .process_resolve_result(result, reference_type.clone());
1691 handle_resolve_error(
1692 result,
1693 reference_type,
1694 origin,
1695 request,
1696 resolve_options,
1697 is_optional,
1698 issue_source,
1699 )
1700 .await
1701}
1702
1703#[turbo_tasks::value(transparent)]
1704struct MatchingBeforeResolvePlugins(Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>);
1705
1706#[turbo_tasks::function]
1707async fn get_matching_before_resolve_plugins(
1708 options: Vc<ResolveOptions>,
1709 request: Vc<Request>,
1710) -> Result<Vc<MatchingBeforeResolvePlugins>> {
1711 let mut matching_plugins = Vec::new();
1712 for &plugin in &options.await?.before_resolve_plugins {
1713 let condition = plugin.before_resolve_condition().resolve().await?;
1714 if *condition.matches(request).await? {
1715 matching_plugins.push(plugin);
1716 }
1717 }
1718 Ok(Vc::cell(matching_plugins))
1719}
1720
1721#[tracing::instrument(level = "trace", skip_all)]
1722async fn handle_before_resolve_plugins(
1723 lookup_path: FileSystemPath,
1724 reference_type: ReferenceType,
1725 request: Vc<Request>,
1726 options: Vc<ResolveOptions>,
1727) -> Result<Option<Vc<ResolveResult>>> {
1728 for plugin in get_matching_before_resolve_plugins(options, request).await? {
1729 if let Some(result) = *plugin
1730 .before_resolve(lookup_path.clone(), reference_type.clone(), request)
1731 .await?
1732 {
1733 return Ok(Some(*result));
1734 }
1735 }
1736 Ok(None)
1737}
1738
1739#[tracing::instrument(level = "trace", skip_all)]
1740async fn handle_after_resolve_plugins(
1741 lookup_path: FileSystemPath,
1742 reference_type: ReferenceType,
1743 request: Vc<Request>,
1744 options: Vc<ResolveOptions>,
1745 result: Vc<ResolveResult>,
1746) -> Result<Vc<ResolveResult>> {
1747 let options_value = options.await?;
1749
1750 let resolved_conditions = options_value
1752 .after_resolve_plugins
1753 .iter()
1754 .map(async |p| Ok((*p, p.after_resolve_condition().resolve().await?)))
1755 .try_join()
1756 .await?;
1757
1758 async fn apply_plugins_to_path(
1759 path: FileSystemPath,
1760 lookup_path: FileSystemPath,
1761 reference_type: ReferenceType,
1762 request: Vc<Request>,
1763 plugins_with_conditions: &[AfterResolvePluginWithCondition],
1764 ) -> Result<Option<Vc<ResolveResult>>> {
1765 for (plugin, after_resolve_condition) in plugins_with_conditions {
1766 if *after_resolve_condition.matches(path.clone()).await?
1767 && let Some(result) = *plugin
1768 .after_resolve(
1769 path.clone(),
1770 lookup_path.clone(),
1771 reference_type.clone(),
1772 request,
1773 )
1774 .await?
1775 {
1776 return Ok(Some(*result));
1777 }
1778 }
1779 Ok(None)
1780 }
1781
1782 let mut changed = false;
1783 let result_value = result.await?;
1784
1785 let mut new_primary = FxIndexMap::default();
1786 let mut new_affecting_sources = Vec::new();
1787
1788 for (key, primary) in result_value.primary.iter() {
1789 if let &ResolveResultItem::Source(source) = primary {
1790 let path = source.ident().path().owned().await?;
1791 if let Some(new_result) = apply_plugins_to_path(
1792 path.clone(),
1793 lookup_path.clone(),
1794 reference_type.clone(),
1795 request,
1796 &resolved_conditions,
1797 )
1798 .await?
1799 {
1800 let new_result = new_result.await?;
1801 changed = true;
1802 new_primary.extend(
1803 new_result
1804 .primary
1805 .iter()
1806 .map(|(_, item)| (key.clone(), item.clone())),
1807 );
1808 new_affecting_sources.extend(new_result.affecting_sources.iter().copied());
1809 } else {
1810 new_primary.insert(key.clone(), primary.clone());
1811 }
1812 } else {
1813 new_primary.insert(key.clone(), primary.clone());
1814 }
1815 }
1816
1817 if !changed {
1818 return Ok(result);
1819 }
1820
1821 let mut affecting_sources = result_value.affecting_sources.to_vec();
1822 affecting_sources.append(&mut new_affecting_sources);
1823
1824 Ok(ResolveResult {
1825 primary: new_primary.into_iter().collect(),
1826 affecting_sources: affecting_sources.into_boxed_slice(),
1827 }
1828 .cell())
1829}
1830
1831#[turbo_tasks::function]
1832async fn resolve_internal(
1833 lookup_path: FileSystemPath,
1834 request: ResolvedVc<Request>,
1835 options: ResolvedVc<ResolveOptions>,
1836) -> Result<Vc<ResolveResult>> {
1837 resolve_internal_inline(lookup_path.clone(), *request, *options).await
1838}
1839
1840async fn resolve_internal_inline(
1841 lookup_path: FileSystemPath,
1842 request: Vc<Request>,
1843 options: Vc<ResolveOptions>,
1844) -> Result<Vc<ResolveResult>> {
1845 let span = tracing::info_span!(
1846 "internal resolving",
1847 lookup_path = display(lookup_path.value_to_string().await?),
1848 name = tracing::field::Empty
1849 );
1850 if !span.is_disabled() {
1851 span.record("name", request.to_string().await?.as_str());
1853 }
1854
1855 async move {
1856 let options_value: &ResolveOptions = &*options.await?;
1857
1858 let request_value = request.await?;
1859
1860 let mut has_alias = false;
1862 if let Some(import_map) = &options_value.import_map {
1863 let request_parts = match &*request_value {
1864 Request::Alternatives { requests } => requests.as_slice(),
1865 _ => &[request.to_resolved().await?],
1866 };
1867 for &request in request_parts {
1868 let result = import_map
1869 .await?
1870 .lookup(lookup_path.clone(), *request)
1871 .await?;
1872 if !matches!(result, ImportMapResult::NoEntry) {
1873 has_alias = true;
1874 let resolved_result = resolve_import_map_result(
1875 &result,
1876 lookup_path.clone(),
1877 lookup_path.clone(),
1878 *request,
1879 options,
1880 request.query().owned().await?,
1881 )
1882 .await?;
1883 if let Some(result) = resolved_result
1890 && !*result.is_unresolvable().await?
1891 {
1892 return Ok(result);
1893 }
1894 }
1895 }
1896 }
1897
1898 let result = match &*request_value {
1899 Request::Dynamic => *ResolveResult::unresolvable(),
1900 Request::Alternatives { requests } => {
1901 let results = requests
1902 .iter()
1903 .map(|req| async {
1904 resolve_internal_inline(lookup_path.clone(), **req, options).await
1905 })
1906 .try_join()
1907 .await?;
1908
1909 merge_results(results)
1910 }
1911 Request::Raw {
1912 path,
1913 query,
1914 force_in_lookup_dir,
1915 fragment,
1916 } => {
1917 let mut results = Vec::new();
1918 let matches = read_matches(
1919 lookup_path.clone(),
1920 rcstr!(""),
1921 *force_in_lookup_dir,
1922 Pattern::new(path.clone()).resolve().await?,
1923 )
1924 .await?;
1925
1926 for m in matches.iter() {
1927 match m {
1928 PatternMatch::File(matched_pattern, path) => {
1929 results.push(
1930 resolved(
1931 RequestKey::new(matched_pattern.clone()),
1932 path.clone(),
1933 lookup_path.clone(),
1934 request,
1935 options_value,
1936 options,
1937 query.clone(),
1938 fragment.clone(),
1939 )
1940 .await?,
1941 );
1942 }
1943 PatternMatch::Directory(matched_pattern, path) => {
1944 results.push(
1945 resolve_into_folder(path.clone(), options)
1946 .with_request(matched_pattern.clone()),
1947 );
1948 }
1949 }
1950 }
1951
1952 merge_results(results)
1953 }
1954 Request::Relative {
1955 path,
1956 query,
1957 force_in_lookup_dir,
1958 fragment,
1959 } => {
1960 resolve_relative_request(
1961 lookup_path.clone(),
1962 request,
1963 options,
1964 options_value,
1965 path,
1966 query.clone(),
1967 *force_in_lookup_dir,
1968 fragment.clone(),
1969 )
1970 .await?
1971 }
1972 Request::Module {
1973 module,
1974 path,
1975 query,
1976 fragment,
1977 } => {
1978 resolve_module_request(
1979 lookup_path.clone(),
1980 request,
1981 options,
1982 options_value,
1983 module,
1984 path,
1985 query.clone(),
1986 fragment.clone(),
1987 )
1988 .await?
1989 }
1990 Request::ServerRelative {
1991 path,
1992 query,
1993 fragment,
1994 } => {
1995 let mut new_pat = path.clone();
1996 new_pat.push_front(rcstr!(".").into());
1997 let relative = Request::relative(new_pat, query.clone(), fragment.clone(), true);
1998
1999 if !has_alias {
2000 ResolvingIssue {
2001 severity: error_severity(options).await?,
2002 request_type: "server relative import: not implemented yet".to_string(),
2003 request: relative.to_resolved().await?,
2004 file_path: lookup_path.clone(),
2005 resolve_options: options.to_resolved().await?,
2006 error_message: Some(
2007 "server relative imports are not implemented yet. Please try an \
2008 import relative to the file you are importing from."
2009 .to_string(),
2010 ),
2011 source: None,
2012 }
2013 .resolved_cell()
2014 .emit();
2015 }
2016
2017 Box::pin(resolve_internal_inline(
2018 lookup_path.root().owned().await?,
2019 relative,
2020 options,
2021 ))
2022 .await?
2023 }
2024 Request::Windows {
2025 path: _,
2026 query: _,
2027 fragment: _,
2028 } => {
2029 if !has_alias {
2030 ResolvingIssue {
2031 severity: error_severity(options).await?,
2032 request_type: "windows import: not implemented yet".to_string(),
2033 request: request.to_resolved().await?,
2034 file_path: lookup_path.clone(),
2035 resolve_options: options.to_resolved().await?,
2036 error_message: Some("windows imports are not implemented yet".to_string()),
2037 source: None,
2038 }
2039 .resolved_cell()
2040 .emit();
2041 }
2042
2043 *ResolveResult::unresolvable()
2044 }
2045 Request::Empty => *ResolveResult::unresolvable(),
2046 Request::PackageInternal { path } => {
2047 let (conditions, unspecified_conditions) = options_value
2048 .in_package
2049 .iter()
2050 .find_map(|item| match item {
2051 ResolveInPackage::ImportsField {
2052 conditions,
2053 unspecified_conditions,
2054 } => Some((Cow::Borrowed(conditions), *unspecified_conditions)),
2055 _ => None,
2056 })
2057 .unwrap_or_else(|| (Default::default(), ConditionValue::Unset));
2058 resolve_package_internal_with_imports_field(
2059 lookup_path.clone(),
2060 request,
2061 options,
2062 path,
2063 &conditions,
2064 &unspecified_conditions,
2065 )
2066 .await?
2067 }
2068 Request::DataUri {
2069 media_type,
2070 encoding,
2071 data,
2072 } => {
2073 let uri: RcStr = stringify_data_uri(media_type, encoding, *data)
2075 .await?
2076 .into();
2077 if options_value.parse_data_uris {
2078 *ResolveResult::primary_with_key(
2079 RequestKey::new(uri.clone()),
2080 ResolveResultItem::Source(ResolvedVc::upcast(
2081 DataUriSource::new(
2082 media_type.clone(),
2083 encoding.clone(),
2084 **data,
2085 lookup_path.clone(),
2086 )
2087 .to_resolved()
2088 .await?,
2089 )),
2090 )
2091 } else {
2092 *ResolveResult::primary_with_key(
2093 RequestKey::new(uri.clone()),
2094 ResolveResultItem::External {
2095 name: uri,
2096 ty: ExternalType::Url,
2097 traced: ExternalTraced::Untraced,
2098 target: None,
2099 },
2100 )
2101 }
2102 }
2103 Request::Uri {
2104 protocol,
2105 remainder,
2106 query: _,
2107 fragment: _,
2108 } => {
2109 let uri: RcStr = format!("{protocol}{remainder}").into();
2110 *ResolveResult::primary_with_key(
2111 RequestKey::new(uri.clone()),
2112 ResolveResultItem::External {
2113 name: uri,
2114 ty: ExternalType::Url,
2115 traced: ExternalTraced::Untraced,
2116 target: None,
2117 },
2118 )
2119 }
2120 Request::Unknown { path } => {
2121 if !has_alias {
2122 ResolvingIssue {
2123 severity: error_severity(options).await?,
2124 request_type: format!("unknown import: `{}`", path.describe_as_string()),
2125 request: request.to_resolved().await?,
2126 file_path: lookup_path.clone(),
2127 resolve_options: options.to_resolved().await?,
2128 error_message: None,
2129 source: None,
2130 }
2131 .resolved_cell()
2132 .emit();
2133 }
2134 *ResolveResult::unresolvable()
2135 }
2136 };
2137
2138 if !matches!(*request_value, Request::Alternatives { .. }) {
2141 if let Some(import_map) = &options_value.fallback_import_map
2143 && *result.is_unresolvable().await?
2144 {
2145 let result = import_map
2146 .await?
2147 .lookup(lookup_path.clone(), request)
2148 .await?;
2149 let resolved_result = resolve_import_map_result(
2150 &result,
2151 lookup_path.clone(),
2152 lookup_path.clone(),
2153 request,
2154 options,
2155 request.query().owned().await?,
2156 )
2157 .await?;
2158 if let Some(result) = resolved_result
2159 && !*result.is_unresolvable().await?
2160 {
2161 return Ok(result);
2162 }
2163 }
2164 }
2165
2166 Ok(result)
2167 }
2168 .instrument(span)
2169 .await
2170}
2171
2172#[turbo_tasks::function]
2173async fn resolve_into_folder(
2174 package_path: FileSystemPath,
2175 options: Vc<ResolveOptions>,
2176) -> Result<Vc<ResolveResult>> {
2177 let options_value = options.await?;
2178
2179 let mut affecting_sources = vec![];
2180 if let Some(package_json_path) = exists(
2181 &package_path.join("package.json")?,
2182 if options_value.collect_affecting_sources {
2183 Some(&mut affecting_sources)
2184 } else {
2185 None
2186 },
2187 )
2188 .await?
2189 {
2190 for resolve_into_package in options_value.into_package.iter() {
2191 match resolve_into_package {
2192 ResolveIntoPackage::MainField { field: name } => {
2193 if let Some(package_json) =
2194 &*read_package_json(Vc::upcast(FileSource::new(package_json_path.clone())))
2195 .await?
2196 && let Some(field_value) = package_json[name.as_str()].as_str()
2197 {
2198 let normalized_request = RcStr::from(normalize_request(field_value));
2199 if normalized_request.is_empty()
2200 || &*normalized_request == "."
2201 || &*normalized_request == "./"
2202 {
2203 continue;
2204 }
2205 let request = Request::parse_string(normalized_request);
2206
2207 let options = if options_value.fully_specified {
2209 options.with_fully_specified(false).resolve().await?
2210 } else {
2211 options
2212 };
2213 let result =
2214 &*resolve_internal_inline(package_path.clone(), request, options)
2215 .await?
2216 .await?;
2217 if !result.is_unresolvable_ref() {
2220 let mut result: ResolveResultBuilder =
2221 result.with_request_ref(rcstr!(".")).into();
2222 if options_value.collect_affecting_sources {
2223 result.affecting_sources.push(ResolvedVc::upcast(
2224 FileSource::new(package_json_path).to_resolved().await?,
2225 ));
2226 result.affecting_sources.extend(affecting_sources);
2227 }
2228 return Ok(ResolveResult::from(result).cell());
2229 }
2230 };
2231 }
2232 ResolveIntoPackage::ExportsField { .. } => {}
2233 }
2234 }
2235 }
2236
2237 if options_value.fully_specified {
2238 return Ok(*ResolveResult::unresolvable_with_affecting_sources(
2239 affecting_sources,
2240 ));
2241 }
2242
2243 let pattern = match &options_value.default_files[..] {
2245 [] => {
2246 return Ok(*ResolveResult::unresolvable_with_affecting_sources(
2247 affecting_sources,
2248 ));
2249 }
2250 [file] => Pattern::Constant(format!("./{file}").into()),
2251 files => Pattern::Alternatives(
2252 files
2253 .iter()
2254 .map(|file| Pattern::Constant(format!("./{file}").into()))
2255 .collect(),
2256 ),
2257 };
2258
2259 let request = Request::parse(pattern);
2260 let result = resolve_internal_inline(package_path.clone(), request, options)
2261 .await?
2262 .with_request(rcstr!("."));
2263
2264 Ok(if !affecting_sources.is_empty() {
2265 result.with_affecting_sources(ResolvedVc::deref_vec(affecting_sources))
2266 } else {
2267 result
2268 })
2269}
2270
2271#[tracing::instrument(level = Level::TRACE, skip_all)]
2272async fn resolve_relative_request(
2273 lookup_path: FileSystemPath,
2274 request: Vc<Request>,
2275 options: Vc<ResolveOptions>,
2276 options_value: &ResolveOptions,
2277 path_pattern: &Pattern,
2278 query: RcStr,
2279 force_in_lookup_dir: bool,
2280 fragment: RcStr,
2281) -> Result<Vc<ResolveResult>> {
2282 debug_assert!(query.is_empty() || query.starts_with("?"));
2283 debug_assert!(fragment.is_empty() || fragment.starts_with("#"));
2284 let lookup_path_ref = lookup_path.clone();
2286 if let Some(result) = apply_in_package(
2287 lookup_path.clone(),
2288 options,
2289 options_value,
2290 |package_path| {
2291 let request = path_pattern.as_constant_string()?;
2292 let prefix_path = package_path.get_path_to(&lookup_path_ref)?;
2293 let request = normalize_request(&format!("./{prefix_path}/{request}"));
2294 Some(request.into())
2295 },
2296 query.clone(),
2297 fragment.clone(),
2298 )
2299 .await?
2300 {
2301 return Ok(result);
2302 }
2303
2304 let mut new_path = path_pattern.clone();
2305
2306 #[derive(Eq, PartialEq, Clone, Hash, Debug)]
2309 enum RequestKeyTransform {
2310 None,
2312 AddedFragment,
2314 AddedExtension {
2317 ext: RcStr,
2319 next: Vec<RequestKeyTransform>,
2322 },
2323 ReplacedExtension {
2324 ext: RcStr,
2327 next: Vec<RequestKeyTransform>,
2330 },
2331 }
2332
2333 impl RequestKeyTransform {
2334 fn undo(
2337 &self,
2338 matched_pattern: &RcStr,
2339 fragment: &RcStr,
2340 pattern: &Pattern,
2341 ) -> impl Iterator<Item = (RcStr, RcStr)> {
2342 let mut result = SmallVec::new();
2343 self.apply_internal(matched_pattern, fragment, pattern, &mut result);
2344 result.into_iter()
2345 }
2346
2347 fn apply_internal(
2348 &self,
2349 matched_pattern: &RcStr,
2350 fragment: &RcStr,
2351 pattern: &Pattern,
2352 result: &mut SmallVec<[(RcStr, RcStr); 2]>,
2353 ) {
2354 match self {
2355 RequestKeyTransform::None => {
2356 if pattern.is_match(matched_pattern.as_str()) {
2357 result.push((matched_pattern.clone(), fragment.clone()));
2358 }
2359 }
2360 RequestKeyTransform::AddedFragment => {
2361 debug_assert!(
2362 !fragment.is_empty(),
2363 "can only have an AddedFragment modification if there was a fragment"
2364 );
2365 if let Some(stripped_pattern) = matched_pattern.strip_suffix(fragment.as_str())
2366 && pattern.is_match(stripped_pattern)
2367 {
2368 result.push((stripped_pattern.into(), RcStr::default()));
2369 }
2370 }
2371 RequestKeyTransform::AddedExtension { ext, next } => {
2372 if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
2373 let stripped_pattern: RcStr = stripped_pattern.into();
2374 Self::apply_all(next, &stripped_pattern, fragment, pattern, result);
2375 }
2376 }
2377 RequestKeyTransform::ReplacedExtension { ext, next } => {
2378 if let Some(stripped_pattern) = matched_pattern.strip_suffix(ext.as_str()) {
2379 let replaced_pattern: RcStr = format!(
2380 "{stripped_pattern}{old_ext}",
2381 old_ext = TS_EXTENSION_REPLACEMENTS.reverse.get(ext).unwrap()
2382 )
2383 .into();
2384 Self::apply_all(next, &replaced_pattern, fragment, pattern, result);
2385 }
2386 }
2387 }
2388 }
2389
2390 fn apply_all(
2391 list: &[RequestKeyTransform],
2392 matched_pattern: &RcStr,
2393 fragment: &RcStr,
2394 pattern: &Pattern,
2395 result: &mut SmallVec<[(RcStr, RcStr); 2]>,
2396 ) {
2397 list.iter()
2398 .for_each(|pm| pm.apply_internal(matched_pattern, fragment, pattern, result));
2399 }
2400 }
2401
2402 let mut modifications = Vec::new();
2403 modifications.push(RequestKeyTransform::None);
2404
2405 if !fragment.is_empty() {
2409 modifications.push(RequestKeyTransform::AddedFragment);
2410 new_path.push(Pattern::Alternatives(vec![
2411 Pattern::Constant(RcStr::default()),
2412 Pattern::Constant(fragment.clone()),
2413 ]));
2414 }
2415
2416 if !options_value.fully_specified {
2417 modifications =
2419 modifications
2420 .iter()
2421 .cloned()
2422 .chain(options_value.extensions.iter().map(|ext| {
2423 RequestKeyTransform::AddedExtension {
2424 ext: ext.clone(),
2425 next: modifications.clone(),
2426 }
2427 }))
2428 .collect();
2429 new_path.push(Pattern::Alternatives(
2434 once(Pattern::Constant(RcStr::default()))
2435 .chain(
2436 options_value
2437 .extensions
2438 .iter()
2439 .map(|ext| Pattern::Constant(ext.clone())),
2440 )
2441 .collect(),
2442 ));
2443 new_path.normalize();
2444 };
2445
2446 struct ExtensionReplacements {
2447 forward: FxHashMap<RcStr, SmallVec<[RcStr; 3]>>,
2448 reverse: FxHashMap<RcStr, RcStr>,
2449 }
2450 static TS_EXTENSION_REPLACEMENTS: Lazy<ExtensionReplacements> = Lazy::new(|| {
2451 let mut forward = FxHashMap::default();
2452 forward.insert(
2453 rcstr!(".js"),
2454 SmallVec::from_vec(vec![rcstr!(".ts"), rcstr!(".tsx"), rcstr!(".js")]),
2455 );
2456
2457 forward.insert(
2458 rcstr!(".mjs"),
2459 SmallVec::from_vec(vec![rcstr!(".mts"), rcstr!(".mjs")]),
2460 );
2461
2462 forward.insert(
2463 rcstr!(".cjs"),
2464 SmallVec::from_vec(vec![rcstr!(".cts"), rcstr!(".cjs")]),
2465 );
2466 let reverse = forward
2467 .iter()
2468 .flat_map(|(k, v)| v.iter().map(|v: &RcStr| (v.clone(), k.clone())))
2469 .collect::<FxHashMap<_, _>>();
2470 ExtensionReplacements { forward, reverse }
2471 });
2472
2473 if options_value.enable_typescript_with_output_extension {
2474 let mut replaced_extensions = SmallVec::<[RcStr; 4]>::new();
2476 let replaced = new_path.replace_final_constants(&mut |c: &RcStr| -> Option<Pattern> {
2477 let (base, ext) = c.split_at(c.rfind('.')?);
2478
2479 let (ext, replacements) = TS_EXTENSION_REPLACEMENTS.forward.get_key_value(ext)?;
2480 for replacement in replacements {
2481 if replacement != ext && !replaced_extensions.contains(replacement) {
2482 replaced_extensions.push(replacement.clone());
2483 debug_assert!(replaced_extensions.len() <= replaced_extensions.inline_size());
2484 }
2485 }
2486
2487 let replacements = replacements
2488 .iter()
2489 .cloned()
2490 .map(Pattern::Constant)
2491 .collect();
2492
2493 if base.is_empty() {
2494 Some(Pattern::Alternatives(replacements))
2495 } else {
2496 Some(Pattern::Concatenation(vec![
2497 Pattern::Constant(base.into()),
2498 Pattern::Alternatives(replacements),
2499 ]))
2500 }
2501 });
2502 if replaced {
2503 modifications = modifications
2505 .iter()
2506 .cloned()
2507 .chain(replaced_extensions.iter().map(|ext| {
2508 RequestKeyTransform::ReplacedExtension {
2509 ext: ext.clone(),
2510 next: modifications.clone(),
2511 }
2512 }))
2513 .collect();
2514 new_path.normalize();
2515 }
2516 }
2517
2518 let matches = read_matches(
2519 lookup_path.clone(),
2520 rcstr!(""),
2521 force_in_lookup_dir,
2522 Pattern::new(new_path.clone()).resolve().await?,
2523 )
2524 .await?;
2525
2526 let mut keys = FxHashSet::default();
2530 let mut results = matches
2531 .iter()
2532 .flat_map(|m| {
2533 if let PatternMatch::File(matched_pattern, path) = m {
2534 Either::Left(
2535 modifications
2536 .iter()
2537 .flat_map(|m| m.undo(matched_pattern, &fragment, path_pattern))
2538 .map(move |result| (result, path)),
2539 )
2540 } else {
2541 Either::Right(empty())
2542 }
2543 })
2544 .filter(move |((matched_pattern, _), _)| keys.insert(matched_pattern.clone()))
2546 .map(|((matched_pattern, fragment), path)| {
2547 resolved(
2548 RequestKey::new(matched_pattern),
2549 path.clone(),
2550 lookup_path.clone(),
2551 request,
2552 options_value,
2553 options,
2554 query.clone(),
2555 fragment,
2556 )
2557 })
2558 .try_join()
2559 .await?;
2560
2561 for m in matches.iter() {
2563 if let PatternMatch::Directory(matched_pattern, path) = m {
2564 results.push(
2565 resolve_into_folder(path.clone(), options).with_request(matched_pattern.clone()),
2566 );
2567 }
2568 }
2569
2570 Ok(merge_results(results))
2571}
2572
2573#[tracing::instrument(level = Level::TRACE, skip_all)]
2574async fn apply_in_package(
2575 lookup_path: FileSystemPath,
2576 options: Vc<ResolveOptions>,
2577 options_value: &ResolveOptions,
2578 get_request: impl Fn(&FileSystemPath) -> Option<RcStr>,
2579 query: RcStr,
2580 fragment: RcStr,
2581) -> Result<Option<Vc<ResolveResult>>> {
2582 for in_package in options_value.in_package.iter() {
2584 let ResolveInPackage::AliasField(field) = in_package else {
2588 continue;
2589 };
2590
2591 let FindContextFileResult::Found(package_json_path, refs) = &*find_context_file(
2592 lookup_path.clone(),
2593 package_json().resolve().await?,
2594 options_value.collect_affecting_sources,
2595 )
2596 .await?
2597 else {
2598 continue;
2599 };
2600
2601 let read =
2602 read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
2603 let Some(package_json) = &*read else {
2604 continue;
2605 };
2606
2607 let Some(field_value) = package_json[field.as_str()].as_object() else {
2608 continue;
2609 };
2610
2611 let package_path = package_json_path.parent();
2612
2613 let Some(request) = get_request(&package_path) else {
2614 continue;
2615 };
2616
2617 let value = if let Some(value) = field_value.get(&*request) {
2618 value
2619 } else if let Some(request) = request.strip_prefix("./") {
2620 let Some(value) = field_value.get(request) else {
2621 continue;
2622 };
2623 value
2624 } else {
2625 continue;
2626 };
2627
2628 let refs = refs.clone();
2629 let request_key = RequestKey::new(request.clone());
2630
2631 if value.as_bool() == Some(false) {
2632 return Ok(Some(*ResolveResult::primary_with_affecting_sources(
2633 request_key,
2634 ResolveResultItem::Ignore,
2635 refs,
2636 )));
2637 }
2638
2639 if let Some(value) = value.as_str() {
2640 if value == &*request {
2641 return Ok(None);
2643 }
2644 let mut result = resolve_internal(
2645 package_path,
2646 Request::parse(Pattern::Constant(value.into()))
2647 .with_query(query.clone())
2648 .with_fragment(fragment.clone()),
2649 options,
2650 )
2651 .with_replaced_request_key(value.into(), request_key);
2652 if options_value.collect_affecting_sources && !refs.is_empty() {
2653 result = result.with_affecting_sources(refs.into_iter().map(|src| *src).collect());
2654 }
2655 return Ok(Some(result));
2656 }
2657
2658 ResolvingIssue {
2659 severity: error_severity(options).await?,
2660 file_path: package_json_path.clone(),
2661 request_type: format!("alias field ({field})"),
2662 request: Request::parse(Pattern::Constant(request))
2663 .to_resolved()
2664 .await?,
2665 resolve_options: options.to_resolved().await?,
2666 error_message: Some(format!("invalid alias field value: {value}")),
2667 source: None,
2668 }
2669 .resolved_cell()
2670 .emit();
2671
2672 return Ok(Some(*ResolveResult::unresolvable_with_affecting_sources(
2673 refs,
2674 )));
2675 }
2676 Ok(None)
2677}
2678
2679#[turbo_tasks::value]
2680enum FindSelfReferencePackageResult {
2681 Found {
2682 name: String,
2683 package_path: FileSystemPath,
2684 },
2685 NotFound,
2686}
2687
2688#[turbo_tasks::function]
2689async fn find_self_reference(
2692 lookup_path: FileSystemPath,
2693) -> Result<Vc<FindSelfReferencePackageResult>> {
2694 let package_json_context =
2695 find_context_file(lookup_path, package_json().resolve().await?, false).await?;
2696 if let FindContextFileResult::Found(package_json_path, _refs) = &*package_json_context {
2697 let read =
2698 read_package_json(Vc::upcast(FileSource::new(package_json_path.clone()))).await?;
2699 if let Some(json) = &*read
2700 && json.get("exports").is_some()
2701 && let Some(name) = json["name"].as_str()
2702 {
2703 return Ok(FindSelfReferencePackageResult::Found {
2704 name: name.to_string(),
2705 package_path: package_json_path.parent(),
2706 }
2707 .cell());
2708 }
2709 }
2710 Ok(FindSelfReferencePackageResult::NotFound.cell())
2711}
2712
2713#[tracing::instrument(level = Level::TRACE, skip_all)]
2714async fn resolve_module_request(
2715 lookup_path: FileSystemPath,
2716 request: Vc<Request>,
2717 options: Vc<ResolveOptions>,
2718 options_value: &ResolveOptions,
2719 module: &Pattern,
2720 path: &Pattern,
2721 query: RcStr,
2722 fragment: RcStr,
2723) -> Result<Vc<ResolveResult>> {
2724 if let Some(result) = apply_in_package(
2726 lookup_path.clone(),
2727 options,
2728 options_value,
2729 |_| {
2730 let full_pattern = Pattern::concat([module.clone(), path.clone()]);
2731 full_pattern.as_constant_string().cloned()
2732 },
2733 query.clone(),
2734 fragment.clone(),
2735 )
2736 .await?
2737 {
2738 return Ok(result);
2739 }
2740
2741 let mut results = vec![];
2742
2743 if let FindSelfReferencePackageResult::Found { name, package_path } =
2747 &*find_self_reference(lookup_path.clone()).await?
2748 && module.is_match(name)
2749 {
2750 let result = resolve_into_package(
2751 path.clone(),
2752 package_path.clone(),
2753 query.clone(),
2754 fragment.clone(),
2755 options,
2756 );
2757 if !(*result.is_unresolvable().await?) {
2758 return Ok(result);
2759 }
2760 }
2761
2762 let result = find_package(
2763 lookup_path.clone(),
2764 module.clone(),
2765 resolve_modules_options(options).resolve().await?,
2766 options_value.collect_affecting_sources,
2767 )
2768 .await?;
2769
2770 if result.packages.is_empty() {
2771 return Ok(*ResolveResult::unresolvable_with_affecting_sources(
2772 result.affecting_sources.clone(),
2773 ));
2774 }
2775
2776 for item in &result.packages {
2782 match item {
2783 FindPackageItem::PackageDirectory { name, dir } => {
2784 results.push(
2785 resolve_into_package(
2786 path.clone(),
2787 dir.clone(),
2788 query.clone(),
2789 fragment.clone(),
2790 options,
2791 )
2792 .with_replaced_request_key(rcstr!("."), RequestKey::new(name.clone())),
2793 );
2794 }
2795 FindPackageItem::PackageFile { name, file } => {
2796 if path.is_match("") {
2797 let resolved = resolved(
2798 RequestKey::new(rcstr!(".")),
2799 file.clone(),
2800 lookup_path.clone(),
2801 request,
2802 options_value,
2803 options,
2804 query.clone(),
2805 fragment.clone(),
2806 )
2807 .await?
2808 .with_replaced_request_key(rcstr!("."), RequestKey::new(name.clone()));
2809 results.push(resolved)
2810 }
2811 }
2812 }
2813 }
2814
2815 let module_result =
2816 merge_results_with_affecting_sources(results, result.affecting_sources.clone());
2817
2818 if options_value.prefer_relative {
2819 let mut module_prefixed = module.clone();
2820 module_prefixed.push_front(rcstr!("./").into());
2821 let pattern = Pattern::concat([module_prefixed.clone(), rcstr!("/").into(), path.clone()]);
2822 let relative = Request::relative(pattern, query, fragment, true)
2823 .to_resolved()
2824 .await?;
2825 let relative_result = Box::pin(resolve_internal_inline(
2826 lookup_path.clone(),
2827 *relative,
2828 options,
2829 ))
2830 .await?;
2831 let relative_result = relative_result.with_stripped_request_key_prefix(rcstr!("./"));
2832
2833 Ok(merge_results(vec![relative_result, module_result]))
2834 } else {
2835 Ok(module_result)
2836 }
2837}
2838
2839#[turbo_tasks::function]
2840async fn resolve_into_package(
2841 path: Pattern,
2842 package_path: FileSystemPath,
2843 query: RcStr,
2844 fragment: RcStr,
2845 options: ResolvedVc<ResolveOptions>,
2846) -> Result<Vc<ResolveResult>> {
2847 let options_value = options.await?;
2848 let mut results = Vec::new();
2849
2850 let is_root_match = path.is_match("") || path.is_match("/");
2851 let could_match_others = path.could_match_others("");
2852
2853 let mut export_path_request = path.clone();
2854 export_path_request.push_front(rcstr!(".").into());
2855 for resolve_into_package in options_value.into_package.iter() {
2856 match resolve_into_package {
2857 ResolveIntoPackage::MainField { .. } => {}
2859 ResolveIntoPackage::ExportsField {
2860 conditions,
2861 unspecified_conditions,
2862 } => {
2863 let package_json_path = package_path.join("package.json")?;
2864 let ExportsFieldResult::Some(exports_field) =
2865 &*exports_field(Vc::upcast(FileSource::new(package_json_path.clone()))).await?
2866 else {
2867 continue;
2868 };
2869
2870 results.push(
2871 handle_exports_imports_field(
2872 package_path.clone(),
2873 package_json_path,
2874 *options,
2875 exports_field,
2876 export_path_request.clone(),
2877 conditions,
2878 unspecified_conditions,
2879 query,
2880 )
2881 .await?,
2882 );
2883
2884 return Ok(merge_results(results));
2887 }
2888 }
2889 }
2890
2891 if is_root_match {
2893 results.push(resolve_into_folder(
2894 package_path.clone(),
2895 options.with_fully_specified(false),
2896 ));
2897 }
2898
2899 if could_match_others {
2900 let mut new_pat = path.clone();
2901 new_pat.push_front(rcstr!(".").into());
2902
2903 let relative = Request::relative(new_pat, query, fragment, true)
2904 .to_resolved()
2905 .await?;
2906 results.push(resolve_internal_inline(package_path.clone(), *relative, *options).await?);
2907 }
2908
2909 Ok(merge_results(results))
2910}
2911
2912#[tracing::instrument(level = Level::TRACE, skip_all)]
2913async fn resolve_import_map_result(
2914 result: &ImportMapResult,
2915 lookup_path: FileSystemPath,
2916 original_lookup_path: FileSystemPath,
2917 original_request: Vc<Request>,
2918 options: Vc<ResolveOptions>,
2919 query: RcStr,
2920) -> Result<Option<Vc<ResolveResult>>> {
2921 Ok(match result {
2922 ImportMapResult::Result(result) => Some(**result),
2923 ImportMapResult::Alias(request, alias_lookup_path) => {
2924 let request = **request;
2925 let lookup_path = match alias_lookup_path {
2926 Some(path) => path.clone(),
2927 None => lookup_path,
2928 };
2929 if request == original_request && lookup_path == original_lookup_path {
2931 None
2932 } else {
2933 let result = resolve_internal(lookup_path, request, options);
2934 Some(result.with_replaced_request_key_pattern(
2935 request.request_pattern(),
2936 original_request.request_pattern(),
2937 ))
2938 }
2939 }
2940 ImportMapResult::External {
2941 name,
2942 ty,
2943 traced,
2944 target,
2945 } => Some(*ResolveResult::primary(ResolveResultItem::External {
2946 name: name.clone(),
2947 ty: *ty,
2948 traced: *traced,
2949 target: target.clone(),
2950 })),
2951 ImportMapResult::AliasExternal {
2952 name,
2953 ty,
2954 traced,
2955 lookup_dir: alias_lookup_path,
2956 } => {
2957 let request = Request::parse_string(name.clone());
2958
2959 if request.resolve().await? == original_request
2961 && *alias_lookup_path == original_lookup_path
2962 {
2963 None
2964 } else {
2965 let is_external_resolvable = !resolve_internal(
2966 alias_lookup_path.clone(),
2967 request,
2968 match ty {
2969 ExternalType::CommonJs => {
2971 node_cjs_resolve_options(alias_lookup_path.root().owned().await?)
2972 }
2973 ExternalType::EcmaScriptModule => {
2974 node_esm_resolve_options(alias_lookup_path.root().owned().await?)
2975 }
2976 ExternalType::Script | ExternalType::Url | ExternalType::Global => options,
2977 },
2978 )
2979 .await?
2980 .is_unresolvable_ref();
2981 if is_external_resolvable {
2982 Some(*ResolveResult::primary(ResolveResultItem::External {
2983 name: name.clone(),
2984 ty: *ty,
2985 traced: *traced,
2986 target: None,
2987 }))
2988 } else {
2989 None
2990 }
2991 }
2992 }
2993 ImportMapResult::Alternatives(list) => {
2994 let results = list
2995 .iter()
2996 .map(|result| {
2997 resolve_import_map_result(
2998 result,
2999 lookup_path.clone(),
3000 original_lookup_path.clone(),
3001 original_request,
3002 options,
3003 query.clone(),
3004 )
3005 })
3006 .try_join()
3007 .await?;
3008
3009 Some(merge_results(results.into_iter().flatten().collect()))
3010 }
3011 ImportMapResult::NoEntry => None,
3012 })
3013}
3014
3015#[tracing::instrument(level = Level::TRACE, skip_all)]
3016async fn resolved(
3017 request_key: RequestKey,
3018 fs_path: FileSystemPath,
3019 original_context: FileSystemPath,
3020 original_request: Vc<Request>,
3021 options_value: &ResolveOptions,
3022 options: Vc<ResolveOptions>,
3023 query: RcStr,
3024 fragment: RcStr,
3025) -> Result<Vc<ResolveResult>> {
3026 let result = &*fs_path.realpath_with_links().await?;
3027 let path = match &result.path_result {
3028 Ok(path) => path,
3029 Err(e) => bail!(e.as_error_message(&fs_path, result)),
3030 };
3031
3032 let path_ref = path.clone();
3033 if let Some(result) = apply_in_package(
3035 path.parent(),
3036 options,
3037 options_value,
3038 |package_path| package_path.get_relative_path_to(&path_ref),
3039 query.clone(),
3040 fragment.clone(),
3041 )
3042 .await?
3043 {
3044 return Ok(result);
3045 }
3046
3047 if let Some(resolved_map) = options_value.resolved_map {
3048 let result = resolved_map
3049 .lookup(path.clone(), original_context.clone(), original_request)
3050 .await?;
3051
3052 let resolved_result = resolve_import_map_result(
3053 &result,
3054 path.parent(),
3055 original_context.clone(),
3056 original_request,
3057 options,
3058 query.clone(),
3059 )
3060 .await?;
3061
3062 if let Some(result) = resolved_result {
3063 return Ok(result);
3064 }
3065 }
3066 let source = ResolvedVc::upcast(
3067 FileSource::new_with_query_and_fragment(path.clone(), query, fragment)
3068 .to_resolved()
3069 .await?,
3070 );
3071 Ok(if options_value.collect_affecting_sources {
3072 ResolveResult::source_with_affecting_sources(
3073 request_key,
3074 source,
3075 result
3076 .symlinks
3077 .iter()
3078 .map(|symlink| async move {
3079 anyhow::Ok(ResolvedVc::upcast(
3080 FileSource::new(symlink.clone()).to_resolved().await?,
3081 ))
3082 })
3083 .try_join()
3084 .await?,
3085 )
3086 } else {
3087 ResolveResult::source_with_key(request_key, source)
3088 }
3089 .cell())
3090}
3091
3092async fn handle_exports_imports_field(
3093 package_path: FileSystemPath,
3094 package_json_path: FileSystemPath,
3095 options: Vc<ResolveOptions>,
3096 exports_imports_field: &AliasMap<SubpathValue>,
3097 mut path: Pattern,
3098 conditions: &BTreeMap<RcStr, ConditionValue>,
3099 unspecified_conditions: &ConditionValue,
3100 query: RcStr,
3101) -> Result<Vc<ResolveResult>> {
3102 let mut results = Vec::new();
3103 let mut conditions_state = FxHashMap::default();
3104
3105 if !query.is_empty() {
3106 path.push(query.into());
3107 }
3108 let req = path;
3109
3110 let values = exports_imports_field.lookup(&req);
3111 for value in values {
3112 let value = value?;
3113 if value.output.add_results(
3114 value.prefix,
3115 value.key,
3116 conditions,
3117 unspecified_conditions,
3118 &mut conditions_state,
3119 &mut results,
3120 ) {
3121 break;
3123 }
3124 }
3125
3126 let mut resolved_results = Vec::new();
3127 for ReplacedSubpathValueResult {
3128 result_path,
3129 conditions,
3130 map_prefix,
3131 map_key,
3132 } in results
3133 {
3134 if let Some(result_path) = result_path.with_normalized_path() {
3135 let request = Request::parse(Pattern::Concatenation(vec![
3136 Pattern::Constant(rcstr!("./")),
3137 result_path.clone(),
3138 ]))
3139 .resolve()
3140 .await?;
3141
3142 let resolve_result = Box::pin(resolve_internal_inline(
3143 package_path.clone(),
3144 request,
3145 options,
3146 ))
3147 .await?;
3148
3149 let resolve_result = if let Some(req) = req.as_constant_string() {
3150 resolve_result.with_request(req.clone())
3151 } else {
3152 match map_key {
3153 AliasKey::Exact => resolve_result.with_request(map_prefix.clone().into()),
3154 AliasKey::Wildcard { .. } => {
3155 let mut old_request_key = result_path;
3165 old_request_key.push_front(rcstr!("./").into());
3167 let new_request_key = req.clone();
3168
3169 resolve_result.with_replaced_request_key_pattern(
3170 Pattern::new(old_request_key),
3171 Pattern::new(new_request_key),
3172 )
3173 }
3174 }
3175 };
3176
3177 let resolve_result = if !conditions.is_empty() {
3178 let resolve_result = resolve_result.await?.with_conditions(&conditions);
3179 resolve_result.cell()
3180 } else {
3181 resolve_result
3182 };
3183 resolved_results.push(resolve_result);
3184 }
3185 }
3186
3187 Ok(merge_results_with_affecting_sources(
3189 resolved_results,
3190 vec![ResolvedVc::upcast(
3191 FileSource::new(package_json_path).to_resolved().await?,
3192 )],
3193 ))
3194}
3195
3196async fn resolve_package_internal_with_imports_field(
3201 file_path: FileSystemPath,
3202 request: Vc<Request>,
3203 resolve_options: Vc<ResolveOptions>,
3204 pattern: &Pattern,
3205 conditions: &BTreeMap<RcStr, ConditionValue>,
3206 unspecified_conditions: &ConditionValue,
3207) -> Result<Vc<ResolveResult>> {
3208 let Pattern::Constant(specifier) = pattern else {
3209 bail!("PackageInternal requests can only be Constant strings");
3210 };
3211 if specifier == "#" || specifier.starts_with("#/") || specifier.ends_with('/') {
3213 ResolvingIssue {
3214 severity: error_severity(resolve_options).await?,
3215 file_path: file_path.clone(),
3216 request_type: format!("package imports request: `{specifier}`"),
3217 request: request.to_resolved().await?,
3218 resolve_options: resolve_options.to_resolved().await?,
3219 error_message: None,
3220 source: None,
3221 }
3222 .resolved_cell()
3223 .emit();
3224 return Ok(*ResolveResult::unresolvable());
3225 }
3226
3227 let imports_result = imports_field(file_path).await?;
3228 let (imports, package_json_path) = match &*imports_result {
3229 ImportsFieldResult::Some(i, p) => (i, p.clone()),
3230 ImportsFieldResult::None => return Ok(*ResolveResult::unresolvable()),
3231 };
3232
3233 handle_exports_imports_field(
3234 package_json_path.parent(),
3235 package_json_path.clone(),
3236 resolve_options,
3237 imports,
3238 Pattern::Constant(specifier.clone()),
3239 conditions,
3240 unspecified_conditions,
3241 RcStr::default(),
3242 )
3243 .await
3244}
3245
3246pub async fn handle_resolve_error(
3247 result: Vc<ModuleResolveResult>,
3248 reference_type: ReferenceType,
3249 origin: Vc<Box<dyn ResolveOrigin>>,
3250 request: Vc<Request>,
3251 resolve_options: Vc<ResolveOptions>,
3252 is_optional: bool,
3253 source: Option<IssueSource>,
3254) -> Result<Vc<ModuleResolveResult>> {
3255 Ok(match result.await {
3256 Ok(result_ref) => {
3257 if result_ref.is_unresolvable_ref() {
3258 emit_unresolvable_issue(
3259 is_optional,
3260 origin,
3261 reference_type,
3262 request,
3263 resolve_options,
3264 source,
3265 )
3266 .await?;
3267 }
3268
3269 result
3270 }
3271 Err(err) => {
3272 emit_resolve_error_issue(
3273 is_optional,
3274 origin,
3275 reference_type,
3276 request,
3277 resolve_options,
3278 err,
3279 source,
3280 )
3281 .await?;
3282 *ModuleResolveResult::unresolvable()
3283 }
3284 })
3285}
3286
3287pub async fn handle_resolve_source_error(
3288 result: Vc<ResolveResult>,
3289 reference_type: ReferenceType,
3290 origin: Vc<Box<dyn ResolveOrigin>>,
3291 request: Vc<Request>,
3292 resolve_options: Vc<ResolveOptions>,
3293 is_optional: bool,
3294 source: Option<IssueSource>,
3295) -> Result<Vc<ResolveResult>> {
3296 async fn is_unresolvable(result: Vc<ResolveResult>) -> Result<bool> {
3297 Ok(*result.resolve().await?.is_unresolvable().await?)
3298 }
3299 Ok(match is_unresolvable(result).await {
3300 Ok(unresolvable) => {
3301 if unresolvable {
3302 emit_unresolvable_issue(
3303 is_optional,
3304 origin,
3305 reference_type,
3306 request,
3307 resolve_options,
3308 source,
3309 )
3310 .await?;
3311 }
3312
3313 result
3314 }
3315 Err(err) => {
3316 emit_resolve_error_issue(
3317 is_optional,
3318 origin,
3319 reference_type,
3320 request,
3321 resolve_options,
3322 err,
3323 source,
3324 )
3325 .await?;
3326 *ResolveResult::unresolvable()
3327 }
3328 })
3329}
3330
3331async fn emit_resolve_error_issue(
3332 is_optional: bool,
3333 origin: Vc<Box<dyn ResolveOrigin>>,
3334 reference_type: ReferenceType,
3335 request: Vc<Request>,
3336 resolve_options: Vc<ResolveOptions>,
3337 err: anyhow::Error,
3338 source: Option<IssueSource>,
3339) -> Result<()> {
3340 let severity = if is_optional || resolve_options.await?.loose_errors {
3341 IssueSeverity::Warning
3342 } else {
3343 IssueSeverity::Error
3344 };
3345 ResolvingIssue {
3346 severity,
3347 file_path: origin.origin_path().owned().await?,
3348 request_type: format!("{reference_type} request"),
3349 request: request.to_resolved().await?,
3350 resolve_options: resolve_options.to_resolved().await?,
3351 error_message: Some(format!("{}", PrettyPrintError(&err))),
3352 source,
3353 }
3354 .resolved_cell()
3355 .emit();
3356 Ok(())
3357}
3358
3359async fn emit_unresolvable_issue(
3360 is_optional: bool,
3361 origin: Vc<Box<dyn ResolveOrigin>>,
3362 reference_type: ReferenceType,
3363 request: Vc<Request>,
3364 resolve_options: Vc<ResolveOptions>,
3365 source: Option<IssueSource>,
3366) -> Result<()> {
3367 let severity = if is_optional || resolve_options.await?.loose_errors {
3368 IssueSeverity::Warning
3369 } else {
3370 IssueSeverity::Error
3371 };
3372 ResolvingIssue {
3373 severity,
3374 file_path: origin.origin_path().owned().await?,
3375 request_type: format!("{reference_type} request"),
3376 request: request.to_resolved().await?,
3377 resolve_options: resolve_options.to_resolved().await?,
3378 error_message: None,
3379 source,
3380 }
3381 .resolved_cell()
3382 .emit();
3383 Ok(())
3384}
3385
3386async fn error_severity(resolve_options: Vc<ResolveOptions>) -> Result<IssueSeverity> {
3387 Ok(if resolve_options.await?.loose_errors {
3388 IssueSeverity::Warning
3389 } else {
3390 IssueSeverity::Error
3391 })
3392}
3393
3394#[derive(
3398 Serialize,
3399 Deserialize,
3400 Debug,
3401 Clone,
3402 PartialEq,
3403 Eq,
3404 Hash,
3405 TraceRawVcs,
3406 TaskInput,
3407 NonLocalValue,
3408 Encode,
3409 Decode,
3410)]
3411pub enum ModulePart {
3412 Evaluation,
3415 Export(RcStr),
3417 RenamedExport {
3419 original_export: RcStr,
3420 export: RcStr,
3421 },
3422 RenamedNamespace { export: RcStr },
3424 Internal(u32),
3426 Locals,
3428 Exports,
3430 Facade,
3433}
3434
3435impl ModulePart {
3436 pub fn evaluation() -> Self {
3437 ModulePart::Evaluation
3438 }
3439
3440 pub fn export(export: RcStr) -> Self {
3441 ModulePart::Export(export)
3442 }
3443
3444 pub fn renamed_export(original_export: RcStr, export: RcStr) -> Self {
3445 ModulePart::RenamedExport {
3446 original_export,
3447 export,
3448 }
3449 }
3450
3451 pub fn renamed_namespace(export: RcStr) -> Self {
3452 ModulePart::RenamedNamespace { export }
3453 }
3454
3455 pub fn internal(id: u32) -> Self {
3456 ModulePart::Internal(id)
3457 }
3458
3459 pub fn locals() -> Self {
3460 ModulePart::Locals
3461 }
3462
3463 pub fn exports() -> Self {
3464 ModulePart::Exports
3465 }
3466
3467 pub fn facade() -> Self {
3468 ModulePart::Facade
3469 }
3470}
3471
3472impl Display for ModulePart {
3473 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3474 match self {
3475 ModulePart::Evaluation => f.write_str("module evaluation"),
3476 ModulePart::Export(export) => write!(f, "export {export}"),
3477 ModulePart::RenamedExport {
3478 original_export,
3479 export,
3480 } => write!(f, "export {original_export} as {export}"),
3481 ModulePart::RenamedNamespace { export } => {
3482 write!(f, "export * as {export}")
3483 }
3484 ModulePart::Internal(id) => write!(f, "internal part {id}"),
3485 ModulePart::Locals => f.write_str("locals"),
3486 ModulePart::Exports => f.write_str("exports"),
3487 ModulePart::Facade => f.write_str("facade"),
3488 }
3489 }
3490}
3491#[cfg(test)]
3492mod tests {
3493 use std::{
3494 fs::{File, create_dir_all},
3495 io::Write,
3496 };
3497
3498 use turbo_rcstr::{RcStr, rcstr};
3499 use turbo_tasks::{TryJoinIterExt, Vc};
3500 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
3501 use turbo_tasks_fs::{DiskFileSystem, FileSystem, FileSystemPath};
3502
3503 use crate::{
3504 resolve::{
3505 ResolveResult, ResolveResultItem, node::node_esm_resolve_options, parse::Request,
3506 pattern::Pattern,
3507 },
3508 source::Source,
3509 };
3510
3511 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3512 async fn test_explicit_js_resolves_to_ts() {
3513 resolve_relative_request_test(TestParams {
3514 files: vec!["foo.js", "foo.ts"],
3515 pattern: rcstr!("./foo.js").into(),
3516 enable_typescript_with_output_extension: true,
3517 fully_specified: false,
3518 expected: vec![("./foo.js", "foo.ts")],
3519 })
3520 .await;
3521 }
3522
3523 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3524 async fn test_implicit_request_ts_priority() {
3525 resolve_relative_request_test(TestParams {
3526 files: vec!["foo.js", "foo.ts"],
3527 pattern: rcstr!("./foo").into(),
3528 enable_typescript_with_output_extension: true,
3529 fully_specified: false,
3530 expected: vec![("./foo", "foo.ts")],
3531 })
3532 .await;
3533 }
3534
3535 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3536 async fn test_ts_priority_over_json() {
3537 resolve_relative_request_test(TestParams {
3538 files: vec!["posts.json", "posts.ts"],
3539 pattern: rcstr!("./posts").into(),
3540 enable_typescript_with_output_extension: true,
3541 fully_specified: false,
3542 expected: vec![("./posts", "posts.ts")],
3543 })
3544 .await;
3545 }
3546
3547 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3548 async fn test_only_js_file_no_ts() {
3549 resolve_relative_request_test(TestParams {
3550 files: vec!["bar.js"],
3551 pattern: rcstr!("./bar.js").into(),
3552 enable_typescript_with_output_extension: true,
3553 fully_specified: false,
3554 expected: vec![("./bar.js", "bar.js")],
3555 })
3556 .await;
3557 }
3558
3559 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3560 async fn test_explicit_ts_request() {
3561 resolve_relative_request_test(TestParams {
3562 files: vec!["foo.js", "foo.ts"],
3563 pattern: rcstr!("./foo.ts").into(),
3564 enable_typescript_with_output_extension: true,
3565 fully_specified: false,
3566 expected: vec![("./foo.ts", "foo.ts")],
3567 })
3568 .await;
3569 }
3570
3571 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3573 async fn test_fragment() {
3574 resolve_relative_request_test(TestParams {
3575 files: vec!["client.ts"],
3576 pattern: rcstr!("./client#frag").into(),
3577 enable_typescript_with_output_extension: true,
3578 fully_specified: false,
3579 expected: vec![("./client", "client.ts")],
3580 })
3581 .await;
3582 }
3583
3584 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3585 async fn test_fragment_as_part_of_filename() {
3586 resolve_relative_request_test(TestParams {
3588 files: vec!["client#component.js", "client#component.ts"],
3589 pattern: rcstr!("./client#component.js").into(),
3590 enable_typescript_with_output_extension: true,
3591 fully_specified: false,
3592 expected: vec![("./client", "client#component.ts")],
3595 })
3596 .await;
3597 }
3598
3599 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3600 async fn test_fragment_with_ts_priority() {
3601 resolve_relative_request_test(TestParams {
3603 files: vec!["page#section.js", "page#section.ts"],
3604 pattern: rcstr!("./page#section").into(),
3605 enable_typescript_with_output_extension: true,
3606 fully_specified: false,
3607 expected: vec![("./page", "page#section.ts")],
3608 })
3609 .await;
3610 }
3611
3612 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3613 async fn test_query() {
3614 resolve_relative_request_test(TestParams {
3615 files: vec!["client.ts", "client.js"],
3616 pattern: rcstr!("./client?q=s").into(),
3617 enable_typescript_with_output_extension: true,
3618 fully_specified: false,
3619 expected: vec![("./client", "client.ts")],
3620 })
3621 .await;
3622 }
3623
3624 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3626 async fn test_dynamic_pattern_with_js_extension() {
3627 resolve_relative_request_test(TestParams {
3631 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3632 pattern: Pattern::Concatenation(vec![
3633 Pattern::Constant(rcstr!("./src/")),
3634 Pattern::Dynamic,
3635 Pattern::Constant(rcstr!(".js")),
3636 ]),
3637 enable_typescript_with_output_extension: true,
3638 fully_specified: false,
3639 expected: vec![
3640 ("./src/foo.js", "src/foo.ts"),
3641 ("./src/bar.js", "src/bar.js"),
3642 ],
3643 })
3644 .await;
3645 }
3646
3647 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3648 async fn test_dynamic_pattern_without_extension() {
3649 resolve_relative_request_test(TestParams {
3653 files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3654 pattern: Pattern::Concatenation(vec![
3655 Pattern::Constant(rcstr!("./src/")),
3656 Pattern::Dynamic,
3657 ]),
3658 enable_typescript_with_output_extension: true,
3659 fully_specified: false,
3660 expected: vec![
3661 ("./src/bar.js", "src/bar.js"),
3662 ("./src/bar", "src/bar.js"),
3663 ("./src/foo.js", "src/foo.js"),
3669 ("./src/foo", "src/foo.js"),
3670 ("./src/foo.ts", "src/foo.ts"),
3671 ],
3672 })
3673 .await;
3674 }
3675
3676 struct TestParams<'a> {
3678 files: Vec<&'a str>,
3679 pattern: Pattern,
3680 enable_typescript_with_output_extension: bool,
3681 fully_specified: bool,
3682 expected: Vec<(&'a str, &'a str)>,
3683 }
3684
3685 async fn resolve_relative_request_test(
3687 TestParams {
3688 files,
3689 pattern,
3690 enable_typescript_with_output_extension,
3691 fully_specified,
3692 expected,
3693 }: TestParams<'_>,
3694 ) {
3695 let scratch = tempfile::tempdir().unwrap();
3696 {
3697 let path = scratch.path();
3698
3699 for file_name in &files {
3700 let file_path = path.join(file_name);
3701 if let Some(parent) = file_path.parent() {
3702 create_dir_all(parent).unwrap();
3703 }
3704 File::create_new(&file_path)
3705 .unwrap()
3706 .write_all(format!("export default '{file_name}'").as_bytes())
3707 .unwrap();
3708 }
3709 }
3710
3711 let path: RcStr = scratch.path().to_str().unwrap().into();
3712 let expected_owned: Vec<(String, String)> = expected
3713 .iter()
3714 .map(|(k, v)| (k.to_string(), v.to_string()))
3715 .collect();
3716
3717 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3718 BackendOptions::default(),
3719 noop_backing_storage(),
3720 ));
3721
3722 tt.run_once(async move {
3723 let fs = Vc::upcast::<Box<dyn FileSystem>>(DiskFileSystem::new(rcstr!("temp"), path));
3724 let lookup_path = fs.root().owned().await?;
3725
3726 let result = resolve_relative_helper(
3727 lookup_path,
3728 pattern,
3729 enable_typescript_with_output_extension,
3730 fully_specified,
3731 )
3732 .await?;
3733
3734 let results: Vec<(String, String)> = result
3735 .primary
3736 .iter()
3737 .map(async |(k, v)| {
3738 Ok((
3739 k.to_string(),
3740 if let ResolveResultItem::Source(source) = v {
3741 source.ident().await?.path.path.to_string()
3742 } else {
3743 unreachable!()
3744 },
3745 ))
3746 })
3747 .try_join()
3748 .await?;
3749
3750 assert_eq!(results, expected_owned);
3751
3752 Ok(())
3753 })
3754 .await
3755 .unwrap();
3756 }
3757
3758 #[turbo_tasks::function]
3759 async fn resolve_relative_helper(
3760 lookup_path: FileSystemPath,
3761 pattern: Pattern,
3762 enable_typescript_with_output_extension: bool,
3763 fully_specified: bool,
3764 ) -> anyhow::Result<Vc<ResolveResult>> {
3765 let request = Request::parse(pattern.clone());
3766
3767 let mut options_value = node_esm_resolve_options(lookup_path.clone())
3768 .with_fully_specified(fully_specified)
3769 .with_extensions(vec![rcstr!(".ts"), rcstr!(".js"), rcstr!(".json")])
3770 .owned()
3771 .await?;
3772 options_value.enable_typescript_with_output_extension =
3773 enable_typescript_with_output_extension;
3774 let options = options_value.clone().cell();
3775 match &*request.await? {
3776 Request::Relative {
3777 path,
3778 query,
3779 force_in_lookup_dir,
3780 fragment,
3781 } => {
3782 super::resolve_relative_request(
3783 lookup_path,
3784 request,
3785 options,
3786 &options_value,
3787 path,
3788 query.clone(),
3789 *force_in_lookup_dir,
3790 fragment.clone(),
3791 )
3792 .await
3793 }
3794 r => panic!("request should be relative, got {r:?}"),
3795 }
3796 }
3797}