mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(resolver): implement browser field (#561)
This commit is contained in:
parent
8774956e66
commit
cac4e73461
13 changed files with 263 additions and 106 deletions
|
|
@ -3,6 +3,7 @@
|
|||
## TODO
|
||||
|
||||
- [ ] use `thiserror` for better error messages
|
||||
- [ ] copy API documentation from webpack https://webpack.js.org/configuration/resolve/#resolve
|
||||
|
||||
#### Resolver Options
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ Tests ported from [enhanced-resolve](https://github.com/webpack/enhanced-resolve
|
|||
- [ ] CachedInputFileSystem.test.js
|
||||
- [ ] SyncAsyncFileSystemDecorator.test.js
|
||||
- [ ] alias.test.js
|
||||
- [ ] browserField.test.js
|
||||
- [x] browserField.test.js (reading the browser field is currently static - not read from the `browserField` option)
|
||||
- [ ] dependencies.test.js
|
||||
- [ ] exportsField.test.js
|
||||
- [x] extension-alias.test.js
|
||||
|
|
|
|||
|
|
@ -1,16 +1,36 @@
|
|||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::request::RequestError;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum ResolveError {
|
||||
NotFound,
|
||||
/// Path not found
|
||||
NotFound(Box<Path>),
|
||||
|
||||
/// Ignored path
|
||||
///
|
||||
/// Derived from ignored path (false value) from browser field in package.json
|
||||
/// ```json
|
||||
/// {
|
||||
/// "browser": {
|
||||
/// "./module": false
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// See <https://github.com/defunctzombie/package-browser-field-spec#ignore-a-module>
|
||||
Ignored(Box<Path>),
|
||||
|
||||
/// The provided path request cannot be parsed
|
||||
Request(RequestError),
|
||||
|
||||
/// All of the aliased extension are not found
|
||||
ExtensionAlias,
|
||||
|
||||
/// JSON parse error
|
||||
JSON(JSONError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct JSONError {
|
||||
pub path: PathBuf,
|
||||
pub message: String,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{fs, path::Path, sync::Arc};
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::{package_json::PackageJson, ResolveError};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FileMetadata {
|
||||
is_file: bool,
|
||||
|
|
@ -13,23 +12,35 @@ pub struct FileMetadata {
|
|||
/// [File System](https://doc.rust-lang.org/stable/std/fs/) with caching
|
||||
#[derive(Default)]
|
||||
pub struct FileSystem {
|
||||
cache: DashMap<PathBuf, Option<FileMetadata>>,
|
||||
cache: DashMap<Box<Path>, Option<FileMetadata>>,
|
||||
package_json_cache: DashMap<Box<Path>, Result<Arc<PackageJson>, ResolveError>>,
|
||||
}
|
||||
|
||||
impl FileSystem {
|
||||
/// <https://doc.rust-lang.org/stable/std/fs/fn.metadata.html>
|
||||
pub fn metadata<P: AsRef<Path>>(&self, path: P) -> Option<FileMetadata> {
|
||||
let path = path.as_ref();
|
||||
pub fn metadata(&self, path: &Path) -> Option<FileMetadata> {
|
||||
if let Some(result) = self.cache.get(path) {
|
||||
return *result;
|
||||
}
|
||||
let file_metadata =
|
||||
fs::metadata(path).ok().map(|metadata| FileMetadata { is_file: metadata.is_file() });
|
||||
self.cache.insert(path.to_path_buf(), file_metadata);
|
||||
self.cache.insert(path.to_path_buf().into_boxed_path(), file_metadata);
|
||||
file_metadata
|
||||
}
|
||||
|
||||
pub fn is_file<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
pub fn is_file(&self, path: &Path) -> bool {
|
||||
self.metadata(path).is_some_and(|m| m.is_file)
|
||||
}
|
||||
|
||||
pub fn read_package_json(&self, path: &Path) -> Result<Arc<PackageJson>, ResolveError> {
|
||||
if let Some(result) = self.package_json_cache.get(path) {
|
||||
return result.value().clone();
|
||||
}
|
||||
let package_json_string = fs::read_to_string(path).unwrap();
|
||||
let result = PackageJson::parse(path.to_path_buf(), &package_json_string)
|
||||
.map(Arc::new)
|
||||
.map_err(|error| ResolveError::from_serde_json_error(path.to_path_buf(), &error));
|
||||
self.package_json_cache.insert(path.to_path_buf().into_boxed_path(), result.clone());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
//! # Oxc Resolver
|
||||
//!
|
||||
//! Tests ported from [enhanced-resolve](https://github.com/webpack/enhanced-resolve).
|
||||
//! ## References:
|
||||
//!
|
||||
//! Algorithm from <https://nodejs.org/api/modules.html#all-together>.
|
||||
//! * Tests ported from [enhanced-resolve](https://github.com/webpack/enhanced-resolve)
|
||||
//! * Algorithm adapted from [Node.js Module Resolution Algorithm](https://nodejs.org/api/modules.html#all-together) and [cjs loader](https://github.com/nodejs/node/blob/main/lib/internal/modules/cjs/loader.js)
|
||||
//! * Some code adapted from [parcel-resolver](https://github.com/parcel-bundler/parcel/blob/v2/packages/utils/node-resolver-rs)
|
||||
|
||||
mod error;
|
||||
mod file_system;
|
||||
|
|
@ -13,7 +15,6 @@ mod request;
|
|||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
|
|
@ -23,7 +24,6 @@ pub use crate::{
|
|||
};
|
||||
use crate::{
|
||||
file_system::FileSystem,
|
||||
package_json::PackageJson,
|
||||
path::PathUtil,
|
||||
request::{Request, RequestPath},
|
||||
};
|
||||
|
|
@ -80,7 +80,7 @@ impl Resolver {
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * Will return `Err` for [ResolveError]
|
||||
/// * See [ResolveError]
|
||||
pub fn resolve<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
|
|
@ -114,6 +114,9 @@ impl Resolver {
|
|||
}
|
||||
// 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('/') {
|
||||
|
|
@ -126,7 +129,7 @@ impl Resolver {
|
|||
return Ok(path);
|
||||
}
|
||||
// c. THROW "not found"
|
||||
return Err(ResolveError::NotFound);
|
||||
return Err(ResolveError::NotFound(path.into_boxed_path()));
|
||||
}
|
||||
// 4. If X begins with '#'
|
||||
// a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
|
||||
|
|
@ -135,6 +138,9 @@ impl Resolver {
|
|||
}
|
||||
}
|
||||
// 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);
|
||||
|
|
@ -143,7 +149,7 @@ impl Resolver {
|
|||
return Ok(path);
|
||||
}
|
||||
// 7. THROW "not found"
|
||||
Err(ResolveError::NotFound)
|
||||
Err(ResolveError::NotFound(path.to_path_buf().into_boxed_path()))
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
|
|
@ -170,15 +176,12 @@ impl Resolver {
|
|||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn load_index(&self, path: &Path, package_json: Option<&PackageJson>) -> ResolveState {
|
||||
fn load_index(&self, path: &Path) -> ResolveState {
|
||||
// 1. If X/index.js is a file, load X/index.js as JavaScript text. STOP
|
||||
// 2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
|
||||
// 3. If X/index.node is a file, load X/index.node as binary addon. STOP
|
||||
for extension in &self.options.extensions {
|
||||
let mut index_path = path.join("index").with_extension(extension);
|
||||
if let Some(resolved_path) = package_json.and_then(|p| p.resolve(&index_path)) {
|
||||
index_path = resolved_path;
|
||||
}
|
||||
let index_path = path.join("index").with_extension(extension);
|
||||
if self.fs.is_file(&index_path) {
|
||||
return Ok(Some(index_path));
|
||||
}
|
||||
|
|
@ -191,9 +194,7 @@ impl Resolver {
|
|||
let package_json_path = path.join("package.json");
|
||||
if self.fs.is_file(&package_json_path) {
|
||||
// a. Parse X/package.json, and look for "main" field.
|
||||
let package_json_string = fs::read_to_string(&package_json_path).unwrap();
|
||||
let package_json = PackageJson::parse(package_json_path.clone(), &package_json_string)
|
||||
.map_err(|error| ResolveError::from_serde_json_error(package_json_path, &error))?;
|
||||
let package_json = self.fs.read_package_json(&package_json_path)?;
|
||||
// b. If "main" is a falsy value, GOTO 2.
|
||||
if let Some(main_field) = &package_json.main {
|
||||
// c. let M = X + (json main field)
|
||||
|
|
@ -203,33 +204,31 @@ impl Resolver {
|
|||
return Ok(Some(path));
|
||||
}
|
||||
// e. LOAD_INDEX(M)
|
||||
if let Some(path) = self.load_index(&main_field_path, Some(&package_json))? {
|
||||
if let Some(path) = self.load_index(&main_field_path)? {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
// f. LOAD_INDEX(X) DEPRECATED
|
||||
// g. THROW "not found"
|
||||
return Err(ResolveError::NotFound);
|
||||
return Err(ResolveError::NotFound(main_field_path.into_boxed_path()));
|
||||
}
|
||||
// 2. LOAD_INDEX(X)
|
||||
self.load_index(path, Some(&package_json))
|
||||
} else {
|
||||
// 2. LOAD_INDEX(X)
|
||||
self.load_index(path, None)
|
||||
}
|
||||
// 2. LOAD_INDEX(X)
|
||||
self.load_index(path)
|
||||
}
|
||||
|
||||
fn load_node_modules(&self, start: &Path, request_str: &str) -> ResolveState {
|
||||
const NODE_MODULES: &str = "node_modules";
|
||||
// 1. let DIRS = NODE_MODULES_PATHS(START)
|
||||
let dirs = start
|
||||
.ancestors()
|
||||
.filter(|path| path.file_name().is_some_and(|name| name != NODE_MODULES));
|
||||
let dirs = Self::node_module_paths(start);
|
||||
// 2. for each DIR in DIRS:
|
||||
for dir in dirs {
|
||||
let node_module_path = dir.join(NODE_MODULES);
|
||||
for node_module_path in dirs {
|
||||
let node_module_path = node_module_path.join(request_str);
|
||||
for main_file in &self.options.main_files {
|
||||
if let Some(path) = self.load_package_self(&node_module_path, main_file)? {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
}
|
||||
// a. LOAD_PACKAGE_EXPORTS(X, DIR)
|
||||
// b. LOAD_AS_FILE(DIR/X)
|
||||
let node_module_path = node_module_path.join(request_str);
|
||||
if !request_str.ends_with('/') {
|
||||
if let Some(path) = self.load_as_file(&node_module_path)? {
|
||||
return Ok(Some(path));
|
||||
|
|
@ -243,6 +242,29 @@ impl Resolver {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
fn node_module_paths(path: &Path) -> impl Iterator<Item = PathBuf> + '_ {
|
||||
path.ancestors()
|
||||
.filter(|path| path.file_name().is_some_and(|name| name != "node_modules"))
|
||||
.map(|path| path.join("node_modules"))
|
||||
}
|
||||
|
||||
fn load_package_self(&self, path: &Path, request_str: &str) -> ResolveState {
|
||||
for dir in path.ancestors() {
|
||||
let package_json_path = dir.join("package.json");
|
||||
if self.fs.is_file(&package_json_path) {
|
||||
let package_json = self.fs.read_package_json(&package_json_path)?;
|
||||
if let Some(request_str) =
|
||||
package_json.resolve_request(path, request_str, &self.options.extensions)?
|
||||
{
|
||||
let request = Request::parse(request_str).map_err(ResolveError::Request)?;
|
||||
// TODO: Do we need to pass query and fragment?
|
||||
return self.require(dir, &request).map(Some);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Given an extension alias map `{".js": [".ts", "js"]}`,
|
||||
/// load the mapping instead of the provided extension
|
||||
///
|
||||
|
|
|
|||
|
|
@ -5,52 +5,88 @@ use std::{
|
|||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::path::PathUtil;
|
||||
use crate::{path::PathUtil, ResolveError};
|
||||
|
||||
// TODO: allocate everything into an arena or SoA
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PackageJson<'a> {
|
||||
pub struct PackageJson {
|
||||
#[serde(skip)]
|
||||
pub path: PathBuf,
|
||||
pub main: Option<&'a str>,
|
||||
pub browser: Option<BrowserField<'a>>,
|
||||
pub main: Option<String>,
|
||||
pub browser: Option<BrowserField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BrowserField<'a> {
|
||||
String(&'a str),
|
||||
Map(HashMap<&'a str, &'a str>),
|
||||
pub enum BrowserField {
|
||||
String(String),
|
||||
Map(HashMap<String, serde_json::Value>),
|
||||
}
|
||||
|
||||
impl<'a> PackageJson<'a> {
|
||||
pub fn parse(path: PathBuf, json: &'a str) -> Result<PackageJson<'a>, serde_json::Error> {
|
||||
let mut package_json: PackageJson = serde_json::from_str(json)?;
|
||||
impl PackageJson {
|
||||
pub fn parse(path: PathBuf, json: &str) -> Result<Self, serde_json::Error> {
|
||||
let mut package_json: Self = serde_json::from_str(json)?;
|
||||
package_json.path = path;
|
||||
Ok(package_json)
|
||||
}
|
||||
|
||||
pub fn resolve(&self, path: &Path) -> Option<PathBuf> {
|
||||
// TODO: return ResolveError if the provided `alias_fields` is not `browser` for future
|
||||
// proof
|
||||
let browser_field = self.browser.as_ref()?;
|
||||
match browser_field {
|
||||
BrowserField::Map(map) => {
|
||||
/// Resolve the request string for this package.json by looking at the `browser` field.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * Returns [ResolveError::Ignored] for `"path": false` in `browser` field.
|
||||
pub fn resolve_request(
|
||||
&self,
|
||||
path: &Path,
|
||||
request_str: &str,
|
||||
extensions: &[String],
|
||||
) -> Result<Option<&str>, ResolveError> {
|
||||
// TODO: return ResolveError if the provided `alias_fields` is not `browser` for future proof
|
||||
match self.browser.as_ref() {
|
||||
Some(BrowserField::Map(map)) => {
|
||||
for (key, value) in map {
|
||||
let resolved_path = self.resolve_browser_field(key, value, path);
|
||||
if resolved_path.is_some() {
|
||||
return resolved_path;
|
||||
if let Some(resolved_str) =
|
||||
self.resolve_browser_field(path, key, value, request_str, extensions)?
|
||||
{
|
||||
return Ok(Some(resolved_str));
|
||||
}
|
||||
}
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
// TODO: implement <https://github.com/defunctzombie/package-browser-field-spec#alternate-main---basic>
|
||||
BrowserField::String(_) => None,
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_browser_field(&self, key: &str, value: &str, path: &Path) -> Option<PathBuf> {
|
||||
// TODO: refactor this mess
|
||||
fn resolve_browser_field<'a>(
|
||||
&'a self,
|
||||
start: &Path,
|
||||
key: &str,
|
||||
value: &'a serde_json::Value,
|
||||
request_str: &str,
|
||||
extensions: &[String],
|
||||
) -> Result<Option<&str>, ResolveError> {
|
||||
let directory = self.path.parent().unwrap(); // `unwrap`: this is a path to package.json, parent is its containing directory
|
||||
// TODO: cache this join
|
||||
(directory.join(key).normalize() == path).then(|| directory.join(value).normalize())
|
||||
let right = directory.join(key).normalize();
|
||||
let left = start.join(request_str).normalize();
|
||||
if key == request_str
|
||||
|| extensions
|
||||
.iter()
|
||||
.any(|ext| Path::new(request_str).with_extension(ext) == Path::new(key))
|
||||
|| right == left
|
||||
|| extensions.iter().any(|ext| left.with_extension(ext) == right)
|
||||
{
|
||||
if let serde_json::Value::String(value) = value {
|
||||
return Ok(Some(value.as_str()));
|
||||
}
|
||||
|
||||
// key match without string value, i.e. `"path": false` for ignore
|
||||
let directory = self.path.parent().unwrap(); // `unwrap`: this is a path to package.json, parent is its containing directory
|
||||
let path_key = directory.join(key).normalize();
|
||||
return Err(ResolveError::Ignored(path_key.into_boxed_path()));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum RequestError {
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Request<'a> {
|
||||
pub path: RequestPath<'a>,
|
||||
pub query: Option<&'a str>,
|
||||
pub fragment: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum RequestPath<'a> {
|
||||
/// `/path`
|
||||
Absolute(&'a str),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
//! <https://github.com/webpack/enhanced-resolve/blob/main/test/browserField.test.js>
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use oxc_resolver::{ResolveError, ResolveOptions, Resolver};
|
||||
|
||||
fn fixture() -> PathBuf {
|
||||
super::fixture().join("browser-module")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore() {
|
||||
let f = fixture();
|
||||
|
||||
let options = ResolveOptions {
|
||||
alias_fields: vec!["browser".into(), "innerBrowser1".into(), "innerBrowser2".into()],
|
||||
..ResolveOptions::default()
|
||||
};
|
||||
|
||||
let resolver = Resolver::new(options);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = [
|
||||
(f.clone(), "./lib/ignore", f.join("lib/ignore.js")),
|
||||
(f.clone(), "./lib/ignore.js", f.join("lib/ignore.js")),
|
||||
(f.join("lib"), "./ignore", f.join("lib/ignore.js")),
|
||||
(f.join("lib"), "./ignore.js", f.join("lib/ignore.js")),
|
||||
];
|
||||
|
||||
for (path, request, expected) in data {
|
||||
let resolution = resolver.resolve(&path, request);
|
||||
let expected = ResolveError::Ignored(expected.into());
|
||||
assert_eq!(resolution, Err(expected), "{path:?} {request}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_file() -> Result<(), ResolveError> {
|
||||
let f = fixture();
|
||||
|
||||
let options =
|
||||
ResolveOptions { alias_fields: vec!["browser".into()], ..ResolveOptions::default() };
|
||||
|
||||
let resolver = Resolver::new(options);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = [
|
||||
("should replace a file 1", f.clone(), "./lib/replaced", f.join("lib/browser.js")),
|
||||
("should replace a file 2", f.clone(), "./lib/replaced.js", f.join("lib/browser.js")),
|
||||
("should replace a file 3", f.join("lib"), "./replaced", f.join("lib/browser.js")),
|
||||
("should replace a file 4", f.join("lib"), "./replaced.js", f.join("lib/browser.js")),
|
||||
("should replace a module with a file 1", f.clone(), "module-a", f.join("browser/module-a.js")),
|
||||
("should replace a module with a file 2", f.join("lib"), "module-a", f.join("browser/module-a.js")),
|
||||
("should replace a module with a module 1", f.clone(), "module-b", f.join("node_modules/module-c.js")),
|
||||
("should replace a module with a module 2", f.join("lib"), "module-b", f.join("node_modules/module-c.js")),
|
||||
// TODO: resolve `innerBrowser1` field in `browser-module/pakckage.json`
|
||||
// ("should resolve in nested property 1", f.clone(), "./lib/main1.js", f.join("lib/main.js")),
|
||||
// TODO: resolve `innerBrowser2` field in `browser-module/pakckage.json`
|
||||
// ("should resolve in nested property 2", f.clone(), "./lib/main2.js", f.join("lib/browser.js")),
|
||||
("should check only alias field properties", f.clone(), "./toString", f.join("lib/toString.js")),
|
||||
];
|
||||
|
||||
for (comment, path, request, expected) in data {
|
||||
let resolution = resolver.resolve(&path, request)?;
|
||||
let resolved_path = resolution.path();
|
||||
assert_eq!(resolved_path, expected, "{comment} {path:?} {request}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -10,7 +10,9 @@ fn fixture() -> PathBuf {
|
|||
|
||||
#[test]
|
||||
fn extension_alias() -> Result<(), ResolveError> {
|
||||
let options = ResolveOptions {
|
||||
let f = fixture();
|
||||
|
||||
let resolver = Resolver::new(ResolveOptions {
|
||||
extensions: vec![".js".into()],
|
||||
main_files: vec!["index.js".into()],
|
||||
extension_alias: vec![
|
||||
|
|
@ -18,9 +20,7 @@ fn extension_alias() -> Result<(), ResolveError> {
|
|||
(".mjs".into(), vec![".mts".into()]),
|
||||
],
|
||||
..ResolveOptions::default()
|
||||
};
|
||||
let resolver = Resolver::new(options);
|
||||
let f = fixture();
|
||||
});
|
||||
|
||||
#[rustfmt::skip]
|
||||
let pass = [
|
||||
|
|
|
|||
|
|
@ -10,43 +10,38 @@ fn fixture() -> PathBuf {
|
|||
|
||||
#[test]
|
||||
fn extensions() -> Result<(), ResolveError> {
|
||||
let fixture = fixture();
|
||||
let f = fixture();
|
||||
|
||||
let options = ResolveOptions {
|
||||
let resolver = Resolver::new(ResolveOptions {
|
||||
extensions: vec![".ts".into(), ".js".into()],
|
||||
..ResolveOptions::default()
|
||||
};
|
||||
|
||||
let resolver = Resolver::new(options);
|
||||
});
|
||||
|
||||
#[rustfmt::skip]
|
||||
let pass = [
|
||||
("should resolve according to order of provided extensions", "./foo", "foo.ts"),
|
||||
(
|
||||
"should resolve according to order of provided extensions (dir index)",
|
||||
"./dir",
|
||||
"dir/index.ts",
|
||||
),
|
||||
("should resolve according to order of provided extensions (dir index)", "./dir", "dir/index.ts"),
|
||||
("should resolve according to main field in module root", ".", "index.js"),
|
||||
("should resolve single file module before directory", "module", "node_modules/module.js"),
|
||||
(
|
||||
"should resolve trailing slash directory before single file",
|
||||
"module/",
|
||||
"node_modules/module/index.ts",
|
||||
),
|
||||
("should resolve trailing slash directory before single file", "module/", "node_modules/module/index.ts"),
|
||||
];
|
||||
|
||||
for (comment, request, expected_path) in pass {
|
||||
let resolution = resolver.resolve(&fixture, request)?;
|
||||
let resolution = resolver.resolve(&f, request)?;
|
||||
let path = resolution.path().canonicalize().unwrap();
|
||||
let expected = fixture.join(expected_path).canonicalize().unwrap();
|
||||
let expected = f.join(expected_path).canonicalize().unwrap();
|
||||
assert_eq!(path, expected, "{comment} {request} {expected_path}");
|
||||
}
|
||||
|
||||
let fail = [("not resolve to file when request has a trailing slash (relative)", "./foo.js/")];
|
||||
#[rustfmt::skip]
|
||||
let fail = [
|
||||
("not resolve to file when request has a trailing slash (relative)", "./foo.js/", f.join("foo.js"))
|
||||
];
|
||||
|
||||
for (comment, request) in fail {
|
||||
let resolution = resolver.resolve(&fixture, request);
|
||||
assert!(resolution.is_err(), "{comment} {request} {resolution:?}");
|
||||
for (comment, request, expected_error) in fail {
|
||||
let resolution = resolver.resolve(&f, request);
|
||||
let error = ResolveError::NotFound(expected_error.into_boxed_path());
|
||||
assert_eq!(resolution, Err(error), "{comment} {request} {resolution:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
mod browser_field;
|
||||
mod extension_alias;
|
||||
mod extensions;
|
||||
mod incorrect_description_file;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use oxc_resolver::{ResolveError, Resolver};
|
|||
#[test]
|
||||
fn resolve() -> Result<(), ResolveError> {
|
||||
let f = super::fixture();
|
||||
|
||||
let resolver = Resolver::default();
|
||||
|
||||
let main1_js_path = f.join("main1.js").to_string_lossy().to_string();
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ fn fixture() -> PathBuf {
|
|||
fn scoped_packages() -> Result<(), ResolveError> {
|
||||
let f = fixture();
|
||||
|
||||
let options =
|
||||
ResolveOptions { alias_fields: vec!["browser".into()], ..ResolveOptions::default() };
|
||||
|
||||
let resolver = Resolver::new(options);
|
||||
let resolver = Resolver::new(ResolveOptions {
|
||||
alias_fields: vec!["browser".into()],
|
||||
..ResolveOptions::default()
|
||||
});
|
||||
|
||||
#[rustfmt::skip]
|
||||
let pass = [
|
||||
|
|
|
|||
|
|
@ -6,21 +6,21 @@ use oxc_resolver::{ResolveError, Resolver};
|
|||
|
||||
#[test]
|
||||
fn simple() -> Result<(), ResolveError> {
|
||||
// mimic `enhanced-resolve/test/simple.test.js`
|
||||
let f = env::current_dir().unwrap().join("tests/enhanced_resolve/test/");
|
||||
|
||||
let resolver = Resolver::default();
|
||||
|
||||
// mimic `enhanced-resolve/test/simple.test.js`
|
||||
let dirname = env::current_dir().unwrap().join("tests/enhanced_resolve/test/");
|
||||
|
||||
let data = [
|
||||
("direct", dirname.clone(), "../lib/index"),
|
||||
("as directory", dirname.clone(), ".."),
|
||||
("as module", dirname.join("../../").canonicalize().unwrap(), "./enhanced_resolve"),
|
||||
("direct", f.clone(), "../lib/index"),
|
||||
("as directory", f.clone(), ".."),
|
||||
("as module", f.join("../../").canonicalize().unwrap(), "./enhanced_resolve"),
|
||||
];
|
||||
|
||||
for (comment, path, request) in data {
|
||||
let resolution = resolver.resolve(&path, request)?;
|
||||
let resolved_path = resolution.path().canonicalize().unwrap();
|
||||
let expected = dirname.join("../lib/index.js").canonicalize().unwrap();
|
||||
let expected = f.join("../lib/index.js").canonicalize().unwrap();
|
||||
assert_eq!(resolved_path, expected, "{comment} {path:?} {request}");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue