turbopack_core/resolve/
options.rs

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