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