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;
6use turbo_tasks::{
7    FxIndexSet, NonLocalValue, ResolvedVc, TryJoinIterExt, Value, 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(ResolvedVc<FileSystemPath>, Vec<RcStr>),
45    /// look into that directory, unless the request has an excluded extension
46    Path {
47        dir: ResolvedVc<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 withing 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: ResolvedVc<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<ResolvedVc<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: ResolvedVc<FileSystemPath>,
143    },
144    Direct(ResolvedVc<ResolveResult>),
145    PrimaryAlternative(Pattern, Option<ResolvedVc<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<ResolvedVc<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| ImportMapping::PrimaryAlternative(s, lookup_path).resolved_cell())
165                    .collect(),
166            )
167        }
168    }
169}
170
171impl AliasTemplate for ResolvedVc<ImportMapping> {
172    type Output<'a> = <Vc<ImportMapping> as AliasTemplate>::Output<'a>;
173
174    fn convert(&self) -> Self::Output<'_> {
175        (**self).convert()
176    }
177
178    fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
179        (**self).replace(capture)
180    }
181}
182
183impl AliasTemplate for Vc<ImportMapping> {
184    type Output<'a> =
185        Pin<Box<dyn Future<Output = Result<ResolvedVc<ReplacedImportMapping>>> + Send + 'a>>;
186
187    fn convert(&self) -> Self::Output<'_> {
188        Box::pin(async move {
189            let this = &*self.await?;
190            Ok(match this {
191                ImportMapping::External(name, ty, traced) => {
192                    ReplacedImportMapping::External(name.clone(), *ty, *traced)
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,
204                },
205                ImportMapping::PrimaryAlternative(name, lookup_dir) => {
206                    ReplacedImportMapping::PrimaryAlternative((*name).clone().into(), *lookup_dir)
207                }
208                ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
209                ImportMapping::Ignore => ReplacedImportMapping::Ignore,
210                ImportMapping::Empty => ReplacedImportMapping::Empty,
211                ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
212                    alternatives
213                        .iter()
214                        .map(|mapping| mapping.convert())
215                        .try_join()
216                        .await?,
217                ),
218                ImportMapping::Dynamic(replacement) => ReplacedImportMapping::Dynamic(*replacement),
219            }
220            .resolved_cell())
221        })
222    }
223
224    fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
225        let capture = capture.clone();
226        Box::pin(async move {
227            let this = &*self.await?;
228            Ok(match this {
229                ImportMapping::External(name, ty, traced) => {
230                    if let Some(name) = name {
231                        ReplacedImportMapping::External(
232                            capture.spread_into_star(name).as_string().map(|s| s.into()),
233                            *ty,
234                            *traced,
235                        )
236                    } else {
237                        ReplacedImportMapping::External(None, *ty, *traced)
238                    }
239                }
240                ImportMapping::PrimaryAlternativeExternal {
241                    name,
242                    ty,
243                    traced,
244                    lookup_dir,
245                } => {
246                    if let Some(name) = name {
247                        ReplacedImportMapping::PrimaryAlternativeExternal {
248                            name: capture.spread_into_star(name).as_string().map(|s| s.into()),
249                            ty: *ty,
250                            traced: *traced,
251                            lookup_dir: *lookup_dir,
252                        }
253                    } else {
254                        ReplacedImportMapping::PrimaryAlternativeExternal {
255                            name: None,
256                            ty: *ty,
257                            traced: *traced,
258                            lookup_dir: *lookup_dir,
259                        }
260                    }
261                }
262                ImportMapping::PrimaryAlternative(name, lookup_dir) => {
263                    ReplacedImportMapping::PrimaryAlternative(
264                        capture.spread_into_star(name),
265                        *lookup_dir,
266                    )
267                }
268                ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
269                ImportMapping::Ignore => ReplacedImportMapping::Ignore,
270                ImportMapping::Empty => ReplacedImportMapping::Empty,
271                ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
272                    alternatives
273                        .iter()
274                        .map(|mapping| mapping.replace(&capture))
275                        .try_join()
276                        .await?,
277                ),
278                ImportMapping::Dynamic(replacement) => {
279                    (*replacement.replace(capture.clone().cell()).await?).clone()
280                }
281            }
282            .resolved_cell())
283        })
284    }
285}
286
287#[turbo_tasks::value(shared)]
288#[derive(Clone, Default)]
289pub struct ImportMap {
290    map: AliasMap<ResolvedVc<ImportMapping>>,
291}
292
293impl ImportMap {
294    /// Creates a new import map.
295    pub fn new(map: AliasMap<ResolvedVc<ImportMapping>>) -> ImportMap {
296        Self { map }
297    }
298
299    /// Creates a new empty import map.
300    pub fn empty() -> Self {
301        Self::default()
302    }
303
304    /// Extends the import map with another import map.
305    pub fn extend_ref(&mut self, other: &ImportMap) {
306        let Self { map } = other.clone();
307        self.map.extend(map);
308    }
309
310    /// Inserts an alias into the import map.
311    pub fn insert_alias(&mut self, alias: AliasPattern, mapping: ResolvedVc<ImportMapping>) {
312        self.map.insert(alias, mapping);
313    }
314
315    /// Inserts an exact alias into the import map.
316    pub fn insert_exact_alias<'a>(
317        &mut self,
318        pattern: impl Into<RcStr> + 'a,
319        mapping: ResolvedVc<ImportMapping>,
320    ) {
321        self.map.insert(AliasPattern::exact(pattern), mapping);
322    }
323
324    /// Inserts a wildcard alias into the import map.
325    pub fn insert_wildcard_alias<'a>(
326        &mut self,
327        prefix: impl Into<RcStr> + 'a,
328        mapping: ResolvedVc<ImportMapping>,
329    ) {
330        self.map.insert(AliasPattern::wildcard(prefix, ""), mapping);
331    }
332
333    /// Inserts a wildcard alias with suffix into the import map.
334    pub fn insert_wildcard_alias_with_suffix<'p, 's>(
335        &mut self,
336        prefix: impl Into<RcStr> + 'p,
337        suffix: impl Into<RcStr> + 's,
338        mapping: ResolvedVc<ImportMapping>,
339    ) {
340        self.map
341            .insert(AliasPattern::wildcard(prefix, suffix), mapping);
342    }
343
344    /// Inserts an alias that resolves an prefix always from a certain location
345    /// to create a singleton.
346    pub fn insert_singleton_alias<'a>(
347        &mut self,
348        prefix: impl Into<RcStr> + 'a,
349        context_path: ResolvedVc<FileSystemPath>,
350    ) {
351        let prefix: RcStr = prefix.into();
352        let wildcard_prefix: RcStr = (prefix.to_string() + "/").into();
353        let wildcard_alias: RcStr = (prefix.to_string() + "/*").into();
354        self.insert_exact_alias(
355            prefix.clone(),
356            ImportMapping::PrimaryAlternative(prefix.clone(), Some(context_path)).resolved_cell(),
357        );
358        self.insert_wildcard_alias(
359            wildcard_prefix,
360            ImportMapping::PrimaryAlternative(wildcard_alias, Some(context_path)).resolved_cell(),
361        );
362    }
363}
364
365#[turbo_tasks::value_impl]
366impl ImportMap {
367    /// Extends the underlying [ImportMap] with another [ImportMap].
368    #[turbo_tasks::function]
369    pub async fn extend(self: Vc<Self>, other: ResolvedVc<ImportMap>) -> Result<Vc<Self>> {
370        let mut import_map = self.owned().await?;
371        import_map.extend_ref(&*other.await?);
372        Ok(import_map.cell())
373    }
374}
375
376#[turbo_tasks::value(shared)]
377#[derive(Clone, Default)]
378pub struct ResolvedMap {
379    pub by_glob: Vec<(
380        ResolvedVc<FileSystemPath>,
381        ResolvedVc<Glob>,
382        ResolvedVc<ImportMapping>,
383    )>,
384}
385
386#[turbo_tasks::value(shared)]
387#[derive(Clone)]
388pub enum ImportMapResult {
389    Result(ResolvedVc<ResolveResult>),
390    External(RcStr, ExternalType, ExternalTraced),
391    AliasExternal {
392        name: RcStr,
393        ty: ExternalType,
394        traced: ExternalTraced,
395        lookup_dir: ResolvedVc<FileSystemPath>,
396    },
397    Alias(ResolvedVc<Request>, Option<ResolvedVc<FileSystemPath>>),
398    Alternatives(Vec<ImportMapResult>),
399    NoEntry,
400}
401
402async fn import_mapping_to_result(
403    mapping: Vc<ReplacedImportMapping>,
404    lookup_path: Vc<FileSystemPath>,
405    request: Vc<Request>,
406) -> Result<ImportMapResult> {
407    Ok(match &*mapping.await? {
408        ReplacedImportMapping::Direct(result) => ImportMapResult::Result(*result),
409        ReplacedImportMapping::External(name, ty, traced) => ImportMapResult::External(
410            if let Some(name) = name {
411                name.clone()
412            } else if let Some(request) = request.await?.request() {
413                request
414            } else {
415                bail!("Cannot resolve external reference without request")
416            },
417            *ty,
418            *traced,
419        ),
420        ReplacedImportMapping::PrimaryAlternativeExternal {
421            name,
422            ty,
423            traced,
424            lookup_dir,
425        } => ImportMapResult::AliasExternal {
426            name: if let Some(name) = name {
427                name.clone()
428            } else if let Some(request) = request.await?.request() {
429                request
430            } else {
431                bail!("Cannot resolve external reference without request")
432            },
433            ty: *ty,
434            traced: *traced,
435            lookup_dir: *lookup_dir,
436        },
437        ReplacedImportMapping::Ignore => {
438            ImportMapResult::Result(ResolveResult::primary(ResolveResultItem::Ignore))
439        }
440        ReplacedImportMapping::Empty => {
441            ImportMapResult::Result(ResolveResult::primary(ResolveResultItem::Empty))
442        }
443        ReplacedImportMapping::PrimaryAlternative(name, context) => {
444            let request = Request::parse(Value::new(name.clone()))
445                .to_resolved()
446                .await?;
447            ImportMapResult::Alias(request, *context)
448        }
449        ReplacedImportMapping::Alternatives(list) => ImportMapResult::Alternatives(
450            list.iter()
451                .map(|mapping| Box::pin(import_mapping_to_result(**mapping, lookup_path, request)))
452                .try_join()
453                .await?,
454        ),
455        ReplacedImportMapping::Dynamic(replacement) => {
456            replacement.result(lookup_path, request).owned().await?
457        }
458    })
459}
460
461#[turbo_tasks::value_impl]
462impl ValueToString for ImportMapResult {
463    #[turbo_tasks::function]
464    async fn to_string(&self) -> Result<Vc<RcStr>> {
465        match self {
466            ImportMapResult::Result(_) => Ok(Vc::cell("Resolved by import map".into())),
467            ImportMapResult::External(_, _, _) => Ok(Vc::cell("TODO external".into())),
468            ImportMapResult::AliasExternal { .. } => Ok(Vc::cell("TODO external".into())),
469            ImportMapResult::Alias(request, context) => {
470                let s = if let Some(path) = context {
471                    let path = path.to_string().await?;
472                    format!(
473                        "aliased to {} inside of {}",
474                        request.to_string().await?,
475                        path
476                    )
477                } else {
478                    format!("aliased to {}", request.to_string().await?)
479                };
480                Ok(Vc::cell(s.into()))
481            }
482            ImportMapResult::Alternatives(alternatives) => {
483                let strings = alternatives
484                    .iter()
485                    .map(|alternative| alternative.clone().cell().to_string())
486                    .try_join()
487                    .await?;
488                let strings = strings
489                    .iter()
490                    .map(|string| string.as_str())
491                    .collect::<Vec<_>>();
492                Ok(Vc::cell(strings.join(" | ").into()))
493            }
494            ImportMapResult::NoEntry => Ok(Vc::cell("No import map entry".into())),
495        }
496    }
497}
498
499impl ImportMap {
500    // Not a turbo-tasks function: the map lookup should be cheaper than the cache
501    // lookup
502    pub async fn lookup(
503        &self,
504        lookup_path: Vc<FileSystemPath>,
505        request: Vc<Request>,
506    ) -> Result<ImportMapResult> {
507        // relative requests must not match global wildcard aliases.
508
509        let request_pattern = request.request_pattern().await?;
510        let (req_rel, rest) = request_pattern.split_could_match("./");
511        let (req_rel_parent, req_rest) =
512            rest.map(|r| r.split_could_match("../")).unwrap_or_default();
513
514        let lookup_rel = req_rel.as_ref().and_then(|req| {
515            self.map
516                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("./"))
517                .next()
518        });
519        let lookup_rel_parent = req_rel_parent.as_ref().and_then(|req| {
520            self.map
521                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("../"))
522                .next()
523        });
524        let lookup = req_rest
525            .as_ref()
526            .and_then(|req| self.map.lookup(req).next());
527
528        let results = lookup_rel
529            .into_iter()
530            .chain(lookup_rel_parent.into_iter())
531            .chain(lookup.into_iter())
532            .map(async |result| {
533                import_mapping_to_result(*result.try_join_into_self().await?, lookup_path, request)
534                    .await
535            })
536            .try_join()
537            .await?;
538
539        Ok(match results.len() {
540            0 => ImportMapResult::NoEntry,
541            1 => results.into_iter().next().unwrap(),
542            2.. => ImportMapResult::Alternatives(results),
543        })
544    }
545}
546
547#[turbo_tasks::value_impl]
548impl ResolvedMap {
549    #[turbo_tasks::function]
550    pub async fn lookup(
551        &self,
552        resolved: Vc<FileSystemPath>,
553        lookup_path: Vc<FileSystemPath>,
554        request: Vc<Request>,
555    ) -> Result<Vc<ImportMapResult>> {
556        let resolved = resolved.await?;
557        for (root, glob, mapping) in self.by_glob.iter() {
558            let root = root.await?;
559            if let Some(path) = root.get_path_to(&resolved) {
560                if glob.await?.matches(path) {
561                    return Ok(import_mapping_to_result(
562                        *mapping.convert().await?,
563                        lookup_path,
564                        request,
565                    )
566                    .await?
567                    .into());
568                }
569            }
570        }
571        Ok(ImportMapResult::NoEntry.into())
572    }
573}
574
575#[turbo_tasks::value(shared)]
576#[derive(Clone, Debug, Default)]
577pub struct ResolveOptions {
578    /// When set, do not apply extensions and default_files for relative
579    /// request. But they are still applied for resolving into packages.
580    pub fully_specified: bool,
581    /// When set, when resolving a module request, try to resolve it as relative
582    /// request first.
583    pub prefer_relative: bool,
584    /// The extensions that should be added to a request when resolving.
585    pub extensions: Vec<RcStr>,
586    /// The locations where to resolve modules.
587    pub modules: Vec<ResolveModules>,
588    /// How to resolve packages.
589    pub into_package: Vec<ResolveIntoPackage>,
590    /// How to resolve in packages.
591    pub in_package: Vec<ResolveInPackage>,
592    /// The default files to resolve in a folder.
593    pub default_files: Vec<RcStr>,
594    /// An import map to use before resolving a request.
595    pub import_map: Option<ResolvedVc<ImportMap>>,
596    /// An import map to use when a request is otherwise unresolvable.
597    pub fallback_import_map: Option<ResolvedVc<ImportMap>>,
598    pub resolved_map: Option<ResolvedVc<ResolvedMap>>,
599    pub before_resolve_plugins: Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>,
600    pub after_resolve_plugins: Vec<ResolvedVc<Box<dyn AfterResolvePlugin>>>,
601    /// Support resolving *.js requests to *.ts files
602    pub enable_typescript_with_output_extension: bool,
603    /// Warn instead of error for resolve errors
604    pub loose_errors: bool,
605    /// Whether to parse data URIs into modules (as opposed to keeping them as externals)
606    pub parse_data_uris: bool,
607
608    pub placeholder_for_future_extensions: (),
609}
610
611#[turbo_tasks::value_impl]
612impl ResolveOptions {
613    /// Returns a new [Vc<ResolveOptions>] with its import map extended to
614    /// include the given import map.
615    #[turbo_tasks::function]
616    pub async fn with_extended_import_map(
617        self: Vc<Self>,
618        import_map: Vc<ImportMap>,
619    ) -> Result<Vc<Self>> {
620        let mut resolve_options = self.owned().await?;
621        resolve_options.import_map = Some(
622            resolve_options
623                .import_map
624                .map(|current_import_map| current_import_map.extend(import_map))
625                .unwrap_or(import_map)
626                .to_resolved()
627                .await?,
628        );
629        Ok(resolve_options.into())
630    }
631
632    /// Returns a new [Vc<ResolveOptions>] with its fallback import map extended
633    /// to include the given import map.
634    #[turbo_tasks::function]
635    pub async fn with_extended_fallback_import_map(
636        self: Vc<Self>,
637        extended_import_map: ResolvedVc<ImportMap>,
638    ) -> Result<Vc<Self>> {
639        let mut resolve_options = self.owned().await?;
640        resolve_options.fallback_import_map =
641            if let Some(current_fallback) = resolve_options.fallback_import_map {
642                Some(
643                    current_fallback
644                        .extend(*extended_import_map)
645                        .to_resolved()
646                        .await?,
647                )
648            } else {
649                Some(extended_import_map)
650            };
651        Ok(resolve_options.into())
652    }
653
654    /// Overrides the extensions used for resolving
655    #[turbo_tasks::function]
656    pub async fn with_extensions(self: Vc<Self>, extensions: Vec<RcStr>) -> Result<Vc<Self>> {
657        let mut resolve_options = self.owned().await?;
658        resolve_options.extensions = extensions;
659        Ok(resolve_options.into())
660    }
661
662    /// Overrides the fully_specified flag for resolving
663    #[turbo_tasks::function]
664    pub async fn with_fully_specified(self: Vc<Self>, fully_specified: bool) -> Result<Vc<Self>> {
665        let mut resolve_options = self.owned().await?;
666        if resolve_options.fully_specified == fully_specified {
667            return Ok(self);
668        }
669        resolve_options.fully_specified = fully_specified;
670        Ok(resolve_options.cell())
671    }
672}
673
674#[turbo_tasks::value(shared)]
675#[derive(Hash, Clone, Debug)]
676pub struct ResolveModulesOptions {
677    pub modules: Vec<ResolveModules>,
678    pub extensions: Vec<RcStr>,
679}
680
681#[turbo_tasks::function]
682pub async fn resolve_modules_options(
683    options: Vc<ResolveOptions>,
684) -> Result<Vc<ResolveModulesOptions>> {
685    let options = options.await?;
686    Ok(ResolveModulesOptions {
687        modules: options.modules.clone(),
688        extensions: options.extensions.clone(),
689    }
690    .into())
691}
692
693#[turbo_tasks::value_trait]
694pub trait ImportMappingReplacement {
695    fn replace(self: Vc<Self>, capture: Vc<Pattern>) -> Vc<ReplacedImportMapping>;
696    fn result(
697        self: Vc<Self>,
698        lookup_path: Vc<FileSystemPath>,
699        request: Vc<Request>,
700    ) -> Vc<ImportMapResult>;
701}