mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 12:51:57 +00:00
357 lines
13 KiB
Rust
357 lines
13 KiB
Rust
use std::{fmt, path::PathBuf};
|
|
|
|
/// Module Resolution Options
|
|
///
|
|
/// Options are directly ported from [enhanced-resolve](https://github.com/webpack/enhanced-resolve#resolver-options).
|
|
///
|
|
/// See [webpack resolve](https://webpack.js.org/configuration/resolve/) for information and examples
|
|
#[derive(Debug, Clone)]
|
|
pub struct ResolveOptions {
|
|
/// Path to TypeScript configuration file.
|
|
///
|
|
/// Default `None`
|
|
pub tsconfig: Option<TsconfigOptions>,
|
|
|
|
/// Create aliases to import or require certain modules more easily.
|
|
/// A trailing $ can also be added to the given object's keys to signify an exact match.
|
|
pub alias: Alias,
|
|
|
|
/// A list of alias fields in description files.
|
|
/// Specify a field, such as `browser`, to be parsed according to [this specification](https://github.com/defunctzombie/package-browser-field-spec).
|
|
/// Can be a path to json object such as `["path", "to", "exports"]`.
|
|
///
|
|
/// Default `[]`
|
|
pub alias_fields: Vec<Vec<String>>,
|
|
|
|
/// Condition names for exports field which defines entry points of a package.
|
|
/// The key order in the exports field is significant. During condition matching, earlier entries have higher priority and take precedence over later entries.
|
|
///
|
|
/// Default `[]`
|
|
pub condition_names: Vec<String>,
|
|
|
|
/// The JSON files to use for descriptions. (There was once a `bower.json`.)
|
|
///
|
|
/// Default `["package.json"]`
|
|
pub description_files: Vec<String>,
|
|
|
|
/// If true, it will not allow extension-less files.
|
|
/// So by default `require('./foo')` works if `./foo` has a `.js` extension,
|
|
/// but with this enabled only `require('./foo.js')` will work.
|
|
///
|
|
/// Default to `true` when [ResolveOptions::extensions] contains an empty string.
|
|
/// Use `Some(false)` to disable the behavior.
|
|
/// See <https://github.com/webpack/enhanced-resolve/pull/285>
|
|
///
|
|
/// Default None, which is the same as `Some(false)` when the above empty rule is not applied.
|
|
pub enforce_extension: EnforceExtension,
|
|
|
|
/// A list of exports fields in description files.
|
|
/// Can be a path to json object such as `["path", "to", "exports"]`.
|
|
///
|
|
/// Default `[["exports"]]`.
|
|
pub exports_fields: Vec<Vec<String>>,
|
|
|
|
/// An object which maps extension to extension aliases.
|
|
///
|
|
/// Default `{}`
|
|
pub extension_alias: Vec<(String, Vec<String>)>,
|
|
|
|
/// Attempt to resolve these extensions in order.
|
|
/// If multiple files share the same name but have different extensions,
|
|
/// will resolve the one with the extension listed first in the array and skip the rest.
|
|
///
|
|
/// Default `[".js", ".json", ".node"]`
|
|
pub extensions: Vec<String>,
|
|
|
|
/// Redirect module requests when normal resolving fails.
|
|
///
|
|
/// Default `[]`
|
|
pub fallback: Alias,
|
|
|
|
/// Request passed to resolve is already fully specified and extensions or main files are not resolved for it (they are still resolved for internal requests).
|
|
///
|
|
/// See also webpack configuration [resolve.fullySpecified](https://webpack.js.org/configuration/module/#resolvefullyspecified)
|
|
///
|
|
/// Default `false`
|
|
pub fully_specified: bool,
|
|
|
|
/// A list of main fields in description files
|
|
///
|
|
/// Default `["main"]`.
|
|
pub main_fields: Vec<String>,
|
|
|
|
/// The filename to be used while resolving directories.
|
|
///
|
|
/// Default `["index"]`
|
|
pub main_files: Vec<String>,
|
|
|
|
/// A list of directories to resolve modules from, can be absolute path or folder name.
|
|
///
|
|
/// Default `["node_modules"]`
|
|
pub modules: Vec<String>,
|
|
|
|
/// Resolve to a context instead of a file.
|
|
///
|
|
/// Default `false`
|
|
pub resolve_to_context: bool,
|
|
|
|
/// Prefer to resolve module requests as relative requests instead of using modules from node_modules directories.
|
|
///
|
|
/// Default `false`
|
|
pub prefer_relative: bool,
|
|
|
|
/// Prefer to resolve server-relative urls as absolute paths before falling back to resolve in ResolveOptions::roots.
|
|
///
|
|
/// Default `false`
|
|
pub prefer_absolute: bool,
|
|
|
|
/// A list of resolve restrictions to restrict the paths that a request can be resolved on.
|
|
///
|
|
/// Default `[]`
|
|
pub restrictions: Vec<Restriction>,
|
|
|
|
/// A list of directories where requests of server-relative URLs (starting with '/') are resolved.
|
|
/// On non-Windows systems these requests are resolved as an absolute path first.
|
|
///
|
|
/// Default `[]`
|
|
pub roots: Vec<PathBuf>,
|
|
|
|
/// Whether to resolve symlinks to their symlinked location.
|
|
/// When enabled, symlinked resources are resolved to their real path, not their symlinked location.
|
|
/// Note that this may cause module resolution to fail when using tools that symlink packages (like npm link).
|
|
///
|
|
/// Default `true`
|
|
pub symlinks: bool,
|
|
|
|
/// Whether to parse [module.builtinModules](https://nodejs.org/api/module.html#modulebuiltinmodules) or not.
|
|
/// For example, "zlib" will throw [ResolveError::Builtin] when set to true.
|
|
///
|
|
/// Default `false`
|
|
pub builtin_modules: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum EnforceExtension {
|
|
Auto,
|
|
Enabled,
|
|
Disabled,
|
|
}
|
|
|
|
impl EnforceExtension {
|
|
pub fn is_auto(&self) -> bool {
|
|
*self == Self::Auto
|
|
}
|
|
|
|
pub fn is_enabled(&self) -> bool {
|
|
*self == Self::Enabled
|
|
}
|
|
|
|
pub fn is_disabled(&self) -> bool {
|
|
*self == Self::Disabled
|
|
}
|
|
}
|
|
|
|
/// Alias for [ResolveOptions::alias] and [ResolveOptions::fallback].
|
|
pub type Alias = Vec<(String, Vec<AliasValue>)>;
|
|
|
|
/// Alias Value for [ResolveOptions::alias] and [ResolveOptions::fallback].
|
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
|
pub enum AliasValue {
|
|
/// The path value
|
|
Path(String),
|
|
|
|
/// The `false` value
|
|
Ignore,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Restriction {
|
|
Path(PathBuf),
|
|
RegExp(String),
|
|
}
|
|
|
|
/// Tsconfig Options
|
|
///
|
|
/// Derived from [tsconfig-paths-webpack-plugin](https://github.com/dividab/tsconfig-paths-webpack-plugin#options)
|
|
#[derive(Debug, Clone)]
|
|
pub struct TsconfigOptions {
|
|
/// Allows you to specify where to find the TypeScript configuration file.
|
|
/// You may provide
|
|
/// * a relative path to the configuration file. It will be resolved relative to cwd.
|
|
/// * an absolute path to the configuration file.
|
|
pub config_file: PathBuf,
|
|
|
|
/// Support for Typescript Project References.
|
|
pub references: TsconfigReferences,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum TsconfigReferences {
|
|
Disabled,
|
|
/// Use the `references` field from tsconfig read from `config_file`.
|
|
Auto,
|
|
/// Manually provided relative or absolute path.
|
|
Paths(Vec<PathBuf>),
|
|
}
|
|
|
|
impl Default for ResolveOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
tsconfig: None,
|
|
alias: vec![],
|
|
alias_fields: vec![],
|
|
condition_names: vec![],
|
|
description_files: vec!["package.json".into()],
|
|
enforce_extension: EnforceExtension::Auto,
|
|
extension_alias: vec![],
|
|
exports_fields: vec![vec!["exports".into()]],
|
|
extensions: vec![".js".into(), ".json".into(), ".node".into()],
|
|
fallback: vec![],
|
|
fully_specified: false,
|
|
main_fields: vec!["main".into()],
|
|
main_files: vec!["index".into()],
|
|
modules: vec!["node_modules".into()],
|
|
resolve_to_context: false,
|
|
prefer_relative: false,
|
|
prefer_absolute: false,
|
|
restrictions: vec![],
|
|
roots: vec![],
|
|
symlinks: true,
|
|
builtin_modules: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ResolveOptions {
|
|
pub(crate) fn sanitize(mut self) -> Self {
|
|
// Set `enforceExtension` to `true` when [ResolveOptions::extensions] contains an empty string.
|
|
// See <https://github.com/webpack/enhanced-resolve/pull/285>
|
|
if self.enforce_extension == EnforceExtension::Auto {
|
|
if !self.extensions.is_empty() && self.extensions.iter().any(String::is_empty) {
|
|
self.enforce_extension = EnforceExtension::Enabled;
|
|
} else {
|
|
self.enforce_extension = EnforceExtension::Disabled;
|
|
}
|
|
}
|
|
self
|
|
}
|
|
}
|
|
|
|
// For tracing
|
|
impl fmt::Display for ResolveOptions {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
if let Some(tsconfig) = &self.tsconfig {
|
|
write!(f, "tsconfig:{tsconfig:?},")?;
|
|
}
|
|
if !self.alias.is_empty() {
|
|
write!(f, "alias:{:?},", self.alias)?;
|
|
}
|
|
if !self.alias_fields.is_empty() {
|
|
write!(f, "alias_fields:{:?},", self.alias_fields)?;
|
|
}
|
|
if !self.condition_names.is_empty() {
|
|
write!(f, "condition_names:{:?},", self.condition_names)?;
|
|
}
|
|
if self.enforce_extension.is_enabled() {
|
|
write!(f, "enforce_extension:{:?},", self.enforce_extension)?;
|
|
}
|
|
if !self.exports_fields.is_empty() {
|
|
write!(f, "exports_fields:{:?},", self.exports_fields)?;
|
|
}
|
|
if !self.extension_alias.is_empty() {
|
|
write!(f, "extension_alias:{:?},", self.extension_alias)?;
|
|
}
|
|
if !self.extensions.is_empty() {
|
|
write!(f, "extensions:{:?},", self.extensions)?;
|
|
}
|
|
if !self.fallback.is_empty() {
|
|
write!(f, "fallback:{:?},", self.fallback)?;
|
|
}
|
|
if self.fully_specified {
|
|
write!(f, "fully_specified:{:?},", self.fully_specified)?;
|
|
}
|
|
if !self.main_fields.is_empty() {
|
|
write!(f, "main_fields:{:?},", self.main_fields)?;
|
|
}
|
|
if !self.main_files.is_empty() {
|
|
write!(f, "main_files:{:?},", self.main_files)?;
|
|
}
|
|
if !self.modules.is_empty() {
|
|
write!(f, "modules:{:?},", self.modules)?;
|
|
}
|
|
if self.resolve_to_context {
|
|
write!(f, "resolve_to_context:{:?},", self.resolve_to_context)?;
|
|
}
|
|
if self.prefer_relative {
|
|
write!(f, "prefer_relative:{:?},", self.prefer_relative)?;
|
|
}
|
|
if self.prefer_absolute {
|
|
write!(f, "prefer_absolute:{:?},", self.prefer_absolute)?;
|
|
}
|
|
if !self.restrictions.is_empty() {
|
|
write!(f, "restrictions:{:?},", self.restrictions)?;
|
|
}
|
|
if !self.roots.is_empty() {
|
|
write!(f, "roots:{:?},", self.roots)?;
|
|
}
|
|
if self.symlinks {
|
|
write!(f, "symlinks:{:?},", self.symlinks)?;
|
|
}
|
|
if self.builtin_modules {
|
|
write!(f, "builtin_modules:{:?},", self.builtin_modules)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::{
|
|
AliasValue, EnforceExtension, ResolveOptions, Restriction, TsconfigOptions,
|
|
TsconfigReferences,
|
|
};
|
|
use std::path::PathBuf;
|
|
|
|
#[test]
|
|
fn enforce_extension() {
|
|
assert!(EnforceExtension::Auto.is_auto());
|
|
assert!(!EnforceExtension::Enabled.is_auto());
|
|
assert!(!EnforceExtension::Disabled.is_auto());
|
|
|
|
assert!(!EnforceExtension::Auto.is_enabled());
|
|
assert!(EnforceExtension::Enabled.is_enabled());
|
|
assert!(!EnforceExtension::Disabled.is_enabled());
|
|
|
|
assert!(!EnforceExtension::Auto.is_disabled());
|
|
assert!(!EnforceExtension::Enabled.is_disabled());
|
|
assert!(EnforceExtension::Disabled.is_disabled());
|
|
}
|
|
|
|
#[test]
|
|
fn display() {
|
|
let options = ResolveOptions {
|
|
tsconfig: Some(TsconfigOptions {
|
|
config_file: PathBuf::from("tsconfig.json"),
|
|
references: TsconfigReferences::Auto,
|
|
}),
|
|
alias: vec![("a".into(), vec![AliasValue::Ignore])],
|
|
alias_fields: vec![vec!["browser".into()]],
|
|
condition_names: vec!["require".into()],
|
|
enforce_extension: EnforceExtension::Enabled,
|
|
extension_alias: vec![(".js".into(), vec![".ts".into()])],
|
|
exports_fields: vec![vec!["exports".into()]],
|
|
fallback: vec![("fallback".into(), vec![AliasValue::Ignore])],
|
|
fully_specified: true,
|
|
resolve_to_context: true,
|
|
prefer_relative: true,
|
|
prefer_absolute: true,
|
|
restrictions: vec![Restriction::Path(PathBuf::from("restrictions"))],
|
|
roots: vec![PathBuf::from("roots")],
|
|
builtin_modules: true,
|
|
..ResolveOptions::default()
|
|
};
|
|
|
|
let expected = r#"tsconfig:TsconfigOptions { config_file: "tsconfig.json", references: Auto },alias:[("a", [Ignore])],alias_fields:[["browser"]],condition_names:["require"],enforce_extension:Enabled,exports_fields:[["exports"]],extension_alias:[(".js", [".ts"])],extensions:[".js", ".json", ".node"],fallback:[("fallback", [Ignore])],fully_specified:true,main_fields:["main"],main_files:["index"],modules:["node_modules"],resolve_to_context:true,prefer_relative:true,prefer_absolute:true,restrictions:[Path("restrictions")],roots:["roots"],symlinks:true,builtin_modules:true,"#;
|
|
assert_eq!(format!("{options}"), expected);
|
|
}
|
|
}
|