---
tags: performance, one-library, pitch
---
# Plan: Static stylesheets
## What
This project aims to convert our styling across the entire Fluent UI repo to use static stylesheets with variables to configure them. By unifying on static css, we can also address our blocking convergence issues.
The css in js approach also blocking our convergence; in v7 we use `merge-styles` while in v0 we use `fela`.
There are a number of other things which will need to be resolved along the way; how we get variables produced from the theme provider, how we do slots and shorthand, and how we converge some of the core utilities. I will use the Avatar as a forcing factor to resolve these.
Planned deliverables:
- [ ] Converged `react-avatar` package that ships in both v0 and v7
- [ ] Guidance on how to convert css in js to raw css
- [ ] Deduping of all related utilities to converge
- [ ] Shared provider solution to enable styling
- [ ] Base components for `react-avatar` (Avatar, Status, Image, Label, Box, Icon)
- [ ] Integration with `compose`
- [ ] Clear guidance for how to write components in the new model
## Appetite
* Unify our styling solution (lean the towers, no fela or merge-styles)
* Super fast performance
* Easier themability (no need to override selectors; just override variables)
* Less dependency on React (how we get to web components)
* Simple theming that doesn't sacrifice perf
## How
1. Create `react-avatar` shared package for `Avatar` and `Status` components.
3. Convert css in js it away from fela/merge-styles to use raw css.
4. Leverage css variables to provide configurability of the styling.
5. Provide a non-styled version (base) and styled version of each component.
6. Provide a way to address shorthand props and slot recomposition. (work with Shift)
7. Ensure backwards compatibility by providing a shim layer for deprecated APIs (`styles` prop.)
8. Make sure there are tests: conformance, api surface, snapshots, visual regression, perf and bundle size.
9. Consume in both suite packages.
## Risks
* May require breaking changes
* Disruptive if conversion not well documented and simple
## Achievements
March 31:
- [x] react-avatar created in branch.
- [x] Northstar code moved in, builds.
- [x] Sass/storybook pipeline working in new package.
- [x] Experimented with custom output for supporting dynamic style registration in child windows.
- [x] javascript styles converted to raw scss with variables.
- [x] Provider modified to load styles as needed based on target window. (Required for current approach of how styles render in child window.)
## Todos
- [ ] css-in-js should still work with compose
- [ ] stylesheet ordering test
- [ ] Get a unified provider going with the stylesheet injection helper working.
- [ ] rtl styling
## Details
### Requirements
Original goals:
https://fluentsite.z22.web.core.windows.net/component-architecture
1. Base components do not reference any styles.
2.
3. Developers write the least amount of code to define their components correctly.
4. Full slots support compatible with northstar slots work.
5. Base components can be composed, but can be easily recomposed with new default props, styles, or slot opinions.
6. Component variants can be easily defined.
### How components register styles
1. Provider exposes `renderStyles` function
2. Components which come with a stylesheet use a `useStylesheet` hook to call `renderStyles` with the appropriate stylesheet and `target` document.
3.
### Handling subcomponent style specificity
Each component comes with a stylesheet. When components render other components in their slots, their styling overrides for the children must be more specific than the rules of the children.
Take for instance `Avatar` renders `Status`. The `Status` may
## Questions
#### How will the css be distributed and consumed?
1. Current: it gets bundled w javascript along with the code.
```
.css stylesheet
.scss -> .ts export { classes }
<Button />
<Provider target>
</Provider>
```
2. Distribute pure css files as well, so that and end user could use css extractor webpack plugin.
2b. Custom plugin for doing extraction.
*
## This week's objectives
- [ ] Use/modify Shift's shared `compose` utility
- [ ] Compose can define `slots` so that `AvatarBase` does not directly depend on a styled `Status` component.
- [ ] Shorthand notation alternatives to support JSX and remove `create` factory hard dependencies.
- [ ] Provider can emit css variables
- [ ] Image/Icon/Label...own package, or remove dependency
- [ ] Simplify how the class names are added; can there be a generic way to recognize a slot class vs a modifier class without building the typical `cx(classes)` logic in the render function?
## Remaining work
- [ ] Measure how costly it would be to leverage style projection to child windows, over using a custom build step to make style injection dynamic based on React context.
- [ ] Provider split out into own package
- [ ] Deprecated API shim for backwards compatibility
- [ ] Ensure existing tests work
- [ ] Add tests
- [ ] [conversion guide](https://hackmd.io/XExkOI0JQIeje8ju2cVoVQ?both) for converging components
## Learnings
**It is unlikely we'll be able to build a perfect converter for javascript styling to static.** Some things can be automated, but not all. Some required a lot of evaluation and refactoring. Some rules that added unnecessary overhead and were able to be condensed. It was faster and less error prone to dissect the styles and tweak them into slot classes and modifier classes.
## Open questions
Some component dependencies in Avatar seem unneeded. Why depend on `Image` or `Label` over an `img` or `label`? It ends up bringing in extra weight.
#### How: project styles to a child window
When a component loads and needs to inject styles, depending on the implementation (either ours or customer's), this could happen in a bunch of ways.
If you look at webpack [`style-loader`](https://github.com/webpack-contrib/style-loader/blob/master/src/runtime/injectStylesIntoStyleTag.js) it will insert the element on module load.
Module load means that on PLT1, all styles end up being added well before we start rendering. And with css extraction, that will further be the case.
The problem is child windows - how do styles get to the child?
To support contextual child windows, the northstar code use target on the context:
```tsx
<Provider target={childDocument}>
<Button />
</Provider>
```
It requires that on `Button` render, we get context and evaluate if we need to render styling on target. It means that every component (written by us or by others) needs to this at render time:
1. get a shared context
2. pull `target` from context
3. determine if styles have been injected into `target` based on a weakmap cache
5. if not, append styles to target head
If the context logic is in `compose`, every component will only be able to register styles on render time if they use compose.
As soon as you don't use `compose`, it doesn't work. We would have to educate third parties to also use `compose` and also use our styling approach.
I think it would cheaper and more versatile approach to support child windows would be:
1. import the classes from static import using a standard loader. Let the loader load the styles into the current document as it does today (or let the extraction plugin do its magic.)
2. for child windows, do the projection trick by copying the styles from the host to the child, and then on new styles being injected, keep doing that. This could be abstracted in a React component:
```tsx
<Provider>
<ProjectStyles from={parent} to={child} />
<Button/>
</Provider>
```
Or even a hook:
```tsx
const ChildWindow = props => {
const [ childDocument ] = React.useState(getChildDocument);
useProjectStyles({ from: document, to: childDocument });
};
```
Project styles would do this:
1. on initial render, copy style elements from source window to destination window
2. track source style element adds through mutationobserver
3. add style to child as needed
4. on teardown, remove style elements
Thoughts?
So far I've been thinking, we should just use a custom css loader tdevs might use webpack css-loader and style-loader to do this. Even in our inner loop today, we have this setup.
This
Option A: Use compose to inject styles always so that it can reference the target defined in the context.
* Token naming - how granular vs vague should they be?
- squareAvatarBorderRadius
- are colors separate
- what about token sets? How. are they applied to various parts of a component?
- Design prop
* Theme shape:
* siteVariables vs componentVariables
* This translate into framework agnostic?
* How do plate sets relate to this?
*
## Overview
Today, rendering a Fluent UI button is slow, and a huge contributing factor is the time spent evaluating styles.
A couple years ago we jumped to css in js as a way to provide partners customization options. That is, rather than bring-your-own-styling solutions, you could directly apply style rules on a component through a javascript API.
This means: when we render a component, we do these steps:
1. Merge the default rules, the contextual rules, and the user supplied rules through object deep merging.
2. Evaluate functional aspects of the rules. Some things depend on props.
3. Send the rule set to a css helper.
4. CSS helper creates a hash of the rules applied to each selector. For every selector, and looks up whether a class name has been generated or not. If so, return it.
5. If not, create a `style` element, inject the rules into it using `insertRule` api, and add the element to the head.
6. Return the class names to the component.
Now the component can render. This is a lot of work to do on every render.
When the rules are first hit uniquely, this is where the bulk of the perf hit comes from. We are generating css, auto prefixing, kebab casing, creating dom elements, injecting content, and adding to the head. Additionally this action invalidates layout for any elements referring to those class names, causing 30-100ms hangs in the ui thread.
Even when we're already registered rules, we are still doing deep merges, function evaluation, and hashing.
We have built a caching system which avoids most of this work on subsequent renders. We build up cache keys based on input props. Only when those prop values have never been visited will we do all this work. however on initial paints, you still accrue a lot of performance penalties.
## Real world problems
In the newly added accessibility pane in Word online, we evaluated performance and found that when opening the pane, the full opening took about 250ms, and on subsequent renders, took about 150ms. Assuming the extra overhead is primarily the styles caching, this ended up equating to about 100/250ms of time. We can do better.
## Goals
* Minimize/eliminate the performance penalty of css in js.
* Leverage css variables for dynamic theming.
* Continue to allow variants with stylesheet overrides (but only use style element order to control specificity.)
* Continue to allow devs to use the components easily without relying on a webpack plugin for anything.
As a desirable goal but still has caveats:
* Do as a non breaking change. Have a way to still fall back to css in js for dynamic overrides.
And once all is working, a followup goal to finalize all optimizations:
* Give developers a webpack plugin approach for extracting out the css registration in their bundle to a plain css file.
## Plan details
There are 3 major aspects of this work:
1. Update the build system css compilation process to enable static styles to be registered on demand rather than on module load. (Preserves multi window support.)
2. Update client code to register static sheets and consume classes on demand.
3. Convert the actual styles (.styles to .scss) to leverage the build system. (May be partially automated.)
### Build improvements
### Client code update
### Conversion of styling
## References