Have you used AI?
Yes
Bug Description
The pitch function constructs the importModule request using this.resourcePath:
this.importModule(
`${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`,
...
);
When the module was imported via !=! matchResource syntax (e.g. Box.module.css!=!./Box), this.resourcePath is the actual resource (Box.tsx), not the matchResource (Box.module.css). The child module's matchResource becomes Box.tsx, so webpack identifies it as a .tsx file rather than a .css file.
This breaks issuer-based rules that check for CSS extensions, e.g.:
{ test: /\.png$/, issuer: /\.(css|scss|sass)$/, type: "asset/resource" }
Assets referenced via url() in the CSS get type: javascript/auto instead of asset/resource, producing url([object Object]).
See this reproduction: https://github.com/mad-kat/next-css-url-match-resource-repro which shows a next.js app with a !=! virtual CSS module and a real .module.css side by side
Suggested fix:
- this.importModule(`${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, ...)
+ this.importModule(`${this._module.matchResource || this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, ...)
Caveat: I used claude to identify the issue we encountered with our setup, but I'm pretty sure this is the main culprit and this could unblock us to use "real" virtual CSS
Link to Minimal Reproduction and step to reproduce
https://github.com/mad-kat/next-css-url-match-resource-repro
npm install
npm run dev
- Open http://localhost:3000
- Inspect the generated CSS. The real
.module.css has a resolved url(), the !=! matchResource module has url([object Object])
Expected Behavior
url() references in virtual CSS modules (via !=! matchResource) should resolve to asset URLs, the same as in real .css files.
Actual Behavior
url() produces url([object Object]) because the asset's issuer is identified as .tsx instead of .css, so the issuer-based asset/resource rule doesn't match.
Environment
System:
OS: macOS 26.3.1
CPU: (14) arm64 Apple M4 Pro
Memory: 6.68 GB / 48.00 GB
Binaries:
Node: 24.13.1
npm: 11.8.0
Packages:
webpack-cli: 7.0.2
mini-css-extract-plugin: (bundled with next@16.2.1)
Have you used AI?
Yes
Bug Description
The pitch function constructs the
importModulerequest usingthis.resourcePath:When the module was imported via
!=!matchResource syntax (e.g.Box.module.css!=!./Box),this.resourcePathis the actual resource (Box.tsx), not the matchResource (Box.module.css). The child module's matchResource becomesBox.tsx, so webpack identifies it as a.tsxfile rather than a.cssfile.This breaks issuer-based rules that check for CSS extensions, e.g.:
Assets referenced via
url()in the CSS get type:javascript/autoinstead ofasset/resource, producingurl([object Object]).See this reproduction: https://github.com/mad-kat/next-css-url-match-resource-repro which shows a next.js app with a
!=!virtual CSS module and a real.module.cssside by sideSuggested fix:
Caveat: I used claude to identify the issue we encountered with our setup, but I'm pretty sure this is the main culprit and this could unblock us to use "real" virtual CSS
Link to Minimal Reproduction and step to reproduce
https://github.com/mad-kat/next-css-url-match-resource-repro
npm installnpm run dev.module.csshas a resolvedurl(), the!=!matchResource module hasurl([object Object])Expected Behavior
url()references in virtual CSS modules (via!=!matchResource) should resolve to asset URLs, the same as in real.cssfiles.Actual Behavior
url()producesurl([object Object])because the asset's issuer is identified as.tsxinstead of.css, so the issuer-based asset/resource rule doesn't match.Environment