mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 21:29:01 +00:00
parent
58f4e234da
commit
5bbad73db5
16 changed files with 167 additions and 29 deletions
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
* [enhanced-resolve](https://github.com/webpack/enhanced-resolve) configurations
|
||||
* built-in [tsconfig-paths-webpack-plugin](https://github.com/dividab/tsconfig-paths-webpack-plugin)
|
||||
* support extending tsconfig defined in `tsconfig.extends`
|
||||
* support paths alias defined in `tsconfig.compilerOptions.paths`
|
||||
* support project references defined `tsconfig.references`
|
||||
|
||||
#### Resolver Options
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use once_cell::sync::OnceCell as OnceLock;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
borrow::{Borrow, Cow},
|
||||
convert::AsRef,
|
||||
hash::{BuildHasherDefault, Hash, Hasher},
|
||||
ops::Deref,
|
||||
|
|
@ -45,18 +45,23 @@ impl<Fs: FileSystem> Cache<Fs> {
|
|||
|
||||
pub fn tsconfig(
|
||||
&self,
|
||||
tsconfig_path: &Path,
|
||||
tsconfig_path: &CachedPath,
|
||||
callback: impl FnOnce(&mut TsConfig) -> Result<(), ResolveError>, // callback for modifying tsconfig with `extends`
|
||||
) -> Result<Arc<TsConfig>, ResolveError> {
|
||||
self.tsconfigs
|
||||
.entry(tsconfig_path.to_path_buf())
|
||||
.entry(tsconfig_path.path().to_path_buf())
|
||||
.or_try_insert_with(|| {
|
||||
let tsconfig_path = if tsconfig_path.is_dir(&self.fs) {
|
||||
Cow::Owned(tsconfig_path.path().join("tsconfig.json"))
|
||||
} else {
|
||||
Cow::Borrowed(tsconfig_path.path())
|
||||
};
|
||||
let mut tsconfig_string = self
|
||||
.fs
|
||||
.read_to_string(tsconfig_path)
|
||||
.read_to_string(&tsconfig_path)
|
||||
.map_err(|_| ResolveError::NotFound(tsconfig_path.to_path_buf()))?;
|
||||
let mut tsconfig =
|
||||
TsConfig::parse(tsconfig_path, &mut tsconfig_string).map_err(|error| {
|
||||
TsConfig::parse(&tsconfig_path, &mut tsconfig_string).map_err(|error| {
|
||||
ResolveError::from_serde_json_error(tsconfig_path.to_path_buf(), &error)
|
||||
})?;
|
||||
callback(&mut tsconfig)?;
|
||||
|
|
|
|||
|
|
@ -224,7 +224,9 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
|
|||
}
|
||||
|
||||
// tsconfig-paths
|
||||
if let Some(path) = self.load_tsconfig_paths(specifier, &mut ResolveContext::default())? {
|
||||
if let Some(path) =
|
||||
self.load_tsconfig_paths(cached_path, specifier, &mut ResolveContext::default())?
|
||||
{
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
|
|
@ -890,11 +892,16 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
|
|||
Err(ResolveError::ExtensionAlias)
|
||||
}
|
||||
|
||||
fn load_tsconfig_paths(&self, specifier: &str, ctx: &mut ResolveContext) -> ResolveState {
|
||||
fn load_tsconfig_paths(
|
||||
&self,
|
||||
cached_path: &CachedPath,
|
||||
specifier: &str,
|
||||
ctx: &mut ResolveContext,
|
||||
) -> ResolveState {
|
||||
let Some(tsconfig_path) = &self.options.tsconfig else { return Ok(None) };
|
||||
let tsconfig_path = self.cache.value(tsconfig_path);
|
||||
let tsconfig = self.load_tsconfig(&tsconfig_path)?;
|
||||
let paths = tsconfig.resolve_path_alias(specifier);
|
||||
let paths = tsconfig.resolve(cached_path.path(), specifier);
|
||||
for path in paths {
|
||||
let cached_path = self.cache.value(&path);
|
||||
if let Ok(path) = self.require_relative(&cached_path, ".", ctx) {
|
||||
|
|
@ -905,28 +912,35 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
|
|||
}
|
||||
|
||||
fn load_tsconfig(&self, cached_path: &CachedPath) -> Result<Arc<TsConfig>, ResolveError> {
|
||||
debug_assert!(cached_path.path().extension().is_some_and(|ext| ext == "json"));
|
||||
self.cache.tsconfig(cached_path.path(), |tsconfig| {
|
||||
if tsconfig.extends().is_empty() {
|
||||
return Ok(());
|
||||
self.cache.tsconfig(cached_path, |tsconfig| {
|
||||
// Extend tsconfig
|
||||
if !tsconfig.extends().is_empty() {
|
||||
let resolver = self.clone_with_options(ResolveOptions {
|
||||
extensions: vec![".json".into()],
|
||||
main_files: vec!["tsconfig".into()],
|
||||
..ResolveOptions::default()
|
||||
});
|
||||
let mut extended_tsconfigs = vec![];
|
||||
for tsconfig_extend_specifier in tsconfig.extends() {
|
||||
let extended_tsconfig_path = resolver.require(
|
||||
cached_path.parent().unwrap(),
|
||||
tsconfig_extend_specifier,
|
||||
&mut ResolveContext::default(),
|
||||
)?;
|
||||
let extended_tsconfig = self.load_tsconfig(&extended_tsconfig_path)?;
|
||||
extended_tsconfigs.push(extended_tsconfig);
|
||||
}
|
||||
for extended_tsconfig in extended_tsconfigs {
|
||||
tsconfig.extend_tsconfig(&extended_tsconfig);
|
||||
}
|
||||
}
|
||||
let resolver = self.clone_with_options(ResolveOptions {
|
||||
extensions: vec![".json".into()],
|
||||
main_files: vec!["tsconfig".into()],
|
||||
..ResolveOptions::default()
|
||||
});
|
||||
let mut extended_tsconfigs = vec![];
|
||||
for tsconfig_extend_specifier in tsconfig.extends() {
|
||||
let extended_tsconfig_path = resolver.require(
|
||||
cached_path.parent().unwrap(),
|
||||
tsconfig_extend_specifier,
|
||||
&mut ResolveContext::default(),
|
||||
)?;
|
||||
let extended_tsconfig = self.load_tsconfig(&extended_tsconfig_path)?;
|
||||
extended_tsconfigs.push(extended_tsconfig);
|
||||
}
|
||||
for extended_tsconfig in extended_tsconfigs {
|
||||
tsconfig.extend_tsconfig(&extended_tsconfig);
|
||||
// Load project references
|
||||
let directory = tsconfig.directory().to_path_buf();
|
||||
for reference in tsconfig.references_mut() {
|
||||
let reference_tsconfig_path =
|
||||
self.cache.value(&directory.normalize_with(&reference.path));
|
||||
let tsconfig = self.cache.tsconfig(&reference_tsconfig_path, |_| Ok(()))?;
|
||||
reference.tsconfig.replace(tsconfig);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ mod scoped_packages;
|
|||
mod simple;
|
||||
mod symlink;
|
||||
mod tsconfig_paths;
|
||||
mod tsconfig_project_references;
|
||||
|
||||
use crate::Resolver;
|
||||
use std::{env, path::PathBuf, sync::Arc, thread};
|
||||
|
|
|
|||
32
crates/oxc_resolver/src/tests/tsconfig_project_references.rs
Normal file
32
crates/oxc_resolver/src/tests/tsconfig_project_references.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//! Tests for tsconfig project references
|
||||
|
||||
use crate::{ResolveOptions, Resolver};
|
||||
use std::env;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let f = env::current_dir().unwrap().join("tests/tsconfig_project_references");
|
||||
|
||||
let resolver = Resolver::new(ResolveOptions {
|
||||
tsconfig: Some(f.join("app")),
|
||||
..ResolveOptions::default()
|
||||
});
|
||||
|
||||
#[rustfmt::skip]
|
||||
let pass = [
|
||||
// Test normal paths alias
|
||||
(f.join("app"), "@/index.ts", f.join("app/aliased/index.ts")),
|
||||
(f.join("app"), "@/../index.ts", f.join("app/index.ts")),
|
||||
// Test project reference
|
||||
(f.join("project_a"), "@/index.ts", f.join("project_a/aliased/index.ts")),
|
||||
(f.join("project_b/src"), "@/index.ts", f.join("project_b/src/aliased/index.ts")),
|
||||
// Does not have paths alias
|
||||
(f.join("project_a"), "./index.ts", f.join("project_a/index.ts")),
|
||||
(f.join("project_c"), "./index.ts", f.join("project_c/index.ts")),
|
||||
];
|
||||
|
||||
for (path, request, expected) in pass {
|
||||
let resolved_path = resolver.resolve(&path, request).map(|f| f.full_path());
|
||||
assert_eq!(resolved_path, Ok(expected), "{request} {path:?}");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
hash::BuildHasherDefault,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{json_comments::strip_comments_in_place, PathUtil};
|
||||
|
|
@ -20,10 +21,26 @@ pub struct TsConfig {
|
|||
#[serde(default, deserialize_with = "deserialize_extends")]
|
||||
extends: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
references: Vec<ProjectReference>,
|
||||
|
||||
#[serde(default)]
|
||||
compiler_options: CompilerOptions,
|
||||
}
|
||||
|
||||
/// Project Reference
|
||||
/// <https://www.typescriptlang.org/docs/handbook/project-references.html>
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProjectReference {
|
||||
/// The path property of each reference can point to a directory containing a tsconfig.json file,
|
||||
/// or to the config file itself (which may have any name).
|
||||
pub path: PathBuf,
|
||||
|
||||
/// Reference to the resolved tsconfig
|
||||
#[serde(skip)]
|
||||
pub tsconfig: Option<Arc<TsConfig>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompilerOptions {
|
||||
|
|
@ -79,6 +96,17 @@ impl TsConfig {
|
|||
&self.extends
|
||||
}
|
||||
|
||||
pub fn references_mut(&mut self) -> &mut Vec<ProjectReference> {
|
||||
self.references.as_mut()
|
||||
}
|
||||
|
||||
fn base_path(&self) -> &Path {
|
||||
self.compiler_options
|
||||
.base_url
|
||||
.as_ref()
|
||||
.map_or_else(|| self.directory(), |path| path.as_ref())
|
||||
}
|
||||
|
||||
pub fn extend_tsconfig(&mut self, tsconfig: &Self) {
|
||||
let compiler_options = &mut self.compiler_options;
|
||||
if compiler_options.base_url.is_none() {
|
||||
|
|
@ -90,6 +118,20 @@ impl TsConfig {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resolve(&self, path: &Path, specifier: &str) -> Vec<PathBuf> {
|
||||
if path.starts_with(self.base_path()) {
|
||||
return self.resolve_path_alias(specifier);
|
||||
}
|
||||
for reference in &self.references {
|
||||
if let Some(tsconfig) = &reference.tsconfig {
|
||||
if path.starts_with(tsconfig.base_path()) {
|
||||
return tsconfig.resolve_path_alias(specifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
|
||||
// Copied from parcel
|
||||
// <https://github.com/parcel-bundler/parcel/blob/b6224fd519f95e68d8b93ba90376fd94c8b76e69/packages/utils/node-resolver-rs/src/tsconfig.rs#L93>
|
||||
pub fn resolve_path_alias(&self, specifier: &str) -> Vec<PathBuf> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["./aliased/*"]
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../project_a/conf.json"
|
||||
},
|
||||
{
|
||||
"path": "../project_b"
|
||||
},
|
||||
{
|
||||
"path": "../project_c/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"paths": {
|
||||
"@/*": ["./aliased/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@/*": ["./aliased/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue