antd 服务端渲染的一些细节
antd 服务端渲染有两种方案 (这两种方案全都只为了首屏渲染时页面不会闪动,仅此而已)
内联样式渲染问题
会将 css 直接生成在 style 标签中 然后挂载到 body 标签中。
单页面应用(SPA)中,这样倒是没有什么缺点,但是如果是多页面应用,那么就会有问题了。会导致 HTMl 变的极大并且每个页面引用的公共组件代码都是全量重复。
整体导出渲染问题
他的整体导出 应该理解为将 css 剥离单独成一个文件。同样也分两种方式
全量导出
会将 antd 样式提取到一个单独文件,此方法可以说是单页面 or 多页面最优解了。但是也有一个问题,体积太大了!!!以 "antd": "^5.8.6",
来说,导出文件大小可达 809kb。
引用 @ant-design/static-style-extract
示例代码,高亮部分代码是可以改动一下变成白名单,通过首屏展示的组件可以单独提取出来优化整体大小。另外不考虑定制主题情况下可以删除 antd.css 中的 where 选择器 来获取最大精简样式文件。
点击查看代码
import React from "react";
import { createCache, extractStyle as extStyle, StyleProvider } from "@ant-design/cssinjs";
import * as antd from "antd";
import { renderToString } from "react-dom/server";
import type { CustomRender } from "./interface";
const blackList: string[] = [
"ConfigProvider",
"Drawer",
"Grid",
"Modal",
"Popconfirm",
"Popover",
"Tooltip",
"Tour",
];
const defaultNode = () => (
<>
{Object.keys(antd)
.filter((name) => !blackList.includes(name) && name[0] === name[0].toUpperCase())
.map((compName) => {
const Comp = antd[compName];
if (compName === "Dropdown") {
return (
<Comp key={compName} menu={{ items: [] }}>
<div />
</Comp>
);
}
return <Comp key={compName} />;
})}
</>
);
export function extractStyle(customTheme?: CustomRender): string {
const cache = createCache();
renderToString(
<StyleProvider cache={cache}>
{customTheme ? customTheme(defaultNode()) : defaultNode()}
</StyleProvider>
);
// Grab style from cache
const styleText = extStyle(cache, true);
return styleText;
}
按需抽取
确实做到了按需抽取,但是有一个非常大的问题,和 内联样式渲染问题 一样, 如果加入了公共组件,那么公共样式会不停重复生成,并没有将公共部分样式代码单独提取到另一个公共样式文件。详情请看:#28
从各个方面来看都不如内联样式渲染。
总结
- 内联样式渲染,单页面引用可以考虑,类似 next.js 做多页面引用可以直接 pass
- 整体导出渲染,两个解决方案都有利弊。在不考虑定制主题情况下 可以直接单独生成一套 antd.css 样式直接引用
- antd css 是通过 [css-in-js] 来实现的。所有的样式都是通过 js 来控制,包括 css module (会给所有标签添加一个对应的 hash 来区分主题或者自定义样式)。服务端渲染的 css 仅仅只是为了首屏渲染不会闪屏(首屏渲染之后 antd 会在单独插入 hash style 替换原来生成好的 css 样式)
小插曲
我本人是使用 antd + next.js 做服务端渲染的,然后打包服务器是每次都会清理 npm 重新安装依赖,但是我的 antd 依赖为 "antd": "^5.8.6",
^
导致打包时候的 hash 和 生产环境的 hash 不同,引用了 css 之后还是会首屏渲染闪屏 hhhh... 然后就是愉快地看源码中