Repository: webpack-contrib/mini-css-extract-plugin (js)

Issue:
TypeError: publicPath.replace is not a function when output.publicPath of webpack config is a function
<!-- Please don't delete this template otherwise your issue will be closed immediately -->
<!-- Before creating an issue please make sure you are using the latest version of webpack. -->

### Bug report

<!-- Please ask questions on discussions, StackOverflow or the webpack Gitter. -->
<!-- https://github.com/webpack/webpack/discussions -->
<!-- https://stackoverflow.com/questions/ask?tags=webpack -->
<!-- https://gitter.im/webpack/webpack -->
<!-- Issues which contain questions or support requests will be closed. -->

When using function in `output.publicPath` of Webpack config, the loader of mini-css-extract-plugin seems to handle this situation incorrectly and leading to an error(see below).

> If this is a bug, I'm glad to make a PR

### Actual Behavior

```plain
assets by status 822 bytes [cached] 1 asset
runtime modules 663 bytes 3 modules
cacheable modules 71 bytes
  ./src/index.js 32 bytes [built] [code generated]
  ./src/index.css 39 bytes [built] [code generated] [1 error]

ERROR in ./src/index.css
Module build failed (from ./node_modules/.pnpm/mini-css-extract-plugin@2.4.5_webpack@5.64.4/node_modules/mini-css-extract-plugin/dist/loader.js):
TypeError: publicPath.replace is not a function
    at Object.pitch (/Users/username/Workspace/test/webpack-demo/node_modules/.pnpm/mini-css-extract-plugin@2.4.5_webpack@5.64.4/node_modules/mini-css-extract-plugin/dist/loader.js:176:114)
 @ ./src/index.js 1:0-21

webpack 5.64.4 compiled with 1 error in 285 ms
```

### Expected Behavior

Expect no errors would be throw.

<!-- "It should work" is not a helpful explanation -->
<!-- Explain exactly how it should behave -->

### How Do We Reproduce?

Reproduce repository: https://github.com/roland-reed/mini-css-extract-plugin-function-public-path, steps are listed in README.md

I dig a little and find that the following code should be related:

https://github.com/webpack-contrib/mini-css-extract-plugin/blob/e8c08a1c252f18b03612903d5ec75ebcddc538e0/src/loader.js#L182-L192

<!-- A great way to do this is to provide your configuration via a GitHub repository -->
<!-- The most helpful is a minimal reproduction with instructions on how to reproduce -->
<!-- Repositories with too many files or large `webpack.config.js` files are not suitable -->
<!-- Please only add small code snippets directly into this issue -->
<!-- https://gist.github.com is a good place for longer code snippets -->
<!-- If your issue is caused by a plugin or loader, please create an issue on the loader/plugin repository instead -->

### Please paste the results of `npx webpack-cli info` here, and mention other relevant information

```plain
 npx webpack-cli info

  System:
    OS: macOS 12.0.1
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 40.96 MB / 32.00 GB
  Binaries:
    Node: 14.15.4 - ~/.nvm/versions/node/v14.15.4/bin/node
    Yarn: 1.22.11 - ~/.nvm/versions/node/v14.15.4/bin/yarn
    npm: 6.14.10 - ~/.nvm/versions/node/v14.15.4/bin/npm
  Browsers:
    Chrome: 96.0.4664.55
    Firefox: 94.0.1
    Safari: 15.1
  Packages:
    css-loader: 6.5.1 => 6.5.1 
    webpack: 5.64.4 => 5.64.4 
    webpack-cli: 4.9.1 => 4.9.1 
```


Pull request description:
fix: crash when `publicPath` is function
<!--
  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 #873

### Breaking Changes

No

### Additional Info

There is limitation - `hash` is `xxxxx`, because we don't know hash at this stage, we are building modules and hash calculation is impossible right now

Patch:
diff --git a/src/loader.js b/src/loader.js
index 6dac326..ca319d5 100644
--- a/src/loader.js
+++ b/src/loader.js
@@ -204,13 +204,22 @@ export function pitch(request) {
       return;
     }
 
-    const isAbsolutePublicPath = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/.test(publicPath);
-    const publicPathForExtract = isAbsolutePublicPath
-      ? publicPath
-      : `${ABSOLUTE_PUBLIC_PATH}${publicPath.replace(
-          /\./g,
-          SINGLE_DOT_PATH_SEGMENT
-        )}`;
+    let publicPathForExtract;
+
+    if (typeof publicPath === "string") {
+      const isAbsolutePublicPath = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/.test(
+        publicPath
+      );
+
+      publicPathForExtract = isAbsolutePublicPath
+        ? publicPath
+        : `${ABSOLUTE_PUBLIC_PATH}${publicPath.replace(
+            /\./g,
+            SINGLE_DOT_PATH_SEGMENT
+          )}`;
+    } else {
+      publicPathForExtract = publicPath;
+    }
 
     this.importModule(
       `${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`,


Test patch:
diff --git a/test/cases/publicpath-function-2/expected/webpack-5-importModule/c9e192c015437a21dea1.svg b/test/cases/publicpath-function-2/expected/webpack-5-importModule/c9e192c015437a21dea1.svg
new file mode 100644
index 0000000..5b3b22a
--- /dev/null
+++ b/test/cases/publicpath-function-2/expected/webpack-5-importModule/c9e192c015437a21dea1.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3046.7 875.7"><title>logo-on-dark-bg</title><path fill="#FFF" d="M387 0l387 218.9v437.9L387 875.7 0 656.8V218.9L387 0z"/><path fill="#8ED6FB" d="M704.9 641.7L399.8 814.3V679.9l190.1-104.6 115 66.4zm20.9-18.9V261.9l-111.6 64.5v232l111.6 64.4zM67.9 641.7L373 814.3V679.9L182.8 575.3 67.9 641.7zM47 622.8V261.9l111.6 64.5v232L47 622.8zm13.1-384.3L373 61.5v129.9L172.5 301.7l-1.6.9-110.8-64.1zm652.6 0l-312.9-177v129.9l200.5 110.2 1.6.9 110.8-64z"/><path fill="#1C78C0" d="M373 649.3L185.4 546.1V341.8L373 450.1v199.2zm26.8 0l187.6-103.1V341.8L399.8 450.1v199.2zM198.1 318.2l188.3-103.5 188.3 103.5-188.3 108.7-188.3-108.7z"/><path fill="#F5FAFA" d="M1164.3 576.3h82.5l84.1-280.2h-80.4l-49.8 198.8-53.1-198.8H1078l-53.6 198.8-49.3-198.8h-80.4l83.6 280.2h82.5l52-179.5 51.5 179.5zM1335.2 437c0 84.1 57.3 146.3 147.4 146.3 69.7 0 107.2-41.8 117.9-61.6l-48.8-37c-8 11.8-30 34.3-68.1 34.3-41.3 0-71.3-26.8-72.9-64.3H1608c.5-5.4.5-10.7.5-16.1 0-91.6-49.3-149.5-136.1-149.5-79.9 0-137.2 63.2-137.2 147.9zm77.7-30.6c3.2-32.1 25.7-56.8 60.6-56.8 33.8 0 58.4 22.5 60 56.8h-120.6zm223.5 169.9h69.7v-28.9c7.5 9.1 35.4 35.9 83.1 35.9 80.4 0 137.2-60.5 137.2-146.8 0-86.8-52.5-147.3-132.9-147.3-48.2 0-76.1 26.8-83.1 36.4V188.9h-73.9v387.4h-.1zm71.8-139.3c0-52.5 31.1-82.5 71.8-82.5 42.9 0 71.8 33.8 71.8 82.5 0 49.8-30 80.9-71.8 80.9-45 0-71.8-36.5-71.8-80.9zm247 239.5h73.9V547.3c7 9.1 34.8 35.9 83.1 35.9 80.4 0 132.9-60.5 132.9-147.3 0-85.7-56.8-146.8-137.2-146.8-47.7 0-75.6 26.8-83.1 36.4V296h-69.7v380.5h.1zm71.8-241.1c0-44.5 26.8-80.9 71.8-80.9 41.8 0 71.8 31.1 71.8 80.9 0 48.8-28.9 82.5-71.8 82.5-40.7 0-71.8-30-71.8-82.5zm231.5 54.1c0 58.9 48.2 93.8 105 93.8 32.2 0 53.6-9.6 68.1-25.2l4.8 18.2h65.4V398.9c0-62.7-26.8-109.8-116.8-109.8-42.9 0-85.2 16.1-110.4 33.2l27.9 50.4c20.9-10.7 46.6-19.8 74.5-19.8 32.7 0 50.9 16.6 50.9 41.3v18.2c-10.2-7-32.2-15.5-60.6-15.5-65.4-.1-108.8 37.4-108.8 92.6zm73.9-2.2c0-23 19.8-39.1 48.2-39.1s48.8 14.5 48.8 39.1c0 23.6-20.4 38.6-48.2 38.6s-48.8-15.5-48.8-38.6zm348.9 30.6c-46.6 0-79.8-33.8-79.8-81.4 0-45 29.5-82 77.2-82 31.6 0 53.1 15.5 65.4 26.8l20.9-62.2c-18.2-13.9-47.2-30-88.4-30-85.2 0-149 62.7-149 147.9s62.2 146.3 149.5 146.3c40.7 0 71.3-17.1 87.3-30l-19.8-60.5c-12.4 10.1-34.9 25.1-63.3 25.1zm110.9 58.4h73.9V431.6l93.8 144.7h86.8L2940.6 423l98.6-127h-83.1l-90 117.9v-225h-73.9v387.4z"/></svg>
\ No newline at end of file
diff --git a/test/cases/publicpath-function-2/expected/webpack-5-importModule/nested/again/style.css b/test/cases/publicpath-function-2/expected/webpack-5-importModule/nested/again/style.css
new file mode 100644
index 0000000..5d0b093
--- /dev/null
+++ b/test/cases/publicpath-function-2/expected/webpack-5-importModule/nested/again/style.css
@@ -0,0 +1,5 @@
+body {
+  background: green;
+  background-image: url(http://example.com/XXXX/c9e192c015437a21dea1.svg);
+}
+
diff --git a/test/cases/publicpath-function-2/expected/webpack-5-importModule/nested/style.css b/test/cases/publicpath-function-2/expected/webpack-5-importModule/nested/style.css
new file mode 100644
index 0000000..03203ea
--- /dev/null
+++ b/test/cases/publicpath-function-2/expected/webpack-5-importModule/nested/style.css
@@ -0,0 +1,5 @@
+body {
+  background: red;
+  background-image: url(http://example.com/XXXX/c9e192c015437a21dea1.svg);
+}
+
diff --git a/test/cases/publicpath-function-2/expected/webpack-5/c9e192c015437a21dea1.svg b/test/cases/publicpath-function-2/expected/webpack-5/c9e192c015437a21dea1.svg
new file mode 100644
index 0000000..5b3b22a
--- /dev/null
+++ b/test/cases/publicpath-function-2/expected/webpack-5/c9e192c015437a21dea1.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3046.7 875.7"><title>logo-on-dark-bg</title><path fill="#FFF" d="M387 0l387 218.9v437.9L387 875.7 0 656.8V218.9L387 0z"/><path fill="#8ED6FB" d="M704.9 641.7L399.8 814.3V679.9l190.1-104.6 115 66.4zm20.9-18.9V261.9l-111.6 64.5v232l111.6 64.4zM67.9 641.7L373 814.3V679.9L182.8 575.3 67.9 641.7zM47 622.8V261.9l111.6 64.5v232L47 622.8zm13.1-384.3L373 61.5v129.9L172.5 301.7l-1.6.9-110.8-64.1zm652.6 0l-312.9-177v129.9l200.5 110.2 1.6.9 110.8-64z"/><path fill="#1C78C0" d="M373 649.3L185.4 546.1V341.8L373 450.1v199.2zm26.8 0l187.6-103.1V341.8L399.8 450.1v199.2zM198.1 318.2l188.3-103.5 188.3 103.5-188.3 108.7-188.3-108.7z"/><path fill="#F5FAFA" d="M1164.3 576.3h82.5l84.1-280.2h-80.4l-49.8 198.8-53.1-198.8H1078l-53.6 198.8-49.3-198.8h-80.4l83.6 280.2h82.5l52-179.5 51.5 179.5zM1335.2 437c0 84.1 57.3 146.3 147.4 146.3 69.7 0 107.2-41.8 117.9-61.6l-48.8-37c-8 11.8-30 34.3-68.1 34.3-41.3 0-71.3-26.8-72.9-64.3H1608c.5-5.4.5-10.7.5-16.1 0-91.6-49.3-149.5-136.1-149.5-79.9 0-137.2 63.2-137.2 147.9zm77.7-30.6c3.2-32.1 25.7-56.8 60.6-56.8 33.8 0 58.4 22.5 60 56.8h-120.6zm223.5 169.9h69.7v-28.9c7.5 9.1 35.4 35.9 83.1 35.9 80.4 0 137.2-60.5 137.2-146.8 0-86.8-52.5-147.3-132.9-147.3-48.2 0-76.1 26.8-83.1 36.4V188.9h-73.9v387.4h-.1zm71.8-139.3c0-52.5 31.1-82.5 71.8-82.5 42.9 0 71.8 33.8 71.8 82.5 0 49.8-30 80.9-71.8 80.9-45 0-71.8-36.5-71.8-80.9zm247 239.5h73.9V547.3c7 9.1 34.8 35.9 83.1 35.9 80.4 0 132.9-60.5 132.9-147.3 0-85.7-56.8-146.8-137.2-146.8-47.7 0-75.6 26.8-83.1 36.4V296h-69.7v380.5h.1zm71.8-241.1c0-44.5 26.8-80.9 71.8-80.9 41.8 0 71.8 31.1 71.8 80.9 0 48.8-28.9 82.5-71.8 82.5-40.7 0-71.8-30-71.8-82.5zm231.5 54.1c0 58.9 48.2 93.8 105 93.8 32.2 0 53.6-9.6 68.1-25.2l4.8 18.2h65.4V398.9c0-62.7-26.8-109.8-116.8-109.8-42.9 0-85.2 16.1-110.4 33.2l27.9 50.4c20.9-10.7 46.6-19.8 74.5-19.8 32.7 0 50.9 16.6 50.9 41.3v18.2c-10.2-7-32.2-15.5-60.6-15.5-65.4-.1-108.8 37.4-108.8 92.6zm73.9-2.2c0-23 19.8-39.1 48.2-39.1s48.8 14.5 48.8 39.1c0 23.6-20.4 38.6-48.2 38.6s-48.8-15.5-48.8-38.6zm348.9 30.6c-46.6 0-79.8-33.8-79.8-81.4 0-45 29.5-82 77.2-82 31.6 0 53.1 15.5 65.4 26.8l20.9-62.2c-18.2-13.9-47.2-30-88.4-30-85.2 0-149 62.7-149 147.9s62.2 146.3 149.5 146.3c40.7 0 71.3-17.1 87.3-30l-19.8-60.5c-12.4 10.1-34.9 25.1-63.3 25.1zm110.9 58.4h73.9V431.6l93.8 144.7h86.8L2940.6 423l98.6-127h-83.1l-90 117.9v-225h-73.9v387.4z"/></svg>
\ No newline at end of file
diff --git a/test/cases/publicpath-function-2/expected/webpack-5/nested/again/style.css b/test/cases/publicpath-function-2/expected/webpack-5/nested/again/style.css
new file mode 100644
index 0000000..4d9fb06
--- /dev/null
+++ b/test/cases/publicpath-function-2/expected/webpack-5/nested/again/style.css
@@ -0,0 +1,5 @@
+body {
+  background: green;
+  background-image: url(http://example.com/4d869bb95ecdf3f870fb/c9e192c015437a21dea1.svg);
+}
+
diff --git a/test/cases/publicpath-function-2/expected/webpack-5/nested/style.css b/test/cases/publicpath-function-2/expected/webpack-5/nested/style.css
new file mode 100644
index 0000000..e27aee7
--- /dev/null
+++ b/test/cases/publicpath-function-2/expected/webpack-5/nested/style.css
@@ -0,0 +1,5 @@
+body {
+  background: red;
+  background-image: url(http://example.com/ca6e489a6807af3e778c/c9e192c015437a21dea1.svg);
+}
+
diff --git a/test/cases/publicpath-function-2/nested/again/style.css b/test/cases/publicpath-function-2/nested/again/style.css
new file mode 100644
index 0000000..254f81b
--- /dev/null
+++ b/test/cases/publicpath-function-2/nested/again/style.css
@@ -0,0 +1,4 @@
+body {
+  background: green;
+  background-image: url(../../react.svg);
+}
diff --git a/test/cases/publicpath-function-2/nested/style.css b/test/cases/publicpath-function-2/nested/style.css
new file mode 100644
index 0000000..c9853be
--- /dev/null
+++ b/test/cases/publicpath-function-2/nested/style.css
@@ -0,0 +1,4 @@
+body {
+  background: red;
+  background-image: url(../react.svg);
+}
diff --git a/test/cases/publicpath-function-2/react.svg b/test/cases/publicpath-function-2/react.svg
new file mode 100644
index 0000000..5b3b22a
--- /dev/null
+++ b/test/cases/publicpath-function-2/react.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3046.7 875.7"><title>logo-on-dark-bg</title><path fill="#FFF" d="M387 0l387 218.9v437.9L387 875.7 0 656.8V218.9L387 0z"/><path fill="#8ED6FB" d="M704.9 641.7L399.8 814.3V679.9l190.1-104.6 115 66.4zm20.9-18.9V261.9l-111.6 64.5v232l111.6 64.4zM67.9 641.7L373 814.3V679.9L182.8 575.3 67.9 641.7zM47 622.8V261.9l111.6 64.5v232L47 622.8zm13.1-384.3L373 61.5v129.9L172.5 301.7l-1.6.9-110.8-64.1zm652.6 0l-312.9-177v129.9l200.5 110.2 1.6.9 110.8-64z"/><path fill="#1C78C0" d="M373 649.3L185.4 546.1V341.8L373 450.1v199.2zm26.8 0l187.6-103.1V341.8L399.8 450.1v199.2zM198.1 318.2l188.3-103.5 188.3 103.5-188.3 108.7-188.3-108.7z"/><path fill="#F5FAFA" d="M1164.3 576.3h82.5l84.1-280.2h-80.4l-49.8 198.8-53.1-198.8H1078l-53.6 198.8-49.3-198.8h-80.4l83.6 280.2h82.5l52-179.5 51.5 179.5zM1335.2 437c0 84.1 57.3 146.3 147.4 146.3 69.7 0 107.2-41.8 117.9-61.6l-48.8-37c-8 11.8-30 34.3-68.1 34.3-41.3 0-71.3-26.8-72.9-64.3H1608c.5-5.4.5-10.7.5-16.1 0-91.6-49.3-149.5-136.1-149.5-79.9 0-137.2 63.2-137.2 147.9zm77.7-30.6c3.2-32.1 25.7-56.8 60.6-56.8 33.8 0 58.4 22.5 60 56.8h-120.6zm223.5 169.9h69.7v-28.9c7.5 9.1 35.4 35.9 83.1 35.9 80.4 0 137.2-60.5 137.2-146.8 0-86.8-52.5-147.3-132.9-147.3-48.2 0-76.1 26.8-83.1 36.4V188.9h-73.9v387.4h-.1zm71.8-139.3c0-52.5 31.1-82.5 71.8-82.5 42.9 0 71.8 33.8 71.8 82.5 0 49.8-30 80.9-71.8 80.9-45 0-71.8-36.5-71.8-80.9zm247 239.5h73.9V547.3c7 9.1 34.8 35.9 83.1 35.9 80.4 0 132.9-60.5 132.9-147.3 0-85.7-56.8-146.8-137.2-146.8-47.7 0-75.6 26.8-83.1 36.4V296h-69.7v380.5h.1zm71.8-241.1c0-44.5 26.8-80.9 71.8-80.9 41.8 0 71.8 31.1 71.8 80.9 0 48.8-28.9 82.5-71.8 82.5-40.7 0-71.8-30-71.8-82.5zm231.5 54.1c0 58.9 48.2 93.8 105 93.8 32.2 0 53.6-9.6 68.1-25.2l4.8 18.2h65.4V398.9c0-62.7-26.8-109.8-116.8-109.8-42.9 0-85.2 16.1-110.4 33.2l27.9 50.4c20.9-10.7 46.6-19.8 74.5-19.8 32.7 0 50.9 16.6 50.9 41.3v18.2c-10.2-7-32.2-15.5-60.6-15.5-65.4-.1-108.8 37.4-108.8 92.6zm73.9-2.2c0-23 19.8-39.1 48.2-39.1s48.8 14.5 48.8 39.1c0 23.6-20.4 38.6-48.2 38.6s-48.8-15.5-48.8-38.6zm348.9 30.6c-46.6 0-79.8-33.8-79.8-81.4 0-45 29.5-82 77.2-82 31.6 0 53.1 15.5 65.4 26.8l20.9-62.2c-18.2-13.9-47.2-30-88.4-30-85.2 0-149 62.7-149 147.9s62.2 146.3 149.5 146.3c40.7 0 71.3-17.1 87.3-30l-19.8-60.5c-12.4 10.1-34.9 25.1-63.3 25.1zm110.9 58.4h73.9V431.6l93.8 144.7h86.8L2940.6 423l98.6-127h-83.1l-90 117.9v-225h-73.9v387.4z"/></svg>
\ No newline at end of file
diff --git a/test/cases/publicpath-function-2/webpack.config.js b/test/cases/publicpath-function-2/webpack.config.js
new file mode 100644
index 0000000..0b88269
--- /dev/null
+++ b/test/cases/publicpath-function-2/webpack.config.js
@@ -0,0 +1,32 @@
+import Self from "../../../src";
+
+module.exports = {
+  entry: {
+    // Specific CSS entry point, with output to a nested folder
+    "nested/style": "./nested/style.css",
+    // Note that relative nesting of output is the same as that of the input
+    "nested/again/style": "./nested/again/style.css",
+  },
+  output: {
+    // Compute publicPath relative to the CSS output
+    publicPath: (pathData) => `http://example.com/${pathData.hash}/`,
+  },
+  module: {
+    rules: [
+      {
+        test: /\.css$/,
+        use: [
+          {
+            loader: Self.loader,
+          },
+          "css-loader",
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new Self({
+      filename: "[name].css",
+    }),
+  ],
+};