twin.macro:在 CSS-in-JS 里写 Tailwind 的小工具
twin.macro 在 GitHub 上拿到了 8,029 Star。
它做的事情很直接:把 Tailwind 的原子类搬进 CSS-in-JS 体系。你可以在 jsx 元素上加 tw 属性写 Tailwind 类名,Babel 在编译期把这些类名翻译成 CSS 对象,再交给 styled-components 或 emotion 处理。运行时没有任何额外代码。

1、 解决什么问题
Tailwind 和 CSS-in-JS 长期是两条路线。Tailwind 写得快、约束统一,但遇到条件样式、组件复用、动态主题时容易写丑;styled-components 和 emotion 灵活,可 class 名一长串,维护成本不低。
twin.macro 把两者焊接在一起。类名用 Tailwind 的写法,组合和条件逻辑用 CSS-in-JS 的能力。编译期处理掉 Tailwind 类,输出干净的 CSS 对象,运行时没有 Tailwind 的样式表,也没有 JS 运行库负担。

2、 怎么用
最简的用法,import 一下,给元素加 tw 属性:
import 'twin.macro'
const Input = () => <input tw="border hover:border-black" />
需要条件样式,用 css prop 配合 tw:
import tw from 'twin.macro'
const Input = ({ hasHover }) => (
<input css={[tw`border`, hasHover && tw`hover:border-black`]} />
)
要建组件也行,tw 可以构造带样式的组件,还能在已有组件上叠加样式:
import tw from 'twin.macro'
const Input = tw.input`border hover:border-black`
const PurpleInput = tw(Input)`border-purple-500`
复杂场景切到 styled import,写函数式 props 到样式的映射,和 emotion 的 API 保持一致。
3、 几个值得一提的特性
变体分组。Tailwind 写响应式或伪类时类名会膨胀,twin 支持括号分组:
<div tw="sm:(w-4 mt-3) lg:(w-8 mt-6)" />
<div tw="hover:(text-black underline) focus:(text-blue-500 underline)" />
嵌套也可以,sm:(bg-black hover:(bg-white w-10)) 这种写法合法。
编译期转换。Babel 跑一遍,tw 调用被替换成 CSS 对象,没有运行时解析。打包体积上不会因为用了 Tailwind 类而带一份 JS 运行库:
tw`text-sm md:text-lg`
// ↓ ↓ ↓ ↓ ↓ ↓
{
fontSize: '0.875rem',
'@media (min-width: 768px)': {
fontSize: '1.125rem',
},
}
Tailwind 配置自动补全。类名打错了会提示,列出配置里接近的候选:
✕ ml-1.25 was not found
Try one of these classes:
- ml-1.5 > 0.375rem
- ml-1 > 0.25rem
- ml-10 > 2.5rem
VSCode 的 Tailwind 官方插件也能直接对接,写 tw 时有自动补全。
theme import。Tailwind config 里的值能在 JS 里直接取:themecolors.purple.500``,方便写在非样式场景里。
!important 简写。类名末尾或开头加 ! 就转成 important:<div tw="hidden!" /> 等价于 display: none !important。多个类一起 important 用括号包:(hidden ml-auto)!。
4、 适合谁
适合已经在用 styled-components 或 emotion,又想用 Tailwind 类名写样式的团队。也适合从 Tailwind 起步、但项目需要 CSS-in-js 的运行时能力(条件样式、props 驱动、动态主题)的开发者。
官方维护了一份配套示例仓库,覆盖 Vite、Next.js、Webpack、Parcel、CRA、Gatsby、Blitz、Storybook 等主流组合,每种栈都有 styled-components 和 emotion 两个分支的 TypeScript 版本。上手基本是 clone 一个示例改,不用自己捣腾 Babel 配置。
5、 注意点
它是 Babel macro,依赖编译期处理。用 Vite、Webpack 这类支持 Babel 的构建链路最顺。CRA 已经内置 macro 支持。如果用 SWC 走纯 Rust 转译链路,需要额外的 babel-plugin-twin 插件才能用 tw 和 css 属性而不写 import。
styled-components 和 emotion 的运行时仍然在,twin 解决的是写法问题,不是替换 CSS-in-JS 的运行时机制。期待它能让运行时样式完全消失的开发者要注意这点。
项目作者提到了它的源头是 babel-plugin-tailwind-components,那段工作由 Brad Cornes 完成,twin.macro 在此基础上做了一层 macro 包装和 API 整理。
它的源头是 babel-plugin-tailwind-components,那段工作由 Brad Cornes 完成,twin.macro 在此基础上做了一层 macro 包装和 API 整理。
389



被折叠的 条评论
为什么被折叠?



