ant-design/docs/blog/hydrate-cssinjs.en-US.md

5.8 KiB

title date author
Where is the dynamic style? 2023-07-21 zombieJ

As we all know, antd v5 uses CSS-in-JS to support the needs of mixed and dynamic styles. On the contrary, it needs to generate styles at runtime, which will cause some performance loss. Therefore, we developed the @ant-design/cssinjs library at the component library level to improve the cache efficiency through certain constraints, so as to achieve the purpose of performance optimization. But we don't stop there. We can skip the stage of generating styles at runtime through some logic.

Where is the dynamic style?

If you have checked the official website of Ant Design, you will find that Ant Design's components do not dynamically insert <style /> to control styles, but use CSS files to control styles:

  • button
  • style

document.head has some css file references:

  • umi.[hash].css
  • style-acss.[hash].css

The former is the style content generated by dumi, such as Demo block, search box style, etc. The latter is the style file generated by SSR. In the custom theme document, we mentioned that we can pre-bake the components used in the page through the overall export method, so as to generate css files for cache hit to improve the next open speed. This is also the way we use in the official website. So the components in the Demo actually reuse this part of the style.

Wait a minute! Isn't CSS-in-JS supposed to generate style hash at runtime and align it with <style />? Why can css files also be aligned? Don't worry, let's take a look.

CSS-in-JS Hydration

Application level CSS-in-JS solutions will calculate the hash value of the generated style and store it in the cache. When rerender, it will first check whether the corresponding style exists in the cache. If it exists, it will be used directly, otherwise it will be generated again. This can avoid repeated generation of styles, so as to improve performance.

CSS-in-JS process

Every dynamically inserted style is also identified by hash. If the <style /> with the hash already exists in the page, it means that inline style injection has been done in SSR. Then <style /> does not need to be created again.

You can find that although the <style /> node can be omitted, hash still deps on the calculated style content. So even if there is reusable style in the page, it still needs to be calculated once. It's really not cost-effective.

Component-level CSS-in-JS

In the component-level CSS-in-JS article, we mentioned that Ant Design's Cache mechanism does not need to calculate the complete style. For the component library, as long as the Token and ComponentName can determine the consistency of the generated style, so we can calculate the hash value in advance:

Component CSS-in-JS

Therefore, we found that we can reuse this mechanism to realize whether the component style has been injected on the client side.

SSR HashMap

In @ant-design/cssinjs, Cache itself contains the style and hash information corresponding to each element. The previous extractStyle method only takes the content of style in Cache for packaging:

// e.g. Real world path is much more complex
{
  "bAMbOo|Button": ["LItTlE", ":where(.bAMbOo).ant-btn { color: red }"],
  "bAMbOo|Spin": ["liGHt", ":where(.bAMbOo).ant-spin { color: blue }"]
}

Get:

:where(.bAMbOo).ant-btn {
  color: red;
}
:where(.bAMbOo).ant-spin {
  color: blue;
}

We go further to reuse the style. We also extract the path and hash value:

{
  "bAMbOo|Button": "LItTlE",
  "bAMbOo|Spin": "liGHt"
}

And also pack into css style:

// Just example. Not real world code
.cssinjs-cache-path {
  content: 'bAMbOo|Button:LItTlE;bAMbOo|Spin:liGHt';
}

In this way, the SSR side will retain all the information we need, and then we only need to extract it on the client side.

CSR HashMap

It's much simpler on the client side. We can extract the HashMap information through getComputedStyle:

// Just example. Not real world code
const measure = document.createElement('div');
measure.className = 'cssinjs-cache-path';
document.body.appendChild(measure);

// Now let's parse the `content`
const { content } = getComputedStyle(measure);

In the component rendering stage, useStyleRegister will first check whether the path exists in HashMap before calculating CSS Object. If it exists, it means that the data has been generated through the server. We only need to extract the style from the existing <style />:

// e.g. Real world path is much more complex
{
  "bAMbOo|Button": ["LItTlE", "READ_FROM_INLINE_STYLE"],
  "bAMbOo|Spin": ["liGHt", "READ_FROM_INLINE_STYLE"]
}

And for the style provided by CSS file (such as the usage of the official website), it will not be removed like <style />, we can directly mark it as from CSS file. Like inline style, they will be skipped in the useInsertionEffect stage.

// e.g. Real world path is much more complex
{
  "bAMbOo|Button": ["LItTlE", "__FROM_CSS_FILE__"],
  "bAMbOo|Spin": ["liGHt", "__FROM_CSS_FILE__"]
}

Summary

CSS-in-JS has been criticized for its runtime performance loss. In Ant Design, if your application uses SSR, you can skip the stage of generating styles at runtime on the client side to improve performance. Of course, we will continue to follow up the development of CSS-in-JS to bring you a better experience.