Skip to content

[Bug] ?inline CSS module filenames break webpack/Next.js in library mode with preserveModules #22210

@Lookwe69

Description

@Lookwe69

Description

When building a Vite library with preserveModules: true and CSS imported via the standard with { type: 'css' } import attribute (processed by vite-plugin-standard-css-modules), the output contains files with ? in their name — e.g. gui-element.styles.scss?inline.js.

Downstream bundlers like webpack and Next.js treat ? as a query string separator when resolving module paths, so they look for ./styles/gui-element.styles.scss (the part before ?) instead of the actual file, causing a build failure.

Error

Module not found: Can't resolve './styles/gui-element.styles.scss'

> 1 | import e from "./styles/gui-element.styles.scss?inline.js";

Root cause

vite-plugin-standard-css-modules transforms import x from './foo.scss' with { type: 'css' } into:

import __raw_x from './foo.scss?inline';
const x = unsafeCSS(__raw_x);

Vite's built-in ?inline CSS mechanism then resolves this to a virtual module with ID path/to/foo.scss?inline. With preserveModules: true, Rolldown uses the module ID directly as the output filename, producing foo.scss?inline.js.

Rolldown's sanitizeFileName option does not apply to preserveModules entry chunks (tracked in rolldown/rolldown#8761), so there is no built-in way to strip the ? before it reaches the filesystem.

Environment

  • Vite: 8.0.6
  • Rolldown: 1.0.0-rc.13
  • Node: 24

Reproduction

// vite.config.js
import { standardCssModules } from 'vite-plugin-standard-css-modules';

export default defineConfig({
  build: {
    rolldownOptions: {
      output: { preserveModules: true },
    },
    lib: { entry: 'src/index.ts', formats: ['es'] },
  },
  plugins: [
    standardCssModules({ outputMode: 'CSSResult' }),
  ],
});
// src/my-element.ts
import styles from './my-element.styles.scss' with { type: 'css' };

Output:

dist/
  my-element.js          → imports "./my-element.styles.scss?inline.js"
  my-element.styles.scss?inline.js   ← breaks webpack/Next.js

Workaround

Use entryFileNames (which covers all modules under preserveModules) to sanitize the filename:

// vite.config.js
rolldownOptions: {
  output: {
    preserveModules: true,
    entryFileNames: (chunk) => `${chunk.name.replace(/\?/g, '_')}.js`,
  },
},

This produces my-element.styles.scss_inline.js and updates all import references accordingly.

Expected behavior

Either:

  1. Rolldown's sanitizeFileName should be applied to preserveModules entry chunks (fixing the root cause in [Bug]: sanitizeFileName is ignored for JS chunks when preserveModules: true with object-form entry rolldown/rolldown#8761), or
  2. Vite should document this as a known caveat of preserveModules + CSS ?inline and recommend the entryFileNames workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions