diff --git a/crates/oxc_resolver/README.md b/crates/oxc_resolver/README.md index 82d418dd7..2ca746972 100644 --- a/crates/oxc_resolver/README.md +++ b/crates/oxc_resolver/README.md @@ -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 diff --git a/crates/oxc_resolver/src/lib.rs b/crates/oxc_resolver/src/lib.rs index 23528e66d..32fb0bc10 100644 --- a/crates/oxc_resolver/src/lib.rs +++ b/crates/oxc_resolver/src/lib.rs @@ -96,13 +96,10 @@ impl ResolverGeneric { }) } + /// require(X) from module at path Y + /// X: request + /// Y: path fn require(&self, path: &Path, request: &Request) -> Result { - // 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 ResolverGeneric { // 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 ResolverGeneric { Err(ResolveError::NotFound(path.into_boxed_path())) } + fn require_path(&self, path: &Path, request_str: &str) -> Result { + // 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 ResolverGeneric { } Err(ResolveError::ExtensionAlias) } + + fn load_roots(&self, path: &Path, request_str: &str) -> Result { + 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())) + } } diff --git a/crates/oxc_resolver/src/options.rs b/crates/oxc_resolver/src/options.rs index b32795748..fde1ecd40 100644 --- a/crates/oxc_resolver/src/options.rs +++ b/crates/oxc_resolver/src/options.rs @@ -61,6 +61,16 @@ pub struct ResolveOptions { /// Default `["node_modules"]` pub modules: Vec, + /// 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![], } } diff --git a/crates/oxc_resolver/tests/enhanced_resolve/test/resolve.rs b/crates/oxc_resolver/tests/enhanced_resolve/test/resolve.rs index 7751195cf..d40cf10fc 100644 --- a/crates/oxc_resolver/tests/enhanced_resolve/test/resolve.rs +++ b/crates/oxc_resolver/tests/enhanced_resolve/test/resolve.rs @@ -1,6 +1,6 @@ //! -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"] diff --git a/crates/oxc_resolver/tests/enhanced_resolve/test/roots.rs b/crates/oxc_resolver/tests/enhanced_resolve/test/roots.rs index ab1e9b4f5..599214f10 100644 --- a/crates/oxc_resolver/tests/enhanced_resolve/test/roots.rs +++ b/crates/oxc_resolver/tests/enhanced_resolve/test/roots.rs @@ -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}"); + } +}