Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ahead of time style processing with static CSS extraction and a Babel or webpack plugin #37

Open
kripod opened this issue Jul 22, 2020 · 4 comments
Labels
enhancement New feature or request

Comments

@kripod
Copy link
Owner

kripod commented Jul 22, 2020

Motivation

As of today, popular runtime CSS-in-JS libraries like Emotion and Styled Components all emit static styles into JavaScript bundles. By this regard, otion is no exception, as seen in the resulting bundle generated by Next.js on the right:

JavaScript generated by calling the `css` function of otion

Basic example

The styling code shown on the previous image is as follows:

css({
  display: "flex",
  flexDirection: reverse ? "column-reverse" : "column",
  alignItems: alignX,
  justifyContent: alignY,

  selectors: {
    "& > * + *": {
      paddingTop: space,
    },
  },
});

It can be split into two parts, possibly with a Babel plugin:

  • Static: css({ display: "flex" })
  • Dynamic: All the remaining key–value pairs from the original css function call.

The static part may be replaced with its corresponding hashed class name string ahead of time.

Details

While replacing constant-parametered calls to css and keyframes works with server-side rendering and inlined <style> tags, some styles may become missing once client-side navigation happens.

To make the solution router-compatible, static styles of the entire website should be collected and then emitted into a single static CSS file (e.g. with webpack), possibly with file name hashing and infinite caching in place. By referring to the result through a <link>, every static styling rule gets loaded in the background, so routing can happen without missing any styles.

When opting for ahead of time static rule processing, filterOutUnusedRules should not be used, as state changes may result in referring to class names filtered out during SSR.

In conclusion, style loading should happen like:

  1. User visits page with critical static CSS inlined into a <style> tag for no round trips over HTTP 1.
  2. Defer loading non-critical static CSS which was collected for the entire website.
  3. As the user navigates through a different page on the site, the rules loaded through <link> stay intact. No inlined styles should be loaded on client-side route changes, as all the static CSS should be available by the time a navigation happens.
@kripod kripod added the enhancement New feature or request label Jul 22, 2020
@kripod
Copy link
Owner Author

kripod commented Jul 22, 2020

As suggested by @amannn, styling rules with discrete values may also be extracted statically, e.g.:

css({ flexDirection: reverse ? "column-reverse" : "column" })

Could be decomposed into two CSS rules and then transformed into:

reverse ? "staticClassName1" : "staticClassName2"

@kripod
Copy link
Owner Author

kripod commented Jul 22, 2020

For future reference, CSS custom properties and data attributes should make the extraction of dynamic rules possible.

@Andarist
Copy link

Q: how do you deal with things like this?

const rest = { flexGrow: 0 }
;<div css={{ flex: 1, ...rest }}/>

@kripod
Copy link
Owner Author

kripod commented Jul 23, 2020

Q: how do you deal with things like this?

const rest = { flexGrow: 0 };
<div css={{ flex: 1, ...rest }}/>

That’s an interesting edge-case, thank you! I’m not sure about the limits of static analysis, but if the value of rest can be resolved in the context (it isn’t a function parameter), then its static and dynamic blocks may be taken apart.

The rest variable shall be left intact. A minifier could eliminate it if not used elsewhere.

Example output:

const rest = { flexGrow: 0 };
const restStatic = "hashedClassName"; // css({ flexGrow: 0 })
const restDynamic = css({ /* … */ }); // Not required here
<div className={`flex1ClassName ${restStatic} ${restDynamic}`} />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants