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_constant_string().cloned(),
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_constant_string().cloned(),
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
285                        .replace(Pattern::new(capture.clone()))
286                        .owned()
287                        .await?
288                }
289            }
290            .resolved_cell())
291        })
292    }
293}
294
295#[turbo_tasks::value(shared)]
296#[derive(Clone, Default)]
297pub struct ImportMap {
298    map: AliasMap<ResolvedVc<ImportMapping>>,
299}
300
301impl ImportMap {
302    /// Creates a new import map.
303    pub fn new(map: AliasMap<ResolvedVc<ImportMapping>>) -> ImportMap {
304        Self { map }
305    }
306
307    /// Creates a new empty import map.
308    pub fn empty() -> Self {
309        Self::default()
310    }
311
312    /// Extends the import map with another import map.
313    pub fn extend_ref(&mut self, other: &ImportMap) {
314        let Self { map } = other.clone();
315        self.map.extend(map);
316    }
317
318    /// Inserts an alias into the import map.
319    pub fn insert_alias(&mut self, alias: AliasPattern, mapping: ResolvedVc<ImportMapping>) {
320        self.map.insert(alias, mapping);
321    }
322
323    /// Inserts an exact alias into the import map.
324    pub fn insert_exact_alias<'a>(
325        &mut self,
326        pattern: impl Into<RcStr> + 'a,
327        mapping: ResolvedVc<ImportMapping>,
328    ) {
329        self.map.insert(AliasPattern::exact(pattern), mapping);
330    }
331
332    /// Inserts a wildcard alias into the import map.
333    pub fn insert_wildcard_alias<'a>(
334        &mut self,
335        prefix: impl Into<RcStr> + 'a,
336        mapping: ResolvedVc<ImportMapping>,
337    ) {
338        self.map
339            .insert(AliasPattern::wildcard(prefix, rcstr!("")), mapping);
340    }
341
342    /// Inserts a wildcard alias with suffix into the import map.
343    pub fn insert_wildcard_alias_with_suffix<'p, 's>(
344        &mut self,
345        prefix: impl Into<RcStr> + 'p,
346        suffix: impl Into<RcStr> + 's,
347        mapping: ResolvedVc<ImportMapping>,
348    ) {
349        self.map
350            .insert(AliasPattern::wildcard(prefix, suffix), mapping);
351    }
352
353    /// Inserts an alias that resolves an prefix always from a certain location
354    /// to create a singleton.
355    pub fn insert_singleton_alias<'a>(
356        &mut self,
357        prefix: impl Into<RcStr> + 'a,
358        context_path: FileSystemPath,
359    ) {
360        let prefix: RcStr = prefix.into();
361        let wildcard_prefix: RcStr = (prefix.to_string() + "/").into();
362        let wildcard_alias: RcStr = (prefix.to_string() + "/*").into();
363        self.insert_exact_alias(
364            prefix.clone(),
365            ImportMapping::PrimaryAlternative(prefix.clone(), Some(context_path.clone()))
366                .resolved_cell(),
367        );
368        self.insert_wildcard_alias(
369            wildcard_prefix,
370            ImportMapping::PrimaryAlternative(wildcard_alias, Some(context_path)).resolved_cell(),
371        );
372    }
373}
374
375#[turbo_tasks::value_impl]
376impl ImportMap {
377    /// Extends the underlying [ImportMap] with another [ImportMap].
378    #[turbo_tasks::function]
379    pub async fn extend(self: Vc<Self>, other: ResolvedVc<ImportMap>) -> Result<Vc<Self>> {
380        let mut import_map = self.owned().await?;
381        import_map.extend_ref(&*other.await?);
382        Ok(import_map.cell())
383    }
384}
385
386#[turbo_tasks::value(shared)]
387#[derive(Clone, Default)]
388pub struct ResolvedMap {
389    pub by_glob: Vec<(FileSystemPath, ResolvedVc<Glob>, ResolvedVc<ImportMapping>)>,
390}
391
392#[turbo_tasks::value(shared)]
393#[derive(Clone)]
394pub enum ImportMapResult {
395    Result(ResolvedVc<ResolveResult>),
396    External(RcStr, ExternalType, ExternalTraced),
397    AliasExternal {
398        name: RcStr,
399        ty: ExternalType,
400        traced: ExternalTraced,
401        lookup_dir: FileSystemPath,
402    },
403    Alias(ResolvedVc<Request>, Option<FileSystemPath>),
404    Alternatives(Vec<ImportMapResult>),
405    NoEntry,
406}
407
408async fn import_mapping_to_result(
409    mapping: Vc<ReplacedImportMapping>,
410    lookup_path: FileSystemPath,
411    request: Vc<Request>,
412) -> Result<ImportMapResult> {
413    Ok(match &*mapping.await? {
414        ReplacedImportMapping::Direct(result) => ImportMapResult::Result(*result),
415        ReplacedImportMapping::External(name, ty, traced) => ImportMapResult::External(
416            if let Some(name) = name {
417                name.clone()
418            } else if let Some(request) = request.await?.request() {
419                request
420            } else {
421                bail!(
422                    "Cannot resolve external reference with dynamic request {:?}",
423                    request.request_pattern().await?.describe_as_string()
424                )
425            },
426            *ty,
427            *traced,
428        ),
429        ReplacedImportMapping::PrimaryAlternativeExternal {
430            name,
431            ty,
432            traced,
433            lookup_dir,
434        } => ImportMapResult::AliasExternal {
435            name: if let Some(name) = name {
436                name.clone()
437            } else if let Some(request) = request.await?.request() {
438                request
439            } else {
440                bail!(
441                    "Cannot resolve external reference with dynamic request {:?}",
442                    request.request_pattern().await?.describe_as_string()
443                )
444            },
445            ty: *ty,
446            traced: *traced,
447            lookup_dir: lookup_dir.clone(),
448        },
449        ReplacedImportMapping::Ignore => {
450            ImportMapResult::Result(ResolveResult::primary(ResolveResultItem::Ignore))
451        }
452        ReplacedImportMapping::Empty => {
453            ImportMapResult::Result(ResolveResult::primary(ResolveResultItem::Empty))
454        }
455        ReplacedImportMapping::PrimaryAlternative(name, context) => {
456            let request = Request::parse(name.clone()).to_resolved().await?;
457            ImportMapResult::Alias(request, context.clone())
458        }
459        ReplacedImportMapping::Alternatives(list) => ImportMapResult::Alternatives(
460            list.iter()
461                .map(|mapping| {
462                    Box::pin(import_mapping_to_result(
463                        **mapping,
464                        lookup_path.clone(),
465                        request,
466                    ))
467                })
468                .try_join()
469                .await?,
470        ),
471        ReplacedImportMapping::Dynamic(replacement) => {
472            replacement.result(lookup_path, request).owned().await?
473        }
474    })
475}
476
477#[turbo_tasks::value_impl]
478impl ValueToString for ImportMapResult {
479    #[turbo_tasks::function]
480    async fn to_string(&self) -> Result<Vc<RcStr>> {
481        match self {
482            ImportMapResult::Result(_) => Ok(Vc::cell(rcstr!("Resolved by import map"))),
483            ImportMapResult::External(_, _, _) => Ok(Vc::cell(rcstr!("TODO external"))),
484            ImportMapResult::AliasExternal { .. } => Ok(Vc::cell(rcstr!("TODO external"))),
485            ImportMapResult::Alias(request, context) => {
486                let s = if let Some(path) = context {
487                    let path = path.value_to_string().await?;
488                    format!(
489                        "aliased to {} inside of {}",
490                        request.to_string().await?,
491                        path
492                    )
493                } else {
494                    format!("aliased to {}", request.to_string().await?)
495                };
496                Ok(Vc::cell(s.into()))
497            }
498            ImportMapResult::Alternatives(alternatives) => {
499                let strings = alternatives
500                    .iter()
501                    .map(|alternative| alternative.clone().cell().to_string())
502                    .try_join()
503                    .await?;
504                let strings = strings
505                    .iter()
506                    .map(|string| string.as_str())
507                    .collect::<Vec<_>>();
508                Ok(Vc::cell(strings.join(" | ").into()))
509            }
510            ImportMapResult::NoEntry => Ok(Vc::cell(rcstr!("No import map entry"))),
511        }
512    }
513}
514
515impl ImportMap {
516    // Not a turbo-tasks function: the map lookup should be cheaper than the cache
517    // lookup
518    pub async fn lookup(
519        &self,
520        lookup_path: FileSystemPath,
521        request: Vc<Request>,
522    ) -> Result<ImportMapResult> {
523        // relative requests must not match global wildcard aliases.
524
525        let request_pattern = request.request_pattern().await?;
526        if matches!(*request_pattern, Pattern::Dynamic | Pattern::DynamicNoSlash) {
527            // You could probably conceive of cases where this isn't correct. But the dynamic will
528            // just match every single entry in the import map, which is not what we want.
529            return Ok(ImportMapResult::NoEntry);
530        }
531
532        let (req_rel, rest) = request_pattern.split_could_match("./");
533        let (req_rel_parent, req_rest) =
534            rest.map(|r| r.split_could_match("../")).unwrap_or_default();
535
536        let lookup_rel = req_rel.as_ref().and_then(|req| {
537            self.map
538                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("./"))
539                .next()
540        });
541        let lookup_rel_parent = req_rel_parent.as_ref().and_then(|req| {
542            self.map
543                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("../"))
544                .next()
545        });
546        let lookup = req_rest
547            .as_ref()
548            .and_then(|req| self.map.lookup(req).next());
549
550        let results = lookup_rel
551            .into_iter()
552            .chain(lookup_rel_parent.into_iter())
553            .chain(lookup.into_iter())
554            .map(async |result| {
555                import_mapping_to_result(*result?.output.await?, lookup_path.clone(), request).await
556            })
557            .try_join()
558            .await?;
559
560        Ok(match results.len() {
561            0 => ImportMapResult::NoEntry,
562            1 => results.into_iter().next().unwrap(),
563            2.. => ImportMapResult::Alternatives(results),
564        })
565    }
566}
567
568#[turbo_tasks::value_impl]
569impl ResolvedMap {
570    #[turbo_tasks::function]
571    pub async fn lookup(
572        &self,
573        resolved: FileSystemPath,
574        lookup_path: FileSystemPath,
575        request: Vc<Request>,
576    ) -> Result<Vc<ImportMapResult>> {
577        for (root, glob, mapping) in self.by_glob.iter() {
578            if let Some(path) = root.get_path_to(&resolved)
579                && glob.await?.matches(path)
580            {
581                return Ok(import_mapping_to_result(
582                    *mapping.convert().await?,
583                    lookup_path,
584                    request,
585                )
586                .await?
587                .into());
588            }
589        }
590        Ok(ImportMapResult::NoEntry.into())
591    }
592}
593
594#[turbo_tasks::value(shared)]
595#[derive(Clone, Debug, Default)]
596pub struct ResolveOptions {
597    /// When set, do not apply extensions and default_files for relative
598    /// request. But they are still applied for resolving into packages.
599    pub fully_specified: bool,
600    /// When set, when resolving a module request, try to resolve it as relative
601    /// request first.
602    pub prefer_relative: bool,
603    /// The extensions that should be added to a request when resolving.
604    pub extensions: Vec<RcStr>,
605    /// The locations where to resolve modules.
606    pub modules: Vec<ResolveModules>,
607    /// How to resolve packages.
608    pub into_package: Vec<ResolveIntoPackage>,
609    /// How to resolve in packages.
610    pub in_package: Vec<ResolveInPackage>,
611    /// The default files to resolve in a folder.
612    pub default_files: Vec<RcStr>,
613    /// An import map to use before resolving a request.
614    pub import_map: Option<ResolvedVc<ImportMap>>,
615    /// An import map to use when a request is otherwise unresolvable.
616    pub fallback_import_map: Option<ResolvedVc<ImportMap>>,
617    pub resolved_map: Option<ResolvedVc<ResolvedMap>>,
618    pub before_resolve_plugins: Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>,
619    pub after_resolve_plugins: Vec<ResolvedVc<Box<dyn AfterResolvePlugin>>>,
620    /// Support resolving *.js requests to *.ts files
621    pub enable_typescript_with_output_extension: bool,
622    /// Warn instead of error for resolve errors
623    pub loose_errors: bool,
624    /// Whether to parse data URIs into modules (as opposed to keeping them as externals)
625    pub parse_data_uris: bool,
626
627    pub placeholder_for_future_extensions: (),
628}
629
630#[turbo_tasks::value_impl]
631impl ResolveOptions {
632    /// Returns a new [Vc<ResolveOptions>] with its import map extended to
633    /// include the given import map.
634    #[turbo_tasks::function]
635    pub async fn with_extended_import_map(
636        self: Vc<Self>,
637        import_map: Vc<ImportMap>,
638    ) -> Result<Vc<Self>> {
639        let mut resolve_options = self.owned().await?;
640        resolve_options.import_map = Some(
641            resolve_options
642                .import_map
643                .map(|current_import_map| current_import_map.extend(import_map))
644                .unwrap_or(import_map)
645                .to_resolved()
646                .await?,
647        );
648        Ok(resolve_options.into())
649    }
650
651    /// Returns a new [Vc<ResolveOptions>] with its fallback import map extended
652    /// to include the given import map.
653    #[turbo_tasks::function]
654    pub async fn with_extended_fallback_import_map(
655        self: Vc<Self>,
656        extended_import_map: ResolvedVc<ImportMap>,
657    ) -> Result<Vc<Self>> {
658        let mut resolve_options = self.owned().await?;
659        resolve_options.fallback_import_map =
660            if let Some(current_fallback) = resolve_options.fallback_import_map {
661                Some(
662                    current_fallback
663                        .extend(*extended_import_map)
664                        .to_resolved()
665                        .await?,
666                )
667            } else {
668                Some(extended_import_map)
669            };
670        Ok(resolve_options.into())
671    }
672
673    /// Overrides the extensions used for resolving
674    #[turbo_tasks::function]
675    pub async fn with_extensions(self: Vc<Self>, extensions: Vec<RcStr>) -> Result<Vc<Self>> {
676        let mut resolve_options = self.owned().await?;
677        resolve_options.extensions = extensions;
678        Ok(resolve_options.into())
679    }
680
681    /// Overrides the fully_specified flag for resolving
682    #[turbo_tasks::function]
683    pub async fn with_fully_specified(self: Vc<Self>, fully_specified: bool) -> Result<Vc<Self>> {
684        let mut resolve_options = self.owned().await?;
685        if resolve_options.fully_specified == fully_specified {
686            return Ok(self);
687        }
688        resolve_options.fully_specified = fully_specified;
689        Ok(resolve_options.cell())
690    }
691}
692
693#[turbo_tasks::value(shared)]
694#[derive(Hash, Clone, Debug)]
695pub struct ResolveModulesOptions {
696    pub modules: Vec<ResolveModules>,
697    pub extensions: Vec<RcStr>,
698}
699
700#[turbo_tasks::function]
701pub async fn resolve_modules_options(
702    options: Vc<ResolveOptions>,
703) -> Result<Vc<ResolveModulesOptions>> {
704    let options = options.await?;
705    Ok(ResolveModulesOptions {
706        modules: options.modules.clone(),
707        extensions: options.extensions.clone(),
708    }
709    .into())
710}
711
712#[turbo_tasks::value_trait]
713pub trait ImportMappingReplacement {
714    #[turbo_tasks::function]
715    fn replace(self: Vc<Self>, capture: Vc<Pattern>) -> Vc<ReplacedImportMapping>;
716    #[turbo_tasks::function]
717    fn result(
718        self: Vc<Self>,
719        lookup_path: FileSystemPath,
720        request: Vc<Request>,
721    ) -> Vc<ImportMapResult>;
722}