🎨 Aesthetic is an end-to-end multi-platform styling framework that offers a strict design system, robust atomic CSS-in-JS engine, a structural style sheet specification (SSS), a low-runtime solution, and much more!
This PR adds a low-runtime Babel plugin that will attempt to extract, evaluate, and compile CSS expressions so that they're not triggered at runtime. The result of this will be passed to a bundler like Webpack and served as raw CSS files.
Do note that this is a "low-runtime" library and not a "zero-runtime" library. From what I know (at this time), it's not possible to do zero-runtime for the following reasons:
Atomic CSS - Since a declaration is only rendered once, it can appear in any file being transpiled with Babel, so which file "should" include the CSS into the document? This is the opposite of libraries like Linaria, Compiled, etc, in which CSS is tightly coupled to the files they are defined in.
Third-party packages - An NPM package may be using Aesthetic, and since they are not typically processed with Babel, the CSS cannot be extracted at build and only at runtime. Because of this, we need to keep the Aesthetic APIs in the bundle, but we can hydrate them when applicable.
Themes - Each theme will have different colors, spacing, tokens, etc. Because of this, each style sheet needs to be processed with every theme during Babel transpilation. This is easy, but how do we replace the style sheets with a "rendered result" for all themes? This is compounded by the fact that each theme supports scheme, contrast, unit, etc, which greatly increases the amount of permutations.
RTL, vendor prefixes, etc - All of these extra "features" must be determined before build, which can result in many permutations. At this time, I'm not confident that 100% of CSS will be extracted, so the runtime should still exist for any outliers.
React - The React package utilizes context for theme and direction, and requires the Aesthetic APIs to exist for this logic to work.
How to extract CSS out?
This is the problem I'm trying to solve. Say an application has 5 themes, each with a different color scheme, contrast level, and design token combinations. We then have this very simple style sheet:
Seems pretty simple right? I wish... The css.tokens.palette.danger.bg.base variable is a CSS hexcode that will be different between every theme, which results in a different class name for each theme. How would this be transformed?
In the example above, we pass an object that maps the theme name to the generated class name, for every property. This solves that problem, but now we have another problem, as this output would greatly increase the bundle size. Maybe we can flatten and remove the properties?
import { createComponentStyles } from '@aesthetic/react';
const styleSheet = createComponentStyles((css) => ({
element: {
theme1: 'a f',
theme2: 'b f',
theme3: 'c f',
theme4: 'd f',
theme5: 'e f',
}
}));
This reduces the file size by quite a bit, but it could still potentially be too large. However, there are still other problems! The above example assumes the application is LTR only, but what if they support LTR and RTL? This means we need to convert text-align for both directions, so how would that look???
Now we're back to this transformation becoming too large. But it still doesn't solve all problems!!! Because we still need to take vendor prefixes into account (if it's enabled)! And the default unit suffix (if not px)! This problem will only grow over time when new features are added to the framework.
The only solution I can think of at the moment is supporting an array of every permutation. We can optimize the implementation a bit by using a function + bitmasks that handle the heavy lifting. For example:
Summary
This PR adds a low-runtime Babel plugin that will attempt to extract, evaluate, and compile CSS expressions so that they're not triggered at runtime. The result of this will be passed to a bundler like Webpack and served as raw CSS files.
Do note that this is a "low-runtime" library and not a "zero-runtime" library. From what I know (at this time), it's not possible to do zero-runtime for the following reasons:
scheme
,contrast
,unit
, etc, which greatly increases the amount of permutations.How to extract CSS out?
This is the problem I'm trying to solve. Say an application has 5 themes, each with a different color scheme, contrast level, and design token combinations. We then have this very simple style sheet:
Seems pretty simple right? I wish... The
css.tokens.palette.danger.bg.base
variable is a CSS hexcode that will be different between every theme, which results in a different class name for each theme. How would this be transformed?In the example above, we pass an object that maps the theme name to the generated class name, for every property. This solves that problem, but now we have another problem, as this output would greatly increase the bundle size. Maybe we can flatten and remove the properties?
This reduces the file size by quite a bit, but it could still potentially be too large. However, there are still other problems! The above example assumes the application is LTR only, but what if they support LTR and RTL? This means we need to convert
text-align
for both directions, so how would that look???Now we're back to this transformation becoming too large. But it still doesn't solve all problems!!! Because we still need to take vendor prefixes into account (if it's enabled)! And the default unit suffix (if not
px
)! This problem will only grow over time when new features are added to the framework.The only solution I can think of at the moment is supporting an array of every permutation. We can optimize the implementation a bit by using a function + bitmasks that handle the heavy lifting. For example:
This is still not ideal, but the path forward is not easy...
Screenshots
Checklist
yarn test
.yarn format
.