Repository: webpack-contrib/css-loader (js)

Issue:
Escaped query string value doesn't propagate to downstream loader
- Operating System: macOS 11.2
- Node Version: 12.18.3
- NPM Version: 6.14.6
- webpack Version: 4.27.0
- css-loader Version: 5.0.1

### Code

**webpack.config.js**

```js
const path = require('path');

module.exports = {
  context: __dirname,
  entry: './example.css',
  output: {
    path: path.resolve(__dirname),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.svg$/i,
        resourceQuery: /color/,
        enforce: 'pre',
        use: {
          loader: path.resolve(__dirname, './svg-color-loader.js'),
        },
      },
      {
        test: /\.svg$/i,
        use: {
          loader: 'file-loader',
        },
      },
      {
        test: /\.css$/i,
        use: {
          loader: 'css-loader',
        },
      },
    ],
  },
};
```

**example.css**

```css
.example {
  background-image: url('./example.svg?color=%23BAAFDB');
}
```

**svg-color-loader.js**

```js
const { parseQuery } = require('loader-utils');

module.exports = function(source) {
  const query = parseQuery(this.resourceQuery);

  // transform `source` based on the value of `query.color`
};
```

### Expected Behavior

The `query` object in `svg-color-loader.js` is `{ color: '#BAAFDB' }`. (This used to be the case in `css-loader` v3.)

### Actual Behavior

The `query` object in `svg-color-loader.js` is `{ color: '' }`.

### Notes

I'm not sure if this is the same issue as #1048.

I am attempting to upgrade `css-loader` from v3 to v5. However it now seems to treat the escaped query string value `%23BAAFDB` differently. Is this a bug in `css-loader` (i.e. it's mistakenly parsing  `%23BAAFDB` as a URL fragment?) or is there some alternative approach I should use for my downstream loader?

Thanks.

Pull request description:
fix: pass query with hash to other loaders
<!--
  HOLY CRAP a Pull Request. We ❤️ those!

  If you remove or skip this template, you'll make the 🐼 sad and the mighty god
  of Github will appear and pile-drive the close button from a great height
  while making animal noises.

  Please place an x (no spaces!) in all [ ] that apply
-->

This PR contains a:

- [x] **bugfix**
- [ ] new **feature**
- [ ] **code refactor**
- [x] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [ ] **metadata update**

### Motivation / Use-Case

fixes #1257

### Breaking Changes

No

### Additional Info

No

Patch:
diff --git a/src/utils.js b/src/utils.js
index 294f9cd..b33bcfe 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -85,10 +85,10 @@ function normalizeUrl(url, isStringValue) {
   }
 
   if (matchNativeWin32Path.test(url)) {
-    return decodeURIComponent(normalizedUrl);
+    return decodeURI(normalizedUrl);
   }
 
-  return decodeURIComponent(unescape(normalizedUrl));
+  return decodeURI(unescape(normalizedUrl));
 }
 
 function requestify(url, rootContext) {


Test patch:
diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap
index 8db06c7..841a14b 100644
--- a/test/__snapshots__/loader.test.js.snap
+++ b/test/__snapshots__/loader.test.js.snap
@@ -64,6 +64,37 @@ Array [
 
 exports[`loader should not generate console.warn when plugins disabled and hideNothingWarning is "true": warnings 1`] = `Array []`;
 
+exports[`loader should pass queries to other loader: errors 1`] = `Array []`;
+
+exports[`loader should pass queries to other loader: module 1`] = `
+"// Imports
+import ___CSS_LOADER_API_IMPORT___ from \\"../../src/runtime/api.js\\";
+import ___CSS_LOADER_GET_URL_IMPORT___ from \\"../../src/runtime/getUrl.js\\";
+import ___CSS_LOADER_URL_IMPORT_0___ from \\"./url/image.svg?color=%23BAAFDB%3F\\";
+var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
+var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___, { hash: \\"#foo\\" });
+// Module
+___CSS_LOADER_EXPORT___.push([module.id, \\".example {\\\\n    background-image: url(\\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \\");\\\\n}\\\\n\\", \\"\\"]);
+// Exports
+export default ___CSS_LOADER_EXPORT___;
+"
+`;
+
+exports[`loader should pass queries to other loader: result 1`] = `
+Array [
+  Array [
+    "./other-loader-query.css",
+    ".example {
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=#foo);
+}
+",
+    "",
+  ],
+]
+`;
+
+exports[`loader should pass queries to other loader: warnings 1`] = `Array []`;
+
 exports[`loader should reuse \`ast\` from "postcss-loader": errors 1`] = `Array []`;
 
 exports[`loader should reuse \`ast\` from "postcss-loader": module 1`] = `
diff --git a/test/fixtures/other-loader-query.css b/test/fixtures/other-loader-query.css
new file mode 100644
index 0000000..bf6816c
--- /dev/null
+++ b/test/fixtures/other-loader-query.css
@@ -0,0 +1,3 @@
+.example {
+    background-image: url('./url/image.svg?color=%23BAAFDB%3F#foo');
+}
diff --git a/test/fixtures/other-loader-query.js b/test/fixtures/other-loader-query.js
new file mode 100644
index 0000000..a720736
--- /dev/null
+++ b/test/fixtures/other-loader-query.js
@@ -0,0 +1,5 @@
+import css from './other-loader-query.css';
+
+__export__ = css;
+
+export default css;
diff --git a/test/fixtures/url/image.svg b/test/fixtures/url/image.svg
new file mode 100644
index 0000000..b1761ca
--- /dev/null
+++ b/test/fixtures/url/image.svg
@@ -0,0 +1,5 @@
+<svg viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
+    <path d="m600 0 530.3 300v600l-530.3 300-530.3-300v-600z" fill="#fff"/>
+    <path d="m1035.6 879.3-418.1 236.5v-184.2l260.5-143.3zm28.6-25.9v-494.6l-153 88.3v317.9zm-901.5 25.9 418.1 236.5v-184.2l-260.5-143.3-157.6 91zm-28.6-25.9v-494.6l153 88.3v317.9zm17.9-526.6 428.8-242.6v178.1l-274.7 151.1-2.1 1.2zm894.3 0-428.8-242.6v178.1l274.7 151.1 2.1 1.2z" fill="#8ed6fb"/>
+    <path d="m580.8 889.7-257-141.3v-280l257 148.4zm36.7 0 257-141.3v-280l-257 148.4zm-276.3-453.7 258-141.9 258 141.9-258 149z" fill="#1c78c0"/>
+</svg>
\ No newline at end of file
diff --git a/test/helpers/svg-color-loader.js b/test/helpers/svg-color-loader.js
new file mode 100644
index 0000000..61a7802
--- /dev/null
+++ b/test/helpers/svg-color-loader.js
@@ -0,0 +1,11 @@
+const querystring = require("querystring");
+
+module.exports = function loader() {
+  const query = querystring.parse(this.resourceQuery.slice(1));
+
+  if (typeof query.color === "undefined" || query.color !== "#BAAFDB?") {
+    throw new Error(`Error, 'color' is '${query.color}'`);
+  }
+
+  return `export default "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";`;
+};
diff --git a/test/loader.test.js b/test/loader.test.js
index e072f71..81df60c 100644
--- a/test/loader.test.js
+++ b/test/loader.test.js
@@ -472,4 +472,42 @@ describe("loader", () => {
     expect(getWarnings(stats)).toMatchSnapshot("warnings");
     expect(getErrors(stats)).toMatchSnapshot("errors");
   });
+
+  it("should pass queries to other loader", async () => {
+    const compiler = getCompiler(
+      "./other-loader-query.js",
+      {},
+      {
+        module: {
+          rules: [
+            {
+              test: /\.svg$/i,
+              resourceQuery: /color/,
+              enforce: "pre",
+              use: {
+                loader: path.resolve(
+                  __dirname,
+                  "./helpers/svg-color-loader.js"
+                ),
+              },
+            },
+            {
+              test: /\.css$/i,
+              rules: [{ loader: path.resolve(__dirname, "../src") }],
+            },
+          ],
+        },
+      }
+    );
+    const stats = await compile(compiler);
+
+    expect(getModuleSource("./other-loader-query.css", stats)).toMatchSnapshot(
+      "module"
+    );
+    expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot(
+      "result"
+    );
+    expect(getWarnings(stats)).toMatchSnapshot("warnings");
+    expect(getErrors(stats)).toMatchSnapshot("errors");
+  });
 });