feat(resolver): implement roots (#576)

This commit is contained in:
Boshen 2023-07-20 14:23:25 +08:00 committed by GitHub
parent 6089898bf0
commit beb0ae9d8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 19 deletions

View file

@ -31,7 +31,7 @@
| | 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 |
| | roots | [] | A list of root paths |
| | symlinks | true | Whether to resolve symlinks to their symlinked location |
| | unsafeCache | false | Use this cache object to unsafely cache the successful requests
@ -59,9 +59,9 @@ Tests ported from [enhanced-resolve](https://github.com/webpack/enhanced-resolve
- [ ] plugins.test.js
- [ ] pnp.test.js
- [ ] pr-53.test.js
- [x] resolve.test.js (partially done)
- [x] resolve.test.js (need to add resolveToContext)
- [ ] restrictions.test.js
- [ ] roots.test.js
- [x] roots.test.js (need to add resolveToContext, resolverPreferAbsolute)
- [x] scoped-packages.test.js
- [x] simple.test.js
- [ ] symlink.test.js

View file

@ -112,25 +112,22 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
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);
}
}
return Err(ResolveError::NotFound(
Path::new(absolute_path).to_path_buf().into_boxed_path(),
));
}
}
// 3. If X begins with './' or '/' or '../'
RequestPath::Relative(relative_path) => {
if let Some(path) = self.load_package_self(path, relative_path)? {
return Ok(path);
}
let path = path.normalize_with(relative_path);
// a. LOAD_AS_FILE(Y + X)
if !relative_path.ends_with('/') {
if let Some(path) = self.load_as_file(&path)? {
return Ok(path);
}
}
// b. LOAD_AS_DIRECTORY(Y + X)
if let Some(path) = self.load_as_directory(&path)? {
return Ok(path);
}
// c. THROW "not found"
return Err(ResolveError::NotFound(path.into_boxed_path()));
return self.require_relative(path, relative_path);
}
// 4. If X begins with '#'
RequestPath::Hash(hash_path) => {
@ -156,6 +153,26 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
Err(ResolveError::NotFound(path.to_path_buf().into_boxed_path()))
}
// 3. If X begins with './' or '/' or '../'
fn require_relative(&self, path: &Path, request_str: &str) -> Result<PathBuf, ResolveError> {
if let Some(path) = self.load_package_self(path, request_str)? {
return Ok(path);
}
let path = path.normalize_with(request_str);
// a. LOAD_AS_FILE(Y + X)
if !request_str.ends_with('/') {
if let Some(path) = self.load_as_file(&path)? {
return Ok(path);
}
}
// b. LOAD_AS_DIRECTORY(Y + X)
if let Some(path) = self.load_as_directory(&path)? {
return Ok(path);
}
// c. THROW "not found"
Err(ResolveError::NotFound(path.into_boxed_path()))
}
#[allow(clippy::unnecessary_wraps)]
fn load_as_file(&self, path: &Path) -> ResolveState {
// enhanced-resolve feature: extension_alias

View file

@ -1,3 +1,5 @@
use std::path::PathBuf;
pub type Alias = Vec<(String, Vec<AliasValue>)>;
#[derive(Debug, Clone)]
@ -58,6 +60,12 @@ pub struct ResolveOptions {
///
/// Default `["node_modules"]`
pub modules: Vec<String>,
/// 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>,
}
impl Default for ResolveOptions {
@ -72,6 +80,7 @@ impl Default for ResolveOptions {
fallback: vec![],
main_files: vec!["index".into()],
modules: vec!["node_modules".into()],
roots: vec![],
}
}
}

View file

@ -5,6 +5,7 @@ mod extensions;
mod fallback;
mod incorrect_description_file;
mod resolve;
mod roots;
mod scoped_packages;
mod simple;

View file

@ -0,0 +1,49 @@
//! <https://github.com/webpack/enhanced-resolve/blob/main/test/roots.test.js>
use std::env;
use oxc_resolver::{AliasValue, Resolution, ResolveError, ResolveOptions, Resolver};
#[test]
fn roots() {
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()],
..ResolveOptions::default()
});
#[rustfmt::skip]
let pass = [
("should respect roots option", "/fixtures/b.js", f.join("b.js")),
("should try another root option, if it exists", "/b.js", f.join("b.js")),
("should respect extension", "/fixtures/b", f.join("b.js")),
("should resolve in directory", "/fixtures/extensions/dir", f.join("extensions/dir/index.js")),
("should respect aliases", "foo/b", 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}");
}
#[rustfmt::skip]
let fail = [
("should not work with relative path", "fixtures/b.js", ResolveError::NotFound(f.clone().into_boxed_path()))
];
for (comment, request, expected) in fail {
let resolution = resolver.resolve(&f, request);
assert_eq!(resolution, Err(expected), "{comment} {request}");
}
}
#[test]
#[ignore = "resolve_to_context"]
fn resolve_to_context() {}
#[test]
#[ignore = "resolver_prefer_absolute"]
fn resolver_prefer_absolute() {}