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