docs/react-v9/contributing/rfcs/shared/build-system/stop-styles-transforms.md
@fluentui/react-componentsWe've begun noticing a pattern where apps are bundled into multiple separate bundles. For example, there's the main bundle for the app itself and a separate bundle for a first UI piece fetched from a CDN. This problem is described in the following issues:
TL;DR: Griffel relies on the order of CSS classes, which can lead to clashes when multiple bundles are involved. See the simple example below:
<!-- CURRENT STATE -->
<!-- main bundle -->
<style>
.order0 {
padding: 10px;
}
.order1 {
padding-left: 5px;
}
</style>
<!-- CDN bundle -->
<style>
.order0 {
padding: 10px;
}
</style>
<!-- HTML -->
<!--
🔴 We expect that a "div" below will have "padding-left: 5px", but instead, it has "padding: 10px".
The issue appears because the CDN bundle loads after the main bundle, resulting in style overrides. This occurs because ".order0" appears in both bundles, and CSS prioritizes the order of appearance.
-->
<div class="order0 order1"></div>
The single reliable solution is to prefix styles with a unique identifier. This prevents CSS rules from colliding, for example:
<!-- PROPOSAL -->
<!-- main bundle -->
<style>
.main-order0 {
padding: 10px;
}
.main-order1 {
padding-left: 5px;
}
</style>
<!-- CDN bundle -->
<style>
.order0 {
padding: 10px;
}
</style>
<!-- HTML -->
<!--
✅ Now the "div" below will have "padding-left: 5px" as CSS rules don't clash anymore.
-->
<div class="main-order0 main-order1"></div>
The proposed idea is to integrate prefix as a part of classes generation in Griffel as a salt for hashing (see hashClassName function):
Integrating prefixing into Griffel is relatively straightforward. However, we currently undergo a pre-processing step in @fluentui/react-components that modifies JavaScript code.
// packages/react-components/react-menu/src/components/MenuDivider/useMenuDividerStyles.styles.ts
// 📝 output is simpfied
const useStyles = makeStyles({
root: {
...shorthands.margin('4px', '-5px', '4px', '-5px'),
},
});
// @fluentui/react-menu/lib/components/MenuDivider/useMenuDividerStyles.styles.js
// 📝 output is simpfied
const useStyles = /*#__PURE__*/ __styles(
{
root: {
B6of3ja: 'fvjh0tl',
t21cq0: ['f1rnx978', 'f1q7jvqi'],
},
},
{
d: ['.fvjh0tl{margin-top:4px;}', '.f1rnx978{margin-right:-5px;}', '.f1q7jvqi{margin-left:-5px;}'],
},
);
Addressing prefixing for pre-processed styles presents a challenge, as they have already been transformed into CSS rules and classes:
__styles() (artifact of AOT compilation in Griffel) to prefix classes that makes the idea of AOT compilation obsoleteWe have proposed a temporary workaround in microsoft/griffel#453. The workaround involves increasing the specificity of the CSS rules in the consuming app. This is achieved by adding a unique class to the root element of the app, for example:
.color-red {
}
/* becomes ⬇️ */
.PREFIX .color-red {
}
As a result, the CSS rules will have a higher specificity and will override any conflicting rules from the application. This approach is not ideal, as it affects performance (makes CSS rules less efficient).
Note: We cannot prefix classes as Fluent components have pre-processed styles:
jsconst useStyles = /*#__PURE__*/ __styles( // Part 1: CSS classes mapping { root: { B6of3ja: 'fvjh0tl' } }, // Part 2: CSS rules { d: ['.fvjh0tl{margin-top:4px;}'] }, );
- We cannot prefix classes in the mapping with current APIs (part 1) as classes have been already hashed~~~~
- We can prefix CSS classes (part 2) properly only but invoking Stylis (CSS preprocessor) on every CSS rule
Note:
__styles()is a result of AOT compilation in Griffel, adding additional responsibilities to it makes the idea of AOT compilation obsolete.
A StackBlitz example demonstrates this workaround.
Note: Applications already undertake this responsibility, as first and third-party packages do not include pre-processed styles.
This option involves eliminating the pre-processing step from @fluentui/react-components, shifting the responsibility to the consuming application.
This is the simplest option, albeit it necessitates adjustments in the consuming app and is not backward compatible. Consequently, apps not utilizing AOT in Griffel will experience noticeable slowdowns during initial loading.
This option is more complex to maintain but ensures backward compatibility and avoids breaking changes.
We will offer three outputs for each package under the following exports in package.json:
"import": "./lib/index.js" (as is) - ESM output with pre-processed styles (default for bundlers)"require": "./lib-commonjs/index.js" (as is) - CJS output with pre-processed styles (default for Node.js)"import-raw": "./lib-raw/index.js" (🆕)- ESM output with unprocessed stylesConsumers desiring to use the class prefixing feature will need to configure their bundler to use the "import-raw" output. For example, in Webpack:
// webpack.config.js
module.exports = {
resolve: {
conditionNames: ['import-raw', '...'],
},
};