Repository: swc-project/swc (rust)

Issue:
When using @swc/helpers with @swc/core 1.3.79, the build output path has added node_modules.
### Describe the bug

I have provided a minimal demo for reproduction in the following repository:
https://github.com/hi-otto/swc-1.3.79-bug

### Input code

```typescript
import test from '~/util/test';

class X {

}

console.log(test, X);
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true
    },
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "./src/*"
      ]
    },
    "externalHelpers": true
  }
}
```


### Playground link

https://github.com/hi-otto/swc-1.3.79-bug

### Expected behavior

why 1.3.79 add `node_modules` before `@swc/helpers`

## dist-1.3.78/index.js
```
import { _ as _class_call_check } from "@swc/helpers/_/_class_call_check";
import test from "./util/test";
var X = function X() {
    "use strict";
    _class_call_check(this, X);
};
console.log(test, X);

```

## dist-1.3.79/index.js
```
import { _ as _class_call_check } from "node_modules/@swc/helpers/esm/_class_call_check";
import test from "./util/test";
var X = function X() {
    "use strict";
    _class_call_check(this, X);
};
console.log(test, X);

```


### Actual behavior

_No response_

### Version

1.3.79

### Additional context

_No response_

Pull request description:
fix(es/module): Don't resolve as `node_modules` from `TscResolver`
**Description:**


**Related issue:**

 - Closes #7863

Patch:
diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs
index 1d46aff06a..d1e034bb47 100644
--- a/crates/swc/src/config/mod.rs
+++ b/crates/swc/src/config/mod.rs
@@ -1951,7 +1951,7 @@ fn build_resolver(base_url: PathBuf, paths: CompiledPaths) -> Box<SwcImportResol
 
     let r = {
         let r = TsConfigResolver::new(
-            NodeModulesResolver::new(Default::default(), Default::default(), true),
+            NodeModulesResolver::without_node_modules(Default::default(), Default::default(), true),
             base_url.clone(),
             paths.clone(),
         );
diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs
index 00782b1b30..304edb49e8 100644
--- a/crates/swc/src/lib.rs
+++ b/crates/swc/src/lib.rs
@@ -193,7 +193,7 @@ pub mod resolver {
         preserve_symlinks: bool,
     ) -> CachingResolver<TsConfigResolver<NodeModulesResolver>> {
         let r = TsConfigResolver::new(
-            NodeModulesResolver::new(target_env, alias, preserve_symlinks),
+            NodeModulesResolver::without_node_modules(target_env, alias, preserve_symlinks),
             base_url,
             paths,
         );
diff --git a/crates/swc_ecma_loader/src/resolvers/node.rs b/crates/swc_ecma_loader/src/resolvers/node.rs
index ed9554a30a..80ade412a9 100644
--- a/crates/swc_ecma_loader/src/resolvers/node.rs
+++ b/crates/swc_ecma_loader/src/resolvers/node.rs
@@ -103,6 +103,7 @@ pub struct NodeModulesResolver {
     alias: AHashMap<String, String>,
     // if true do not resolve symlink
     preserve_symlinks: bool,
+    ignore_node_modules: bool,
 }
 
 static EXTENSIONS: &[&str] = &["ts", "tsx", "js", "jsx", "json", "node"];
@@ -118,6 +119,21 @@ impl NodeModulesResolver {
             target_env,
             alias,
             preserve_symlinks,
+            ignore_node_modules: false,
+        }
+    }
+
+    /// Create a node modules resolver which does not care about `node_modules`
+    pub fn without_node_modules(
+        target_env: TargetEnv,
+        alias: AHashMap<String, String>,
+        preserve_symlinks: bool,
+    ) -> Self {
+        Self {
+            target_env,
+            alias,
+            preserve_symlinks,
+            ignore_node_modules: true,
         }
     }
 
@@ -369,6 +385,10 @@ impl NodeModulesResolver {
         base_dir: &Path,
         target: &str,
     ) -> Result<Option<PathBuf>, Error> {
+        if self.ignore_node_modules {
+            return Ok(None);
+        }
+
         let absolute_path = to_absolute_path(base_dir)?;
         let mut path = Some(&*absolute_path);
         while let Some(dir) = path {
diff --git a/crates/swc_ecma_loader/src/resolvers/tsc.rs b/crates/swc_ecma_loader/src/resolvers/tsc.rs
index b52942805d..cc2f07f219 100644
--- a/crates/swc_ecma_loader/src/resolvers/tsc.rs
+++ b/crates/swc_ecma_loader/src/resolvers/tsc.rs
@@ -119,6 +119,20 @@ where
                     "Resolved `{}` as `{}` from `{}`",
                     module_specifier, resolved, base
                 );
+
+                if let FileName::Real(target) = &resolved {
+                    // If node_modules is in path, we should return module specifier.
+                    if target.components().any(|c| {
+                        if let Component::Normal(v) = c {
+                            v == "node_modules"
+                        } else {
+                            false
+                        }
+                    }) {
+                        return Ok(FileName::Real(module_specifier.into()));
+                    }
+                }
+
                 Ok(resolved)
             }
 


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7863/1/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7863/1/input/.swcrc
new file mode 100644
index 0000000000..9733b67ce0
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7863/1/input/.swcrc
@@ -0,0 +1,15 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "typescript",
+            "tsx": true
+        },
+        "baseUrl": ".",
+        "paths": {
+            "~/*": [
+                "./src/*"
+            ]
+        },
+        "externalHelpers": true
+    }
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7863/1/input/1.js b/crates/swc/tests/fixture/issues-7xxx/7863/1/input/1.js
new file mode 100644
index 0000000000..7098039128
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7863/1/input/1.js
@@ -0,0 +1,7 @@
+import test from '~/util/test';
+
+class X {
+
+}
+
+console.log(test, X);
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7863/1/output/1.js b/crates/swc/tests/fixture/issues-7xxx/7863/1/output/1.js
new file mode 100644
index 0000000000..a825da71da
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7863/1/output/1.js
@@ -0,0 +1,7 @@
+import { _ as _class_call_check } from "@swc/helpers/_/_class_call_check";
+import test from "./src/util/test";
+var X = function X() {
+    "use strict";
+    _class_call_check(this, X);
+};
+console.log(test, X);
diff --git a/node-swc/__tests__/transform/issue_4730_test.mjs b/node-swc/__tests__/transform/issue_4730_test.mjs
index 2839fe3dc7..8f00a8f092 100644
--- a/node-swc/__tests__/transform/issue_4730_test.mjs
+++ b/node-swc/__tests__/transform/issue_4730_test.mjs
@@ -37,7 +37,7 @@ it("should work", async () => {
         Object.defineProperty(exports, \\"__esModule\\", {
             value: true
         });
-        const _interop_require_wildcard = require(\\"node_modules/@swc/helpers/esm/_interop_require_wildcard\\");
+        const _interop_require_wildcard = require(\\"@swc/helpers/_/_interop_require_wildcard\\");
         const _b = require(\\"../packages/b/src\\");
         async function display() {
             const displayA = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard._(require(\\"../packages/a/src\\"))).then((c)=>c.displayA);