Repository: oxc-project/oxc-resolver
Pull request #443 (2025-04-04 12:31:01)

fix: handle query and fragment in pacakge.json `exports` and `imports` field
package.json `exports` and `imports` field con contain queries or fragments:

`"exports": { ".": { "default": "./foo.js?query#fragment" }`

closes #417

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/443?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.68%. Comparing base [(`ffbdfa5`)](https://app.codecov.io/gh/oxc-project/oxc-resolver/commit/ffbdfa5499e8cb834f2d5e6ce3dbd4e411d87cb6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project) to head [(`3bf44ec`)](https://app.codecov.io/gh/oxc-project/oxc-resolver/commit/3bf44ec734cdcb26c4352bf691488de694820782?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #443   +/-   ##
=======================================
  Coverage   94.67%   94.68%           
=======================================
  Files          13       13           
  Lines        2705     2710    +5     
=======================================
+ Hits         2561     2566    +5     
  Misses        144      144           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/443?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
codspeed-hq[bot]: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/oxc-project/oxc-resolver/branches/04-04-fix_handle_query_and_fragment_in_pacakge.json_exports_and_imports_field)

### Merging #443 will **not alter performance**

<sub>Comparing <code>04-04-fix_handle_query_and_fragment_in_pacakge.json_exports_and_imports_field</code> (3bf44ec) with <code>main</code> (ffbdfa5)</sub>



### Summary

`✅ 3` untouched benchmarks  





Patch:
diff --git a/src/lib.rs b/src/lib.rs
index bf023f5..f0f2df8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1634,6 +1634,12 @@ impl<C: Cache> ResolverGeneric<C> {
 
         // 1. If target is a String, then
         if let Some(target) = target.as_string() {
+            // Target string con contain queries or fragments:
+            // `"exports": { ".": { "default": "./foo.js?query#fragment" }`
+            let parsed = Specifier::parse(target).map_err(ResolveError::Specifier)?;
+            ctx.with_query_fragment(parsed.query, parsed.fragment);
+            let target = parsed.path();
+
             // 1. If target does not start with "./", then
             if !target.starts_with("./") {
                 // 1. If isImports is false, or if target starts with "../" or "/", or if target is a valid URL, then


Test patch:
diff --git a/fixtures/enhanced_resolve/test/fixtures/exports-field/node_modules/exports-field/package.json b/fixtures/enhanced_resolve/test/fixtures/exports-field/node_modules/exports-field/package.json
index 6b105c0..4311b58 100644
--- a/fixtures/enhanced_resolve/test/fixtures/exports-field/node_modules/exports-field/package.json
+++ b/fixtures/enhanced_resolve/test/fixtures/exports-field/node_modules/exports-field/package.json
@@ -13,6 +13,8 @@
       "node": "./lib/",
       "default": "./lib/"
     },
-    "./dist/a.js": "./../../a.js"
+    "./dist/a.js": "./../../a.js",
+    "./query.js": "./x.js?query",
+    "./fragment.js": "./x.js#fragment"
   }
 }
diff --git a/fixtures/enhanced_resolve/test/fixtures/imports-field/package.json b/fixtures/enhanced_resolve/test/fixtures/imports-field/package.json
index 248351a..93e97d5 100644
--- a/fixtures/enhanced_resolve/test/fixtures/imports-field/package.json
+++ b/fixtures/enhanced_resolve/test/fixtures/imports-field/package.json
@@ -7,7 +7,9 @@
     "#b": "../b.js",
     "#ccc/": "c/",
     "#c": "c",
-    "#a/": "a/"
+    "#a/": "a/",
+    "#query": "./a.js?query",
+    "#fragment": "./a.js#fragment"
   },
   "other": {
     "imports": {
diff --git a/src/tests/exports_field.rs b/src/tests/exports_field.rs
index bce72d0..eceef3f 100644
--- a/src/tests/exports_field.rs
+++ b/src/tests/exports_field.rs
@@ -29,6 +29,8 @@ fn test_simple() {
     let pass = [
         ("resolve root using exports field, not a main field", f.clone(), "exports-field", f.join("node_modules/exports-field/x.js")),
         ("resolver should respect condition names", f.clone(), "exports-field/dist/main.js", f.join("node_modules/exports-field/lib/lib2/main.js")),
+        ("should resolve query", f.clone(), "exports-field/query.js", f.join("node_modules/exports-field/x.js?query")),
+        ("should resolve fragment", f.clone(), "exports-field/fragment.js", f.join("node_modules/exports-field/x.js#fragment")),
         // enhanced_resolve behaves differently to node.js. enhanced_resolve fallbacks when an
         // array item is unresolved, where as node.js fallbacks when an array has an
         // InvalidPackageTarget error.
diff --git a/src/tests/imports_field.rs b/src/tests/imports_field.rs
index 6dc5782..1aa507c 100644
--- a/src/tests/imports_field.rs
+++ b/src/tests/imports_field.rs
@@ -26,6 +26,8 @@ fn test_simple() {
     #[rustfmt::skip]
     let pass = [
         ("should resolve using imports field instead of self-referencing", f.clone(), "#imports-field", f.join("b.js")),
+        ("should resolve query", f.clone(), "#query", f.join("a.js?query")),
+        ("should resolve fragment", f.clone(), "#fragment", f.join("a.js#fragment")),
         ("should resolve using imports field instead of self-referencing for a subpath", f.join("dir"), "#imports-field", f.join("b.js")),
         ("should resolve package #1", f.clone(), "#a/dist/main.js", f.join("node_modules/a/lib/lib2/main.js")),
         ("should resolve package #3", f.clone(), "#ccc/index.js", f.join("node_modules/c/index.js")),