一、什么 StyleX?
StyleX
是一个强大的 CSS-in-JS 库,用于定义优化用户界面的样式,在 Meta 的 facebook 等多款应用中已经使用多年。StyleX 使用 JavaScript 实现,不需要 postcss/less/sass 等 css 处理器支持。
二、学习资源
三、一个 React 组件汇总使用方
1)定义变量
在一个组件开始之前,需要执行变量定义是特殊的。defineVars 需要单独定义在 xxx.stylex.ext
文件, ext 可以是以下 6 种形式:
- .stylex.js
- .stylex.mjs
- .stylex.cjs
- .stylex.ts
- .stylex.tsx
- .stylex.jsx
以下是一个示例:colors.stylex.ts
ts
const colors = stylex.defineVars({
accent: "blue",
background: "white",
line: "gray",
textPrimary: "black",
textSecondary: "#333",
});
每一个属性编译后都会产生一个css 变量:
2)一个 React 组件展示大部部分 stylex 的写法
- 导入方式
- 动画帧
- 主题
- 动态样式
- 静态样式
- 动画帧
ts
import * as stylex from "@stylexjs/stylex";
import React from "react";
export function Route() {
// 动画帧
const pulse = stylex.keyframes({
"0%": { transform: "scale(1)" },
"50%": { transform: "scale(1.1)" },
"100%": { transform: "scale(1)" },
});
// 定义主题变量
const theme = stylex.createTheme(colors, {
accentColor: "red",
backgroundColor: "gray",
lineColor: "purple",
textPrimaryColor: "black",
textSecondaryColor: "brown",
});
// 创建样式
const styles = stylex.create({
// 静态样式
root: {
backgroundColor: "red",
padding: "1rem",
paddingInlineStart: "2rem",
position: stylex.firstThatWorks("sticky", "-webkit-sticky", "fixed"), // 回退方案
},
// 动态样式
dynamic: (r, g, b) => ({
color: `rgb(${r}, ${g}, ${b})`,
backgroundColor: colors.background,
}),
// 帧动画样式
pulse: {
animationName: pulse,
animationDuration: "1s",
animationIterationCount: "infinite",
},
});
return (
<div
// 使用 props 消费对象
{...(stylex.props(styles.root, styles.dynamic(state.opacity)), theme)}
>Your Html</div>
);
}
经过编译之后输出的内容:
注意:这是仅仅是一个示例,目标是将 stylex 的特性放在一个 React 组件展示。需要注意的是变量需要单独文件定义。
四、安装与使用
以 Remix Vite 为例,为什么用 Remix ? 因为可以很方便的测试服务端渲染的支持情况。
1)初始化项目
sh
npx create-remix@latest --template remix-run/remix/templates/unstable-vite-express
2)安装依赖
sh
pnpm add @stylexjs/stylex
pnpm add vite-plugin-stylex -D
3)定义指令 @stylex
css
@stylex stylesheet; // 然后将指令导入 root.tsx 中。
五、6 个 JS 核心 api
- stylex.create():使用对象的形式
创建
静态和动态样式 - stylex.props(): 使用对象的形式
设置
React 的 Props 对象 - stylex.keyframes(): 创建 css 关键帧动画
- stylex.firstThatWorks(): 定义样式回退方案
- stylex.defineVars(): 定义变量
- stylex.createTheme(): 定义主题
六、定义变量文件 var.styles.tsx
ts
import * as stylex from "@stylexjs/stylex";
export const tokens = stylex.defineVars({
accent: "blue",
background: "white",
line: "gray",
textPrimary: "black",
textSecondary: "#333",
});
定义静态样式不能使用 ESM 的默认输出,一般使用 export 输出一个变量即可。
七、定义静态样式
ts
const styles = stylex.create({
root: {
backgroundColor: "red",
padding: "1rem",
paddingInlineStart: "2rem",
position: stylex.firstThatWorks("sticky", "-webkit-sticky", "fixed"),
},
});
ts
export default function Route() {
return (
<div {...stylex.props(styles.root)}>
<div>123</div>
</div>
);
}
使用 create 函数定义静态属性,并获取 styles,使用 props 函数消费 styles 中的对象。
八、定义与消费动态样式
ts
const styles = stylex.create({
dynamic: (r, g, b) => ({
color: `rgb(${tokens.background}, ${g}, ${b})`, // 函数参数
background: tokens.textPrimary, // 消费主题变量
}),
});
ts
export default function Route() {
return (
<div {...stylex.props(styles.root)}>
<div {...stylex.props(styles.dynamic(23, 56, 65))}></div>
</div>
);
}
create 函数传入的映射对象是一个函数。注意当前 Remix 插件中,仅仅支持 create 直接传入静态属性,如果是动态属性,推荐使用 函数
形式定义。
九、定义主题
ts
import * as stylex from "@stylexjs/stylex";
export const tokens = stylex.defineVars({
accent: "blue",
background: "white",
line: "gray",
textPrimary: "black",
textSecondary: "#333",
});
1)定义动画帧
ts
const pulse = stylex.keyframes({
"0%": { transform: "scale(1)" },
"50%": { transform: "scale(1.1)" },
"100%": { transform: "scale(1)" },
});
十、定义伪元素和伪类
ts
import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({
button: {
backgroundColor: {
default: "lightblue",
":hover": "blue",
":active": "darkblue",
},
input: {
// pseudo-element
"::placeholder": {
color: "#999",
},
color: {
default: "#333",
// pseudo-class
":invalid": "red",
},
},
},
});
十一、源码解析
1)构建形式
- stylex-monorepo 构建
- 基于 flow 构建静态类型
- apps/ 中包含文档与示例
- packages/ 中包含源码实现
- 同时基于 vercel + nextjs 部署。
2)重要依赖 styleq
styleQ 是一个快速、小型的 JavaScript 运行时,用于合并 CSS 编译器生成的 HTML 类名称。
stylex 目前使用基于 js 构建使用 flow 作为类型检查。默认输出 _styles
:
tsx
export const stylex: IStyleX = _stylex;
export default (_stylex: IStyleX);
3)什么是猴子补丁
monkey_patch
是指在运行时修改或扩展现有的代码,通常是在不修改原始源代码的情况下对现有类、模块或对象进行修改。这种技术通常在需要临时修复或改进第三方库或模块时使用,而不必直接修改它们的源代码。
ts
const __implementations: { [string]: $FlowFixMe } = {};
export function __monkey_patch__(
key: string,
implementation: $FlowFixMe,
): void {
if (key === 'types') {
Object.assign(types, implementation);
} else {
__implementations[key] = implementation;
}
}
猴子补丁在:
- gen-types 生成类型时中使用
- dev-runtime 中运行实现 stylex 的各种方法
4)create 函数
ts
export const create: Stylex$Create = stylexCreate;
create 时创建 css 属性对象的函数,与 props 函数一起消费。
5)props 函数实现
props 其实是消费 create 函数的内容的函数,从返回值类型就能知道是当前元素的 className 和样式 style 组合成的对象:
ts
$ReadOnly<{
className?: string,
style?: $ReadOnly<{ [string]: string | number }>,
}
props 生成 className 和 style 就是之前的用 styleq
实现的:
ts
const [className, style] = styleq(styles)
const create: Stylex$Create = __implementations.create;
return create<S>(styles);
主要函数其实就是 create/props 两个函数。其他的函数实现也是类似。props 函数输出的对象大致是这样的:
十二、vite 插件 vite-plugin-stylex 源码
vite-plugin-stylex 插件源码,基于 trubo 构建,核心源码在 /packages/vite-plugin-stylex
中,vite 插件源码是基于 babel
能力进行转换:
- "@babel/core": "^7.23.5",
- "@babel/plugin-syntax-flow": "^7.23.3",
- "@babel/plugin-syntax-jsx": "^7.23.3",
- "@babel/plugin-syntax-typescript": "^7.23.3",
- "@stylexjs/babel-plugin": "^0.3.0"
在 Vite 种使用 babel 的转换器:
ts
const result = await babel.transformAsync(inputCode, {
babelrc: false,
filename: id,
plugins: [
/\.jsx?/.test(path.extname(id))
? flowSyntaxPlugin
: typescriptSyntaxPlugin,
jsxSyntaxPlugin,
[
stylexBabelPlugin,
{
dev: !isProd,
unstable_moduleResolution,
importSources: stylexImports,
runtimeInjection: !isCompileMode,
...options,
},
],
],
});
十三、第三方支持
- babel 插件
- runtime-dev 运行时
- eslint 插件
- nextjs 插件
- open-props 属性
- rollup 插件
- webpack 插件支持
当然 vite 中第三方插件,其中值得一提的是 open-props
, open-props
中包含了众多的变量、动画等等内容,如果你熟悉 open-props 这个库很有用。
十四、小结
本文比较全面的解析了 StyleX 的内容,包含如何使用: 创建/消费/变量/主题/帧动画/...
等等。当然除了使用也包含 vite 与 stylex 的部分前端工程化实现。希望能够帮助读者能够比较全面的理解 stylex。