去年 4 月,Facebook 公布了其重大的全新设计。这是一个雄心勃勃的项目,是对一个拥有大量用户的庞大网站进行重建。为了完成这项工作,他们使用了他们创建并开源的几项技术,例如 React、GraphQL、Relay,以及一个名为 stylex 的全新 CSS-in-JS 库。
这个新库是 Facebook 内部的,但他们已经分享了足够的信息来创建开源实现,即 style9。
为什么要使用另一个 CIJ 库?
已经有很多 CSS-in-JS (CIJ) 库,因此可能并不明显为什么需要另一个库。style9 与所有其他 CIJ 解决方案具有相同的优势,正如 Christopher Chedeau 所阐述的那样,包括作用域选择器、死代码消除、确定性解析,以及在 CSS 和 JavaScript 之间共享值的能力。
但是,style9 有几件事是独特的。
最小的运行时
虽然样式是在 JavaScript 中定义的,但它们被编译器提取到一个普通的 CSS 文件中。这意味着,您的最终 JavaScript 文件中没有运送任何样式。唯一剩下的只是最终的类名,最小的运行时将有条件地应用它们,就像您通常做的那样。这会导致更小的代码包,减少内存使用,并加快渲染速度。
由于值是在编译时提取的,因此无法使用真正动态的值。这些幸运的是并不常见,而且由于它们是唯一的,因此不会受到内联定义的影响。更常见的是有条件地应用样式,这当然得到支持。由于 babel 的 path.evaluate
,局部常量和数学表达式也得到了支持。
原子输出
由于 style9 的工作方式,每个属性声明都可以被设置为具有单个属性的独立类。因此,例如,如果我们在代码中的多个地方使用 opacity: 0
,它只会出现在生成的 CSS 中一次。这样做的好处是,CSS 文件的大小会随着唯一声明的数量而增长,而不是随着总声明数量的增长而增长。由于大多数属性被多次使用,因此这会导致生成的 CSS 文件明显更小。例如,Facebook 旧主页使用 413 KB 的压缩 CSS。重新设计后的所有页面使用 74 KB。同样,更小的文件大小会导致更好的性能。

有些人可能会抱怨这一点,生成的类名没有语义,它们是不透明的,并且忽略了级联。这是真的。我们将 CSS 视为编译目标。但这是有充分理由的。通过质疑先前被认为最佳的做法,我们可以改善用户和开发人员体验。
此外,style9 还有许多其他很棒的功能,包括:使用 TypeScript 的类型化样式、未使用样式消除、使用 JavaScript 变量的能力,以及对媒体查询、伪选择器和关键帧的支持。
使用方法
首先,像往常一样安装它
npm install style9
style9 具有 Rollup、Webpack、Gatsby 和 Next.js 的插件,它们都基于 Babel 插件。有关如何使用它们的说明,请参见 存储库。这里,我们将使用 webpack 插件。
const Style9Plugin = require('style9/webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
// This will transform the style9 calls
{
test: /\.(tsx|ts|js|mjs|jsx)$/,
use: Style9Plugin.loader
},
// This is part of the normal Webpack CSS extraction
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
// This will sort and remove duplicate declarations in the final CSS file
new Style9Plugin(),
// This is part of the normal Webpack CSS extraction
new MiniCssExtractPlugin()
]
};
定义样式
创建样式的语法与其他库非常相似。我们首先使用样式对象调用 style9.create
import style9 from 'style9';
const styles = style9.create({
button: {
padding: 0,
color: 'rebeccapurple'
},
padding: {
padding: 12
},
icon: {
width: 24,
height: 24
}
});
由于所有声明都会导致原子类,因此 flex: 1
和 background: blue
之类的简写将不起作用,因为它们设置了多个属性。可以扩展的属性(例如 padding
、margin
、overflow
等)将自动转换为其长格式变体。如果使用 TypeScript,则在使用不支持的属性时会收到错误。
解析样式
要生成类名,我们现在可以调用由 style9.create
返回的函数。它接受我们想要使用的样式键作为参数
const className = styles('button');
该函数的工作方式是,右边的样式优先,并将与左边的样式合并,就像 Object.assign
一样。以下将导致一个元素,其填充为 12px,文本为 rebeccapurple
。
const className = styles('button', 'padding');
我们可以使用以下任何一种格式有条件地应用样式
// logical AND
styles('button', hasPadding && 'padding');
// ternary
styles('button', isGreen ? 'green' : 'red');
// object of booleans
styles({
button: true,
green: isGreen,
padding: hasPadding
});
这些函数调用将在编译期间被删除,并用直接字符串串联替换。上面代码中的第一行将被替换为类似 'c1r9f2e5 ' + hasPadding ? 'cu2kwdz ' : ''
的内容。没有留下运行时。
组合样式
我们可以通过使用属性名称访问样式对象并将其传递给 style9
来扩展样式对象。
const styles = style9.create({ blue: { color: 'blue; } });
const otherStyles = style9.create({ red: { color: 'red; } });
// will be red
const className = style9(styles.blue, otherStyles.red);
就像函数调用一样,右边的样式优先。但是,在这种情况下,类名无法静态解析。相反,属性值将被替换为类,并在运行时连接。属性就像以前一样被添加到 CSS 文件中。
总结
CSS-in-JS 的好处是实实在在的。也就是说,当我们在代码中嵌入样式时,我们正在承担性能成本。通过在构建时提取值,我们可以同时获得两全其美。我们从将样式与标记代码并置以及使用现有 JavaScript 基础设施的能力中获益,同时也能生成最佳的样式表。
如果 style9 对您来说听起来很有趣,请查看存储库并尝试一下。如果您有任何问题,请随时打开一个 issue 或与我们联系。
鸣谢
感谢 Giuseppe Gurgone 对 style-sheet 和 dss 的贡献,感谢 Nicolas Gallagher 对 react-native-web 的贡献,感谢 Satyajit Sahoo 和 Callstack 的所有人对 linaria 的贡献,感谢 Christopher Chedeau、Sebastian McKenzie、Frank Yan、Ashley Watkins、Naman Goel 以及 Facebook 上所有在 stylex 上工作的人,感谢他们愿意公开分享他们的经验教训。还有其他任何我可能遗漏的人。
我认为最好称为样式组件
这不够描述性。好的名称应该是自描述性的。
哇!这太棒了!我对样式组件有点挣扎,我只是觉得它不像类和样式表那样容易使用。这看起来像是两全其美!
这确实与 Treat 很相似