logoAnt Design

⌘ K
  • 设计
  • 研发
  • 组件
  • 博客
  • 资源
  • 国内镜像
5.25.0
  • 👀 视觉回归测试
  • 为什么禁用日期这么难?
  • 封装 Form.Item 实现数组转对象
  • 行省略计算
  • 📢 v4 维护周期截止
  • antd 里常用的 TypeScript 工具方法
  • 一个构建的幽灵
  • 当 Ant Design 遇上 CSS 变量
  • API 的历史债务
  • 灵动的 Notification
  • 色彩模型与颜色选择器
  • 主题拓展
  • 虚拟表格来了!
  • 快乐工作主题(一)
  • 动态样式去哪儿了?
  • Suspense 引发的样式丢失问题
  • 打包体积优化
  • 你好,GitHub Actions
  • 所见即所得
  • 静态方法之痛
  • SSR 静态样式导出
  • 依赖排查
  • 贡献者开发维护指南
  • 转载-如何提交无法解答的问题
  • 新的 Tooltip 对齐方式
  • 非必要的渲染
  • 如何成长为 Collaborator
  • Modal hook 的有趣 BUG
  • antd 测试库迁移的那些事儿
  • Tree 的勾选传导
  • getContainer 的一些变化
  • 组件级别的 CSS-in-JS

SSR 静态样式导出

相关资源

Ant Design X
Ant Design Charts
Ant Design Pro
Pro Components
Ant Design Mobile
Ant Design Mini
Ant Design Web3
Ant Design Landing-首页模板集
Scaffolds-脚手架市场
Umi-React 应用开发框架
dumi-组件/文档研发工具
qiankun-微前端框架
Ant Motion-设计动效
国内镜像站点 🇨🇳

社区

Awesome Ant Design
Medium
Twitter
yuque logoAnt Design 语雀专栏
Ant Design 知乎专栏
体验科技专栏
seeconf logoSEE Conf-蚂蚁体验科技大会
加入我们

帮助

GitHub
更新日志
常见问题
报告 Bug
议题
讨论区
StackOverflow
SegmentFault

Ant XTech logo更多产品

yuque logo语雀-构建你的数字花园
AntV logoAntV-数据可视化解决方案
Egg logoEgg-企业级 Node.js 框架
Kitchen logoKitchen-Sketch 工具集
Galacean logoGalacean-互动图形解决方案
xtech logo蚂蚁体验科技
主题编辑器
Made with ❤ by
蚂蚁集团和 Ant Design 开源社区
loading

传统的 js + css 网站,SSR 一般只需要处理好首次渲染的注水问题。而当 CSS-in-JS 技术的引入,开发者则需要额外关注如何将样式导出到 HTML 中,以保证首次渲染的正确性。我们提供了非常多的实现方式,也正好在此聊聊其中的思路。如果你需要完整的文档或者示例欢迎查阅《定制主题》。

Inline Style

最简单的方式就是将样式直接内联到 HTML 中,这样就不需要额外的请求。这种方式的缺点是,样式无法被浏览器缓存,每次请求都需要重新下载。而且,如果样式过多,会导致 HTML 文件过大,影响首次渲染的速度。

在 v5 alpha 版本中,为了兜底 SSR 样式渲染,我们参考 Emotion 的实现,为每个元素前都加上对应的内联样式:

html
<div>
<style>
:where(.css-bAmBOo).ant-btn {
// ...
}
</style>
<button className="ant-btn css-bAmBOo">Hello World</button>
</div>

这个实现简单有效,唯一的缺点则是对于 :nth 选择会有样式污染。但是考虑到 antd 组件其实很少使用这个样式,副作用对我们没什么影响。

开始的时候运行良好,antd 的官网几乎无需改造就直接支持 SSR 样式满足了 SEO 需求。但是随着我们组件逐渐迁移到 CSS-in-JS 版本后,我们发现站点的构建产物变得十分巨大,慢慢的变得不可用。在查看 HTML 后,我们发现默认内联方式并不好,它会导致样式被成倍的内联,例如一个页面里有 3 个 Button,那它就会重复内联 3 次:

html
<div>
<style>
:where(.css-bAmBOo).ant-btn {
// ...
}
</style>
<button className="ant-btn css-bAmBOo">Hello World 1</button>
<style>
:where(.css-bAmBOo).ant-btn {
// ...
}
</style>
<button className="ant-btn css-bAmBOo">Hello World 2</button>
<style>
:where(.css-bAmBOo).ant-btn {
// ...
}
</style>
<button className="ant-btn css-bAmBOo">Hello World 3</button>
</div>

而当大部分组件都转成 CSS-in-JS 后,内联样式会变得十分巨大。所以我们在后期移除了自动内联的功能,转成了需要手动收取的形式:

tsx
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import { renderToString } from 'react-dom/server';
const cache = createCache();
// HTML Content
const html = renderToString(
<StyleProvider cache={cache}>
<MyApp />
</StyleProvider>,
);
// Style Content
const styleText = extractStyle(cache);

这是传统的 CSS-in-JS 注入实现。就如引言所述,内联样式无法缓存会造成额外的加载开销。也因此,我们尝试探索一些新的实现方式,可以获得如原生 CSS 的加载体验。

Static Extract Style

我们在思考是否可以如 v4 版本一样,预先烘焙组件的样式来使前端消费,所以提出了 [RFC] Static Extract style。它的思路很简单,我们只需要提前将所有的组件进行一次渲染就可以从 cache 中获得完整的样式,然后将其写入到 css 文件中即可。

tsx
const cache = createCache();
// HTML Content
renderToString(
<StyleProvider cache={cache}>
<Button />
<Switch />
<Input />
{/* Rest antd components */}
</StyleProvider>,
);
// Style Content
const styleText = extractStyle(cache);

当然,这对于开发者而言稍微有点麻烦。所以我们提取了一个三方包来实现该需求:

tsx
import { extractStyle } from '@ant-design/static-style-extract';
import fs from 'fs';
// `extractStyle` containers all the antd component
// excludes popup like component which is no need in ssr: Modal, message, notification, etc.
const css = extractStyle();
fs.writeFile(...);

如果开发者使用了混合主题,也可以自行实现混合需求:

tsx
// `node` is the components set we prepared
const css = extractStyle((node) => (
<>
<ConfigProvider theme={theme1}>{node}</ConfigProvider>
<ConfigProvider theme={theme2}>{node}</ConfigProvider>
<ConfigProvider theme={theme3}>{node}</ConfigProvider>
</>
));

Part Static Extract Style

在大部分情况下,上面的用法已经满足了需求。但是有时候,我们会希望兼顾 CSS-in-JS 的灵活性,又获得静态文件缓存的好处。那么这个时候我们就需要在应用层面进行下手,在渲染导出所需的内容后,不同于 Inline Style,而是将其转为文件存储。通过简单的 hash 就可以实现文件的缓存:

tsx
import { createHash } from 'crypto';
// Get Style content like above
const styleText = extractStyle(cache);
const hash = createHash('md5').update(styleText).digest('hex');
const cssFileName = `css-${hash.substring(0, 8)}.css`;
if (!fs.existsSync(cssFileName)) {
fs.writeFileSync(cssFileName, styleText);
}

然后在 HTML 模板侧添加对应的 CSS 文件:

html
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="${hashCssFileUrl}" />
</head>
<body>
<div id="root">${html}</div>
</body>
</html>

完整实现点击此处查阅。

访问不同的页面时会生成对应的 CSS,每个 CSS 都会有其对应的 Hash 值。当 Hash 命中时,则说明该 CSS 文件已经被落盘,可以直接使用。继而对于客户端而言就是一次正常的 CSS 文件访问,同样享受缓存能力。

对于不同的用户访问相同的页面所需的样式不同或者说自定义主题不同,都可以通过该 Hash 作区分。

总结

对于不复杂的应用而言,我们更推荐使用前者 Static Extract Style。它已经足够简单,但是对于想更细粒度控制 SSR 样式渲染以获得更好的访问速度体验的开发者,则可以试试部分静态化的能力。以上。