turbopack_core/resolve/
options.rs

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/// A location where to resolve modules.
29#[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    /// when inside of path, use the list of directories to
43    /// resolve inside these
44    Nested(FileSystemPath, Vec<RcStr>),
45    /// look into that directory, unless the request has an excluded extension
46    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/// The different ways to resolve a package, as described in package.json.
74#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, Serialize, Deserialize, NonLocalValue)]
75pub enum ResolveIntoPackage {
76    /// Using the [exports] field.
77    ///
78    /// [exports]: https://nodejs.org/api/packages.html#exports
79    ExportsField {
80        conditions: ResolutionConditions,
81        unspecified_conditions: ConditionValue,
82    },
83    /// Using a [main]-like field (e.g. [main], [module], [browser], etc.).
84    ///
85    /// [main]: https://nodejs.org/api/packages.html#main
86    /// [module]: https://esbuild.github.io/api/#main-fields
87    /// [browser]: https://esbuild.github.io/api/#main-fields
88    MainField { field: RcStr },
89}
90
91// The different ways to resolve a request within a package
92#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, Serialize, Deserialize, NonLocalValue)]
93pub enum ResolveInPackage {
94    /// Using a alias field which allows to map requests
95    AliasField(RcStr),
96    /// Using the [imports] field.
97    ///
98    /// [imports]: https://nodejs.org/api/packages.html#imports
99    ImportsField {
100        conditions: ResolutionConditions,
101        unspecified_conditions: ConditionValue,
102    },
103}
104
105#[turbo_tasks::value(shared)]
106#[derive(Clone)]
107pub enum ImportMapping {
108    /// If specified, the optional name overrides the request, importing that external instead.
109    External(Option<RcStr>, ExternalType, ExternalTraced),
110    /// Try to make the request external if it is is resolvable from the specified
111    /// directory, and fall back to resolving the original request if it fails.
112    ///
113    /// If specified, the optional name overrides the request, importing that external instead.
114    PrimaryAlternativeExternal {
115        name: Option<RcStr>,
116        ty: ExternalType,
117        traced: ExternalTraced,
118        lookup_dir: FileSystemPath,
119    },
120    /// An already resolved result that will be returned directly.
121    Direct(ResolvedVc<ResolveResult>),
122    /// A request alias that will be resolved first, and fall back to resolving
123    /// the original request if it fails. Useful for the tsconfig.json
124    /// `compilerOptions.paths` option and Next aliases.
125    PrimaryAlternative(RcStr, Option<FileSystemPath>),
126    Ignore,
127    Empty,
128    Alternatives(Vec<ResolvedVc<ImportMapping>>),
129    Dynamic(ResolvedVc<Box<dyn ImportMappingReplacement>>),
130}
131
132/// An `ImportMapping` that was applied to a pattern. See `ImportMapping` for
133/// more details on the variants.
134#[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    /// Creates a new import map.
300    pub fn new(map: AliasMap<ResolvedVc<ImportMapping>>) -> ImportMap {
301        Self { map }
302    }
303
304    /// Creates a new empty import map.
305    pub fn empty() -> Self {
306        Self::default()
307    }
308
309    /// Extends the import map with another import map.
310    pub fn extend_ref(&mut self, other: &ImportMap) {
311        let Self { map } = other.clone();
312        self.map.extend(map);
313    }
314
315    /// Inserts an alias into the import map.
316    pub fn insert_alias(&mut self, alias: AliasPattern, mapping: ResolvedVc<ImportMapping>) {
317        self.map.insert(alias, mapping);
318    }
319
320    /// Inserts an exact alias into the import map.
321    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    /// Inserts a wildcard alias into the import map.
330    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    /// Inserts a wildcard alias with suffix into the import map.
339    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    /// Inserts an alias that resolves an prefix always from a certain location
350    /// to create a singleton.
351    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    /// Extends the underlying [ImportMap] with another [ImportMap].
374    #[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    // Not a turbo-tasks function: the map lookup should be cheaper than the cache
507    // lookup
508    pub async fn lookup(
509        &self,
510        lookup_path: FileSystemPath,
511        request: Vc<Request>,
512    ) -> Result<ImportMapResult> {
513        // relative requests must not match global wildcard aliases.
514
515        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    /// When set, do not apply extensions and default_files for relative
587    /// request. But they are still applied for resolving into packages.
588    pub fully_specified: bool,
589    /// When set, when resolving a module request, try to resolve it as relative
590    /// request first.
591    pub prefer_relative: bool,
592    /// The extensions that should be added to a request when resolving.
593    pub extensions: Vec<RcStr>,
594    /// The locations where to resolve modules.
595    pub modules: Vec<ResolveModules>,
596    /// How to resolve packages.
597    pub into_package: Vec<ResolveIntoPackage>,
598    /// How to resolve in packages.
599    pub in_package: Vec<ResolveInPackage>,
600    /// The default files to resolve in a folder.
601    pub default_files: Vec<RcStr>,
602    /// An import map to use before resolving a request.
603    pub import_map: Option<ResolvedVc<ImportMap>>,
604    /// An import map to use when a request is otherwise unresolvable.
605    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    /// Support resolving *.js requests to *.ts files
610    pub enable_typescript_with_output_extension: bool,
611    /// Warn instead of error for resolve errors
612    pub loose_errors: bool,
613    /// Whether to parse data URIs into modules (as opposed to keeping them as externals)
614    pub parse_data_uris: bool,
615
616    pub placeholder_for_future_extensions: (),
617}
618
619#[turbo_tasks::value_impl]
620impl ResolveOptions {
621    /// Returns a new [Vc<ResolveOptions>] with its import map extended to
622    /// include the given import map.
623    #[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    /// Returns a new [Vc<ResolveOptions>] with its fallback import map extended
641    /// to include the given import map.
642    #[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    /// Overrides the extensions used for resolving
663    #[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    /// Overrides the fully_specified flag for resolving
671    #[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}