1use std::{collections::BTreeMap, future::Future, pin::Pin};
2
3use anyhow::{Result, bail};
4use bincode::{Decode, Encode};
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{
7 FxIndexSet, NonLocalValue, ResolvedVc, TryJoinIterExt, ValueToString, Vc,
8 debug::ValueDebugFormat, trace::TraceRawVcs,
9};
10use turbo_tasks_fs::{FileSystemPath, glob::Glob};
11
12use crate::resolve::{
13 AliasPattern, ExternalTraced, ExternalType, ResolveResult, ResolveResultItem,
14 alias_map::{AliasMap, AliasTemplate},
15 parse::Request,
16 pattern::Pattern,
17 plugin::{AfterResolvePlugin, BeforeResolvePlugin},
18};
19
20#[turbo_tasks::value(shared)]
21#[derive(Hash, Debug)]
22pub struct LockedVersions {}
23
24#[turbo_tasks::value(transparent)]
25#[derive(Debug)]
26pub struct ExcludedExtensions(#[bincode(with = "turbo_bincode::indexset")] pub FxIndexSet<RcStr>);
27
28#[derive(
30 TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, ValueDebugFormat, NonLocalValue, Encode, Decode,
31)]
32pub enum ResolveModules {
33 Nested(FileSystemPath, Vec<RcStr>),
36 Path {
38 dir: FileSystemPath,
39 excluded_extensions: ResolvedVc<ExcludedExtensions>,
40 },
41}
42
43#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Copy, Debug, NonLocalValue, Encode, Decode)]
44pub enum ConditionValue {
45 Set,
46 Unset,
47 Unknown,
48}
49
50impl From<bool> for ConditionValue {
51 fn from(v: bool) -> Self {
52 if v {
53 ConditionValue::Set
54 } else {
55 ConditionValue::Unset
56 }
57 }
58}
59
60pub type ResolutionConditions = BTreeMap<RcStr, ConditionValue>;
61
62#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, NonLocalValue, Encode, Decode)]
64pub enum ResolveIntoPackage {
65 ExportsField {
69 conditions: ResolutionConditions,
70 unspecified_conditions: ConditionValue,
71 },
72 MainField { field: RcStr },
78}
79
80#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, NonLocalValue, Encode, Decode)]
82pub enum ResolveInPackage {
83 AliasField(RcStr),
85 ImportsField {
89 conditions: ResolutionConditions,
90 unspecified_conditions: ConditionValue,
91 },
92}
93
94#[turbo_tasks::value(shared)]
95#[derive(Clone)]
96pub enum ImportMapping {
97 External(Option<RcStr>, ExternalType, ExternalTraced),
99 PrimaryAlternativeExternal {
104 name: Option<RcStr>,
105 ty: ExternalType,
106 traced: ExternalTraced,
107 lookup_dir: FileSystemPath,
108 },
109 Direct(ResolvedVc<ResolveResult>),
111 PrimaryAlternative(RcStr, Option<FileSystemPath>),
115 Ignore,
116 Empty,
117 Alternatives(Vec<ResolvedVc<ImportMapping>>),
118 Dynamic(ResolvedVc<Box<dyn ImportMappingReplacement>>),
119}
120
121#[turbo_tasks::value(shared)]
124#[derive(Clone)]
125pub enum ReplacedImportMapping {
126 External {
127 name_override: Option<RcStr>,
128 ty: ExternalType,
129 traced: ExternalTraced,
130 target: Option<FileSystemPath>,
131 },
132 PrimaryAlternativeExternal {
133 name: Option<RcStr>,
134 ty: ExternalType,
135 traced: ExternalTraced,
136 lookup_dir: FileSystemPath,
137 },
138 Direct(ResolvedVc<ResolveResult>),
139 PrimaryAlternative(Pattern, Option<FileSystemPath>),
140 Ignore,
141 Empty,
142 Alternatives(Vec<ResolvedVc<ReplacedImportMapping>>),
143 Dynamic(ResolvedVc<Box<dyn ImportMappingReplacement>>),
144}
145
146impl ImportMapping {
147 pub fn primary_alternatives(
148 list: Vec<RcStr>,
149 lookup_path: Option<FileSystemPath>,
150 ) -> ImportMapping {
151 if list.is_empty() {
152 ImportMapping::Ignore
153 } else if list.len() == 1 {
154 ImportMapping::PrimaryAlternative(list.into_iter().next().unwrap(), lookup_path)
155 } else {
156 ImportMapping::Alternatives(
157 list.into_iter()
158 .map(|s| {
159 ImportMapping::PrimaryAlternative(s, lookup_path.clone()).resolved_cell()
160 })
161 .collect(),
162 )
163 }
164 }
165}
166
167impl AliasTemplate for ResolvedVc<ImportMapping> {
168 type Output<'a> = <Vc<ImportMapping> as AliasTemplate>::Output<'a>;
169
170 fn convert(&self) -> Self::Output<'_> {
171 (**self).convert()
172 }
173
174 fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
175 (**self).replace(capture)
176 }
177}
178
179impl AliasTemplate for Vc<ImportMapping> {
180 type Output<'a> =
181 Pin<Box<dyn Future<Output = Result<ResolvedVc<ReplacedImportMapping>>> + Send + 'a>>;
182
183 fn convert(&self) -> Self::Output<'_> {
184 Box::pin(async move {
185 let this = &*self.await?;
186 Ok(match this {
187 ImportMapping::External(name, ty, traced) => ReplacedImportMapping::External {
188 name_override: name.clone(),
189 ty: *ty,
190 traced: *traced,
191 target: None,
193 },
194 ImportMapping::PrimaryAlternativeExternal {
195 name,
196 ty,
197 traced,
198 lookup_dir,
199 } => ReplacedImportMapping::PrimaryAlternativeExternal {
200 name: name.clone(),
201 ty: *ty,
202 traced: *traced,
203 lookup_dir: lookup_dir.clone(),
204 },
205 ImportMapping::PrimaryAlternative(name, lookup_dir) => {
206 ReplacedImportMapping::PrimaryAlternative(
207 (*name).clone().into(),
208 lookup_dir.clone(),
209 )
210 }
211 ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
212 ImportMapping::Ignore => ReplacedImportMapping::Ignore,
213 ImportMapping::Empty => ReplacedImportMapping::Empty,
214 ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
215 alternatives
216 .iter()
217 .map(|mapping| mapping.convert())
218 .try_join()
219 .await?,
220 ),
221 ImportMapping::Dynamic(replacement) => ReplacedImportMapping::Dynamic(*replacement),
222 }
223 .resolved_cell())
224 })
225 }
226
227 fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
228 let capture = capture.clone();
229 Box::pin(async move {
230 let this = &*self.await?;
231 Ok(match this {
232 ImportMapping::External(name, ty, traced) => {
233 if let Some(name) = name {
234 ReplacedImportMapping::External {
235 name_override: capture
236 .spread_into_star(name)
237 .as_constant_string()
238 .cloned(),
239 ty: *ty,
240 traced: *traced,
241 target: None,
242 }
243 } else {
244 ReplacedImportMapping::External {
245 name_override: None,
246 ty: *ty,
247 traced: *traced,
248 target: None,
249 }
250 }
251 }
252 ImportMapping::PrimaryAlternativeExternal {
253 name,
254 ty,
255 traced,
256 lookup_dir,
257 } => {
258 if let Some(name) = name {
259 ReplacedImportMapping::PrimaryAlternativeExternal {
260 name: capture.spread_into_star(name).as_constant_string().cloned(),
261 ty: *ty,
262 traced: *traced,
263 lookup_dir: lookup_dir.clone(),
264 }
265 } else {
266 ReplacedImportMapping::PrimaryAlternativeExternal {
267 name: None,
268 ty: *ty,
269 traced: *traced,
270 lookup_dir: lookup_dir.clone(),
271 }
272 }
273 }
274 ImportMapping::PrimaryAlternative(name, lookup_dir) => {
275 ReplacedImportMapping::PrimaryAlternative(
276 capture.spread_into_star(name),
277 lookup_dir.clone(),
278 )
279 }
280 ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
281 ImportMapping::Ignore => ReplacedImportMapping::Ignore,
282 ImportMapping::Empty => ReplacedImportMapping::Empty,
283 ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
284 alternatives
285 .iter()
286 .map(|mapping| mapping.replace(&capture))
287 .try_join()
288 .await?,
289 ),
290 ImportMapping::Dynamic(replacement) => {
291 replacement
292 .replace(Pattern::new(capture.clone()))
293 .owned()
294 .await?
295 }
296 }
297 .resolved_cell())
298 })
299 }
300}
301
302#[turbo_tasks::value(shared)]
303#[derive(Clone, Default)]
304pub struct ImportMap {
305 map: AliasMap<ResolvedVc<ImportMapping>>,
306}
307
308impl ImportMap {
309 pub fn new(map: AliasMap<ResolvedVc<ImportMapping>>) -> ImportMap {
311 Self { map }
312 }
313
314 pub fn empty() -> Self {
316 Self::default()
317 }
318
319 pub fn extend_ref(&mut self, other: &ImportMap) {
321 let Self { map } = other.clone();
322 self.map.extend(map);
323 }
324
325 pub fn insert_alias(&mut self, alias: AliasPattern, mapping: ResolvedVc<ImportMapping>) {
327 self.map.insert(alias, mapping);
328 }
329
330 pub fn insert_exact_alias<'a>(
332 &mut self,
333 pattern: impl Into<RcStr> + 'a,
334 mapping: ResolvedVc<ImportMapping>,
335 ) {
336 self.map.insert(AliasPattern::exact(pattern), mapping);
337 }
338
339 pub fn insert_wildcard_alias<'a>(
341 &mut self,
342 prefix: impl Into<RcStr> + 'a,
343 mapping: ResolvedVc<ImportMapping>,
344 ) {
345 self.map
346 .insert(AliasPattern::wildcard(prefix, rcstr!("")), mapping);
347 }
348
349 pub fn insert_wildcard_alias_with_suffix<'p, 's>(
351 &mut self,
352 prefix: impl Into<RcStr> + 'p,
353 suffix: impl Into<RcStr> + 's,
354 mapping: ResolvedVc<ImportMapping>,
355 ) {
356 self.map
357 .insert(AliasPattern::wildcard(prefix, suffix), mapping);
358 }
359
360 pub fn insert_singleton_alias<'a>(
363 &mut self,
364 prefix: impl Into<RcStr> + 'a,
365 context_path: FileSystemPath,
366 ) {
367 let prefix: RcStr = prefix.into();
368 let wildcard_prefix: RcStr = (prefix.to_string() + "/").into();
369 let wildcard_alias: RcStr = (prefix.to_string() + "/*").into();
370 self.insert_exact_alias(
371 prefix.clone(),
372 ImportMapping::PrimaryAlternative(prefix.clone(), Some(context_path.clone()))
373 .resolved_cell(),
374 );
375 self.insert_wildcard_alias(
376 wildcard_prefix,
377 ImportMapping::PrimaryAlternative(wildcard_alias, Some(context_path)).resolved_cell(),
378 );
379 }
380}
381
382#[turbo_tasks::value_impl]
383impl ImportMap {
384 #[turbo_tasks::function]
386 pub async fn extend(self: Vc<Self>, other: ResolvedVc<ImportMap>) -> Result<Vc<Self>> {
387 let mut import_map = self.owned().await?;
388 import_map.extend_ref(&*other.await?);
389 Ok(import_map.cell())
390 }
391}
392
393#[turbo_tasks::value(shared)]
394#[derive(Clone, Default)]
395pub struct ResolvedMap {
396 pub by_glob: Vec<(FileSystemPath, ResolvedVc<Glob>, ResolvedVc<ImportMapping>)>,
397}
398
399#[turbo_tasks::value(shared)]
400#[derive(Clone)]
401pub enum ImportMapResult {
402 Result(ResolvedVc<ResolveResult>),
403 External {
404 name: RcStr,
405 ty: ExternalType,
406 traced: ExternalTraced,
407 target: Option<FileSystemPath>,
408 },
409 AliasExternal {
410 name: RcStr,
411 ty: ExternalType,
412 traced: ExternalTraced,
413 lookup_dir: FileSystemPath,
414 },
415 Alias(ResolvedVc<Request>, Option<FileSystemPath>),
416 Alternatives(Vec<ImportMapResult>),
417 NoEntry,
418}
419
420async fn import_mapping_to_result(
421 mapping: Vc<ReplacedImportMapping>,
422 lookup_path: FileSystemPath,
423 request: Vc<Request>,
424) -> Result<ImportMapResult> {
425 Ok(match &*mapping.await? {
426 ReplacedImportMapping::Direct(result) => ImportMapResult::Result(*result),
427 ReplacedImportMapping::External {
428 name_override,
429 ty,
430 traced,
431 target,
432 } => ImportMapResult::External {
433 name: if let Some(name) = name_override {
434 name.clone()
435 } else if let Some(request) = request.await?.request() {
436 request
437 } else {
438 bail!(
439 "Cannot resolve external reference with dynamic request {:?}",
440 request.request_pattern().await?.describe_as_string()
441 )
442 },
443 ty: *ty,
444 traced: *traced,
445 target: target.clone(),
446 },
447 ReplacedImportMapping::PrimaryAlternativeExternal {
448 name,
449 ty,
450 traced,
451 lookup_dir,
452 } => ImportMapResult::AliasExternal {
453 name: if let Some(name) = name {
454 name.clone()
455 } else if let Some(request) = request.await?.request() {
456 request
457 } else {
458 bail!(
459 "Cannot resolve external reference with dynamic request {:?}",
460 request.request_pattern().await?.describe_as_string()
461 )
462 },
463 ty: *ty,
464 traced: *traced,
465 lookup_dir: lookup_dir.clone(),
466 },
467 ReplacedImportMapping::Ignore => {
468 ImportMapResult::Result(ResolveResult::primary(ResolveResultItem::Ignore))
469 }
470 ReplacedImportMapping::Empty => {
471 ImportMapResult::Result(ResolveResult::primary(ResolveResultItem::Empty))
472 }
473 ReplacedImportMapping::PrimaryAlternative(name, context) => {
474 let request = Request::parse(name.clone()).to_resolved().await?;
475 ImportMapResult::Alias(request, context.clone())
476 }
477 ReplacedImportMapping::Alternatives(list) => ImportMapResult::Alternatives(
478 list.iter()
479 .map(|mapping| {
480 Box::pin(import_mapping_to_result(
481 **mapping,
482 lookup_path.clone(),
483 request,
484 ))
485 })
486 .try_join()
487 .await?,
488 ),
489 ReplacedImportMapping::Dynamic(replacement) => {
490 replacement.result(lookup_path, request).owned().await?
491 }
492 })
493}
494
495#[turbo_tasks::value_impl]
496impl ValueToString for ImportMapResult {
497 #[turbo_tasks::function]
498 async fn to_string(&self) -> Result<Vc<RcStr>> {
499 match self {
500 ImportMapResult::Result(_) => Ok(Vc::cell(rcstr!("Resolved by import map"))),
501 ImportMapResult::External { .. } => Ok(Vc::cell(rcstr!("TODO external"))),
502 ImportMapResult::AliasExternal { .. } => Ok(Vc::cell(rcstr!("TODO external"))),
503 ImportMapResult::Alias(request, context) => {
504 let s = if let Some(path) = context {
505 let path = path.value_to_string().await?;
506 format!(
507 "aliased to {} inside of {}",
508 request.to_string().await?,
509 path
510 )
511 } else {
512 format!("aliased to {}", request.to_string().await?)
513 };
514 Ok(Vc::cell(s.into()))
515 }
516 ImportMapResult::Alternatives(alternatives) => {
517 let strings = alternatives
518 .iter()
519 .map(|alternative| alternative.clone().cell().to_string())
520 .try_join()
521 .await?;
522 let strings = strings
523 .iter()
524 .map(|string| string.as_str())
525 .collect::<Vec<_>>();
526 Ok(Vc::cell(strings.join(" | ").into()))
527 }
528 ImportMapResult::NoEntry => Ok(Vc::cell(rcstr!("No import map entry"))),
529 }
530 }
531}
532
533impl ImportMap {
534 pub async fn lookup(
537 &self,
538 lookup_path: FileSystemPath,
539 request: Vc<Request>,
540 ) -> Result<ImportMapResult> {
541 let request_pattern = request.request_pattern().await?;
544 if matches!(*request_pattern, Pattern::Dynamic | Pattern::DynamicNoSlash) {
545 return Ok(ImportMapResult::NoEntry);
548 }
549
550 let (req_rel, rest) = request_pattern.split_could_match("./");
551 let (req_rel_parent, req_rest) =
552 rest.map(|r| r.split_could_match("../")).unwrap_or_default();
553
554 let lookup_rel = req_rel.as_ref().and_then(|req| {
555 self.map
556 .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("./"))
557 .next()
558 });
559 let lookup_rel_parent = req_rel_parent.as_ref().and_then(|req| {
560 self.map
561 .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("../"))
562 .next()
563 });
564 let lookup = req_rest
565 .as_ref()
566 .and_then(|req| self.map.lookup(req).next());
567
568 let results = lookup_rel
569 .into_iter()
570 .chain(lookup_rel_parent.into_iter())
571 .chain(lookup.into_iter())
572 .map(async |result| {
573 import_mapping_to_result(*result?.output.await?, lookup_path.clone(), request).await
574 })
575 .try_join()
576 .await?;
577
578 Ok(match results.len() {
579 0 => ImportMapResult::NoEntry,
580 1 => results.into_iter().next().unwrap(),
581 2.. => ImportMapResult::Alternatives(results),
582 })
583 }
584}
585
586#[turbo_tasks::value_impl]
587impl ResolvedMap {
588 #[turbo_tasks::function]
589 pub async fn lookup(
590 &self,
591 resolved: FileSystemPath,
592 lookup_path: FileSystemPath,
593 request: Vc<Request>,
594 ) -> Result<Vc<ImportMapResult>> {
595 for (root, glob, mapping) in self.by_glob.iter() {
596 if let Some(path) = root.get_path_to(&resolved)
597 && glob.await?.matches(path)
598 {
599 return Ok(import_mapping_to_result(
600 *mapping.convert().await?,
601 lookup_path,
602 request,
603 )
604 .await?
605 .cell());
606 }
607 }
608 Ok(ImportMapResult::NoEntry.cell())
609 }
610}
611
612#[turbo_tasks::value(shared)]
613#[derive(Clone, Debug, Default)]
614pub struct ResolveOptions {
615 pub fully_specified: bool,
618 pub prefer_relative: bool,
621 pub extensions: Vec<RcStr>,
623 pub modules: Vec<ResolveModules>,
625 pub into_package: Vec<ResolveIntoPackage>,
627 pub in_package: Vec<ResolveInPackage>,
629 pub default_files: Vec<RcStr>,
631 pub import_map: Option<ResolvedVc<ImportMap>>,
633 pub fallback_import_map: Option<ResolvedVc<ImportMap>>,
635 pub resolved_map: Option<ResolvedVc<ResolvedMap>>,
636 pub before_resolve_plugins: Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>,
637 pub after_resolve_plugins: Vec<ResolvedVc<Box<dyn AfterResolvePlugin>>>,
638 pub enable_typescript_with_output_extension: bool,
640 pub loose_errors: bool,
642 pub collect_affecting_sources: bool,
644 pub parse_data_uris: bool,
646
647 pub placeholder_for_future_extensions: (),
648}
649
650#[turbo_tasks::value_impl]
651impl ResolveOptions {
652 #[turbo_tasks::function]
655 pub async fn with_extended_import_map(
656 self: Vc<Self>,
657 import_map: Vc<ImportMap>,
658 ) -> Result<Vc<Self>> {
659 let mut resolve_options = self.owned().await?;
660 resolve_options.import_map = Some(
661 resolve_options
662 .import_map
663 .map(|current_import_map| current_import_map.extend(import_map))
664 .unwrap_or(import_map)
665 .to_resolved()
666 .await?,
667 );
668 Ok(resolve_options.cell())
669 }
670
671 #[turbo_tasks::function]
674 pub async fn with_extended_fallback_import_map(
675 self: Vc<Self>,
676 extended_import_map: ResolvedVc<ImportMap>,
677 ) -> Result<Vc<Self>> {
678 let mut resolve_options = self.owned().await?;
679 resolve_options.fallback_import_map =
680 if let Some(current_fallback) = resolve_options.fallback_import_map {
681 Some(
682 current_fallback
683 .extend(*extended_import_map)
684 .to_resolved()
685 .await?,
686 )
687 } else {
688 Some(extended_import_map)
689 };
690 Ok(resolve_options.cell())
691 }
692
693 #[turbo_tasks::function]
695 pub async fn with_extensions(self: Vc<Self>, extensions: Vec<RcStr>) -> Result<Vc<Self>> {
696 let mut resolve_options = self.owned().await?;
697 resolve_options.extensions = extensions;
698 Ok(resolve_options.cell())
699 }
700
701 #[turbo_tasks::function]
703 pub async fn with_fully_specified(self: Vc<Self>, fully_specified: bool) -> Result<Vc<Self>> {
704 let mut resolve_options = self.owned().await?;
705 if resolve_options.fully_specified == fully_specified {
706 return Ok(self);
707 }
708 resolve_options.fully_specified = fully_specified;
709 Ok(resolve_options.cell())
710 }
711}
712
713#[turbo_tasks::value(shared)]
714#[derive(Hash, Clone, Debug)]
715pub struct ResolveModulesOptions {
716 pub modules: Vec<ResolveModules>,
717 pub extensions: Vec<RcStr>,
718}
719
720#[turbo_tasks::function]
721pub async fn resolve_modules_options(
722 options: Vc<ResolveOptions>,
723) -> Result<Vc<ResolveModulesOptions>> {
724 let options = options.await?;
725 Ok(ResolveModulesOptions {
726 modules: options.modules.clone(),
727 extensions: options.extensions.clone(),
728 }
729 .cell())
730}
731
732#[turbo_tasks::value_trait]
733pub trait ImportMappingReplacement {
734 #[turbo_tasks::function]
735 fn replace(self: Vc<Self>, capture: Vc<Pattern>) -> Vc<ReplacedImportMapping>;
736 #[turbo_tasks::function]
737 fn result(
738 self: Vc<Self>,
739 lookup_path: FileSystemPath,
740 request: Vc<Request>,
741 ) -> Vc<ImportMapResult>;
742}