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    /// Collect affecting sources for each resolve result.  Useful for tracing.
625    pub collect_affecting_sources: bool,
626    /// Whether to parse data URIs into modules (as opposed to keeping them as externals)
627    pub parse_data_uris: bool,
628
629    pub placeholder_for_future_extensions: (),
630}
631
632#[turbo_tasks::value_impl]
633impl ResolveOptions {
634    /// Returns a new [Vc<ResolveOptions>] with its import map extended to
635    /// include the given import map.
636    #[turbo_tasks::function]
637    pub async fn with_extended_import_map(
638        self: Vc<Self>,
639        import_map: Vc<ImportMap>,
640    ) -> Result<Vc<Self>> {
641        let mut resolve_options = self.owned().await?;
642        resolve_options.import_map = Some(
643            resolve_options
644                .import_map
645                .map(|current_import_map| current_import_map.extend(import_map))
646                .unwrap_or(import_map)
647                .to_resolved()
648                .await?,
649        );
650        Ok(resolve_options.into())
651    }
652
653    /// Returns a new [Vc<ResolveOptions>] with its fallback import map extended
654    /// to include the given import map.
655    #[turbo_tasks::function]
656    pub async fn with_extended_fallback_import_map(
657        self: Vc<Self>,
658        extended_import_map: ResolvedVc<ImportMap>,
659    ) -> Result<Vc<Self>> {
660        let mut resolve_options = self.owned().await?;
661        resolve_options.fallback_import_map =
662            if let Some(current_fallback) = resolve_options.fallback_import_map {
663                Some(
664                    current_fallback
665                        .extend(*extended_import_map)
666                        .to_resolved()
667                        .await?,
668                )
669            } else {
670                Some(extended_import_map)
671            };
672        Ok(resolve_options.into())
673    }
674
675    /// Overrides the extensions used for resolving
676    #[turbo_tasks::function]
677    pub async fn with_extensions(self: Vc<Self>, extensions: Vec<RcStr>) -> Result<Vc<Self>> {
678        let mut resolve_options = self.owned().await?;
679        resolve_options.extensions = extensions;
680        Ok(resolve_options.into())
681    }
682
683    /// Overrides the fully_specified flag for resolving
684    #[turbo_tasks::function]
685    pub async fn with_fully_specified(self: Vc<Self>, fully_specified: bool) -> Result<Vc<Self>> {
686        let mut resolve_options = self.owned().await?;
687        if resolve_options.fully_specified == fully_specified {
688            return Ok(self);
689        }
690        resolve_options.fully_specified = fully_specified;
691        Ok(resolve_options.cell())
692    }
693}
694
695#[turbo_tasks::value(shared)]
696#[derive(Hash, Clone, Debug)]
697pub struct ResolveModulesOptions {
698    pub modules: Vec<ResolveModules>,
699    pub extensions: Vec<RcStr>,
700}
701
702#[turbo_tasks::function]
703pub async fn resolve_modules_options(
704    options: Vc<ResolveOptions>,
705) -> Result<Vc<ResolveModulesOptions>> {
706    let options = options.await?;
707    Ok(ResolveModulesOptions {
708        modules: options.modules.clone(),
709        extensions: options.extensions.clone(),
710    }
711    .into())
712}
713
714#[turbo_tasks::value_trait]
715pub trait ImportMappingReplacement {
716    #[turbo_tasks::function]
717    fn replace(self: Vc<Self>, capture: Vc<Pattern>) -> Vc<ReplacedImportMapping>;
718    #[turbo_tasks::function]
719    fn result(
720        self: Vc<Self>,
721        lookup_path: FileSystemPath,
722        request: Vc<Request>,
723    ) -> Vc<ImportMapResult>;
724}