Broken-css-atomized JSS solution for zero runtime

introduce

Broken-css follows the philosophy of “write your code normally and let the compiler take care of the optimizations.” You’ll write your CSS the way you’re familiar with it, and let the compiler automatically atomize your styles for reuse.

The broken-CSS core implementation consists of the following two libraries

  • @broken-css/core
  • @broken-css/webpack-loader

@broken-css/core

What @broken-CSS /core does is simply give @broken-CSS /webpack-loader a signal that it needs to compile, and the @broken-CSS /core source code is very simple, only three lines.

export constcss = (_literal: TemplateStringsArray, ... _DOES_NOT_SUPPORT_EXPRESSIONS:never[]) :string= > {
  throw new SyntaxError('Do not call css on runtime! ')};Copy the code

Because CSS… The expression is replaced by a string after compilation, so the function is not actually executed at runtime, so this error is not thrown.

@broken-css/webpack-loader

@broken-css/webpack-loader completes the core steps, replacing the JSS in the code with the atomized CSS class name, and importing the compiled atomized CSS into the corresponding JS file, so that WebPack takes over the process of importing CSS. To reuse related loaders and plugins.

example

Let’s say we have two components Foo and Bar

// Foo.tsx
import { css } from "@broken-css/core";
import React, { FC } from "react";

const Foo: FC = () = > {
  return (
    <div className={css`
      color: red;
      font-size: 24px;
      border: 1px solid black;
      @keyframes shake {
        10%, 90% {
          transform: translate3d(-1px.0.0);
        }

        20%, 80% {
          transform: translate3d(2px.0.0);
        }

        30%, 50%, 70% {
          transform: translate3d(-4px.0.0);
        }

        40%, 60% {
          transform: translate3d(4px.0.0); }} &:hover {
        animation: shake 0.82 s cubic-bezier(36..07..19..97.) both;
        transform: translate3d(0.0.0);
        backface-visibility: hidden;
        perspective: 1000px;
      }
      &::after {
        content: ' after';
        color: brown; } `} >foo</div>
  );
}

export default Foo;

// Bar.tsx

import { css } from "@broken-css/core";
import React, { FC } from "react";

const Bar: FC = () = > {
  return (
    <div className={css`
      color: red;
      font-size: 24px;
      border: 1px black solid; `} >bar</div>
  );
}

export default Bar;
Copy the code

When compiled, it will become

// Foo.tsx
import { css } from "@broken-css/core";
import React, { FC } from "react";

const Foo: FC = () = > {
  return <div className={"_0e91 _b38a _43fe _b04b _4b6c"} >foo</div>;
};

export default Foo;
;require(".. /node_modules/.cache/broken-css-webpack-loader/broken.css");

// Bar.tsx

import { css } from "@broken-css/core";
import React, { FC } from "react";

const Bar: FC = () = > {
  return <div className={"_43fe _b04b _f617"} >bar</div>;
};

export default Bar;
;require(".. /node_modules/.cache/broken-css-webpack-loader/broken.css");

Copy the code
/** broken.css **/
@keyframes shake {
    10%.90% {
        transform: translate3d(-1px.0.0);
    }
    20%.80% {
        transform: translate3d(2px.0.0);
    }
    30%.50%.70% {
        transform: translate3d(-4px.0.0);
    }
    40%.60% {
        transform: translate3d(4px.0.0);
    }
}

._0e91:hover {
    animation: shake 0.82 s cubic-bezier(.36.07.19.97) both;
    transform: translate3d(0.0.0);
    backface-visibility: hidden;
    perspective: 1000px;
}

._b38a::after {
    content: ' after';
    color: brown;
}

._43fe {color: red; } ._b04b {font-size: 24px; } ._4b6c {border: 1pxsolid black; } ._f617 {border: 1pxblack solid; }Copy the code

features

atomization

As the example shows, broken-CSS computs a hash value to represent the style rule based on the content of the style, and this hash value is replaced as the class name in the corresponding JS file. Because the hash value is calculated based on the content, the hash value from the same style in two files is the same. Therefore, it can be screened out in the subsequent deduplication steps to achieve the purpose of reuse.

One more thing to say here is that for the style border: 1px solid black; And border: 1px black solid; Broken-css is not considered to be the same style, although the effect is the same. There are too many boundary cases to consider if you want to achieve this level of reuse. I hope there is a simple general method to solve this problem. I am still working on it.

Volume advantage

With broken-CSS, the size of your CSS will not decrease significantly at first, but as the project progresses, more and more styles will be duplicated, making it more likely that they will be reused, and the size will decrease. If the volume changes are combined into a line, the size increases in traditional CSS in a straight line. Broken-css is a curve.

Illustration from Atomic CSS-in-JS

Pseudo class supports

Broken-css supports pseudo-class selectors for objects such as &::after {… }, broken-CSS calculates the hash value based on the overall style rule, and then replaces & with the corresponding hash value.

// a.js
const cls1 = css`
	color: red;
`

// b.js

const cls2 = css`
	&:hover {
		color: red;
		font-size: 24px;
	}
`
Copy the code

Should compile to

// a.js
const cls1 = 'c1'

// b.js

const cls2 = 'c1 c2'
Copy the code
.c1..c1:hover { color: red; }
.c2 { font-size: 24px; }  
Copy the code

@ Rule Support

The @ rule support does not require any special handling of broken-CSS compile-time, so you can use the animation and media query rules freely. Broken-css doesn’t do anything to them, just unpack them into the final CSS file.

One thing I struggled with here was whether to do the isolation of @keyframes, but I found it was not easy to do in the subsequent thinking, for example, the scope of the name is isolated in each CSS… How do you reuse this during the call? Global scope, which means maintaining a state table and parsing the global CSS code to replace the corresponding names, is also complicated.

CSS variable

Broken-css treats a CSS variable as a normal style declaration without any special treatment, again calculating a hash value based on its contents and assigning a unique class name

const cls1 = css`
	--main-color: red;
	backgroud-color: var(--main-color);
`

const cls2 = css`
	backgroud-color: var(--main-color);
`
Copy the code

Will be compiled to

const cls1 = 'c1 c2'
const cls2 = 'c2'
Copy the code
.c1{-main-color: red; }
.c2 { backgroud-color: var(--main-color); }
Copy the code

SSR

Since broken-CSS pulls the JSS out of the CSS file after compilation, you can return component-generated HTML fragments and read the CSS file to decide what form to send to the browser.

import { collect } from '@linaria/server';

const css = fs.readFileSync('./dist/styles.css'.'utf8');
const html = ReactDOMServer.renderToString(<App />);
const { critical, other } = collect(html, css);
Copy the code

This is the way Linaria implements SSR, and broken-CSS also works in this way. The problem is that broken-CSS does not have an API like Collect for extracting styles that are not in the front screen, so it returns full CSS. There will be some redundant styles. In fact, you can use @Linaria/Server just for the sake of pulling this functionality, it’s not tied to a particular JSS framework.

Smart Grammar Hints

I am not familiar with other ides, and this problem can be solved perfectly if you use VSCode. The BROKEN-CSS API form is compatible with the VScode-Styled – Components extension.

Afterword.

Broken-css is my current interest project during my byte internship. The project was inspired when Facebook shared how they were using Stylex to drastically reduce their CSS size and Linaria, but stylex wasn’t open source at the time, and I wasn’t happy with their React-native API format. I’m going to try and write one myself. After I had this idea, I discussed it with my mentor at that time. Although the technical direction had nothing to do with our business technology stack at that time, my mentor encouraged me to do some technical exploration.

I spent two or three days of the week thinking about the technical solution, another two or three days writing the entire framework, and finally had the prototype out on Sunday.

After the prototype was completed, I did a technical share of broken-CSS within the company and posted a technical article on an internal forum. Since then, development of the whole project has largely stopped. The secondary reason the project stopped was that the JSS didn’t fit with the direction of our business technology stack. Our business technology stack is mainly Vue, and there is no difficulty in using JSS in Vue, but the development experience and maintenance difficulties are not friendly. Try reading the code below.


<script lang="ts">
import Vue from 'vue'
import { css } from '@broken-css/core`

export default Vue.component('counter', {
	mainCls: css` height: 200px; `,
	countCls: css ` font-size: 20px; color: blue; `,
	data() {
		return {
			count: 0,
		}
	},
	methods: {
		inc() {
			this.count++
		}
	}
})

</script>

<template>
  <div :class="mainCls">
	  count: <span :class="countCls">{{ count }}</span>
		<button :onClick="inc" >Inc</button>
  </div>
</template>
Copy the code

The main reason was that I personally lost interest in broken-CSS, because after implementing it, I was exposed to new concepts and new possibilities opened up in my mind. For example, Vue3’s setup syntax makes the development experience of using JSS in Vue equally friendly, and how to design both Vue and React friendly. Vue3 makes a similar attempt to extend the dynamics of Broken-CSS with CSS Variable. VueConf @hcysunyang’s “Compile Vue SFC to X” share has given me a lot of inspiration on compilation.

While these things can also be migrated to Broken-CSS, I’m happy with the current implementation of Broken-CSS, which does what it was designed to do, for all its flaws.

For a few reasons, mostly bad code :), I don’t want to and can’t open source the code. If you’re interested in broken-CSS, feel free to discuss it with me on Feishu, wechat, and #gmail.com.

Or, more directly, welcome to join my byte e-commerce advertising team. Now we have a large number of HC for internship, school recruitment and social recruitment. Students who are interested can directly add me on wechat.