mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(resolver): implement resolveToContext (#694)
This commit is contained in:
parent
59f5dc1906
commit
9b2d3fce6b
8 changed files with 153 additions and 70 deletions
|
|
@ -26,7 +26,7 @@
|
|||
| ✅ | modules | ["node_modules"] | A list of directories to resolve modules from, can be absolute path or folder name |
|
||||
| | 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 |
|
||||
| ✅ | 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 |
|
||||
| ✅ | restrictions | [] | A list of resolve restrictions |
|
||||
|
|
@ -61,9 +61,9 @@ Crossed out test files are irrelevant.
|
|||
- [ ] plugins.test.js
|
||||
- [ ] pnp.test.js
|
||||
- [x] ~pr-53.test.js~
|
||||
- [x] resolve.test.js (need to add resolveToContext)
|
||||
- [x] resolve.test.js
|
||||
- [x] restrictions.test.js (partially done, regex is not supported yet)
|
||||
- [x] roots.test.js (need to add resolveToContext)
|
||||
- [x] roots.test.js
|
||||
- [x] scoped-packages.test.js
|
||||
- [x] simple.test.js
|
||||
- [x] symlink.test.js
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
|
|||
Self { options: options.sanitize(), cache: Cache::default() }
|
||||
}
|
||||
|
||||
pub fn new_with_file_system(options: ResolveOptions, file_system: Fs) -> Self {
|
||||
pub fn new_with_file_system(file_system: Fs, options: ResolveOptions) -> Self {
|
||||
Self { cache: Cache::new(file_system), ..Self::new(options) }
|
||||
}
|
||||
|
||||
|
|
@ -424,6 +424,12 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
|
|||
}
|
||||
|
||||
fn load_as_directory(&self, cached_path: &CachedPath, ctx: &ResolveContext) -> ResolveState {
|
||||
if !cached_path.is_dir(&self.cache.fs) {
|
||||
return Ok(None);
|
||||
}
|
||||
if self.options.resolve_to_context {
|
||||
return Ok(Some(cached_path.clone()));
|
||||
}
|
||||
// TODO: Only package.json is supported, so warn about having other values
|
||||
// Checking for empty files is needed for omitting checks on package.json
|
||||
// 1. If X/package.json is a file,
|
||||
|
|
@ -481,10 +487,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
|
|||
}
|
||||
}
|
||||
// c. LOAD_AS_DIRECTORY(DIR/X)
|
||||
if cached_path.is_dir(&self.cache.fs) {
|
||||
if let Some(path) = self.load_as_directory(&cached_path, ctx)? {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
if let Some(path) = self.load_as_directory(&cached_path, ctx)? {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
node_module_path.pop();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,11 @@ pub struct ResolveOptions {
|
|||
/// 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`
|
||||
|
|
@ -136,6 +141,7 @@ impl Default for ResolveOptions {
|
|||
fully_specified: false,
|
||||
main_files: vec!["index".into()],
|
||||
modules: vec!["node_modules".into()],
|
||||
resolve_to_context: false,
|
||||
prefer_relative: false,
|
||||
prefer_absolute: false,
|
||||
restrictions: vec![],
|
||||
|
|
|
|||
|
|
@ -27,26 +27,35 @@ fn alias() {
|
|||
("/e/dir/file", ""),
|
||||
]);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let options = ResolveOptions {
|
||||
alias: vec![
|
||||
("aliasA".into(), vec![AliasValue::Path("a".into())]),
|
||||
("b$".into(), vec![AliasValue::Path("a/index".into())]),
|
||||
("c$".into(), vec![AliasValue::Path("/a/index".into())]),
|
||||
("multiAlias".into(), vec![AliasValue::Path("b".into()), AliasValue::Path("c".into()), AliasValue::Path("d".into()), AliasValue::Path("e".into()), AliasValue::Path("a".into())]),
|
||||
// ("recursive".into(), vec![AliasValue::Path("recursive/dir".into())]),
|
||||
("/d/dir".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("/d/index.js".into(), vec![AliasValue::Path("/c/index".into())]),
|
||||
// alias configuration should work
|
||||
("#".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("@".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("ignored".into(), vec![AliasValue::Ignore]),
|
||||
],
|
||||
modules: vec!["/".into()],
|
||||
..ResolveOptions::default()
|
||||
};
|
||||
|
||||
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(options, file_system);
|
||||
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(
|
||||
file_system,
|
||||
ResolveOptions {
|
||||
alias: vec![
|
||||
("aliasA".into(), vec![AliasValue::Path("a".into())]),
|
||||
("b$".into(), vec![AliasValue::Path("a/index".into())]),
|
||||
("c$".into(), vec![AliasValue::Path("/a/index".into())]),
|
||||
(
|
||||
"multiAlias".into(),
|
||||
vec![
|
||||
AliasValue::Path("b".into()),
|
||||
AliasValue::Path("c".into()),
|
||||
AliasValue::Path("d".into()),
|
||||
AliasValue::Path("e".into()),
|
||||
AliasValue::Path("a".into()),
|
||||
],
|
||||
),
|
||||
// ("recursive".into(), vec![AliasValue::Path("recursive/dir".into())]),
|
||||
("/d/dir".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("/d/index.js".into(), vec![AliasValue::Path("/c/index".into())]),
|
||||
// alias configuration should work
|
||||
("#".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("@".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("ignored".into(), vec![AliasValue::Ignore]),
|
||||
],
|
||||
modules: vec!["/".into()],
|
||||
..ResolveOptions::default()
|
||||
},
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let pass = [
|
||||
|
|
|
|||
|
|
@ -27,23 +27,32 @@ fn fallback() {
|
|||
("/e/dir/file", ""),
|
||||
]);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let options = ResolveOptions {
|
||||
fallback: vec![
|
||||
("aliasA".into(), vec![AliasValue::Path("a".into())]),
|
||||
("b$".into(), vec![AliasValue::Path("a/index".into())]),
|
||||
("c$".into(), vec![AliasValue::Path("/a/index".into())]),
|
||||
("multiAlias".into(), vec![AliasValue::Path("b".into()), AliasValue::Path("c".into()), AliasValue::Path("d".into()), AliasValue::Path("e".into()), AliasValue::Path("a".into())]),
|
||||
("recursive".into(), vec![AliasValue::Path("recursive/dir".into())]),
|
||||
("/d/dir".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("/d/index.js".into(), vec![AliasValue::Path("/c/index".into())]),
|
||||
("ignored".into(), vec![AliasValue::Ignore]),
|
||||
],
|
||||
modules: vec!["/".into()],
|
||||
..ResolveOptions::default()
|
||||
};
|
||||
|
||||
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(options, file_system);
|
||||
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(
|
||||
file_system,
|
||||
ResolveOptions {
|
||||
fallback: vec![
|
||||
("aliasA".into(), vec![AliasValue::Path("a".into())]),
|
||||
("b$".into(), vec![AliasValue::Path("a/index".into())]),
|
||||
("c$".into(), vec![AliasValue::Path("/a/index".into())]),
|
||||
(
|
||||
"multiAlias".into(),
|
||||
vec![
|
||||
AliasValue::Path("b".into()),
|
||||
AliasValue::Path("c".into()),
|
||||
AliasValue::Path("d".into()),
|
||||
AliasValue::Path("e".into()),
|
||||
AliasValue::Path("a".into()),
|
||||
],
|
||||
),
|
||||
("recursive".into(), vec![AliasValue::Path("recursive/dir".into())]),
|
||||
("/d/dir".into(), vec![AliasValue::Path("/c/dir".into())]),
|
||||
("/d/index.js".into(), vec![AliasValue::Path("/c/index".into())]),
|
||||
("ignored".into(), vec![AliasValue::Ignore]),
|
||||
],
|
||||
modules: vec!["/".into()],
|
||||
..ResolveOptions::default()
|
||||
},
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let pass = [
|
||||
|
|
|
|||
|
|
@ -6,12 +6,8 @@ use crate::{AliasValue, ResolveOptions, ResolverGeneric};
|
|||
|
||||
use super::memory_fs::MemoryFS;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "windows"))] // MemoryFS's path separator is always `/` so the test will not pass in windows.
|
||||
fn test() {
|
||||
use crate::Resolution;
|
||||
|
||||
let file_system = MemoryFS::new(&[
|
||||
fn file_system() -> MemoryFS {
|
||||
MemoryFS::new(&[
|
||||
("/a/node_modules/package1/index.js", ""),
|
||||
("/a/node_modules/package1/file.js", ""),
|
||||
("/a/node_modules/package2/package.json", r#"{"main":"a"}"#),
|
||||
|
|
@ -24,19 +20,27 @@ fn test() {
|
|||
("/a/abc.js", ""),
|
||||
("/a/dir/index.js", ""),
|
||||
("/a/index.js", ""),
|
||||
]);
|
||||
])
|
||||
}
|
||||
|
||||
let options = ResolveOptions {
|
||||
alias: vec![
|
||||
("alias1".into(), vec![AliasValue::Path("/a/abc".into())]),
|
||||
("alias2".into(), vec![AliasValue::Path("/a".into())]),
|
||||
],
|
||||
alias_fields: vec!["browser".into()],
|
||||
fully_specified: true,
|
||||
..ResolveOptions::default()
|
||||
};
|
||||
#[test]
|
||||
#[cfg(not(target_os = "windows"))] // MemoryFS's path separator is always `/` so the test will not pass in windows.
|
||||
fn test() {
|
||||
use crate::Resolution;
|
||||
let file_system = file_system();
|
||||
|
||||
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(options, file_system);
|
||||
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(
|
||||
file_system,
|
||||
ResolveOptions {
|
||||
alias: vec![
|
||||
("alias1".into(), vec![AliasValue::Path("/a/abc".into())]),
|
||||
("alias2".into(), vec![AliasValue::Path("/a".into())]),
|
||||
],
|
||||
alias_fields: vec!["browser".into()],
|
||||
fully_specified: true,
|
||||
..ResolveOptions::default()
|
||||
},
|
||||
);
|
||||
|
||||
let failing_resolves = [
|
||||
("no extensions", "./abc"),
|
||||
|
|
@ -70,3 +74,41 @@ fn test() {
|
|||
assert_eq!(resolution, Ok(PathBuf::from(expected)), "{comment} {request}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "windows"))] // MemoryFS's path separator is always `/` so the test will not pass in windows.
|
||||
fn resolve_to_context() {
|
||||
use crate::Resolution;
|
||||
|
||||
let file_system = file_system();
|
||||
|
||||
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(
|
||||
file_system,
|
||||
ResolveOptions {
|
||||
alias: vec![
|
||||
("alias1".into(), vec![AliasValue::Path("/a/abc".into())]),
|
||||
("alias2".into(), vec![AliasValue::Path("/a".into())]),
|
||||
],
|
||||
alias_fields: vec!["browser".into()],
|
||||
fully_specified: true,
|
||||
resolve_to_context: true,
|
||||
..ResolveOptions::default()
|
||||
},
|
||||
);
|
||||
|
||||
let successful_resolves = [
|
||||
("current folder", ".", "/a"),
|
||||
("current folder 2", "./", "/a"),
|
||||
("relative directory", "./dir", "/a/dir"),
|
||||
("relative directory 2", "./dir/", "/a/dir"),
|
||||
("relative directory with query and fragment", "./dir?123#456", "/a/dir?123#456"),
|
||||
("relative directory with query and fragment 2", "./dir/?123#456", "/a/dir?123#456"),
|
||||
("absolute directory", "/a/dir", "/a/dir"),
|
||||
("directory in package", "package3/dir", "/a/node_modules/package3/dir"),
|
||||
];
|
||||
|
||||
for (comment, request, expected) in successful_resolves {
|
||||
let resolution = resolver.resolve("/a", request).map(Resolution::full_path);
|
||||
assert_eq!(resolution, Ok(PathBuf::from(expected)), "{comment} {request}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,17 +71,17 @@ fn prefer_relative() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "add resolveToContext option"]
|
||||
fn resolve_context() {
|
||||
let f = super::fixture();
|
||||
let resolver = Resolver::default();
|
||||
let resolver =
|
||||
Resolver::new(ResolveOptions { resolve_to_context: true, ..ResolveOptions::default() });
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = [
|
||||
("context for fixtures", f.clone(), "./", f.clone()),
|
||||
("context for fixtures/lib", f.clone(), "./lib", f.join("lib")),
|
||||
("context for fixtures with ..", f.clone(), "./lib/../../fixtures/./lib/..", f.clone()),
|
||||
("context for fixtures with query", f.clone(), "./?query", f.clone().with_file_name("?query")),
|
||||
("context for fixtures with query", f.clone(), "./?query", f.clone().with_file_name("fixtures?query")),
|
||||
];
|
||||
|
||||
for (comment, path, request, expected) in data {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
//! <https://github.com/webpack/enhanced-resolve/blob/main/test/roots.test.js>
|
||||
|
||||
use std::env;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use crate::{AliasValue, Resolution, ResolveError, ResolveOptions, Resolver};
|
||||
|
||||
fn dirname() -> PathBuf {
|
||||
env::current_dir().unwrap().join("tests/enhanced_resolve/test")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roots() {
|
||||
let f = super::fixture();
|
||||
|
|
@ -11,7 +15,7 @@ fn roots() {
|
|||
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()],
|
||||
roots: vec![dirname(), f.clone()],
|
||||
..ResolveOptions::default()
|
||||
});
|
||||
|
||||
|
|
@ -42,8 +46,17 @@ fn roots() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "resolve_to_context"]
|
||||
fn resolve_to_context() {}
|
||||
fn resolve_to_context() {
|
||||
let f = super::fixture();
|
||||
let resolver = Resolver::new(ResolveOptions {
|
||||
roots: vec![dirname(), f.clone()],
|
||||
resolve_to_context: true,
|
||||
..ResolveOptions::default()
|
||||
});
|
||||
let resolved_path = resolver.resolve(&f, "/fixtures/lib").map(Resolution::full_path);
|
||||
let expected = f.join("lib");
|
||||
assert_eq!(resolved_path, Ok(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefer_absolute() {
|
||||
|
|
@ -52,7 +65,7 @@ fn prefer_absolute() {
|
|||
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()],
|
||||
roots: vec![dirname(), f.clone()],
|
||||
prefer_absolute: true,
|
||||
..ResolveOptions::default()
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue