Motivation: classNames-concept on prod & dev should not deviate from local development
- MUIv4 forcefully removes all meaningful CSS-classnames during build process by compiling all jss-classnames to
jss<id>
- Not only is this a big deviation from what the developer, unit tests and CI/CD-pipeline see, it can also highly impact CSS-behavior if CSS-selectors are used.
- This makes styling of 3rd-party components e.g. FOSS-plugins but even Backstage itself very hard or at least instable and brittle.
- This PR disables jss-className compilation by re-implementing the name-generation algorithm in
createGenerateClassName.ts
which then enables styling the dark themed version ofCatalogReactUserListPicker
.
// createGenerateClass.ts
/** This function is used to disable jss-className-compilation on prod. Motivation: classNames-concept on prod should not deviate from dev.
*
* pulled from https://github.com/mui/material-ui/blob/master/packages/mui-styles/src/createGenerateClassName/createGenerateClassName.js
========================================================== */
// pulled from @material-ui/core/styles/ThemeProvider/nested
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
const nested = hasSymbol ? Symbol.for('mui.nested') : '__THEME_NESTED__';
/**
* This is the list of the style rule name we use as drop in replacement for the built-in
* pseudo classes (:checked, :disabled, :focused, etc.).
*
* Why do they exist in the first place?
* These classes are used at a specificity of 2.
* It allows them to override previously defined styles as well as
* being untouched by simple user overrides.
*/
const stateClasses = [
'checked',
'disabled',
'error',
'focused',
'focusVisible',
'required',
'expanded',
'selected',
];
const getNextCounterId = (currentCounter: number): number => {
const newCounter = currentCounter + 1;
if (process.env.NODE_ENV !== 'production') {
if (newCounter >= 1e10) {
// eslint-disable-next-line no-console
console.warn(
[
'MUI: You might have a memory leak.',
'The ruleCounter is not supposed to grow that much.',
].join('')
);
}
}
return newCounter;
};
// Returns a function which generates unique class names based on counters.
// When new generator function is created, rule counter is reset.
// We need to reset the rule counter for SSR for each request.
//
// It's inspired by
// https://github.com/cssinjs/jss/blob/4e6a05dd3f7b6572fdd3ab216861d9e446c20331/src/utils/createGenerateClassName.js
export function createGenerateClassName(options: { seed?: string } = {}) {
const { seed } = options;
const seedPrefix = seed ? `${seed}-` : '';
let ruleCounter = 0;
return (rule: any, styleSheet: any) => {
const name = styleSheet.options.name;
if (name && name.indexOf('Mui') === 0 && !styleSheet.options.link) {
if (stateClasses.indexOf(rule.key) !== -1) {
return `Mui-${rule.key}`;
}
const prefix = `${seedPrefix}${name}-${rule.key}`;
if (!styleSheet.options.theme[nested] || seed !== '') {
return prefix;
}
ruleCounter = getNextCounterId(ruleCounter);
return `${prefix}-${ruleCounter}`;
}
ruleCounter = getNextCounterId(ruleCounter);
const suffix = `${rule.key}-${ruleCounter}`;
if (styleSheet.options.classNamePrefix) {
return `${seedPrefix}${styleSheet.options.classNamePrefix}-${suffix}`;
}
return `${seedPrefix}${suffix}`;
};
}
// App.tsx
import { createGenerateClassName } from './createGenerateClassName';
import { StylesProvider } from '@material-ui/styles';
function App() {
return (
<StylesProvider generateClassName={createGenerateClassName()}>
<App />
</StylesProvider>
);
}