Repository: swc-project/swc (rust)

Issue:
Add ".js" extensions on ESM import/export paths for ".jsx" files during transpilation
### Describe the feature

When using the `resolveFully: true` flag regular `.js` file imports/exports get their file extensions added in the transpiled code and it would be cool if this could be extended to files imported with the `.jsx` file extension.

**Current Behaviour**
Using this command: `swc ./src -d ./lib --strip-leading-paths`

And these files:
.swcrc
```json
{
  "module": {
    "type": "es6",
    "resolveFully": true
  },
  "jsc": {
    "baseUrl": "."
    "parser": {
      "syntax": "ecmascript"
      "jsx": true
    }
  }
}
```

src/button.jsx
```javascript
export function Button(props) {
  return <button {...props} />;
}
```

src/greet.js
```javascript
export function greet() {
  return "Hello";
}
```

src/index.js
```javascript
export { greet } from "./greet";
export { Button } from "./button";
```

Outputs:
- lib/button.js
- lib/greet.js
- lib/index.js

Where index.js contains
```javascript
export { greet } from "./greet.js";
export { Button } from "./button";
```

**Desired Behaviour**
Imports for files that were originally had a  `.jsx` extension should resolve to full imports/exports with a `.js` extension.

```javascript
export { greet } from "./greet.js";
export { Button } from "./button.js";
```

**Related issues**
- https://github.com/swc-project/swc/issues/8742

### Babel plugin or link to the feature description

_No response_

### Additional context

_No response_

Pull request description:
fix(es/module): Resolve `.jsx` imports fully
**Related issue:**

 - Closes #8935

Patch:
diff --git a/crates/swc_ecma_transforms_module/src/path.rs b/crates/swc_ecma_transforms_module/src/path.rs
index f1ae18c17c..13128fa40c 100644
--- a/crates/swc_ecma_transforms_module/src/path.rs
+++ b/crates/swc_ecma_transforms_module/src/path.rs
@@ -173,6 +173,12 @@ where
                 false
             };
 
+            let file_stem_matches = if let Some(stem) = target_path.file_stem() {
+                stem == orig_filename
+            } else {
+                false
+            };
+
             if self.config.resolve_fully && is_resolved_as_js {
             } else if orig_filename == "index" {
                 // Import: `./foo/index`
@@ -188,6 +194,8 @@ where
                 // Resolved: `./foo/index.js`
 
                 target_path.pop();
+            } else if is_resolved_as_non_js && self.config.resolve_fully && file_stem_matches {
+                target_path.set_extension("js");
             } else if !is_resolved_as_js && !is_resolved_as_index && !is_exact {
                 target_path.set_file_name(orig_filename);
             } else if is_resolved_as_non_js && is_exact {


Test patch:
diff --git a/crates/swc/tests/fixture/issues-8xxx/8184/1/output/src/a/index.jsx b/crates/swc/tests/fixture/issues-8xxx/8184/1/output/src/a/index.jsx
new file mode 100644
index 0000000000..c1d787c735
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8184/1/output/src/a/index.jsx
@@ -0,0 +1,6 @@
+import React from "react";
+var a = function(props) {
+    return /*#__PURE__*/ React.createElement("div", null);
+};
+a.propTypes = {};
+export default a;
diff --git a/crates/swc/tests/fixture/issues-8xxx/8935/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8935/input/.swcrc
new file mode 100644
index 0000000000..f605dc9802
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8935/input/.swcrc
@@ -0,0 +1,13 @@
+{
+    "module": {
+        "type": "es6",
+        "resolveFully": true
+    },
+    "jsc": {
+        "baseUrl": ".",
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8935/input/src/button.jsx b/crates/swc/tests/fixture/issues-8xxx/8935/input/src/button.jsx
new file mode 100644
index 0000000000..342c94a4c0
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8935/input/src/button.jsx
@@ -0,0 +1,5 @@
+export function Button(props) {
+    return <button {...props} />;
+  }export function Button(props) {
+  return <button {...props} />;
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8935/input/src/greet.js b/crates/swc/tests/fixture/issues-8xxx/8935/input/src/greet.js
new file mode 100644
index 0000000000..c6088ded78
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8935/input/src/greet.js
@@ -0,0 +1,3 @@
+export function greet() {
+    return "Hello";
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8935/input/src/index.js b/crates/swc/tests/fixture/issues-8xxx/8935/input/src/index.js
new file mode 100644
index 0000000000..5a61607d5d
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8935/input/src/index.js
@@ -0,0 +1,2 @@
+export { greet } from "./greet";
+export { Button } from "./button";
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8935/output/src/button.jsx b/crates/swc/tests/fixture/issues-8xxx/8935/output/src/button.jsx
new file mode 100644
index 0000000000..311147c2df
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8935/output/src/button.jsx
@@ -0,0 +1,6 @@
+export function Button(props) {
+    return /*#__PURE__*/ React.createElement("button", props);
+}
+export function Button(props) {
+    return /*#__PURE__*/ React.createElement("button", props);
+}
diff --git a/crates/swc/tests/fixture/issues-8xxx/8935/output/src/greet.js b/crates/swc/tests/fixture/issues-8xxx/8935/output/src/greet.js
new file mode 100644
index 0000000000..de8c1d9362
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8935/output/src/greet.js
@@ -0,0 +1,3 @@
+export function greet() {
+    return "Hello";
+}
diff --git a/crates/swc/tests/fixture/issues-8xxx/8935/output/src/index.js b/crates/swc/tests/fixture/issues-8xxx/8935/output/src/index.js
new file mode 100644
index 0000000000..ac1896108c
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8935/output/src/index.js
@@ -0,0 +1,2 @@
+export { greet } from "./greet.js";
+export { Button } from "./button.js";
diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs
index bbdd6aa7f9..fc20ec1e5d 100644
--- a/crates/swc/tests/projects.rs
+++ b/crates/swc/tests/projects.rs
@@ -796,6 +796,7 @@ fn tests(input_dir: PathBuf) {
 
                 if !entry.file_name().to_string_lossy().ends_with(".ts")
                     && !entry.file_name().to_string_lossy().ends_with(".js")
+                    && !entry.file_name().to_string_lossy().ends_with(".jsx")
                     && !entry.file_name().to_string_lossy().ends_with(".tsx")
                 {
                     continue;
diff --git a/crates/swc_ecma_transforms_module/tests/paths/issue-4605/2/output/index.ts b/crates/swc_ecma_transforms_module/tests/paths/issue-4605/2/output/index.ts
index 2061bc7522..37dcc5d213 100644
--- a/crates/swc_ecma_transforms_module/tests/paths/issue-4605/2/output/index.ts
+++ b/crates/swc_ecma_transforms_module/tests/paths/issue-4605/2/output/index.ts
@@ -1,1 +1,1 @@
-import "./src/rel.decorator";
+import "./src/rel.decorator.js";
diff --git a/crates/swc_ecma_transforms_module/tests/paths/issue-4607/output/index.ts b/crates/swc_ecma_transforms_module/tests/paths/issue-4607/output/index.ts
index b712ffa2a6..15b9a77e55 100644
--- a/crates/swc_ecma_transforms_module/tests/paths/issue-4607/output/index.ts
+++ b/crates/swc_ecma_transforms_module/tests/paths/issue-4607/output/index.ts
@@ -1,2 +1,2 @@
-import boo from "./src/utils/shared/foo/boo";
+import boo from "./src/utils/shared/foo/boo.js";
 console.log(boo());
diff --git a/crates/swc_ecma_transforms_module/tests/paths/issue-4619/2/output/index.ts b/crates/swc_ecma_transforms_module/tests/paths/issue-4619/2/output/index.ts
index 2061bc7522..37dcc5d213 100644
--- a/crates/swc_ecma_transforms_module/tests/paths/issue-4619/2/output/index.ts
+++ b/crates/swc_ecma_transforms_module/tests/paths/issue-4619/2/output/index.ts
@@ -1,1 +1,1 @@
-import "./src/rel.decorator";
+import "./src/rel.decorator.js";
diff --git a/crates/swc_ecma_transforms_module/tests/paths/issue-6779/1/output/index.ts b/crates/swc_ecma_transforms_module/tests/paths/issue-6779/1/output/index.ts
index 9307fab4cc..8b6f665f6e 100644
--- a/crates/swc_ecma_transforms_module/tests/paths/issue-6779/1/output/index.ts
+++ b/crates/swc_ecma_transforms_module/tests/paths/issue-6779/1/output/index.ts
@@ -1,5 +1,5 @@
 async function main() {
-    const addFunction = (await import("./add")).default // This doesn't work
+    const addFunction = (await import("./add.js")).default // This doesn't work
     ;
     console.log('2 + 3 =', addFunction(2, 3));
 }
diff --git a/crates/swc_ecma_transforms_module/tests/paths/issue-6779/2/output/index.ts b/crates/swc_ecma_transforms_module/tests/paths/issue-6779/2/output/index.ts
index 3f3c56319c..8dc7cb79b1 100644
--- a/crates/swc_ecma_transforms_module/tests/paths/issue-6779/2/output/index.ts
+++ b/crates/swc_ecma_transforms_module/tests/paths/issue-6779/2/output/index.ts
@@ -1,4 +1,4 @@
-import addFunction from "./add";
+import addFunction from "./add.js";
 async function main() {
     console.log('2 + 3 =', addFunction(2, 3));
 }