Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/rude-beds-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rspack/binding": patch
"@rspack/core": patch
"@rspack/cli": patch
---

feat: resolve.byDependency
1 change: 1 addition & 0 deletions crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ export interface RawResolveOptions {
symlinks?: boolean
tsConfigPath?: string
modules?: Array<string>
byDependency?: Record<string, RawResolveOptions>
}
export interface RawSnapshotStrategy {
hash: boolean
Expand Down
16 changes: 15 additions & 1 deletion crates/rspack_binding_options/src/options/raw_resolve.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;

use napi_derive::napi;
use rspack_core::{Alias, AliasMap, Resolve};
use rspack_core::{Alias, AliasMap, ByDependency, DependencyCategory, Resolve};
use serde::Deserialize;

pub type AliasValue = serde_json::Value;
Expand All @@ -26,6 +26,7 @@ pub struct RawResolveOptions {
pub symlinks: Option<bool>,
pub ts_config_path: Option<String>,
pub modules: Option<Vec<String>>,
pub by_dependency: Option<HashMap<String, RawResolveOptions>>,
}

fn normalize_alias(alias: Option<RawAliasOption>) -> anyhow::Result<Option<Alias>> {
Expand Down Expand Up @@ -76,6 +77,18 @@ impl TryFrom<RawResolveOptions> for Resolve {
let fallback = normalize_alias(value.fallback)?;
let modules = value.modules;
let tsconfig = value.ts_config_path.map(std::path::PathBuf::from);
let by_dependency = value
.by_dependency
.map(|i| {
i.into_iter()
.map(|(k, v)| {
let v = v.try_into()?;
Ok((DependencyCategory::from(k.as_str()), v))
})
.collect::<Result<ByDependency, Self::Error>>()
})
.transpose()?;

Ok(Resolve {
modules,
prefer_relative,
Expand All @@ -88,6 +101,7 @@ impl TryFrom<RawResolveOptions> for Resolve {
symlinks,
tsconfig,
fallback,
by_dependency,
})
}
}
190 changes: 11 additions & 179 deletions crates/rspack_core/src/compiler/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
use dashmap::DashMap;
use rustc_hash::FxHasher;

use crate::{AliasMap, DependencyType};
use crate::DependencyType;
use crate::{DependencyCategory, Resolve};

pub type ResolveResult = nodejs_resolver::ResolveResult<nodejs_resolver::Resource>;
Expand Down Expand Up @@ -42,7 +42,9 @@ impl ResolverFactory {
pub fn new(base_options: Resolve) -> Self {
let cache = Arc::new(nodejs_resolver::Cache::default());
let resolver = Resolver(nodejs_resolver::Resolver::new(
base_options.clone().to_inner_options(cache.clone(), false),
base_options
.clone()
.to_inner_options(cache.clone(), false, DependencyCategory::Unknown),
));
Self {
cache,
Expand All @@ -58,192 +60,22 @@ impl ResolverFactory {
} else {
let base_options = self.base_options.clone();
let merged_options = match &options.resolve_options {
Some(o) => merge_resolver_options(base_options, o.clone()),
None => match &self.base_options.condition_names {
None => {
let is_esm = matches!(options.dependency_category, DependencyCategory::Esm);
let condition_names = if is_esm {
vec![
String::from("import"),
String::from("module"),
String::from("webpack"),
String::from("development"),
String::from("browser"),
]
} else {
vec![
String::from("require"),
String::from("module"),
String::from("webpack"),
String::from("development"),
String::from("browser"),
]
};
let options = Resolve {
condition_names: Some(condition_names),
..self.base_options.clone()
};
merge_resolver_options(base_options, options)
}
_ => base_options,
},
Some(o) => base_options.merge(o.clone()),
None => base_options,
};
let resolver = Arc::new(Resolver(nodejs_resolver::Resolver::new(
merged_options.to_inner_options(self.cache.clone(), options.resolve_to_context),
merged_options.to_inner_options(
self.cache.clone(),
options.resolve_to_context,
options.dependency_category,
),
)));
self.resolvers.insert(options, resolver.clone());
resolver
}
}
}

fn merge_resolver_options(base: Resolve, other: Resolve) -> Resolve {
fn overwrite<T, F>(a: Option<T>, b: Option<T>, f: F) -> Option<T>
where
T: Clone,
F: FnOnce(T, T) -> T,
{
match (a, b) {
(Some(a), Some(b)) => Some(f(a, b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}

let alias = overwrite(base.alias, other.alias, |pre, mut now| {
now.extend(pre.into_iter());
let now: indexmap::IndexSet<(String, Vec<AliasMap>)> = now.into_iter().collect();
now.into_iter().collect()
});
let fallback = overwrite(base.fallback, other.fallback, |pre, mut now| {
now.extend(pre.into_iter());
let now: indexmap::IndexSet<(String, Vec<AliasMap>)> = now.into_iter().collect();
now.into_iter().collect()
});
let prefer_relative = overwrite(base.prefer_relative, other.prefer_relative, |_, value| {
value
});
let symlinks = overwrite(base.symlinks, other.symlinks, |_, value| value);
let browser_field = overwrite(base.browser_field, other.browser_field, |_, value| value);
let extensions = overwrite(base.extensions, other.extensions, |base, value| {
normalize_string_array(&base, value)
});
let main_files = overwrite(base.main_files, other.main_files, |base, value| {
normalize_string_array(&base, value)
});
let main_fields = overwrite(base.main_fields, other.main_fields, |base, value| {
normalize_string_array(&base, value)
});
let modules = overwrite(base.modules, other.modules, |base, value| {
normalize_string_array(&base, value)
});
let condition_names = overwrite(
base.condition_names,
other.condition_names,
|base, value| normalize_string_array(&base, value),
);
let tsconfig = other.tsconfig;

Resolve {
fallback,
modules,
alias,
prefer_relative,
symlinks,
browser_field,
extensions,
main_files,
main_fields,
condition_names,
tsconfig,
}
}

fn normalize_string_array(a: &[String], b: Vec<String>) -> Vec<String> {
b.into_iter().fold(vec![], |mut acc, item| {
if item.eq("...") {
acc.append(&mut a.to_vec());
} else {
acc.push(item);
}
acc
})
}

#[cfg(test)]
mod test {
use super::*;

fn to_string(a: Vec<&str>) -> Vec<String> {
a.into_iter().map(String::from).collect()
}

#[test]
fn test_merge_resolver_options() {
use crate::AliasMap;
let base = Resolve {
extensions: Some(to_string(vec!["a", "b"])),
alias: Some(vec![("c".to_string(), vec![AliasMap::Ignored])]),
symlinks: Some(false),
main_files: Some(to_string(vec!["d", "e", "f"])),
main_fields: Some(to_string(vec!["g", "h", "i"])),
browser_field: Some(true),
condition_names: Some(to_string(vec!["j", "k"])),
..Default::default()
};
let another = Resolve {
extensions: Some(to_string(vec!["a1", "b1"])),
alias: Some(vec![("c2".to_string(), vec![AliasMap::Ignored])]),
prefer_relative: Some(true),
browser_field: Some(true),
main_files: Some(to_string(vec!["d1", "e", "..."])),
main_fields: Some(to_string(vec!["...", "h", "..."])),
condition_names: Some(to_string(vec!["f", "..."])),
..Default::default()
};
let options = merge_resolver_options(base, another);
assert_eq!(
options.extensions.expect("should be Ok"),
to_string(vec!["a1", "b1"])
);
assert!(options.prefer_relative.expect("should be Ok"));
assert!(!options.symlinks.expect("should be Ok"));
assert_eq!(
options.main_files.expect("should be Ok"),
vec!["d1", "e", "d", "e", "f"]
);
assert_eq!(
options.main_fields.expect("should be Ok"),
vec!["g", "h", "i", "h", "g", "h", "i"]
);
assert_eq!(
options.alias.expect("should be Ok"),
vec![
("c2".to_string(), vec![AliasMap::Ignored]),
("c".to_string(), vec![AliasMap::Ignored])
]
);
assert_eq!(options.condition_names.expect("should be Ok").len(), 3);
}

#[test]
fn test_normalize_string_array() {
let base = to_string(vec!["base0", "base1"]);
assert!(normalize_string_array(&base, vec![]).is_empty());
assert_eq!(
normalize_string_array(&base, to_string(vec!["a", "b"])),
to_string(vec!["a", "b"])
);
assert_eq!(
normalize_string_array(&base, to_string(vec!["...", "a", "...", "b", "..."])),
to_string(vec![
"base0", "base1", "a", "base0", "base1", "b", "base0", "base1"
])
);
}
}

#[derive(Debug)]
pub struct Resolver(pub(crate) nodejs_resolver::Resolver);

Expand Down
14 changes: 14 additions & 0 deletions crates/rspack_core/src/dependency/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ pub enum DependencyCategory {
Wasm,
}

impl From<&str> for DependencyCategory {
fn from(value: &str) -> Self {
match value {
"esm" => Self::Esm,
"commonjs" => Self::CommonJS,
"url" => Self::Url,
"wasm" => Self::Wasm,
"css-import" => Self::CssImport,
"css-compose" => Self::CssCompose,
_ => Self::Unknown,
}
}
}

pub trait Dependency:
CodeGeneratable + AsAny + DynHash + DynClone + DynEq + Send + Sync + Debug
{
Expand Down
Loading