-
-
Notifications
You must be signed in to change notification settings - Fork 833
Description
In production builds (Volto with css-loader + LESS), classes generated for .module.less files appear with the literal placeholder [local] instead of the actual local class name. Example observed:
Carousel-module__[local]___KTvpt
Root cause: there is a custom getLocalIdent in volto/webpack-plugins/webpack-less-plugin.js. When you define getLocalIdent, css-loader stops post‑processing the template (localIdentName). The custom function calls interpolateName, which does not know the [local] token, so it remains unchanged. In development (dev=true) this getLocalIdent is not applied, hiding the bug.
To Reproduce
- In a Volto project using the original webpack-less-plugin.js, create Example.module.less:
.container { color: red; }
- Import it in a React component:
import styles from './Example.module.less'; <div className={styles.container}>Test</div>
- Run a production build:
pnpm --filter @plone/volto build - Inspect the final HTML or CSS (build/public/static/css/*.css) and locate the generated class:
Example-module__[local]___a1B2C - Start in dev mode (pnpm --filter @plone/volto start) and confirm the class name is correct (no literal [local]).
Expected behavior
The placeholder [local] should be replaced by the original local class name:
Example-module__container___a1B2C
Preserving the pattern defined in localIdentName: '[name][local]_[hash:base64:5]'.
Additional context
Original config snippet:
modules: {
auto: true,
localIdentName: '[name]__[local]___[hash:base64:5]',
getLocalIdent: getLocalIdent,
}Custom function:
function getLocalIdent(loaderContext, localIdentName, localName, options) {
const relativeResourcePath = /* ... */;
options.content = `${options.hashPrefix}${relativeResourcePath}\x00${localName}`;
return interpolateName(loaderContext, localIdentName, options);
}interpolateName replaces [hash], [name], [path], etc., but not [local]. Without css-loader’s internal post-processing, [local] stays literal.
Why it went unnoticed: dev environment doesn’t apply getLocalIdent; more .module.less components were added recently; production class names weren’t inspected before.
Impact:
- Unexpected class names with literal tokens.
- Harder debugging and substring-based global overrides.
- Potential inconsistency where selectors assumed expansion of [local].
Fix options:
- Patch getLocalIdent to manually replace [local]:
function getLocalIdent(loaderContext, localIdentName, localName, options) { const relativeResourcePath = normalizePath( path.relative(options.context, loaderContext.resourcePath), ); options.content = `${options.hashPrefix}${relativeResourcePath}\x00${localName}`; const finalLocalIdentName = localIdentName.includes('[local]') ? localIdentName.replace(/\[local]/g, localName) : localIdentName; return interpolateName(loaderContext, finalLocalIdentName, options); }