next.js/turbopack/crates/turbopack-core/src/resolve/options.rs
options.rs761 lines26.6 KB
use std::{collections::BTreeMap, future::Future, pin::Pin};

use anyhow::{Result, bail};
use bincode::{Decode, Encode};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
    FxIndexSet, NonLocalValue, ResolvedVc, TryJoinIterExt, ValueToString, Vc,
    debug::ValueDebugFormat, trace::TraceRawVcs, turbofmt,
};
use turbo_tasks_fs::{FileSystemPath, glob::Glob};

use crate::{
    issue::Issue,
    resolve::{
        AliasPattern, ExternalTraced, ExternalType, ResolveResult, ResolveResultItem,
        alias_map::{AliasMap, AliasTemplate},
        parse::Request,
        pattern::Pattern,
        plugin::{AfterResolvePlugin, BeforeResolvePlugin},
    },
};

#[turbo_tasks::value(transparent)]
#[derive(Debug)]
pub struct ExcludedExtensions(#[bincode(with = "turbo_bincode::indexset")] pub FxIndexSet<RcStr>);

/// A location where to resolve modules.
#[derive(
    TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, ValueDebugFormat, NonLocalValue, Encode, Decode,
)]
pub enum ResolveModules {
    /// when inside of path, use the list of directories to
    /// resolve inside these
    Nested(FileSystemPath, Vec<RcStr>),
    /// look into that directory, unless the request has an excluded extension
    Path {
        dir: FileSystemPath,
        excluded_extensions: ResolvedVc<ExcludedExtensions>,
    },
}

#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Copy, Debug, NonLocalValue, Encode, Decode)]
pub enum ConditionValue {
    Set,
    Unset,
    Unknown,
}

impl From<bool> for ConditionValue {
    fn from(v: bool) -> Self {
        if v {
            ConditionValue::Set
        } else {
            ConditionValue::Unset
        }
    }
}

pub type ResolutionConditions = BTreeMap<RcStr, ConditionValue>;

/// The different ways to resolve a package, as described in package.json.
#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, NonLocalValue, Encode, Decode)]
pub enum ResolveIntoPackage {
    /// Using the [exports] field.
    ///
    /// [exports]: https://nodejs.org/api/packages.html#exports
    ExportsField {
        conditions: ResolutionConditions,
        unspecified_conditions: ConditionValue,
    },
    /// Using a [main]-like field (e.g. [main], [module], [browser], etc.).
    ///
    /// [main]: https://nodejs.org/api/packages.html#main
    /// [module]: https://esbuild.github.io/api/#main-fields
    /// [browser]: https://esbuild.github.io/api/#main-fields
    MainField { field: RcStr },
}

// The different ways to resolve a request within a package
#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Debug, NonLocalValue, Encode, Decode)]
pub enum ResolveInPackage {
    /// Using a alias field which allows to map requests
    AliasField(RcStr),
    /// Using the [imports] field.
    ///
    /// [imports]: https://nodejs.org/api/packages.html#imports
    ImportsField {
        conditions: ResolutionConditions,
        unspecified_conditions: ConditionValue,
    },
}

#[turbo_tasks::value(shared)]
#[derive(Clone)]
pub enum ImportMapping {
    /// If specified, the optional name overrides the request, importing that external instead.
    External(Option<RcStr>, ExternalType, ExternalTraced),
    /// Try to make the request external if it is is resolvable from the specified
    /// directory, and fall back to resolving the original request if it fails.
    ///
    /// If specified, the optional name overrides the request, importing that external instead.
    PrimaryAlternativeExternal {
        name: Option<RcStr>,
        ty: ExternalType,
        traced: ExternalTraced,
        lookup_dir: FileSystemPath,
    },
    /// An already resolved result that will be returned directly.
    Direct(ResolvedVc<ResolveResult>),
    /// A request alias that will be resolved first, and fall back to resolving
    /// the original request if it fails. Useful for the tsconfig.json
    /// `compilerOptions.paths` option and Next aliases.
    PrimaryAlternative(RcStr, Option<FileSystemPath>),
    /// See [`ResolveResultItem::Ignore`]
    Ignore,
    /// See [`ResolveResultItem::Empty`]
    Empty,
    /// See [`ResolveResultItem::Error`]
    Error(ResolvedVc<Box<dyn Issue>>),
    Alternatives(Vec<ResolvedVc<ImportMapping>>),
    Dynamic(ResolvedVc<Box<dyn ImportMappingReplacement>>),
}

/// An `ImportMapping` that was applied to a pattern. See `ImportMapping` for
/// more details on the variants.
#[turbo_tasks::value(shared)]
#[derive(Clone)]
pub enum ReplacedImportMapping {
    External {
        name_override: Option<RcStr>,
        ty: ExternalType,
        traced: ExternalTraced,
        target: Option<FileSystemPath>,
    },
    PrimaryAlternativeExternal {
        name: Option<RcStr>,
        ty: ExternalType,
        traced: ExternalTraced,
        lookup_dir: FileSystemPath,
    },
    Direct(ResolvedVc<ResolveResult>),
    PrimaryAlternative(Pattern, Option<FileSystemPath>),
    Ignore,
    Empty,
    Alternatives(Vec<ResolvedVc<ReplacedImportMapping>>),
    Dynamic(ResolvedVc<Box<dyn ImportMappingReplacement>>),
    Error(ResolvedVc<Box<dyn Issue>>),
}

impl ImportMapping {
    pub fn primary_alternatives(
        list: Vec<RcStr>,
        lookup_path: Option<FileSystemPath>,
    ) -> ImportMapping {
        if list.is_empty() {
            ImportMapping::Ignore
        } else if list.len() == 1 {
            ImportMapping::PrimaryAlternative(list.into_iter().next().unwrap(), lookup_path)
        } else {
            ImportMapping::Alternatives(
                list.into_iter()
                    .map(|s| {
                        ImportMapping::PrimaryAlternative(s, lookup_path.clone()).resolved_cell()
                    })
                    .collect(),
            )
        }
    }
}

impl AliasTemplate for ResolvedVc<ImportMapping> {
    type Output<'a> = <Vc<ImportMapping> as AliasTemplate>::Output<'a>;

    fn convert(&self) -> Self::Output<'_> {
        (**self).convert()
    }

    fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
        (**self).replace(capture)
    }
}

impl AliasTemplate for Vc<ImportMapping> {
    type Output<'a> =
        Pin<Box<dyn Future<Output = Result<ResolvedVc<ReplacedImportMapping>>> + Send + 'a>>;

    fn convert(&self) -> Self::Output<'_> {
        Box::pin(async move {
            let this = &*self.await?;
            Ok(match this {
                ImportMapping::External(name, ty, traced) => ReplacedImportMapping::External {
                    name_override: name.clone(),
                    ty: *ty,
                    traced: *traced,
                    // TODO
                    target: None,
                },
                ImportMapping::PrimaryAlternativeExternal {
                    name,
                    ty,
                    traced,
                    lookup_dir,
                } => ReplacedImportMapping::PrimaryAlternativeExternal {
                    name: name.clone(),
                    ty: *ty,
                    traced: *traced,
                    lookup_dir: lookup_dir.clone(),
                },
                ImportMapping::PrimaryAlternative(name, lookup_dir) => {
                    ReplacedImportMapping::PrimaryAlternative(
                        (*name).clone().into(),
                        lookup_dir.clone(),
                    )
                }
                ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
                ImportMapping::Ignore => ReplacedImportMapping::Ignore,
                ImportMapping::Empty => ReplacedImportMapping::Empty,
                ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
                    alternatives
                        .iter()
                        .map(|mapping| mapping.convert())
                        .try_join()
                        .await?,
                ),
                ImportMapping::Dynamic(replacement) => ReplacedImportMapping::Dynamic(*replacement),
                ImportMapping::Error(issue) => ReplacedImportMapping::Error(*issue),
            }
            .resolved_cell())
        })
    }

    fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a> {
        let capture = capture.clone();
        Box::pin(async move {
            let this = &*self.await?;
            Ok(match this {
                ImportMapping::External(name, ty, traced) => {
                    if let Some(name) = name {
                        ReplacedImportMapping::External {
                            name_override: capture
                                .spread_into_star(name)
                                .as_constant_string()
                                .cloned(),
                            ty: *ty,
                            traced: *traced,
                            target: None,
                        }
                    } else {
                        ReplacedImportMapping::External {
                            name_override: None,
                            ty: *ty,
                            traced: *traced,
                            target: None,
                        }
                    }
                }
                ImportMapping::PrimaryAlternativeExternal {
                    name,
                    ty,
                    traced,
                    lookup_dir,
                } => {
                    if let Some(name) = name {
                        ReplacedImportMapping::PrimaryAlternativeExternal {
                            name: capture.spread_into_star(name).as_constant_string().cloned(),
                            ty: *ty,
                            traced: *traced,
                            lookup_dir: lookup_dir.clone(),
                        }
                    } else {
                        ReplacedImportMapping::PrimaryAlternativeExternal {
                            name: None,
                            ty: *ty,
                            traced: *traced,
                            lookup_dir: lookup_dir.clone(),
                        }
                    }
                }
                ImportMapping::PrimaryAlternative(name, lookup_dir) => {
                    ReplacedImportMapping::PrimaryAlternative(
                        capture.spread_into_star(name),
                        lookup_dir.clone(),
                    )
                }
                ImportMapping::Direct(v) => ReplacedImportMapping::Direct(*v),
                ImportMapping::Ignore => ReplacedImportMapping::Ignore,
                ImportMapping::Empty => ReplacedImportMapping::Empty,
                ImportMapping::Alternatives(alternatives) => ReplacedImportMapping::Alternatives(
                    alternatives
                        .iter()
                        .map(|mapping| mapping.replace(&capture))
                        .try_join()
                        .await?,
                ),
                ImportMapping::Dynamic(replacement) => {
                    replacement
                        .replace(Pattern::new(capture.clone()))
                        .owned()
                        .await?
                }
                ImportMapping::Error(issue) => ReplacedImportMapping::Error(*issue),
            }
            .resolved_cell())
        })
    }
}

#[turbo_tasks::value(shared)]
#[derive(Clone, Default)]
pub struct ImportMap {
    map: AliasMap<ResolvedVc<ImportMapping>>,
}

impl ImportMap {
    /// Creates a new import map.
    pub fn new(map: AliasMap<ResolvedVc<ImportMapping>>) -> ImportMap {
        Self { map }
    }

    /// Creates a new empty import map.
    pub fn empty() -> Self {
        Self::default()
    }

    /// Extends the import map with another import map.
    pub fn extend_ref(&mut self, other: &ImportMap) {
        let Self { map } = other.clone();
        self.map.extend(map);
    }

    /// Inserts an alias into the import map.
    pub fn insert_alias(&mut self, alias: AliasPattern, mapping: ResolvedVc<ImportMapping>) {
        self.map.insert(alias, mapping);
    }

    /// Inserts an exact alias into the import map.
    pub fn insert_exact_alias<'a>(
        &mut self,
        pattern: impl Into<RcStr> + 'a,
        mapping: ResolvedVc<ImportMapping>,
    ) {
        self.map.insert(AliasPattern::exact(pattern), mapping);
    }

    /// Inserts a wildcard alias into the import map.
    pub fn insert_wildcard_alias<'a>(
        &mut self,
        prefix: impl Into<RcStr> + 'a,
        mapping: ResolvedVc<ImportMapping>,
    ) {
        self.map
            .insert(AliasPattern::wildcard(prefix, rcstr!("")), mapping);
    }

    /// Inserts a wildcard alias with suffix into the import map.
    pub fn insert_wildcard_alias_with_suffix<'p, 's>(
        &mut self,
        prefix: impl Into<RcStr> + 'p,
        suffix: impl Into<RcStr> + 's,
        mapping: ResolvedVc<ImportMapping>,
    ) {
        self.map
            .insert(AliasPattern::wildcard(prefix, suffix), mapping);
    }

    /// Inserts an alias that resolves an prefix always from a certain location
    /// to create a singleton.
    pub fn insert_singleton_alias<'a>(
        &mut self,
        prefix: impl Into<RcStr> + 'a,
        context_path: FileSystemPath,
    ) {
        let prefix: RcStr = prefix.into();
        let wildcard_prefix: RcStr = (prefix.to_string() + "/").into();
        let wildcard_alias: RcStr = (prefix.to_string() + "/*").into();
        self.insert_exact_alias(
            prefix.clone(),
            ImportMapping::PrimaryAlternative(prefix.clone(), Some(context_path.clone()))
                .resolved_cell(),
        );
        self.insert_wildcard_alias(
            wildcard_prefix,
            ImportMapping::PrimaryAlternative(wildcard_alias, Some(context_path)).resolved_cell(),
        );
    }
}

#[turbo_tasks::value_impl]
impl ImportMap {
    /// Extends the underlying [ImportMap] with another [ImportMap].
    #[turbo_tasks::function]
    pub async fn extend(self: Vc<Self>, other: ResolvedVc<ImportMap>) -> Result<Vc<Self>> {
        let mut import_map = self.owned().await?;
        import_map.extend_ref(&*other.await?);
        Ok(import_map.cell())
    }
}

#[turbo_tasks::value(shared)]
#[derive(Clone, Default)]
pub struct ResolvedMap {
    pub by_glob: Vec<(FileSystemPath, ResolvedVc<Glob>, ResolvedVc<ImportMapping>)>,
}

#[turbo_tasks::value(shared)]
#[derive(Clone)]
pub enum ImportMapResult {
    Result(ResolvedVc<ResolveResult>),
    External {
        name: RcStr,
        ty: ExternalType,
        traced: ExternalTraced,
        target: Option<FileSystemPath>,
    },
    AliasExternal {
        name: RcStr,
        ty: ExternalType,
        traced: ExternalTraced,
        lookup_dir: FileSystemPath,
    },
    Alias(ResolvedVc<Request>, Option<FileSystemPath>),
    Alternatives(Vec<ImportMapResult>),
    NoEntry,
    Error(ResolvedVc<Box<dyn Issue>>),
}

async fn import_mapping_to_result(
    mapping: Vc<ReplacedImportMapping>,
    lookup_path: FileSystemPath,
    request: Vc<Request>,
) -> Result<ImportMapResult> {
    Ok(match &*mapping.await? {
        ReplacedImportMapping::Direct(result) => ImportMapResult::Result(*result),
        ReplacedImportMapping::External {
            name_override,
            ty,
            traced,
            target,
        } => ImportMapResult::External {
            name: if let Some(name) = name_override {
                name.clone()
            } else if let Some(request) = request.await?.request() {
                request
            } else {
                bail!(
                    "Cannot resolve external reference with dynamic request {:?}",
                    request.request_pattern().await?.describe_as_string()
                )
            },
            ty: *ty,
            traced: *traced,
            target: target.clone(),
        },
        ReplacedImportMapping::PrimaryAlternativeExternal {
            name,
            ty,
            traced,
            lookup_dir,
        } => ImportMapResult::AliasExternal {
            name: if let Some(name) = name {
                name.clone()
            } else if let Some(request) = request.await?.request() {
                request
            } else {
                bail!(
                    "Cannot resolve external reference with dynamic request {:?}",
                    request.request_pattern().await?.describe_as_string()
                )
            },
            ty: *ty,
            traced: *traced,
            lookup_dir: lookup_dir.clone(),
        },
        ReplacedImportMapping::Ignore => ImportMapResult::Result(
            ResolveResult::primary(ResolveResultItem::Ignore).resolved_cell(),
        ),
        ReplacedImportMapping::Empty => ImportMapResult::Result(
            ResolveResult::primary(ResolveResultItem::Empty).resolved_cell(),
        ),
        ReplacedImportMapping::PrimaryAlternative(name, context) => {
            let request = Request::parse(name.clone()).to_resolved().await?;
            ImportMapResult::Alias(request, context.clone())
        }
        ReplacedImportMapping::Alternatives(list) => ImportMapResult::Alternatives(
            list.iter()
                .map(|mapping| {
                    Box::pin(import_mapping_to_result(
                        **mapping,
                        lookup_path.clone(),
                        request,
                    ))
                })
                .try_join()
                .await?,
        ),
        ReplacedImportMapping::Dynamic(replacement) => {
            replacement.result(lookup_path, request).owned().await?
        }
        ReplacedImportMapping::Error(issue) => ImportMapResult::Error(*issue),
    })
}

#[turbo_tasks::value_impl]
impl ValueToString for ImportMapResult {
    #[turbo_tasks::function]
    async fn to_string(&self) -> Result<Vc<RcStr>> {
        match self {
            ImportMapResult::Result(_) => Ok(Vc::cell(rcstr!("Resolved by import map"))),
            ImportMapResult::External { .. } => Ok(Vc::cell(rcstr!("TODO external"))),
            ImportMapResult::AliasExternal { .. } => Ok(Vc::cell(rcstr!("TODO external"))),
            ImportMapResult::Alias(request, context) => {
                let s = if let Some(path) = context {
                    turbofmt!("aliased to {} inside of {path}", *request).await?
                } else {
                    turbofmt!("aliased to {}", *request).await?
                };
                Ok(Vc::cell(s))
            }
            ImportMapResult::Alternatives(alternatives) => {
                // The .cell() calls happen synchronously during iteration, before try_join()
                // runs the futures. This is safe because cells are created in deterministic
                // order (the order of alternatives in the Vec).
                let strings = alternatives
                    .iter()
                    .map(|alternative| alternative.clone().cell().to_string())
                    .try_join()
                    .await?;
                let strings = strings
                    .iter()
                    .map(|string| string.as_str())
                    .collect::<Vec<_>>();
                Ok(Vc::cell(strings.join(" | ").into()))
            }
            ImportMapResult::NoEntry => Ok(Vc::cell(rcstr!("No import map entry"))),
            ImportMapResult::Error(issue) => Ok(Vc::cell(
                format!(
                    "error: {}",
                    issue
                        .into_trait_ref()
                        .await?
                        .title()
                        .await?
                        .to_unstyled_string()
                )
                .into(),
            )),
        }
    }
}

impl ImportMap {
    // Not a turbo-tasks function: the map lookup should be cheaper than the cache
    // lookup
    pub async fn lookup(
        &self,
        lookup_path: FileSystemPath,
        request: Vc<Request>,
    ) -> Result<ImportMapResult> {
        // relative requests must not match global wildcard aliases.

        let request_pattern = request.request_pattern().await?;
        if matches!(*request_pattern, Pattern::Dynamic | Pattern::DynamicNoSlash) {
            // You could probably conceive of cases where this isn't correct. But the dynamic will
            // just match every single entry in the import map, which is not what we want.
            return Ok(ImportMapResult::NoEntry);
        }

        let (req_rel, rest) = request_pattern.split_could_match("./");
        let (req_rel_parent, req_rest) =
            rest.map(|r| r.split_could_match("../")).unwrap_or_default();

        let lookup_rel = req_rel.as_ref().and_then(|req| {
            self.map
                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("./"))
                .next()
        });
        let lookup_rel_parent = req_rel_parent.as_ref().and_then(|req| {
            self.map
                .lookup_with_prefix_predicate(req, |prefix| prefix.starts_with("../"))
                .next()
        });
        let lookup = req_rest
            .as_ref()
            .and_then(|req| self.map.lookup(req).next());

        let results = lookup_rel
            .into_iter()
            .chain(lookup_rel_parent)
            .chain(lookup)
            .map(async |result| {
                import_mapping_to_result(*result?.output.await?, lookup_path.clone(), request).await
            })
            .try_join()
            .await?;

        Ok(match results.len() {
            0 => ImportMapResult::NoEntry,
            1 => results.into_iter().next().unwrap(),
            2.. => ImportMapResult::Alternatives(results),
        })
    }
}

#[turbo_tasks::value_impl]
impl ResolvedMap {
    #[turbo_tasks::function]
    pub async fn lookup(
        &self,
        resolved: FileSystemPath,
        lookup_path: FileSystemPath,
        request: Vc<Request>,
    ) -> Result<Vc<ImportMapResult>> {
        for (root, glob, mapping) in self.by_glob.iter() {
            if let Some(path) = root.get_path_to(&resolved)
                && glob.await?.matches(path)
            {
                return Ok(import_mapping_to_result(
                    *mapping.convert().await?,
                    lookup_path,
                    request,
                )
                .await?
                .cell());
            }
        }
        Ok(ImportMapResult::NoEntry.cell())
    }
}

#[turbo_tasks::value(shared)]
#[derive(Clone, Debug, Default)]
pub struct ResolveOptions {
    /// When set, do not apply extensions and default_files for relative
    /// request. But they are still applied for resolving into packages.
    pub fully_specified: bool,
    /// When set, when resolving a module request, try to resolve it as relative
    /// request first.
    pub prefer_relative: bool,
    /// The extensions that should be added to a request when resolving.
    pub extensions: Vec<RcStr>,
    /// The locations where to resolve modules.
    pub modules: Vec<ResolveModules>,
    /// How to resolve packages.
    pub into_package: Vec<ResolveIntoPackage>,
    /// How to resolve in packages.
    pub in_package: Vec<ResolveInPackage>,
    /// The default files to resolve in a folder.
    pub default_files: Vec<RcStr>,
    /// An import map to use before resolving a request.
    pub import_map: Option<ResolvedVc<ImportMap>>,
    /// An import map to use when a request is otherwise unresolvable.
    pub fallback_import_map: Option<ResolvedVc<ImportMap>>,
    pub resolved_map: Option<ResolvedVc<ResolvedMap>>,
    pub before_resolve_plugins: Vec<ResolvedVc<Box<dyn BeforeResolvePlugin>>>,
    pub after_resolve_plugins: Vec<ResolvedVc<Box<dyn AfterResolvePlugin>>>,
    /// Support resolving *.js requests to *.ts files
    pub enable_typescript_with_output_extension: bool,
    /// Warn instead of error for resolve errors
    pub loose_errors: bool,
    /// Collect affecting sources for each resolve result.  Useful for tracing.
    pub collect_affecting_sources: bool,
    /// Whether to parse data URIs into modules (as opposed to keeping them as externals)
    pub parse_data_uris: bool,

    pub placeholder_for_future_extensions: (),
}

#[turbo_tasks::value_impl]
impl ResolveOptions {
    /// Returns a new `Vc<ResolveOptions>` with its import map extended to include the given import
    /// map.
    #[turbo_tasks::function]
    pub async fn with_extended_import_map(
        self: Vc<Self>,
        import_map: Vc<ImportMap>,
    ) -> Result<Vc<Self>> {
        let mut resolve_options = self.owned().await?;
        resolve_options.import_map = Some(
            resolve_options
                .import_map
                .map(|current_import_map| current_import_map.extend(import_map))
                .unwrap_or(import_map)
                .to_resolved()
                .await?,
        );
        Ok(resolve_options.cell())
    }

    /// Returns a new `Vc<ResolveOptions>` with its fallback import map extended to include the
    /// given import map.
    #[turbo_tasks::function]
    pub async fn with_extended_fallback_import_map(
        self: Vc<Self>,
        extended_import_map: ResolvedVc<ImportMap>,
    ) -> Result<Vc<Self>> {
        let mut resolve_options = self.owned().await?;
        resolve_options.fallback_import_map =
            if let Some(current_fallback) = resolve_options.fallback_import_map {
                Some(
                    current_fallback
                        .extend(*extended_import_map)
                        .to_resolved()
                        .await?,
                )
            } else {
                Some(extended_import_map)
            };
        Ok(resolve_options.cell())
    }

    /// Overrides the extensions used for resolving
    #[turbo_tasks::function]
    pub async fn with_extensions(self: Vc<Self>, extensions: Vec<RcStr>) -> Result<Vc<Self>> {
        let mut resolve_options = self.owned().await?;
        resolve_options.extensions = extensions;
        Ok(resolve_options.cell())
    }

    /// Overrides the fully_specified flag for resolving
    #[turbo_tasks::function]
    pub async fn with_fully_specified(self: Vc<Self>, fully_specified: bool) -> Result<Vc<Self>> {
        let mut resolve_options = self.owned().await?;
        if resolve_options.fully_specified == fully_specified {
            return Ok(self);
        }
        resolve_options.fully_specified = fully_specified;
        Ok(resolve_options.cell())
    }
}

#[turbo_tasks::value(shared)]
#[derive(Hash, Clone, Debug)]
pub struct ResolveModulesOptions {
    pub modules: Vec<ResolveModules>,
    pub extensions: Vec<RcStr>,
}

#[turbo_tasks::function]
pub async fn resolve_modules_options(
    options: Vc<ResolveOptions>,
) -> Result<Vc<ResolveModulesOptions>> {
    let options = options.await?;
    Ok(ResolveModulesOptions {
        modules: options.modules.clone(),
        extensions: options.extensions.clone(),
    }
    .cell())
}

#[turbo_tasks::value_trait]
pub trait ImportMappingReplacement {
    #[turbo_tasks::function]
    fn replace(self: Vc<Self>, capture: Vc<Pattern>) -> Vc<ReplacedImportMapping>;
    #[turbo_tasks::function]
    fn result(
        self: Vc<Self>,
        lookup_path: FileSystemPath,
        request: Vc<Request>,
    ) -> Vc<ImportMapResult>;
}
Quest for Codev2.0.0
/
SIGN IN