Try   HackMD

v8 IE11 polyfill alternative

Originally for v8, our IE11 plan was to use a polyfill for supporting CSS variables. Initial testing (with internal fixes) with static css files showed it working well. However after more testing, and afterswitching to using dynamically created styles, and updating to the latest version, numerous problems have been revealed.

  • The polyfill stops reacting at some point to new DOM mutations, so styles stop getting updated
  • Severe performance concerns
  • Some rules don't get reflected, like ::before and ::after pseudo selectors.
  • Does not react to insertRule calls

So, we will use an alternative approach:

We will provide a way to "inject" an IE11-specific implementation to makeVariantClasses. This version will on-the-fly replace variable values with resolved variables, allowing us to leverage

Injecting it

There are two ways we can allow the polyfill to be injected: global state and context. Global state is better for browser scenarios, because we can avoid any risk of impacting bundle size. Context is better for encapsulation, but risks penalizing bundle size if async imports aren't leveraged.

Through global state:

App developers should inject the Fluent UI polyfills script:

<html>
  <head>
    <script type="text/javascript">
    if (typeof navigator !== 'undefined' && /rv:11.0/.test(navigator.userAgent)) {
      document.write('https://cdn.com/fui-ie11-polyfill.js');
    }
    </script>
  </head>
</html>
)

This will place a an alternative implementation of makeVariantClasses on the window.__fui__overrides global, which the utility will use as an alternative implementation for resolving classes. This ensures that non-ie11 users will be unaffected in runtime performance and bundle size, while IE11 will still enable variants work to ship.

For more details on polyfill work, see issue:

https://github.com/microsoft/fluentui/issues/15591

f

How it works

makeVariantClasses take in as input styles (css rules) and variants (tokens):

makeVariantClasses({ styles: { root: { background: 'var(--button-background)', '&:hover': { background: 'var(--button-hovered-background, var(--button-background))' } }, }, variants: { root: { background: 'red', hovered: { background: 'pink' } }, primary: { background: 'blue' hovered: { background: 'lightblue' } } } })

We produce the following:

  1. css objects which are passed to the style renderer
  2. return the class map returned from the renderer
stylesThatGotRegistered = '.root-1{background: var(--button-background);--button-background:red;} etc.'; classes = { root: 'root-1', // the styles + the root variant css variables _primary: 'primary-2' // the variable overrides to apply when primary. }

Using css variables, the primary class can be 1 single class which defines many values. Those values can be referenced from within any selector, which in turn can target any sub element in the component.

In IE11, because we don't have variables support, we will need to re-evaluate the styles not just on unique window/theme/direction, but also each unique variant permutation we encounter.

Alternative IE11 path

Pre-requisites:

  • Variants can not use css variables; or we need to support that. They can use the theme object directly.

Instead of this:

variants: { root: { iconSize: 'var(--global-icon-size1)' } }

This:

variants: (theme: Theme) => ({ root: { iconSize: theme.global.icon.size1 } })
  • Variables can not easily cascade to children

Execution steps:

  1. We need to resolve the root tokens (flatten to an object) using processVariants.
  2. Based on the state input we need to know which variants apply. (E.g. primary is variant; is state.primary truthy, or does state.variant equal primary?)
  3. For each applicable variant, we resolve those tokens into a flattened object and merge on top of the flattented root tokens. This creates a list of replacable values.
  4. Iterate through styles and replace var(...) matches with values from the list. Take fallbacks into consideration.
  5. Register classes, return result.
  6. Optionally, we can cache the result if variant match results (true/false) are added to the graph key.

Functions

https://codesandbox.io/s/replace-css-variables-x6v4q?file=/src/replaceCSSVariables.ts

replaceCSSVariables(inputCSS, variableMap) replaces variable values in the input css with the values defined in the map.

resolveCSSVariableValue(value, variableMap returns the css property value based on if the value contains var(...) references, and if thos values are available in the given map.