Skip to main content

turbopack_core/resolve/
options.rs

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, turbofmt,
9};
10use turbo_tasks_fs::{FileSystemPath, glob::Glob};
11
12use crate::{
13    issue::Issue,
14    resolve::{
15        AliasPattern, ExternalTraced, ExternalType, ResolveResult, ResolveResultItem,
16        alias_map::{AliasMap, AliasTemplate},
17        parse::Request,
18        pattern::Pattern,
19        plugin::{AfterResolvePlugin, BeforeResolvePlugin},
20    },
21};
22
23#[turbo_tasks::value(transparent)]
24#[derive(Debug)]
25pub struct ExcludedExtensions(#[bincode(with = "turbo_bincode::indexset")] pub FxIndexSet<RcStr>);
26
27/// A location where to resolve modules.
28#[derive(
29    TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, ValueDebugFormat, NonLocalValue, Encode, Decode,
30)]
31pub enum ResolveModules {
32    /// when inside of path, use the list of directories to
33    /// resolve inside these
34    Nested(FileSystemPath, Vec<RcStr>),
35    /// look into that directory, unless the request has an excluded extension
36    Path {
37        dir: FileSystemPath,
38        excluded_extensions: ResolvedVc<ExcludedExtensions>,
39    },
40}
41
42#[derive(
43    TraceRawVcs, Hash, PartialEq, Eq, Clone, Copy, Debug, NonLocalValue, Encode, Decode, Default,
44)]
45pub enum ConditionValue {
46    Set,
47    #[default]
48    Unset,
49    Unknown,
50}
51
52impl From<bool> for ConditionValue {
53    fn from(v: bool) -> Self {
54        if v {
55            ConditionValue::Set
56        } else {
57            ConditionValue::Unset
58        }
59    }
60}
61
62pub type ResolutionConditions = BTreeMap<RcStr, ConditionValue>;
63
64/// The different ways to resolve a package, as described in package.json.
65#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, NonLocalValue, Encode, Decode)]
66pub enum ResolveIntoPackage {
67    /// Using the [exports] field.
68    ///
69    /// [exports]: https://nodejs.org/api/packages.html#exports
70    ExportsField {
71        conditions: ResolutionConditions,
72        unspecified_conditions: ConditionValue,
73    },
74    /// Using a [main]-like field (e.g. [main], [module], [browser], etc.).
75    ///
76    /// [main]: https://nodejs.org/api/packages.html#main
77    /// [module]: https://esbuild.github.io/api/#main-fields
78    /// [browser]: https://esbuild.github.io/api/#main-fields
79    MainField { field: RcStr },
80}
81
82// The different ways to resolve a request within a package
83#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, NonLocalValue, Encode, Decode)]
84pub enum ResolveInPackage {
85    /// Using a alias field which allows to map requests
86    AliasField(RcStr),
87    /// Using the [imports] field.
88    ///
89    /// [imports]: https://nodejs.org/api/packages.html#imports
90    ImportsField {
91        conditions: ResolutionConditions,
92        unspecified_conditions: ConditionValue,
93    },
94}
95
96#[turbo_tasks::value(shared)]
97#[derive(Clone)]
98pub enum ImportMapping {
99    /// If specified, the optional name overrides the request, importing that external instead.
100    External(Option<RcStr>, ExternalType, ExternalTraced),
101    /// Try to make the request external if it is is resolvable from the specified
102    /// directory, and fall back to resolving the original request if it fails.
103    ///
104    /// If specified, the optional name overrides the request, importing that external instead.
105    PrimaryAlternativeExternal {
106        name: Option<RcStr>,
107        ty: ExternalType,
108        traced: ExternalTraced,
109        lookup_dir: FileSystemPath,
110    },
111    /// An already resolved result that will be returned directly.
112    Direct(ResolvedVc<ResolveResult>),
113    /// A request alias that will be resolved first, and fall back to resolving
114    /// the original request if it fails. Useful for the tsconfig.json
115    /// `compilerOptions.paths` option and Next aliases.
116    PrimaryAlternative(RcStr, Option<FileSystemPath>),
117    /// See [`ResolveResultItem::Ignore`]
118    Ignore,
119    /// See [`ResolveResultItem::Empty`]
120    Empty,
121    /// See [`ResolveResultItem::Error`]
122    Error(ResolvedVc<Box<dyn Issue>>),
123    Alternatives(Vec<ResolvedVc<ImportMapping>>),
124    Dynamic(ResolvedVc<Box<dyn ImportMappingReplacement>>),
125}
126
127/// An `ImportMapping` that was applied to a pattern. See `ImportMapping` for
128/// more details on the variants.
129#[turbo_tasks::value(shared)]
130#[derive(Clone)]
131pub enum ReplacedImportMapping {
132    External {
133        name_override: Option<RcStr>,
134        ty: ExternalType,
135        traced: ExternalTraced,
136        target: Option<FileSystemPath>,
137    },
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    Error(ResolvedVc<Box<dyn Issue>>),
151}
152
153impl ImportMapping {
154    pub fn primary_alternatives(
155        list: Vec<RcStr>,
156        lookup_path: Option<FileSystemPath>,
157    ) -> ImportMapping {
158        if list.is_empty() {
159            ImportMapping::Ignore
160        } else if list.len() == 1 {
161            ImportMapping::PrimaryAlternative(list.into_iter().next().unwrap(), lookup_path)
162        } else {
163            ImportMapping::Alternatives(
164                list.into_iter()
165                    .map(|s| {
166                        ImportMapping::PrimaryAlternative(s, lookup_path.clone()).resolved_cell()
167                    })
168                    .collect(),
169            )
170        }
171    }
172}
173
174impl AliasTemplate for ResolvedVc<ImportMapping> {
175    type Output<'a> = <Vc<ImportMapping> as AliasTemplate>::Output<'a>;
176
177    fn convert(&self) -> Self::Output<'_> {
178        (**self).convert()
179    }
180
181    fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
182        (**self).replace(capture)
183    }
184}
185
186impl AliasTemplate for Vc<ImportMapping> {
187    type Output<'a> =
188        Pin<Box<dyn Future<Output = Result<ResolvedVc<ReplacedImportMapping>>> + Send + 'a>>;
189
190    fn convert(&self) -> Self::Output<'_> {
191        Box::pin(async move {
192            let this = &*self.await?;
193            Ok(match this {
194                ImportMapping::External(name, ty, traced) => ReplacedImportMapping::External {
195                    name_override: name.clone(),
196                    ty: *ty,
197                    traced: *traced,
198                    // TODO
199                    target: None,
200                },
201                ImportMapping::PrimaryAlternativeExternal {
202                    name,
203                    ty,
204                    traced,
205                    lookup_dir,
206                } => ReplacedImportMapping::PrimaryAlternativeExternal {
207                    name: name.clone(),
208                    ty: *ty,
209                    traced: *traced,
210                    lookup_dir: lookup_dir.clone(),
211                },
212                ImportMapping::PrimaryAlternative(name, lookup_dir) => {
213                    ReplacedImportMapping::PrimaryAlternative(
214                        (*name).clone().into(),
215                        lookup_dir.clone(),
216                    )
217                }
218                ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
219                ImportMapping::Ignore => ReplacedImportMapping::Ignore,
220                ImportMapping::Empty => ReplacedImportMapping::Empty,
221                ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
222                    alternatives
223                        .iter()
224                        .map(|mapping| mapping.convert())
225                        .try_join()
226                        .await?,
227                ),
228                ImportMapping::Dynamic(replacement) => ReplacedImportMapping::Dynamic(*replacement),
229                ImportMapping::Error(issue) => ReplacedImportMapping::Error(*issue),
230            }
231            .resolved_cell())
232        })
233    }
234
235    fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
236        let capture = capture.clone();
237        Box::pin(async move {
238            let this = &*self.await?;
239            Ok(match this {
240                ImportMapping::External(name, ty, traced) => {
241                    if let Some(name) = name {
242                        ReplacedImportMapping::External {
243                            name_override: capture
244                                .spread_into_star(name)
245                                .as_constant_string()
246                                .cloned(),
247                            ty: *ty,
248                            traced: *traced,
249                            target: None,
250                        }
251                    } else {
252                        ReplacedImportMapping::External {
253                            name_override: None,
254                            ty: *ty,
255                            traced: *traced,
256                            target: None,
257                        }
258                    }
259                }
260                ImportMapping::PrimaryAlternativeExternal {
261                    name,
262                    ty,
263                    traced,
264                    lookup_dir,
265                } => {
266                    if let Some(name) = name {
267                        ReplacedImportMapping::PrimaryAlternativeExternal {
268                            name: capture.spread_into_star(name).as_constant_string().cloned(),
269                            ty: *ty,
270                            traced: *traced,
271                            lookup_dir: lookup_dir.clone(),
272                        }
273                    } else {
274                        ReplacedImportMapping::PrimaryAlternativeExternal {
275                            name: None,
276                            ty: *ty,
277                            traced: *traced,
278                            lookup_dir: lookup_dir.clone(),
279                        }
280                    }
281                }
282                ImportMapping::PrimaryAlternative(name, lookup_dir) => {
283                    ReplacedImportMapping::PrimaryAlternative(
284                        capture.spread_into_star(name),
285                        lookup_dir.clone(),
286                    )
287                }
288                ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
289                ImportMapping::Ignore => ReplacedImportMapping::Ignore,
290                ImportMapping::Empty => ReplacedImportMapping::Empty,
291                ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
292                    alternatives
293                        .iter()
294                        .map(|mapping| mapping.replace(&capture))
295                        .try_join()
296                        .await?,
297                ),
298                ImportMapping::Dynamic(replacement) => {
299                    replacement
300                        .replace(Pattern::new(capture.clone()))
301                        .owned()
302                        .await?
303                }
304                ImportMapping::Error(issue) => ReplacedImportMapping::Error(*issue),
305            }
306            .resolved_cell())
307        })
308    }
309}
310
311#[turbo_tasks::value(shared)]
312#[derive(Clone, Default)]
313pub struct ImportMap {
314    map: AliasMap<ResolvedVc<ImportMapping>>,
315}
316
317impl ImportMap {
318    /// Creates a new import map.
319    pub fn new(map: AliasMap<ResolvedVc<ImportMapping>>) -> ImportMap {
320        Self { map }
321    }
322
323    /// Creates a new empty import map.
324    pub fn empty() -> Self {
325        Self::default()
326    }
327
328    /// Extends the import map with another import map.
329    pub fn extend_ref(&mut self, other: &ImportMap) {
330        let Self { map } = other.clone();
331        self.map.extend(map);
332    }
333
334    /// Inserts an alias into the import map.
335    pub fn insert_alias(&mut self, alias: AliasPattern, mapping: ResolvedVc<ImportMapping>) {
336        self.map.insert(alias, mapping);
337    }
338
339    /// Inserts an exact alias into the import map.
340    pub fn insert_exact_alias<'a>(
341        &mut self,
342        pattern: impl Into<RcStr> + 'a,
343        mapping: ResolvedVc<ImportMapping>,
344    ) {
345        self.map.insert(AliasPattern::exact(pattern), mapping);
346    }
347
348    /// Inserts a wildcard alias into the import map.
349    pub fn insert_wildcard_alias<'a>(
350        &mut self,
351        prefix: impl Into<RcStr> + 'a,
352        mapping: ResolvedVc<ImportMapping>,
353    ) {
354        self.map
355            .insert(AliasPattern::wildcard(prefix, rcstr!("")), mapping);
356    }
357
358    /// Inserts a wildcard alias with suffix into the import map.
359    pub fn insert_wildcard_alias_with_suffix<'p, 's>(
360        &mut self,
361        prefix: impl Into<RcStr> + 'p,
362        suffix: impl Into<RcStr> + 's,
363        mapping: ResolvedVc<ImportMapping>,
364    ) {
365        self.map
366            .insert(AliasPattern::wildcard(prefix, suffix), mapping);
367    }
368
369    /// Inserts an alias that resolves an prefix always from a certain location
370    /// to create a singleton.
371    pub fn insert_singleton_alias<'a>(
372        &mut self,
373        prefix: impl Into<RcStr> + 'a,
374        context_path: FileSystemPath,
375    ) {
376        let prefix: RcStr = prefix.into();
377        let wildcard_prefix: RcStr = (prefix.to_string() + "/").into();
378        let wildcard_alias: RcStr = (prefix.to_string() + "/*").into();
379        self.insert_exact_alias(
380            prefix.clone(),
381            ImportMapping::PrimaryAlternative(prefix.clone(), Some(context_path.clone()))
382                .resolved_cell(),
383        );
384        self.insert_wildcard_alias(
385            wildcard_prefix,
386            ImportMapping::PrimaryAlternative(wildcard_alias, Some(context_path)).resolved_cell(),
387        );
388    }
389}
390
391#[turbo_tasks::value_impl]
392impl ImportMap {
393    /// Extends the underlying [ImportMap] with another [ImportMap].
394    #[turbo_tasks::function]
395    pub async fn extend(self: Vc<Self>, other: ResolvedVc<ImportMap>) -> Result<Vc<Self>> {
396        let mut import_map = self.owned().await?;
397        import_map.extend_ref(&*other.await?);
398        Ok(import_map.cell())
399    }
400}
401
402#[turbo_tasks::value(shared)]
403#[derive(Clone, Default)]
404pub struct ResolvedMap {
405    pub by_glob: Vec<(FileSystemPath, ResolvedVc<Glob>, ResolvedVc<ImportMapping>)>,
406}
407
408#[turbo_tasks::value(shared)]
409#[derive(Clone)]
410pub enum ImportMapResult {
411    Result(ResolvedVc<ResolveResult>),
412    External {
413        name: RcStr,
414        ty: ExternalType,
415        traced: ExternalTraced,
416        target: Option<FileSystemPath>,
417    },
418    AliasExternal {
419        name: RcStr,
420        ty: ExternalType,
421        traced: ExternalTraced,
422        lookup_dir: FileSystemPath,
423    },
424    Alias(ResolvedVc<Request>, Option<FileSystemPath>),
425    Alternatives(Vec<ImportMapResult>),
426    NoEntry,
427    Error(ResolvedVc<Box<dyn Issue>>),
428}
429
430async fn import_mapping_to_result(
431    mapping: Vc<ReplacedImportMapping>,
432    lookup_path: FileSystemPath,
433    request: Vc<Request>,
434) -> Result<ImportMapResult> {
435    Ok(match &*mapping.await? {
436        ReplacedImportMapping::Direct(result) => ImportMapResult::Result(*result),
437        ReplacedImportMapping::External {
438            name_override,
439            ty,
440            traced,
441            target,
442        } => ImportMapResult::External {
443            name: if let Some(name) = name_override {
444                name.clone()
445            } else if let Some(request) = request.await?.request() {
446                request
447            } else {
448                bail!(
449                    "Cannot resolve external reference with dynamic request {:?}",
450                    request.request_pattern().await?.describe_as_string()
451                )
452            },
453            ty: *ty,
454            traced: *traced,
455            target: target.clone(),
456        },
457        ReplacedImportMapping::PrimaryAlternativeExternal {
458            name,
459            ty,
460            traced,
461            lookup_dir,
462        } => ImportMapResult::AliasExternal {
463            name: if let Some(name) = name {
464                name.clone()
465            } else if let Some(request) = request.await?.request() {
466                request
467            } else {
468                bail!(
469                    "Cannot resolve external reference with dynamic request {:?}",
470                    request.request_pattern().await?.describe_as_string()
471                )
472            },
473            ty: *ty,
474            traced: *traced,
475            lookup_dir: lookup_dir.clone(),
476        },
477        ReplacedImportMapping::Ignore => ImportMapResult::Result(
478            ResolveResult::primary(ResolveResultItem::Ignore).resolved_cell(),
479        ),
480        ReplacedImportMapping::Empty => ImportMapResult::Result(
481            ResolveResult::primary(ResolveResultItem::Empty).resolved_cell(),
482        ),
483        ReplacedImportMapping::PrimaryAlternative(name, context) => {
484            let request = Request::parse(name.clone()).to_resolved().await?;
485            ImportMapResult::Alias(request, context.clone())
486        }
487        ReplacedImportMapping::Alternatives(list) => ImportMapResult::Alternatives(
488            list.iter()
489                .map(|mapping| {
490                    Box::pin(import_mapping_to_result(
491                        **mapping,
492                        lookup_path.clone(),
493                        request,
494                    ))
495                })
496                .try_join()
497                .await?,
498        ),
499        ReplacedImportMapping::Dynamic(replacement) => {
500            replacement.result(lookup_path, request).owned().await?
501        }
502        ReplacedImportMapping::Error(issue) => ImportMapResult::Error(*issue),
503    })
504}
505
506#[turbo_tasks::value_impl]
507impl ValueToString for ImportMapResult {
508    #[turbo_tasks::function]
509    async fn to_string(&self) -> Result<Vc<RcStr>> {
510        match self {
511            ImportMapResult::Result(_) => Ok(Vc::cell(rcstr!("Resolved by import map"))),
512            ImportMapResult::External { .. } => Ok(Vc::cell(rcstr!("TODO external"))),
513            ImportMapResult::AliasExternal { .. } => Ok(Vc::cell(rcstr!("TODO external"))),
514            ImportMapResult::Alias(request, context) => {
515                let s = if let Some(path) = context {
516                    turbofmt!("aliased to {} inside of {path}", *request).await?
517                } else {
518                    turbofmt!("aliased to {}", *request).await?
519                };
520                Ok(Vc::cell(s))
521            }
522            ImportMapResult::Alternatives(alternatives) => {
523                // The .cell() calls happen synchronously during iteration, before try_join()
524                // runs the futures. This is safe because cells are created in deterministic
525                // order (the order of alternatives in the Vec).
526                let strings = alternatives
527                    .iter()
528                    .map(|alternative| alternative.clone().cell().to_string())
529                    .try_join()
530                    .await?;
531                let strings = strings
532                    .iter()
533                    .map(|string| string.as_str())
534                    .collect::<Vec<_>>();
535                Ok(Vc::cell(strings.join(" | ").into()))
536            }
537            ImportMapResult::NoEntry => Ok(Vc::cell(rcstr!("No import map entry"))),
538            ImportMapResult::Error(issue) => Ok(Vc::cell(
539                format!(
540                    "error: {}",
541                    issue
542                        .into_trait_ref()
543                        .await?
544                        .title()
545                        .await?
546                        .to_unstyled_string()
547                )
548                .into(),
549            )),
550        }
551    }
552}
553
554impl ImportMap {
555    // Not a turbo-tasks function: the map lookup should be cheaper than the cache
556    // lookup
557    pub async fn lookup(
558        &self,
559        lookup_path: FileSystemPath,
560        request: Vc<Request>,
561    ) -> Result<ImportMapResult> {
562        // relative requests must not match global wildcard aliases.
563
564        let request_pattern = request.request_pattern().await?;
565        if matches!(*request_pattern, Pattern::Dynamic | Pattern::DynamicNoSlash) {
566            // You could probably conceive of cases where this isn't correct. But the dynamic will
567            // just match every single entry in the import map, which is not what we want.
568            return Ok(ImportMapResult::NoEntry);
569        }
570
571        let (req_rel, rest) = request_pattern.split_could_match("./");
572        let (req_rel_parent, req_rest) =
573            rest.map(|r| r.split_could_match("../")).unwrap_or_default();
574
575        let lookup_rel = req_rel.as_ref().and_then(|req| {
576            self.map
577                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("./"))
578                .next()
579        });
580        let lookup_rel_parent = req_rel_parent.as_ref().and_then(|req| {
581            self.map
582                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("../"))
583                .next()
584        });
585        let lookup = req_rest
586            .as_ref()
587            .and_then(|req| self.map.lookup(req).next());
588
589        let results = lookup_rel
590            .into_iter()
591            .chain(lookup_rel_parent)
592            .chain(lookup)
593            .map(async |result| {
594                import_mapping_to_result(*result?.output.await?, lookup_path.clone(), request).await
595            })
596            .try_join()
597            .await?;
598
599        Ok(match results.len() {
600            0 => ImportMapResult::NoEntry,
601            1 => results.into_iter().next().unwrap(),
602            2.. => ImportMapResult::Alternatives(results),
603        })
604    }
605}
606
607#[turbo_tasks::value_impl]
608impl ResolvedMap {
609    #[turbo_tasks::function]
610    pub async fn lookup(
611        &self,
612        resolved: FileSystemPath,
613        lookup_path: FileSystemPath,
614        request: Vc<Request>,
615    ) -> Result<Vc<ImportMapResult>> {
616        for (root, glob, mapping) in self.by_glob.iter() {
617            if let Some(path) = root.get_path_to(&resolved)
618                && glob.await?.matches(path)
619            {
620                return Ok(import_mapping_to_result(
621                    *mapping.convert().await?,
622                    lookup_path,
623                    request,
624                )
625                .await?
626                .cell());
627            }
628        }
629        Ok(ImportMapResult::NoEntry.cell())
630    }
631}
632
633#[turbo_tasks::value(shared)]
634#[derive(Clone, Debug, Default)]
635pub struct ResolveOptions {
636    /// When set, do not apply extensions and default_files for relative
637    /// request. But they are still applied for resolving into packages.
638    pub fully_specified: bool,
639    /// When set, when resolving a module request, try to resolve it as relative
640    /// request first.
641    pub prefer_relative: bool,
642    /// The extensions that should be added to a request when resolving.
643    pub extensions: Vec<RcStr>,
644    /// The locations where to resolve modules.
645    pub modules: Vec<ResolveModules>,
646    /// How to resolve packages.
647    pub into_package: Vec<ResolveIntoPackage>,
648    /// How to resolve in packages.
649    pub in_package: Vec<ResolveInPackage>,
650    /// The default files to resolve in a folder.
651    pub default_files: Vec<RcStr>,
652    /// An import map to use before resolving a request.
653    pub import_map: Option<ResolvedVc<ImportMap>>,
654    /// An import map to use when a request is otherwise unresolvable.
655    pub fallback_import_map: Option<ResolvedVc<ImportMap>>,
656    pub resolved_map: Option<ResolvedVc<ResolvedMap>>,
657    pub before_resolve_plugins: Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>,
658    pub after_resolve_plugins: Vec<ResolvedVc<Box<dyn AfterResolvePlugin>>>,
659    /// Support resolving *.js requests to *.ts files
660    pub enable_typescript_with_output_extension: bool,
661    /// Warn instead of error for resolve errors
662    pub loose_errors: bool,
663    /// Collect affecting sources for each resolve result.  Useful for tracing.
664    pub collect_affecting_sources: bool,
665    /// Whether to parse data URIs into modules (as opposed to keeping them as externals)
666    pub parse_data_uris: bool,
667
668    pub placeholder_for_future_extensions: (),
669}
670
671#[turbo_tasks::value_impl]
672impl ResolveOptions {
673    /// Returns a new `Vc<ResolveOptions>` with its import map extended to include the given import
674    /// map.
675    #[turbo_tasks::function]
676    pub async fn with_extended_import_map(
677        self: Vc<Self>,
678        import_map: Vc<ImportMap>,
679    ) -> Result<Vc<Self>> {
680        let mut resolve_options = self.owned().await?;
681        resolve_options.import_map = Some(
682            resolve_options
683                .import_map
684                .map(|current_import_map| current_import_map.extend(import_map))
685                .unwrap_or(import_map)
686                .to_resolved()
687                .await?,
688        );
689        Ok(resolve_options.cell())
690    }
691
692    /// Returns a new `Vc<ResolveOptions>` with its fallback import map extended to include the
693    /// given import map.
694    #[turbo_tasks::function]
695    pub async fn with_extended_fallback_import_map(
696        self: Vc<Self>,
697        extended_import_map: ResolvedVc<ImportMap>,
698    ) -> Result<Vc<Self>> {
699        let mut resolve_options = self.owned().await?;
700        resolve_options.fallback_import_map =
701            if let Some(current_fallback) = resolve_options.fallback_import_map {
702                Some(
703                    current_fallback
704                        .extend(*extended_import_map)
705                        .to_resolved()
706                        .await?,
707                )
708            } else {
709                Some(extended_import_map)
710            };
711        Ok(resolve_options.cell())
712    }
713
714    /// Overrides the extensions used for resolving
715    #[turbo_tasks::function]
716    pub async fn with_extensions(self: Vc<Self>, extensions: Vec<RcStr>) -> Result<Vc<Self>> {
717        let mut resolve_options = self.owned().await?;
718        resolve_options.extensions = extensions;
719        Ok(resolve_options.cell())
720    }
721
722    /// Overrides the fully_specified flag for resolving
723    #[turbo_tasks::function]
724    pub async fn with_fully_specified(self: Vc<Self>, fully_specified: bool) -> Result<Vc<Self>> {
725        let mut resolve_options = self.owned().await?;
726        if resolve_options.fully_specified == fully_specified {
727            return Ok(self);
728        }
729        resolve_options.fully_specified = fully_specified;
730        Ok(resolve_options.cell())
731    }
732}
733
734#[turbo_tasks::value(shared)]
735#[derive(Hash, Clone, Debug)]
736pub struct ResolveModulesOptions {
737    pub modules: Vec<ResolveModules>,
738    pub extensions: Vec<RcStr>,
739}
740
741#[turbo_tasks::function]
742pub async fn resolve_modules_options(
743    options: Vc<ResolveOptions>,
744) -> Result<Vc<ResolveModulesOptions>> {
745    let options = options.await?;
746    Ok(ResolveModulesOptions {
747        modules: options.modules.clone(),
748        extensions: options.extensions.clone(),
749    }
750    .cell())
751}
752
753#[turbo_tasks::value_trait]
754pub trait ImportMappingReplacement {
755    #[turbo_tasks::function]
756    fn replace(self: Vc<Self>, capture: Vc<Pattern>) -> Vc<ReplacedImportMapping>;
757    #[turbo_tasks::function]
758    fn result(
759        self: Vc<Self>,
760        lookup_path: FileSystemPath,
761        request: Vc<Request>,
762    ) -> Vc<ImportMapResult>;
763}