Skip to content

Commit

Permalink
fix(es/loader): Make tsc resolver work for bare specifier (#8550)
Browse files Browse the repository at this point in the history
**Description:**

From the typescript
[baseUrl](https://www.typescriptlang.org/docs/handbook/modules/reference.html#baseurl)
doc: When using bare specifiers (module specifiers that don’t begin with
./, ../, or /), baseUrl has a higher precedence than node_modules
package lookups.

In the current tsc resolver implementation, when resolving bare module
specifiers, baseUrl was not used except for `paths`, this cause the
belowing resolution failed, but it worked when used in typescript
project.

`tsconfig.json` / `.swcrc`:
```json
{
    baseUrl: "."
    paths: {
        "@common/*": ["src/common/*"]
    }
}
```

File structure:
- ./src/common/helper.ts
- ./src/index.ts


./src/index.ts content:
```ts
 // tsc can resolve this, but tsc resolver cannot
import sth from "src/common/helper"
```
  • Loading branch information
XHMM authored Jan 27, 2024
1 parent 039c168 commit d6a4615
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 1 deletion.
9 changes: 8 additions & 1 deletion crates/swc_ecma_loader/src/resolvers/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,14 @@ where
}
}

if let Ok(v) = self.invoke_inner_resolver(&self.base_url_filename, module_specifier) {
let path = self.base_url.join(module_specifier);
#[cfg(windows)]
let path_string: String = path.to_string_lossy().replace("\\", "/");
#[cfg(not(windows))]
let path_string: String = path.to_string_lossy().to_string();

// https://www.typescriptlang.org/docs/handbook/modules/reference.html#baseurl
if let Ok(v) = self.invoke_inner_resolver(base, path_string.as_str()) {
return Ok(v);
}

Expand Down
92 changes: 92 additions & 0 deletions crates/swc_ecma_loader/tests/tsc_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,98 @@ fn pattern_1() {
}
}

#[test]
fn base_url_works_for_bare_specifier() {
let mut map = HashMap::default();
map.insert("./src/common/helper".to_string(), "helper".to_string());

let r = TsConfigResolver::new(
TestResolver(map),
".".into(),
vec![("@common/*".into(), vec!["src/common/*".into()])],
);

{
let resolved = r
.resolve(&FileName::Anon, "@common/helper")
.expect("should resolve");

assert_eq!(
resolved,
Resolution {
filename: FileName::Custom("helper".into()),
slug: None
}
);
}

{
let resolved = r
.resolve(&FileName::Anon, "src/common/helper")
.expect("should resolve");

assert_eq!(
resolved,
Resolution {
filename: FileName::Custom("helper".into()),
slug: None
}
);
}

{
let resolved = r
.resolve(&FileName::Anon, "./src/common/helper")
.expect("should resolve");

assert_eq!(
resolved,
Resolution {
filename: FileName::Custom("helper".into()),
slug: None
}
);
}
}

#[test]
fn base_url_precedence() {
let mut map = HashMap::default();
map.insert("./jquery".to_string(), "jq in base url".to_string());
map.insert("jquery".to_string(), "jq in node module".to_string());
map.insert("react".to_string(), "react in node module".to_string());

let r = TsConfigResolver::new(TestResolver(map), ".".into(), vec![]);

{
let resolved = r
.resolve(&FileName::Anon, "jquery")
.expect("should resolve from base url");

assert_eq!(
resolved,
Resolution {
filename: FileName::Custom("jq in base url".into()),
slug: None
}
);
}

{
let resolved = r
.resolve(&FileName::Anon, "react")
.expect("should resolve from node modules");

assert_eq!(
resolved,
Resolution {
filename: FileName::Custom("react in node module".into()),
slug: None
}
);
}
}

struct TestResolver(AHashMap<String, String>);

impl Resolve for TestResolver {
Expand Down

0 comments on commit d6a4615

Please sign in to comment.