"大厂换了 X 快了 30%"这种句式我一般直接划走。但这次有两个细节让我停下来了。一是主导迁移的工程师 Kenneth Skovhus 压根没手改样式,而是写了个确定性 codemod,前后跑出 500 多个 PR、动过约 10 万行代码;二是 Linear 挑 StyleX 的理由里有一条跟性能无关——styled(Button) 这种模式让"从组件外面改它的样式"太容易了,团队想把这个口子故意焊死,理由写得很直白:写代码的越来越多是 agent,没人管得住它往你的 Button 上堆什么。
这条理由我没办法不当真。性能收益可以辩论,agent 乱改样式是我每天都在处理的事。所以我决定不搬运报道,直接把两边的机制拆开验一遍:那 30% 的差距到底从哪来的,StyleX 的"限制"到底有多硬。环境是 Node v22.23.2,styled-components 装的当前最新 6.5.3,StyleX 是 0.19.1,React 18.3.1——正好是 Linear 当年升级后痛感变强的那个版本。
先复现 styled-components 的运行时账单
styled-components 的机制一句话就能说完:样式定义是带插值函数的模板字符串,真正的 CSS 要等组件渲染的时候才生成,生成完注入页面的 <style> 标签里。同一个组件渲染一百次,如果 props 组合各不相同,理论上就要生成一百份 CSS。理论归理论,我还是想看到数字。写一个带三个动态插值的 Button:
jsconst <span>Button</span> = styled<span>.button</span>`
<span>padding</span>: ${(<span>p</span>) => <span>p</span>.$pad || <span>8</span>}px;
<span>background</span>: ${(<span>p</span>) => <span>p</span>.$bg || "<span>#eee</span>"};
<span>color</span>: ${(<span>p</span>) => <span>p</span>.$fg || "<span>#111</span>"};
<span>border-radius</span>: <span>6px</span>;
`;
然后在 SSR 里渲染两组按钮:一组 10 个完全相同的 props,一组 200 个不同组合(20 种 padding × 100 种背景色),统计渲染耗时、唯一类名数、注入的 CSS 字节数。类名和注入内容从 styled-components 的内部 mainSheet 里捞,脚本完整贴在文末。结果比我想象的更极端:
A.10个相同props首次: 唯一类名=1 渲染耗时=5.1ms 注入<span>CSS</span>=<span>74</span>B
B.10个相同props再跑: 唯一类名=1 渲染耗时=0.4ms 注入<span>CSS</span>=<span>74</span>B
C.200种不同组合首次: 唯一类名=199 渲染耗时=19.4ms 注入<span>CSS</span>=<span>17380</span>B
D.200种不同组合再跑: 唯一类名=199 渲染耗时=4.1ms 注入<span>CSS</span>=<span>17380</span>B
逐行解读一下。相同 props 的一组,10 个实例共享 1 个类名——styled-components 对同组合有进程内缓存,所以 B 组再跑只要 0.4ms。但 200 种不同组合的一组,首次渲染生成了 199 个互不相同的 hash 类名,往页面里塞了 17.4KB 的 CSS。随手抓两条注入的规则看:
css<span>.bAkkBP</span>{<span>padding</span>:<span>8px</span>;<span>background</span>:<span>#eee</span>;<span>color</span>:<span>#111</span>;<span>border-radius</span>:<span>6px</span>;}<span>/*!sc*/</span>
<span>.cGxFCN</span>{<span>padding</span>:<span>8px</span>;<span>background</span>:<span>hsl</span>(<span>0</span> <span>70%</span> <span>50%</span>);<span>color</span>:<span>#111</span>;<span>border-radius</span>:<span>6px</span>;}<span>/*!sc*/</span>
padding:8px、border-radius:6px` 这些一模一样的声明,在每个类里都完整重复一遍。CSS 体积跟"组合数"成正比,而不是跟"出现的样式值"成正比。更微妙的是耗时分布:首渲 19.4ms,缓存命中后再跑只要 4.1ms——也就是说这部分开销集中在每次新组合第一次出现的那个瞬间,用户第一次点到那个状态时,卡的就是这一下。
跑 C 组的时候 styled-components 还在控制台里自己报警了:
<span>Over</span> <span>200</span> classes were generated <span>for</span> component styled.button
<span>with</span> the id <span>of</span> "sc-bdvwhi".
Consider <span>using</span> the attrs <span>method</span>, together <span>with</span> a style object
<span>for</span> frequently changed styles.
官方文档自己都承认这个模式有问题,给的缓解方案(attrs + style object)本质是把样式从 CSS 挪成内联 style。一个库要靠"劝你别多用我核心功能"来救性能,这个信号挺说明问题的。
换条路:样式在构建期就全部算完
带着上面的预期去看 StyleX,我原以为会看到一套类似的 API 加一个更快的实现。完全不是。StyleX 里没有 styled 这个概念,样式定义在编译期就得写死:
<span>import</span> * <span>as</span> stylex <span>from</span> <span>'@stylexjs/stylex'</span>;
<span>const</span> styles = stylex.<span>create</span>({
<span>base</span>: { <span>color</span>: <span>'#111'</span>, <span>borderRadius</span>: <span>6</span> },
<span>pad0</span>: { <span>padding</span>: <span>8</span> },
<span>// ...pad1 到 pad19 bg0: { backgroundColor: 'hsl(0 70% 50%)' },</span>
<span>// ...bg1 到 bg9</span>
});
stylex.create里的每个值必须是编译期常量,插值函数、运行时计算统统不行。动态变化靠 stylex.props(styles.base, styles.pad13, styles.bg7) 这种组合现成样式的方式来表达。所以我构造了同样的 200 种组合场景:20 种 padding 定义加 10 种背景色定义,200 个实例各取一种组合。
然后是 StyleX 和 styled-components 路线分岔的地方:这个文件要过一遍 Babel 插件编译。我在这里踩了今天唯一的坑——编译器报缺 @babel/preset-env,装上才过。这就是两条路线的成本结构差异:styled-components 开箱即用(代价运行时付),StyleX 的构建链路你得自己搭(代价提前付,但一定要付清楚)。编译完成后跑同样的统计:
<span>StyleX: 200实例(200种组合) 唯一原子类=32 props耗时首次=0.67ms 再跑=0.36ms</span>
<span>运行时注入CSS: 0(编译产物中 stylex.create 残留 0 处,动态样式函数残留 0 处)</span>
200 个实例、200 种组合,最终只剩 32 个原子类——2 个 base、20 个 padding、10 个背景色,每个类只在构建产出的 stylex.css 里出现一次,整个文件 1892 字节。运行时做的事只剩一件:按顺序把类名字符串拼起来,0.67ms。所谓"页面切换零 CSS 注入"在机制层面就是这么回事:没有可以注入的东西,样式在 build 的时候已经全量产出成静态 CSS 文件了。
编译产物本身值得看一眼。源文件里那段 stylex.create({...}) 编译后变成了:
<span>const</span> styles = {
<span>base</span>: { <span>color</span>: <span>'x1votaj3'</span>, <span>borderRadius</span>: <span>'x1kogg8i'</span>, <span>$$css</span>: <span>true</span> },
<span>pad13</span>: { <span>padding</span>: <span>'xp9y95v'</span>, <span>$$css</span>: <span>true</span> },
<span>// ...</span>
};
样式值没了,只剩类名字符串。构建期把该算的都算完了,运行时想慢都没有空间。对比实验 1 里那 199 个 hash 类,这就是 Linear 说的"运行时 CSS-in-JS 让用户为样式生成买单"的具体形状,17.4KB 对 1.9KB、19.4ms 对 0.67ms,差出来的就是这两段。
"太严格"是特性,不是妥协
性能解释得通,但还没到非迁不可的地步——毕竟 17KB 的 CSS 对多数应用不算致命。真正让我理解 Linear 动机的是第三个实验。我把 Linear 团队抱怨的场景直接怼给 StyleX 编译器:在 stylex.create 里尝试写 styled-components 里司空见惯的选择器。
<span>// 四种写法,只有第一种能活</span>
{ <span>padding</span>: <span>8</span>, <span>':hover'</span>: { <span>backgroundColor</span>: <span>'#ddd'</span> } } <span>// ✅ 编译通过</span>
{ <span>padding</span>: <span>8</span>, <span>'& > span'</span>: { <span>color</span>: <span>'red'</span> } } <span>// ❌ Invalid pseudo or at-rule</span>
{ <span>padding</span>: <span>8</span>, <span>'& span'</span>: { <span>color</span>: <span>'red'</span> } } <span>// ❌ Invalid pseudo or at-rule</span>
{ <span>padding</span>: <span>8</span>, <span>'span'</span>: { <span>color</span>: <span>'red'</span> } } <span>// ❌ Invalid pseudo or at-rule</span>
子代组合器、后代通配、标签选择器,全部在编译期被拒,报错都是同一个 Invalid pseudo or at-rule。:hover 这类伪类是官方支持的,仅此而已。
这意味着在 StyleX 里,"从外面改里面"这条路从语法层面就不存在。styled-components 的 styled(Button) 本质上靠后代选择器穿透组件边界,好用,也正因为它好用——一个 AI agent 拿到你的设计系统,最省事的路径就是在外面套一层 styled 把所有孙子元素重排一遍,编译器不会说一个不字。StyleX 把这条路焊死了:想改样式,要么改组件自己的 stylex.create,要么从外面传 props 类名进来,全部有据可查。
Syntax 播客里 Wes Bos 和 Scott Tolinski 讨论这波 StyleX 迁移潮时有个说法,大意是这种僵化对人类开发者很不友好,但 agent 反而如鱼得水。我跑完这三个实验后的感受更具体一点:StyleX 真正的产出其实是约束——约束恰好是现在这种"代码大半由 agent 生成"的工作流里最稀缺的东西。Yahoo 出身的 Reid Burke 在这场讨论里也提醒过:styled-components 那个图灵完备的开放 API,恰恰是自动化迁移难以施展的原因,Linear 能写 codemod 十万行机械替换,前提是目标语言(StyleX)足够死板。
这笔账怎么算
先说我的局限:全程是 Node SSR 环境,测的是样式生成和注入的机制成本,Linear 那 20%-35% 的主线程收益发生在真实浏览器里,我没有复现条件,这个数字只能存疑照录。另外 200 种组合是个构造出来的极端场景,你的页面如果样式组合数很少,styled-components 的进程内缓存会让两边差距小得多。
但机制层面的结论我认为是站得住的:styled-components 的成本随"组合数"线性涨,且集中在用户第一次触达的瞬间;StyleX 的成本全部前置到构建期,运行时只剩字符串拼接。如果你的应用页面重、样式组合爆炸、团队里 AI 生成代码的占比越来越高,这 1000 个 PR 花得不冤。反过来,一个几十个页面的内部系统,为了这个迁移去搭 StyleX 的构建链路(你得配 Babel 插件,这是我自己刚踩过的坑),大概率是给自己找事。styled-components 进入维护模式这件事是真实的背景压力,但它没死,存量项目不必恐慌。我自己那个小项目还没决定迁。不过这次的实验脚本都贴在文末了,哪天样式组合数真的炸了,跑一遍就知道该不该动。
实验脚本与原始输出(Node v22.23.2,styled-components 6.5.3,@stylexjs/stylex 0.19.1。先 npm i styled-components react react-dom @stylexjs/stylex @babel/core @stylexjs/babel-plugin)
实验1:styled-components 运行时行为与统计(exp1_styled_components.js,node 直接跑)
// 实验1(完整版):styled-components 运行时样式生成 + 注入
const <span>React</span> = require(<span>"react"</span>)<span>;</span>
const { renderToString } = require("react-dom/server")<span>;</span>
const <span>styled</span> = require(<span>"styled-components"</span>).default<span>;</span>
const <span>priv</span> = require(<span>"styled-components"</span>).__PRIVATE__<span>;</span>
const <span>Button</span> = styled.button`
padding: ${(p) => p.$pad || 8}px<span>;</span>
background: ${(p) => p.$bg || "<span>#eee"};</span>
color: ${(p) => p.$fg || "<span>#111"};</span>
border-radius: 6px<span>;</span>
`<span>;</span>
function bench(label, variants) {
// 清掉上一次的全局缓存,模拟"冷启动的页面"
priv.mainSheet.clearRules && priv.mainSheet.names && priv.mainSheet.clearNames()<span>;</span>
const <span>t0</span> = process.hrtime.bigint()<span>;</span>
const <span>els</span> = variants.map((v, i) => React.createElement(Button, { key: i, ...v }, <span>"ok"</span>))<span>;</span>
const <span>html</span> = renderToString(React.createElement(React.Fragment, null, els))<span>;</span>
const <span>t1</span> = process.hrtime.bigint()<span>;</span>
// 统计唯一实例类名
const <span>classes</span> = [...html.matchAll(/class=<span>"sc-\w+ (\w+)"</span>/g)].map((m) => m[<span>1</span>])<span>;</span>
const <span>unique</span> = new Set(classes).size<span>;</span>
// 用 getGroup 捞出注入的规则文本
let <span>cssText</span> = <span>""</span><span>;</span>
const <span>tag</span> = priv.mainSheet.tag<span>;</span>
for (let <span>g</span> = <span>0</span><span>; g < 64; g++) {</span>
try { const <span>r</span> = tag.getGroup(g)<span>; if (r) cssText += r; } catch (e) { break; }</span>
}
console.log(
`${label}: 实例=${variants.length} 唯一类名=${unique} 渲染耗时=${((Number(t1 - t0)) / 1e6).toFixed(1)}ms 注入<span>CSS</span>=<span>${Buffer.byteLength(cssText)}</span>B`
)<span>;</span>
return { html, cssText }<span>;</span>
}
const <span>mk</span> = (n, vary) =>
Array.from({ length: n }, (_, i) =>
vary ? { $pad: 8 + (i % 20), $bg: `hsl(${i * 2} 70% 50%)`, $fg: "<span>#111" } : { $pad: 8, $bg: "#eee", $fg: "#111" }</span>
)<span>;</span>
const <span>r1</span> = bench(<span>"A.10个相同props首次"</span>, mk(<span>10</span>, <span>false</span>))<span>;</span>
const <span>r2</span> = bench(<span>"B.10个相同props再跑"</span>, mk(<span>10</span>, <span>false</span>))<span>;</span>
const <span>r3</span> = bench(<span>"C.200种不同组合首次"</span>, mk(<span>200</span>, <span>true</span>))<span>;</span>
const <span>r4</span> = bench(<span>"D.200种不同组合再跑"</span>, mk(<span>200</span>, <span>true</span>))<span>;</span>
console.log("\n--- 实例类名抽样(A组前3个)---")<span>;</span>
console.log((r1.html.match(/<span>class</span>=<span>"sc-[^"</span>]+<span>"/g) || []).slice(0, 3).join("</span> | <span>"));
console.log("</span>\n--- 注入规则文本抽样(C组,截<span>400</span>字节)---<span>");
console.log(r3.cssText.slice(0, 400));
</span>
实验2:StyleX 版组件(stylex_bench.js,需先经 @stylexjs/babel-plugin 编译再运行——正文说"你得配 Babel 插件"的坑就是它)
// 实验<span>2</span>:StyleX —— 样式在构建期编译成原子类,运行时只做类名拼接
import * as stylex <span>from</span> '<span>@stylexjs</span>/stylex';
const styles = stylex<span>.create</span>({
base: { <span>color</span>: <span>'#111'</span>, borderRadius: <span>6</span> },
pad0: { <span>padding</span>: <span>8</span> },
pad1: { <span>padding</span>: <span>9</span> },
pad2: { <span>padding</span>: <span>10</span> },
pad3: { <span>padding</span>: <span>11</span> },
pad4: { <span>padding</span>: <span>12</span> },
pad5: { <span>padding</span>: <span>13</span> },
pad6: { <span>padding</span>: <span>14</span> },
pad7: { <span>padding</span>: <span>15</span> },
pad8: { <span>padding</span>: <span>16</span> },
pad9: { <span>padding</span>: <span>17</span> },
pad10: { <span>padding</span>: <span>18</span> },
pad11: { <span>padding</span>: <span>19</span> },
pad12: { <span>padding</span>: <span>20</span> },
pad13: { <span>padding</span>: <span>21</span> },
pad14: { <span>padding</span>: <span>22</span> },
pad15: { <span>padding</span>: <span>23</span> },
pad16: { <span>padding</span>: <span>24</span> },
pad17: { <span>padding</span>: <span>25</span> },
pad18: { <span>padding</span>: <span>26</span> },
pad19: { <span>padding</span>: <span>27</span> },
bg0: { backgroundColor: <span>'hsl(0 70% 50%)'</span> },
bg1: { backgroundColor: <span>'hsl(10 70% 50%)'</span> },
bg2: { backgroundColor: <span>'hsl(20 70% 50%)'</span> },
bg3: { backgroundColor: <span>'hsl(30 70% 50%)'</span> },
bg4: { backgroundColor: <span>'hsl(40 70% 50%)'</span> },
bg5: { backgroundColor: <span>'hsl(50 70% 50%)'</span> },
bg6: { backgroundColor: <span>'hsl(60 70% 50%)'</span> },
bg7: { backgroundColor: <span>'hsl(70 70% 50%)'</span> },
bg8: { backgroundColor: <span>'hsl(80 70% 50%)'</span> },
bg9: { backgroundColor: <span>'hsl(90 70% 50%)'</span> },
});
// <span>200</span>个实例 = <span>200</span>种不同组合(pad <span>0</span>..<span>19</span> x bg <span>0</span>..<span>9</span>),模拟一页里颜色/间距各异的按钮
export function buildEls() {
const t0 = process<span>.hrtime</span><span>.bigint</span>();
const els = <span>[]</span>;
for (let <span>i</span> = <span>0</span>; <span>i</span> < <span>200</span>; <span>i</span>++) {
const props = stylex<span>.props</span>(styles<span>.base</span>, styles<span>[`pad${i % 20}`]</span>, styles<span>[`bg${i % 10}`]</span>);
els<span>.push</span>(props);
}
const t1 = process<span>.hrtime</span><span>.bigint</span>();
return { els, ms: <span>Number</span>(t1 - t0) / <span>1</span>e6 };
}
实验3:选择器限制验证(exp3_restrict.js,node 直接跑)
// 实验<span>3</span>:StyleX 的选择器限制 —— Linear 团队要的"故意变难"
// 试三种写法:伪类(官方支持)、子代组合器(受限)、后代通配(应该被拒)
const tests = {
"hover伪类": { <span>padding</span>: <span>8</span>, <span>':hover'</span>: { backgroundColor: <span>'#ddd'</span> } },
"子代组合器": { <span>padding</span>: <span>8</span>, <span>'& > span'</span>: { <span>color</span>: <span>'red'</span> } },
"后代通配": { <span>padding</span>: <span>8</span>, <span>'& span'</span>: { <span>color</span>: <span>'red'</span> } },
"标签选择器": { <span>padding</span>: <span>8</span>, <span>'span'</span>: { <span>color</span>: <span>'red'</span> } },
};
const babel = require('<span>@babel</span>/core');
const fs = require('fs');
for (const <span>[name, obj]</span> of <span>Object</span><span>.entries</span>(tests)) {
const <span>src</span> = `import * as stylex <span>from</span> '<span>@stylexjs</span>/stylex';\nexport const styles = stylex<span>.create</span>({ box: ${JSON<span>.stringify</span>(obj)<span>.replace</span>(/":/g, <span>'": '</span>).<span>replace</span>(/,<span>"/g, ', "</span><span>') } });\n`;
fs.writeFileSync('</span>tmp_case.js<span>', src);
let ok = false, msg = '</span><span>';
try {
babel.transformSync(src, { filename: '</span>tmp_case.js<span>', babelrc: false, configFile: false, plugins: [['</span>@stylexjs/babel-plugin<span>', { dev: false, runtimeInjection: false, genCSS: true, styleResolution: '</span>application-order<span>' }]] });
ok = true; msg = '</span>编译通过<span>';
} catch (e) {
msg = (e.message || '</span><span>').split('</span>\n<span>').slice(0, 2).join('</span> <span>').slice(0, 120);
}
console.log(`${name}: ${ok ? '</span>✅ 编译通过<span>' : '</span>❌ 被拒 -> <span>' + msg}`);
}
</span>
原始输出(exp1_output.txt):
<span>A</span>.<span>10</span>个相同props首次: 实例=<span>10</span> 唯一类名=<span>1</span> 渲染耗时=<span>5.1ms</span> 注入CSS=<span>74</span>B
B.<span>10</span>个相同props再跑: 实例=<span>10</span> 唯一类名=<span>1</span> 渲染耗时=<span>0.4ms</span> 注入CSS=<span>74</span>B
style: {
},
C.<span>200</span>种不同组合首次: 实例=<span>200</span> 唯一类名=<span>199</span> 渲染耗时=<span>19.4ms</span> 注入CSS=<span>17380</span>B
D.<span>200</span>种不同组合再跑: 实例=<span>200</span> 唯一类名=<span>199</span> 渲染耗时=<span>4.1ms</span> 注入CSS=<span>17380</span>B
--- 实例类名抽样(A组前<span>3</span>个)---
class=<span>"sc-bdvwhi bAkkBP"</span> | class=<span>"sc-bdvwhi bAkkBP"</span> | class=<span>"sc-bdvwhi bAkkBP"</span>
--- 注入规则文本抽样(C组,截<span>400</span>字节)---
.bAkkBP{<span>padding</span>:<span>8px</span>;<span>background</span>:<span>#eee</span>;<span>color</span>:<span>#111</span>;<span>border-radius</span>:<span>6px</span>;}<span>/*!sc*/</span>
<span>.cGxFCN</span>{<span>padding</span>:<span>8px</span>;<span>background</span>:<span>hsl</span>(<span>0</span> <span>70%</span> <span>50%</span>);<span>color</span>:<span>#111</span>;<span>border-radius</span>:<span>6px</span>;}<span>/*!sc*/</span>
<span>.blQrLW</span>{<span>padding</span>:<span>9px</span>;<span>background</span>:<span>hsl</span>(<span>2</span> <span>70%</span> <span>50%</span>);<span>color</span>:<span>#111</span>;<span>border-radius</span>:<span>6px</span>;}<span>/*!sc*/</span>
<span>.ibusMI</span>{<span>padding</span>:<span>10px</span>;<span>background</span>:<span>hsl</span>(<span>4</span> <span>70%</span> <span>50%</span>);<span>color</span>:<span>#111</span>;<span>border-radius</span>:<span>6px</span>;}<span>/*!sc*/</span>
<span>.diQgpT</span>{<span>padding</span>:<span>11px</span>;<span>background</span>:<span>hsl</span>(<span>6</span> <span>70%</span> <span>50%</span>);<span>color</span>:<span>#111</span>;<span>border-radius</span>:<span>6</span>
(exp2_output.txt)
StyleX: <span>200</span>实例(<span>200</span>种组合) 唯一原子类=<span>32</span> props耗时首次=<span>0.67</span>ms 再跑=<span>0.36</span>ms
<span>--- className 抽样(前3个实例)---</span>
x1votaj3 x1kogg8i xe8ttls x1q5ffdr | x1votaj3 x1kogg8i xskserf x10za3gn | x1votaj3 x1kogg8i x7z7khe x13qear8
编译产物中 stylex.<span>create</span> 调用残留: <span>0</span> 处
编译产物中箭头函数动态求值样式残留: <span>0</span> 处
(exp3_output.txt)
hover伪类: ✅ 编译通过
子代组合器: ❌ 被拒 -> <span>/root/</span>stylex_lab/tmp_case.<span>js</span>: <span>Invalid</span> pseudo or at-rule.
后代通配: ❌ 被拒 -> <span>/root/</span>stylex_lab/tmp_case.<span>js</span>: <span>Invalid</span> pseudo or at-rule.
标签选择器: ❌ 被拒 -> <span>/root/</span>stylex_lab/tmp_case.<span>js</span>: <span>Invalid</span> pseudo or at-rule.
实验2构建产物 stylex.css(全文 1892 字节):
x1votaj3 { ltr: .x1votaj3{<span>color</span>:<span>#111</span>}; rtl: null; }
x1kogg8i { ltr: .x1kogg8i{<span>border-radius</span>:<span>6px</span>}; rtl: null; }
xe8ttls { ltr: .xe8ttls{<span>padding</span>:<span>8px</span>}; rtl: null; }
xskserf { ltr: .xskserf{<span>padding</span>:<span>9px</span>}; rtl: null; }
x7z7khe { ltr: .x7z7khe{<span>padding</span>:<span>10px</span>}; rtl: null; }
xaqq2fw { ltr: .xaqq2fw{<span>padding</span>:<span>11px</span>}; rtl: null; }
xc7ga6q { ltr: .xc7ga6q{<span>padding</span>:<span>12px</span>}; rtl: null; }
xh073nz { ltr: .xh073nz{<span>padding</span>:<span>13px</span>}; rtl: null; }
x1gnqi22 { ltr: .x1gnqi22{<span>padding</span>:<span>14px</span>}; rtl: null; }
x6w2896 { ltr: .x6w2896{<span>padding</span>:<span>15px</span>}; rtl: null; }
x1tamke2 { ltr: .x1tamke2{<span>padding</span>:<span>16px</span>}; rtl: null; }
xcaa3tu { ltr: .xcaa3tu{<span>padding</span>:<span>17px</span>}; rtl: null; }
x1dypa6k { ltr: .x1dypa6k{<span>padding</span>:<span>18px</span>}; rtl: null; }
x6jh44v { ltr: .x6jh44v{<span>padding</span>:<span>19px</span>}; rtl: null; }
x1qhigcl { ltr: .x1qhigcl{<span>padding</span>:<span>20px</span>}; rtl: null; }
x131h7f2 { ltr: .x131h7f2{<span>padding</span>:<span>21px</span>}; rtl: null; }
xp9y95v { ltr: .xp9y95v{<span>padding</span>:<span>22px</span>}; rtl: null; }
x19soz2t { ltr: .x19soz2t{<span>padding</span>:<span>23px</span>}; rtl: null; }
xggk2y7 { ltr: .xggk2y7{<span>padding</span>:<span>24px</span>}; rtl: null; }
xp9dabg { ltr: .xp9dabg{<span>padding</span>:<span>25px</span>}; rtl: null; }
xor4a8r { ltr: .xor4a8r{<span>padding</span>:<span>26px</span>}; rtl: null; }
x1179rhn { ltr: .x1179rhn{<span>padding</span>:<span>27px</span>}; rtl: null; }
x1q5ffdr { ltr: .x1q5ffdr{<span>background-color</span>:<span>hsl</span>(<span>0</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
x10za3gn { ltr: .x10za3gn{<span>background-color</span>:<span>hsl</span>(<span>10</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
x13qear8 { ltr: .x13qear8{<span>background-color</span>:<span>hsl</span>(<span>20</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
x1srw8zz { ltr: .x1srw8zz{<span>background-color</span>:<span>hsl</span>(<span>30</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
xwwom7w { ltr: .xwwom7w{<span>background-color</span>:<span>hsl</span>(<span>40</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
x89yzdq { ltr: .x89yzdq{<span>background-color</span>:<span>hsl</span>(<span>50</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
xco7pv2 { ltr: .xco7pv2{<span>background-color</span>:<span>hsl</span>(<span>60</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
x1iu9uxe { ltr: .x1iu9uxe{<span>background-color</span>:<span>hsl</span>(<span>70</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
xqr8tve { ltr: .xqr8tve{<span>background-color</span>:<span>hsl</span>(<span>80</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
xgy0tcm { ltr: .xgy0tcm{<span>background-color</span>:<span>hsl</span>(<span>90</span> <span>70%</span> <span>50%</span>)}; rtl: null; }
核心不是 API 优劣,而是成本前置还是后置。页面重、样式组合爆炸、AI 生成代码占比高的团队值得动手;几十页的内部系统不必为构建链路买单。