Repository: swc-project/swc (rust)

Issue:
spack: error when resolving packages with pnpm
### Describe the bug

Running `spack` with [pnpm](https://pnpm.io/) errors because spack is not able to resolve the packages from `node_modules`. It works fine with `npm`.

Error:
```
node:internal/process/promises:246
triggerUncaughtException(err, true /_ fromPromise _/);
^

[Error: load_transformed failed

Caused by:
0: failed to analyze module
1: failed to resolve is-odd from /home/sachinraja/test/spack-pnpm-bug/node_modules/is-even/index.js
2: index not found] {
code: 'GenericFailure'
}
```

### Input code

```typescript
import isEven from "is-even";

isEven(2);
```


### Config

```js
// spack.config.js
const { config } = require("@swc/core/spack");

module.exports = config({
  entry: {
    web: __dirname + "/index.ts",
  },
  output: {
    name: "index.js",
    path: __dirname + "/dist",
  },
});
```


### Reproduction
https://github.com/sachinraja/spack-pnpm-bug

### Expected behavior

spack resolves and inlines the `is-even` package from `node_modules`

### Version

1.2.116

### Additional context

[pnpm uses symlinks](https://pnpm.io/symlinked-node-modules-structure)

Pull request:
fix(bundler): support pnpm
**Description:**
after swc_ecma_loader find the module path, call canonicalize to make it resolve the symbolic links.

**Related issue (if exists):**
- Closes #2945

Patch:
diff --git a/crates/dbg-swc/src/reduce_min/deps.rs b/crates/dbg-swc/src/reduce_min/deps.rs
index 81452f9cb6..520541fd72 100644
--- a/crates/dbg-swc/src/reduce_min/deps.rs
+++ b/crates/dbg-swc/src/reduce_min/deps.rs
@@ -20,6 +20,7 @@ pub fn collect_deps(cm: Arc<SourceMap>, working_dir: &Path, entry: &Path) -> Res
         resolver: Box::new(NodeModulesResolver::new(
             TargetEnv::Node,
             Default::default(),
+            false,
         )),
     };
 
diff --git a/crates/node/src/bundle.rs b/crates/node/src/bundle.rs
index 78180451ef..df4c447e84 100644
--- a/crates/node/src/bundle.rs
+++ b/crates/node/src/bundle.rs
@@ -221,9 +221,19 @@ pub(crate) fn bundle(
         .unwrap_or_default();
 
     let resolver: Box<dyn Resolve> = if let Some((base_url, paths)) = paths {
-        Box::new(paths_resolver(target_env, alias, base_url, paths))
+        Box::new(paths_resolver(
+            target_env,
+            alias,
+            base_url,
+            paths,
+            static_items.config.preserve_symlinks,
+        ))
     } else {
-        Box::new(environment_resolver(target_env, alias))
+        Box::new(environment_resolver(
+            target_env,
+            alias,
+            static_items.config.preserve_symlinks,
+        ))
     };
 
     Ok(AsyncTask::with_optional_signal(
diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs
index 1df6e0675b..7f5d56cd92 100644
--- a/crates/swc/src/lib.rs
+++ b/crates/swc/src/lib.rs
@@ -179,16 +179,25 @@ pub mod resolver {
         alias: AHashMap<String, String>,
         base_url: PathBuf,
         paths: CompiledPaths,
+        preserve_symlinks: bool,
     ) -> CachingResolver<TsConfigResolver<NodeModulesResolver>> {
-        let r = TsConfigResolver::new(NodeModulesResolver::new(target_env, alias), base_url, paths);
+        let r = TsConfigResolver::new(
+            NodeModulesResolver::new(target_env, alias, preserve_symlinks),
+            base_url,
+            paths,
+        );
         CachingResolver::new(40, r)
     }
 
     pub fn environment_resolver(
         target_env: TargetEnv,
         alias: AHashMap<String, String>,
+        preserve_symlinks: bool,
     ) -> NodeResolver {
-        CachingResolver::new(40, NodeModulesResolver::new(target_env, alias))
+        CachingResolver::new(
+            40,
+            NodeModulesResolver::new(target_env, alias, preserve_symlinks),
+        )
     }
 }
 
diff --git a/crates/swc_bundler/examples/bundle.rs b/crates/swc_bundler/examples/bundle.rs
index 69f2b756f4..a0a575366f 100644
--- a/crates/swc_bundler/examples/bundle.rs
+++ b/crates/swc_bundler/examples/bundle.rs
@@ -80,7 +80,7 @@ fn do_test(_entry: &Path, entries: HashMap<String, FileName>, inline: bool, mini
             Loader { cm: cm.clone() },
             CachingResolver::new(
                 4096,
-                NodeModulesResolver::new(TargetEnv::Node, Default::default()),
+                NodeModulesResolver::new(TargetEnv::Node, Default::default(), true),
             ),
             swc_bundler::Config {
                 require: false,
diff --git a/crates/swc_ecma_loader/src/resolvers/node.rs b/crates/swc_ecma_loader/src/resolvers/node.rs
index 9162de5a65..b1661159c7 100644
--- a/crates/swc_ecma_loader/src/resolvers/node.rs
+++ b/crates/swc_ecma_loader/src/resolvers/node.rs
@@ -83,20 +83,33 @@ enum StringOrBool {
 pub struct NodeModulesResolver {
     target_env: TargetEnv,
     alias: AHashMap<String, String>,
+    // if true do not resolve symlink
+    preserve_symlinks: bool,
 }
 
 static EXTENSIONS: &[&str] = &["ts", "tsx", "js", "jsx", "json", "node"];
 
 impl NodeModulesResolver {
     /// Create a node modules resolver for the target runtime environment.
-    pub fn new(target_env: TargetEnv, alias: AHashMap<String, String>) -> Self {
-        Self { target_env, alias }
+    pub fn new(
+        target_env: TargetEnv,
+        alias: AHashMap<String, String>,
+        preserve_symlinks: bool,
+    ) -> Self {
+        Self {
+            target_env,
+            alias,
+            preserve_symlinks,
+        }
     }
 
     fn wrap(&self, path: Option<PathBuf>) -> Result<FileName, Error> {
         if let Some(path) = path {
-            let path = path.clean();
-            return Ok(FileName::Real(path));
+            if self.preserve_symlinks {
+                return Ok(FileName::Real(path.clean()));
+            } else {
+                return Ok(FileName::Real(path.canonicalize()?));
+            }
         }
         bail!("index not found")
     }
diff --git a/crates/swc_node_bundler/src/v1/mod.rs b/crates/swc_node_bundler/src/v1/mod.rs
index 44072bbe61..ab53d75ca6 100644
--- a/crates/swc_node_bundler/src/v1/mod.rs
+++ b/crates/swc_node_bundler/src/v1/mod.rs
@@ -51,6 +51,9 @@ pub struct Config {
 
     #[serde(default)]
     pub alias: AHashMap<TargetEnv, AHashMap<String, String>>,
+
+    #[serde(default)]
+    pub preserve_symlinks: bool,
 }
 
 impl Config {


Test patch:
diff --git a/crates/swc_node_bundler/tests/fixture.rs b/crates/swc_node_bundler/tests/fixture.rs
index 3d897c8ad4..52100bc00b 100644
--- a/crates/swc_node_bundler/tests/fixture.rs
+++ b/crates/swc_node_bundler/tests/fixture.rs
@@ -62,7 +62,7 @@ fn pass(input_dir: PathBuf) {
                 compiler.globals(),
                 cm.clone(),
                 &loader,
-                environment_resolver(TargetEnv::Node, Default::default()),
+                environment_resolver(TargetEnv::Node, Default::default(), false),
                 Config {
                     require: true,
                     disable_inliner: true,