stylex icon indicating copy to clipboard operation
stylex copied to clipboard

Implement `stylex.defineConsts`

Open nmn opened this issue 1 year ago • 0 comments

Depends on #517


Implement a new stylex.defineConsts API that works exactly like stylex.defineVars with two differences:

  1. It is impossible to create themes for these sets of constants.
  2. It is possible to define types of values that are not supported within defineVars.
    • Initially, we would want to support shared media queries.

How this will enable sharable media queries:

export const viewports = stylex.defineConsts({
  sm: stylex.types.media('(min-width: 768px)'),
  md: stylex.types.media('(min-width: 1024px)'),
  lg: stylex.types.media('(min-width: 1600px)'),
});

These can then be used like this:

const styles = stylex.create({
  base: {
    fontSize: {
      default: 16,
      [`@media ${viewports.sm}`]: 18, 
      [`@media ${viewports.md}`]: 20, 
      [`@media ${viewports.lg}`]: 24, 
      '@media (min-width: 1024px)': 20, 
    },
  },

Behind the scenes, shared media queries should compile to the @custom-media proposal which can then be compiled away in the CSS post-processing step. (probably by lightningcss)


There are two major challenges to consider with this feature:

Avoiding conflicts

Once we introduce shared media queries, we will no longer be able to detect overlapping media queries within a value statically. This is a solvable problem with combination of compilation-time and post-processing transformations.

Give the examples above, assume the initial constants are compiled to the following CSS:

@custom-media --vw-sm (min-width: 768px);
@custom-media --vw-md (min-width: 1024px);
@custom-media --vw-lg (min-width: 1600px);

However, in order to resolve any conflicts, the styles above would be transformed into:

const styles = stylex.create({
  base: {
    fontSize: {
      default: 16,
      '@media (--vw-sm) and not (--vw-md) and not (--vw-lg) and not (min-width: 1024px)': 18, 
      '@media (--vw-md) and not (--vw-lg) and not (min-width: 1024px)': 20, 
      '@media (--vw-lg) and not (min-width: 1024px)': 24, 
      '@media (min-width: 1024px)': 20, 
    },
  },

These styles would then be simplified after the final CSS file is generated.

Dealing with duplicate CSS rules

Since StyleX cannot know the value of a shared media query at compile time, the generated className will be different from the hash had the literal value been used instead.

Therefore, there is no way to support shareable constants (media queries or otherwise) without duplicating some CSS rules.

However, our CSS post-processing should be able to find and combine duplicate rules and minimize the performance impact of this duplication.

.hash1, .hash2 { <rule generated twice with different classNames> }

The assumption here is that if you define shared constants, you should be expected to use them consistently across the codebase. We could also implement a lint rule to enforce this?

nmn avatar Oct 09 '24 03:10 nmn