Repository: swc-project/swc (rust)

Issue:
Wrong resolved import paths when using 'paths'
### Describe the bug

I just ran into #4057 which is open and stated as fixed by #5222. Nevertheless it was reopened as requested by @alexeagle (the thread is locked so I created a new issue) in combination with bazel and swc (also where I ran into the paths problem). I diged into it and looked into the `swc_ecma_tranforms_module`. I think it is not a problem with symlinks (at least they were no problem in my small test setup) like referenced in #4057 but with how the absolute path of the resolved file path is generated [(swc_ecma_transforms_module/src/path.rs#L262-L271).](https://github.com/swc-project/swc/blob/c9a55603ae7369e43b8a4dea4e3abdfdd43389ca/crates/swc_ecma_transforms_module/src/path.rs#L262-L271)

My test setup: https://github.com/joelMuehlena/swc-paths-bug

Output from running ./bash in the root of my test setup with a modified SWC version for more output.

```bash
# Output of a custom script
Current dir is /home/joel/swc-paths-bug-repro
Build from 'root (/home/joel/swc-paths-bug-repro)'

# Executed command
  ./swc compile tmp/execroot/test/base/src/index.ts tmp/execroot/test/base/lib/test/a.ts  --out-dir swc_out --config-file base/.swcrc

# Output of previous cmd (swc modified for some output)
resolve_import: base=Real("/home/joel/swc-paths-bug-repro/base/src/index.ts"), module_specifier=@test/a
target before transform=Real("./lib/test/a")
file with import to resolve="/home/joel/swc-paths-bug-repro/base/src/index.ts" # This is correct
import target base="./lib/test/a" # This seems also correct (created by the Resolver itself)

# Output produced by the method referenced earlier
absolute_path="/home/joel/swc-paths-bug-repro/base/src/index.ts" # Already absolute and correct -> nothing altered

path="./lib/test/a", current_dir="/home/joel/swc-paths-bug-repro" # Here the issue begin with CWD is the where the swc binary was called but which is not the ts project root.
absolute_path="/home/joel/swc-paths-bug-repro/lib/test/a"

# Output after creating the absolute path
import target base2="/home/joel/swc-paths-bug-repro/lib/test/a"
resolved path="../../lib/test/a"
```

### Input code

_No response_

### Config

```json
{
  "exclude": ["node_modules"],
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true,
      "dynamicImport": true
    },
    "transform": null,
    "target": "es2021",
    "loose": false,
    "externalHelpers": false,
    "keepClassNames": false,
    "baseUrl": ".",
    "paths": {
      "@test/*": ["lib/test/*"]
    }
  },
  "module": { "type": "commonjs" },
  "sourceMaps": true
}
```


### Playground link

_No response_

### Expected behavior

Correct resolving of the paths.

### Actual behavior

Resolving with the dir from where swc ran instead of the project root.

### Version

main branch

### Additional context

_No response_

Pull request:
fix(es/module): Use `jsc.baseUrl` while resolving absolute paths
**Description:**


**Related issue:**

 - Closes #7761

Patch:
diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs
index 9ef2e5ca14..43a87f999d 100644
--- a/crates/swc/src/config/mod.rs
+++ b/crates/swc/src/config/mod.rs
@@ -1958,7 +1958,7 @@ fn build_resolver(base_url: PathBuf, paths: CompiledPaths) -> Box<SwcImportResol
         );
         let r = CachingResolver::new(40, r);
 
-        let r = NodeImportResolver::new(r);
+        let r = NodeImportResolver::with_base_dir(r, Some(base_url.clone()));
         Arc::new(r)
     };
 
diff --git a/crates/swc_ecma_transforms_module/src/path.rs b/crates/swc_ecma_transforms_module/src/path.rs
index 7d8964d18e..abc448767b 100644
--- a/crates/swc_ecma_transforms_module/src/path.rs
+++ b/crates/swc_ecma_transforms_module/src/path.rs
@@ -86,14 +86,30 @@ where
     R: Resolve,
 {
     resolver: R,
+    base_dir: Option<PathBuf>,
 }
 
 impl<R> NodeImportResolver<R>
 where
     R: Resolve,
 {
+    #[deprecated(note = "Use `with_base_dir`")]
     pub fn new(resolver: R) -> Self {
-        Self { resolver }
+        Self::with_base_dir(resolver, None)
+    }
+
+    pub fn with_base_dir(resolver: R, base_dir: Option<PathBuf>) -> Self {
+        #[cfg(not(target_arch = "wasm32"))]
+        if let Some(base_dir) = &base_dir {
+            assert!(
+                base_dir.is_absolute(),
+                "base_dir({}) must be absolute. Please ensure that `jsc.baseUrl` is specified \
+                 correctly.",
+                base_dir.display()
+            );
+        }
+
+        Self { resolver, base_dir }
     }
 }
 
@@ -193,8 +209,8 @@ where
         };
 
         if base.is_absolute() != target.is_absolute() {
-            base = Cow::Owned(absolute_path(&base)?);
-            target = absolute_path(&target)?;
+            base = Cow::Owned(absolute_path(self.base_dir.as_deref(), &base)?);
+            target = absolute_path(self.base_dir.as_deref(), &target)?;
         }
 
         let rel_path = diff_paths(
@@ -259,11 +275,14 @@ impl_ref!(P, &'_ P);
 impl_ref!(P, Box<P>);
 impl_ref!(P, Arc<P>);
 
-fn absolute_path(path: &Path) -> io::Result<PathBuf> {
+fn absolute_path(base_dir: Option<&Path>, path: &Path) -> io::Result<PathBuf> {
     let absolute_path = if path.is_absolute() {
         path.to_path_buf()
     } else {
-        std::env::current_dir()?.join(path)
+        match base_dir {
+            Some(base_dir) => base_dir.join(path),
+            None => current_dir()?.join(path),
+        }
     }
     .clean();
 
diff --git a/node-swc/src/binding.js b/node-swc/src/binding.js
index ea14db72eb..24f51fc28c 100644
--- a/node-swc/src/binding.js
+++ b/node-swc/src/binding.js
@@ -1,3 +1,9 @@
+/* tslint:disable */
+/* eslint-disable */
+/* prettier-ignore */
+
+/* auto-generated by NAPI-RS */
+
 const { existsSync, readFileSync } = require('fs')
 const { join } = require('path')
 
@@ -11,7 +17,7 @@ function isMusl() {
   // For Node 10
   if (!process.report || typeof process.report.getReport !== 'function') {
     try {
-      const lddPath = require('child_process').execSync('which ldd').toString().trim();
+      const lddPath = require('child_process').execSync('which ldd').toString().trim()
       return readFileSync(lddPath, 'utf8').includes('musl')
     } catch (e) {
       return true
@@ -102,6 +108,15 @@ switch (platform) {
     }
     break
   case 'darwin':
+    localFileExisted = existsSync(join(__dirname, 'swc.darwin-universal.node'))
+    try {
+      if (localFileExisted) {
+        nativeBinding = require('./swc.darwin-universal.node')
+      } else {
+        nativeBinding = require('@swc/core-darwin-universal')
+      }
+      break
+    } catch {}
     switch (arch) {
       case 'x64':
         localFileExisted = existsSync(join(__dirname, 'swc.darwin-x64.node'))


Test patch:
diff --git a/crates/swc/tests/fixture/deno/paths/cjs-001/input/.swcrc b/crates/swc/tests/fixture/deno/paths/cjs-001/input/.swcrc
index 937de5e555..df14886538 100644
--- a/crates/swc/tests/fixture/deno/paths/cjs-001/input/.swcrc
+++ b/crates/swc/tests/fixture/deno/paths/cjs-001/input/.swcrc
@@ -1,10 +1,14 @@
 {
     "jsc": {
+        "baseUrl": ".",
         "paths": {
-            "*": ["src/*", "src2/*"]
+            "*": [
+                "src/*",
+                "src2/*"
+            ]
         }
     },
     "module": {
         "type": "commonjs"
     }
-}
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-3xxx/3272/1/input/.swcrc b/crates/swc/tests/fixture/issues-3xxx/3272/1/input/.swcrc
index e257eb9e37..c6abe3e0fa 100644
--- a/crates/swc/tests/fixture/issues-3xxx/3272/1/input/.swcrc
+++ b/crates/swc/tests/fixture/issues-3xxx/3272/1/input/.swcrc
@@ -1,5 +1,6 @@
 {
     "jsc": {
+        "baseUrl": ".",
         "paths": {
             "server/*": [
                 "./*"
diff --git a/crates/swc_ecma_transforms_module/tests/common_js.rs b/crates/swc_ecma_transforms_module/tests/common_js.rs
index df4e99126f..297121d971 100644
--- a/crates/swc_ecma_transforms_module/tests/common_js.rs
+++ b/crates/swc_ecma_transforms_module/tests/common_js.rs
@@ -24,11 +24,11 @@ fn tr(
     let unresolved_mark = Mark::new();
     let top_level_mark = Mark::new();
 
-    let avalible_set = FeatureFlag::all();
+    let available_set = FeatureFlag::all();
 
     chain!(
         resolver(unresolved_mark, top_level_mark, typescript),
-        common_js(unresolved_mark, config, avalible_set, Some(comments)),
+        common_js(unresolved_mark, config, available_set, Some(comments)),
     )
 }
 
diff --git a/crates/swc_ecma_transforms_module/tests/path_node.rs b/crates/swc_ecma_transforms_module/tests/path_node.rs
index 7f4544a909..6ce2a4388f 100644
--- a/crates/swc_ecma_transforms_module/tests/path_node.rs
+++ b/crates/swc_ecma_transforms_module/tests/path_node.rs
@@ -1,7 +1,4 @@
-use std::{
-    env::current_dir,
-    path::{Path, PathBuf},
-};
+use std::path::{Path, PathBuf};
 
 use indexmap::IndexMap;
 use serde::Deserialize;
@@ -37,7 +34,9 @@ fn node_modules() {
 
 #[test]
 fn issue_4730() {
-    let dir = Path::new("tests/fixture-manual/issue-4730");
+    let dir = Path::new("tests/fixture-manual/issue-4730")
+        .canonicalize()
+        .unwrap();
     let input_dir = dir.join("input");
     let output_dir = dir.join("output");
 
@@ -47,11 +46,7 @@ fn issue_4730() {
             let mut paths = IndexMap::new();
             paths.insert(
                 "@print/a".into(),
-                vec![current_dir()
-                    .unwrap()
-                    .join("tests")
-                    .join("fixture-manual")
-                    .join("issue-4730")
+                vec![dir
                     .join("input")
                     .join("packages")
                     .join("a")
@@ -62,11 +57,7 @@ fn issue_4730() {
             );
             paths.insert(
                 "@print/b".into(),
-                vec![current_dir()
-                    .unwrap()
-                    .join("tests")
-                    .join("fixture-manual")
-                    .join("issue-4730")
+                vec![dir
                     .join("input")
                     .join("packages")
                     .join("b")
@@ -97,14 +88,21 @@ fn paths_resolver(
     base_url: impl AsRef<Path>,
     rules: Vec<(String, Vec<String>)>,
 ) -> JscPathsProvider {
-    let base_url = base_url.as_ref().to_path_buf();
+    let base_url = base_url
+        .as_ref()
+        .to_path_buf()
+        .canonicalize()
+        .expect("failed to canonicalize");
     dbg!(&base_url);
 
-    NodeImportResolver::new(TsConfigResolver::new(
-        NodeModulesResolver::new(swc_ecma_loader::TargetEnv::Node, Default::default(), true),
-        base_url,
-        rules,
-    ))
+    NodeImportResolver::with_base_dir(
+        TsConfigResolver::new(
+            NodeModulesResolver::new(swc_ecma_loader::TargetEnv::Node, Default::default(), true),
+            base_url.clone(),
+            rules,
+        ),
+        Some(base_url),
+    )
 }
 
 #[derive(Deserialize)]
diff --git a/node-swc/__tests__/transform/issue_4730_test.mjs b/node-swc/__tests__/transform/issue_4730_test.mjs
index dbe62c0cd4..f2886191d5 100644
--- a/node-swc/__tests__/transform/issue_4730_test.mjs
+++ b/node-swc/__tests__/transform/issue_4730_test.mjs
@@ -1,5 +1,5 @@
 import swc from "../../..";
-import { dirname, join } from "path";
+import { dirname, join, resolve } from "path";
 import { platform } from "os";
 import { fileURLToPath } from "url";
 
@@ -21,6 +21,7 @@ it("should work", async () => {
                 dynamicImport: true,
             },
             target: "es2020",
+            baseUrl: resolve('.'),
             paths: {
                 "@print/a": [join(dir, "./packages/a/src/index.ts")],
                 "@print/b": [join(dir, "./packages/b/src/index.ts")],