feat(resolver): add preferRelative and preferAbsolute (#577)

This commit is contained in:
Boshen 2023-07-20 15:41:06 +08:00 committed by GitHub
parent beb0ae9d8e
commit 30b9731843
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 50 deletions

View file

@ -28,8 +28,8 @@
| | plugins | [] | A list of additional resolve plugins which should be applied |
| | resolver | undefined | A prepared Resolver to which the plugins are attached |
| | resolveToContext | false | Resolve to a context instead of a file |
| | preferRelative | false | Prefer to resolve module requests as relative request and fallback to resolving as module |
| | preferAbsolute | false | Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots |
| | preferRelative | false | Prefer to resolve module requests as relative request and fallback to resolving as module |
| | preferAbsolute | false | Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots |
| | restrictions | [] | A list of resolve restrictions |
| ✅ | roots | [] | A list of root paths |
| | symlinks | true | Whether to resolve symlinks to their symlinked location |
@ -61,7 +61,7 @@ Tests ported from [enhanced-resolve](https://github.com/webpack/enhanced-resolve
- [ ] pr-53.test.js
- [x] resolve.test.js (need to add resolveToContext)
- [ ] restrictions.test.js
- [x] roots.test.js (need to add resolveToContext, resolverPreferAbsolute)
- [x] roots.test.js (need to add resolveToContext)
- [x] scoped-packages.test.js
- [x] simple.test.js
- [ ] symlink.test.js

View file

@ -96,13 +96,10 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
})
}
/// require(X) from module at path Y
/// X: request
/// Y: path
fn require(&self, path: &Path, request: &Request) -> Result<PathBuf, ResolveError> {
// X: request
// Y: path
// require(X) from module at path Y
let mut path = path;
let request_str;
match request.path {
// 1. If X is a core module,
// a. return the core module
@ -110,47 +107,20 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
// 2. If X begins with '/'
// a. set Y to be the file system root
RequestPath::Absolute(absolute_path) => {
path = Path::new("/");
request_str = absolute_path;
if !self.options.roots.is_empty() {
for root in &self.options.roots {
if let Ok(path) =
self.require_relative(root, absolute_path.trim_start_matches('/'))
{
return Ok(path);
}
if !self.options.prefer_relative && self.options.prefer_absolute {
if let Ok(path) = self.require_path(path, absolute_path) {
return Ok(path);
}
return Err(ResolveError::NotFound(
Path::new(absolute_path).to_path_buf().into_boxed_path(),
));
}
self.load_roots(path, absolute_path)
}
// 3. If X begins with './' or '/' or '../'
RequestPath::Relative(relative_path) => {
return self.require_relative(path, relative_path);
}
RequestPath::Relative(relative_path) => self.require_relative(path, relative_path),
// 4. If X begins with '#'
RequestPath::Hash(hash_path) => {
request_str = hash_path;
}
RequestPath::Hash(hash_path) => self.require_path(path, hash_path),
// a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
RequestPath::Module(module_path) => {
request_str = module_path;
}
RequestPath::Module(module_path) => self.require_path(path, module_path),
}
// 5. LOAD_PACKAGE_SELF(X, dirname(Y))
if let Some(path) = self.load_package_self(path, request_str)? {
return Ok(path);
}
// 6. LOAD_NODE_MODULES(X, dirname(Y))
if let Some(path) = self.load_node_modules(path, request_str)? {
return Ok(path);
}
if let Some(path) = self.load_as_file(&path.join(request_str))? {
return Ok(path);
}
// 7. THROW "not found"
Err(ResolveError::NotFound(path.to_path_buf().into_boxed_path()))
}
// 3. If X begins with './' or '/' or '../'
@ -173,6 +143,22 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
Err(ResolveError::NotFound(path.into_boxed_path()))
}
fn require_path(&self, path: &Path, request_str: &str) -> Result<PathBuf, ResolveError> {
// 5. LOAD_PACKAGE_SELF(X, dirname(Y))
if let Some(path) = self.load_package_self(path, request_str)? {
return Ok(path);
}
// 6. LOAD_NODE_MODULES(X, dirname(Y))
if let Some(path) = self.load_node_modules(path, request_str)? {
return Ok(path);
}
if let Some(path) = self.load_as_file(&path.join(request_str))? {
return Ok(path);
}
// 7. THROW "not found"
Err(ResolveError::NotFound(path.to_path_buf().into_boxed_path()))
}
#[allow(clippy::unnecessary_wraps)]
fn load_as_file(&self, path: &Path) -> ResolveState {
// enhanced-resolve feature: extension_alias
@ -345,4 +331,17 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
}
Err(ResolveError::ExtensionAlias)
}
fn load_roots(&self, path: &Path, request_str: &str) -> Result<PathBuf, ResolveError> {
debug_assert!(request_str.starts_with('/'));
if self.options.roots.is_empty() {
return self.require_path(Path::new("/"), request_str);
}
for root in &self.options.roots {
if let Ok(path) = self.require_relative(root, request_str.trim_start_matches('/')) {
return Ok(path);
}
}
Err(ResolveError::NotFound(path.to_path_buf().into_boxed_path()))
}
}

View file

@ -61,6 +61,16 @@ pub struct ResolveOptions {
/// Default `["node_modules"]`
pub modules: Vec<String>,
/// 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 directories where requests of server-relative URLs (starting with '/') are resolved.
/// On non-Windows systems these requests are resolved as an absolute path first.
///
@ -80,6 +90,8 @@ impl Default for ResolveOptions {
fallback: vec![],
main_files: vec!["index".into()],
modules: vec!["node_modules".into()],
prefer_relative: false,
prefer_absolute: false,
roots: vec![],
}
}

View file

@ -1,6 +1,6 @@
//! <https://github.com/webpack/enhanced-resolve/blob/main/test/resolve.test.js>
use oxc_resolver::{Resolution, Resolver};
use oxc_resolver::{Resolution, ResolveOptions, Resolver};
#[test]
fn resolve() {
@ -11,7 +11,7 @@ fn resolve() {
let main1_js_path = f.join("main1.js").to_string_lossy().to_string();
#[rustfmt::skip]
let data = [
let pass = [
("absolute path", f.clone(), main1_js_path.as_str(), f.join("main1.js")),
("file with .js", f.clone(), "./main1.js", f.join("main1.js")),
("file without extension", f.clone(), "./main1", f.join("main1.js")),
@ -42,7 +42,7 @@ fn resolve() {
// ("handle fragment escaping", f.clone(), "./no\0#fragment/\0#/\0##fragment", f.join("no\0#fragment/\0#\0#.js#fragment")),
];
for (comment, path, request, expected) in data {
for (comment, path, request, expected) in pass {
let resolved_path = resolver.resolve(&path, request).map(Resolution::full_path);
assert_eq!(resolved_path, Ok(expected), "{comment} {path:?} {request}");
}
@ -53,8 +53,23 @@ fn resolve() {
fn issue238_resolve() {}
#[test]
#[ignore = "preferRelativeResolve"]
fn prefer_relative_resolve() {}
fn prefer_relative() {
let f = super::fixture();
let resolver =
Resolver::new(ResolveOptions { prefer_relative: true, ..ResolveOptions::default() });
#[rustfmt::skip]
let pass = [
("should correctly resolve with preferRelative 1", "main1.js", f.join("main1.js")),
("should correctly resolve with preferRelative 2", "m1/a.js", f.join("node_modules/m1/a.js")),
];
for (comment, request, expected) in pass {
let resolved_path = resolver.resolve(&f, request).map(Resolution::full_path);
assert_eq!(resolved_path, Ok(expected), "{comment} {request}");
}
}
#[test]
#[ignore = "add resolveToContext option"]

View file

@ -45,5 +45,24 @@ fn roots() {
fn resolve_to_context() {}
#[test]
#[ignore = "resolver_prefer_absolute"]
fn resolver_prefer_absolute() {}
fn prefer_absolute() {
let f = super::fixture();
let resolver = Resolver::new(ResolveOptions {
extensions: vec![".js".into()],
alias: vec![("foo".into(), vec![AliasValue::Path("/fixtures".into())])],
roots: vec![env::current_dir().unwrap().join("tests/enhanced_resolve/test"), f.clone()],
prefer_absolute: true,
..ResolveOptions::default()
});
#[rustfmt::skip]
let pass = [
("should resolve an absolute path (prefer absolute)", f.join("b.js").to_string_lossy().to_string(), f.join("b.js")),
];
for (comment, request, expected) in pass {
let resolved_path = resolver.resolve(&f, &request).map(Resolution::full_path);
assert_eq!(resolved_path, Ok(expected), "{comment} {request}");
}
}