Skip to main content

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