以下文章来源于前端充电宝?,作者CUGGZ
最近,Facebook(已更名为 Meta )开源了其内部使用的 CSS 解决方案:StyleX,目前在 Github 上已经获得了 3.2k Star。Facebook、WhatsApp、Instagram、Workplace、Threads等产品都在使用 StyleX 作为其样式解决方案。本文就来看看 StyleX 是的概念、特点和使用方法!
StyleX 是 Facebook 在 2019 年 React Conf 上首次公开的内部 CSS-in-JS 库。
从那时起,它一直为 Facebook、Instagram 和 WhatsApp 提供支持,赢得了开发者的广泛关注。然而,随着时间的推移,CSS-in-JS 工具的受欢迎程度可能有所下降,原因是它们在性能上存在高昂的权衡。不过,StyleX 通过一个巧妙的 Babel 插件解决了这些问题。
StyleX 是一个强大、富有表现力、确定、可靠且可扩展的样式系统。它在其他样式库的基础上汲取了最佳的想法,并创造出一些既熟悉又独特的功能,使开发者在开发过程中能够更加得心应手。
StyleX 将 CSS-in-JS 库的开发者体验与编译时工具相结合,实现了静态 CSS 的高性能和可扩展性。然而,StyleX 并不仅仅是一个新的基于编译器的 CSS-in-JS 库。它的设计旨在满足大型应用、可重用组件库和静态类型代码库的需求。以下是 StyleX 的几个关键特点:
支持 CSS 的表现性子集,避免复杂的选择器,确保生成的 CSS 中不存在特异性冲突。
将样式转换、组织和优化为“原子”CSS 类名,无需学习或管理单独的实用类名库。
允许跨文件和组件边界合并样式,非常适合允许用户自定义的组件库。
提供类型信息,并提供类型工具,以允许对组件接受的属性和值进行精细控制。
StyleX 的主要特点如下:
快速:StyleX 在编译时和运行时都具备高效的性能。Babel 转换不会对构建过程产生显著影响。在运行时,StyleX 避免了使用 JavaScript 插入样式的开销,并仅在必要时高效地组合类名字符串。生成的 CSS 经过优化,确保即使是大型网站的样式也能被浏览器快速解析。
可扩展:StyleX 旨在适应像 Meta 这样的超大型代码库。通过原子构建和文件级缓存,Babel 插件能够处理数万个组件在编译时的样式处理。由于 StyleX 设计为封装样式,它允许在隔离环境中开发新组件,并期望一旦在其他组件中使用时能够可预测地呈现。
可预测性:StyleX 会自动管理 CSS 选择器的特异性,以确保生成的规则之间不会发生冲突。它为开发人员提供了一个可靠地应用样式的系统,并确保“最后应用的样式始终生效”。
类型安全:使用 TypeScript 或 Flow 类型来约束组件接受的样式,每个样式属性和变量都具有完全的类型定义。这有助于提高代码的可读性和可维护性,同时减少潜在的错误和冲突。
样式去重:StyleX 鼓励在同一文件中编写样式和组件。这种方法有助于使样式在长期内更具可读性和可维护性。StyleX 能够利用静态分析和构建时工具来跨组件去重样式,并删除未使用的样式。
可测试性:StyleX 可以配置为输出调试类名,而不是功能性的原子类名。这可以用于生成快照,以便在对设计进行轻微更改时不会经常变化。通过这种方式,开发人员可以更轻松地测试和验证样式的正确性,从而提高开发效率和产品质量。
在早期的 Facebook 网站开发中,他们使用了一种类似于 CSS Module 的方案来处理样式,但这种方法存在着许多问题。于是,他们萌生了创建一个新的解决方案,即 CSS-in-JS。在普通用户访问 facebook.com 时,他们会下载数十兆字节的 CSS,其中很多都是未使用的。为了优化初始加载速度,Facebook 采用了延迟加载 CSS 的策略,但这又导致了更新速度变慢。此外,使用复杂的选择器也可能会导致样式冲突。为了解决这些问题,开发者们常常会使用?!important
?或更复杂的选择器,这使得整个样式系统变得越来越复杂。
几年前,当 Facebook 开始重构其网站时,他们急需一个更好的解决方案。于是,他们设计并构建了 StyleX。
StyleX 的设计注重可扩展性,其架构经过多年的使用经验已经得到了验证。随着时间的推移,他们不断在不降低性能和可扩展性的前提下添加新功能,使得 StyleX 更加易于使用。使用 StyleX 极大地改进了应用的可扩展性和表达性。在 facebook.com 的重构过程中,他们成功地将 CSS 捆绑包从数十兆字节的懒加载 CSS 降低到了几百千字节的单个捆绑包。
Meta 创建 StyleX 的目的不仅是为了满足 Web 上 React 开发人员的样式需求,而且是为了统一 React 在 Web 和 Native 平台上的样式解决方案。通过采用 StyleX,他们得以实现跨平台样式的一致性,从而提高开发效率和产品质量。
Meta 的目标是使 StyleX 尽可能精简和易于学习。因此不想过度复杂化而发明太多的API,而是希望能够尽可能利用常见的 JavaScript 模式,以提供尽可能简洁的API接口。
从本质上讲,StyleX 可以归纳为两个函数:
stylex.create
:用于创建样式
stylex.props
:用于将这些样式应用到元素上。
在这两个函数中,Meta 选择依赖常见的 JavaScript 模式,而不是为 StyleX 引入独特的 API 或模式。例如,没有为条件样式设计 API,而是支持使用布尔值或三元表达式来有条件地应用样式。
下面来编写一个按钮组件:
import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({
base: {
appearance: "none",
borderWidth: 0,
borderStyle: "none",
backgroundColor: "blue",
color: "white",
borderRadius: 4,
paddingBlock: 4,
paddingInline: 8,
},
});
export default function Button({
onClick,
children,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
}>) {
return (
<button {...stylex.props(styles.base)} onClick={onClick}>
{children}
</button>
);
}
StyleX将样式与组件紧密关联,从开发体验和代码可读性的角度来看,这是一个巨大的优势。它采用了类似于 Emotion 的方式编写CSS样式,使得代码易于理解和维护。同时,StyleX 还能够在编译时对CSS进行处理,从而获得运行时系统无法提供的优势。
然而,与 Tailwind 不同的是,StyleX 没有提供类似于 Tailwind 的简写样式的便利性。虽然失去了 Tailwind 的简写样式,但换取了更多的样式控制权。假设希望允许按钮的使用者仅更改按钮的颜色和背景颜色。对于 Tailwind 组件,可以为此定义特定的props
,但如果希望用户能够调整更多样式,这种方法就不太具有扩展性。因此,一些作者允许额外的extraClasses
属性,用户可以随意添加样式。但这样做会导致用户可以无限制地更改样式,这使得后续版本控制变得困难。
StyleX 对于这个问题有一个很棒的解决方案:
import type { StyleXStyles } from "@stylexjs/stylex/lib/StyleXTypes";
export default function Button({
onClick,
children,
style,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
style?: StyleXStyles<{
backgroundColor?: string;
color?: string;
}>;
}>) {
return (
<button {...stylex.props(styles.base, style)} onClick={onClick}>
{children}
</button>
);
}
我们添加了一个名为style
的属性,并将其限制为只能覆盖希望覆盖的样式。由于在stylex.props
调用中将style
放在styles.base
之后,可以确保覆盖样式会适当地覆盖基础样式。这样我们就可以对Button进行版本控制,因为已经明确了哪些CSS可以更改,哪些不可以。
当想要覆盖样式时,使用 Button 的方式如下:
const buttonStyles = stylex.create({
red: {
backgroundColor: "red",
color: "blue",
},
});
<StyleableButton onClick={onClick} **style={buttonStyles.red}**>
Styleable Button
</StyleableButton>
StyleX 支持条件和动态样式。下面来为按钮添加一个强调标志:
import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({
...,
emphasized: {
fontWeight: "bold",
},
});
export default function Button({
onClick,
children,
emphasized,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
emphasized?: boolean;
}>) {
return (
<button
{...stylex.props(styles.base, emphasized && styles.emphasized)}
onClick={onClick}
>
{children}
</button>
);
}
我们只需要在样式定义中添加另一个部分来定义强调样式,并根据标志条件性地应用样式,非常简单!
这只是 StyleX 可以实现的一小部分功能。如果需要在运行时生成位置或颜色等值,样式也可以是动态的。只需添加另一个?stylex.create
?来定义变体,然后根据属性使用正确的变体样式,就可以轻松支持选项如?variant
。
StyleX 团队还将 OpenProps 的所有内容移植到了 StyleX,这意味着可以轻松访问大量间距选项、颜色、动画等功能。
StyleX 是一套协同工作的工具集组成的,包括:
Babel 插件:作为 StyleX 的核心,此插件在编译时查找并提取源代码中的所有样式定义,并将其转换为原子类名。通过去重、排序和写入 CSS 文件等辅助功能,它为打包工具插件的实现提供了支持。
运行时库:这是一个轻量级的运行时库,用于处理更高级的动态样式组合模式。经过优化后,它具有高效性能,并利用结果缓存技术提升响应速度。
ESlint 插件:通过与 ESlint 集成,此插件能够在开发过程中实时检测并规范使用 StyleX 的代码,确保遵循最佳实践。
与打包工具和框架的集成:StyleX 提供了与多种打包工具和框架的集成选项,以确保与项目的顺畅整合。
为了优化性能,Babel 插件在可能的情况下会预先计算最终的类名,从而消除运行时的性能开销,甚至包括同一文件中类名的合并操作。当组件在相同文件中静态地定义和使用样式时,运行时开销将被完全消除。
Meta 已经将 StyleX 确立为内部所有 Web 界面中样式组件的首选解决方案。无论是 Facebook、WhatsApp、Instagram、Workplace 还是 Threads 等主要外部和内部产品,Meta 都采用 StyleX 来为 React 组件提供样式,从而转变了组件的编写方式并解决了团队之前所面临的样式组件封装和扩展问题。
Meta 不仅在原始功能上扩展了 StyleX,还使其工程师能够利用它来编写静态和动态样式。目前,Meta 的团队正在使用 StyleX 的主题 API 开发“通用”组件,这些组件可以适应不同 Meta 产品中采用的各种设计系统的外观。由于 StyleX 遵循 React Native 样式系统引入的封装原则,Meta 正在逐步加强对跨平台样式的支持。