Panda 编译时原子化 CSS-in-JS 框架的跨平台方案
对编译时原子化CSS框架的思考
笔者对目前市面上,一些流行的 编译时 动态生成css
的解决方案还算有一点研究,比如 tailwindcss
,unocss
,( windicss
已死),又或者是 CSS-in-JS
思想下的 Linaria。
注意关键词
编译时(build time)
不是runtime
,所以不包括众多运行时CSS-in-JS
libs 或者是twind
这种方案
其中之前也写了 weapp-tailwindcss
这样一个开源项目,来和大家探讨 tailwindcss
的跨平台方案。
虽然大部分情况下 tailwindcss
已经非常强了,我们可以通过 plugin
/ preset
/ @apply
等等功能,为自己的项目提炼出大量的样式,缩短我们的类名,提升我们的效率,但是 tailwindcss
有个问题,就是 js
天然不亲和。
是的,回想我们在使用 tailwindcss
的时候,要不就是直接在模板文件里写原子类,要不就是在类 js
文件里操纵原子类的字面量,要不就是在 css
去 @apply/theme
来提取或组合多个原子类。
这看似灵活的同时,却给我们加了诸多限制。试想这样一个场景,一个现成使用 tailwindcss
的项目, 有一天突然要去改所有原子类的前缀(prefix
),那这个改动将是一个浩劫!因为 prefix
这个配置项,会影响到整个 tailwindcss
上下文提取器的正则匹配方式,这意味着所有使用原子类的标签都要改!
而 CSS-in-JS
方案就不需要,因为类名是计算出来的,这显然更加灵活。
编译时 CSS-in-JS 方案对比
这里重点介绍 2
种方案,一种是 Linaria
,另外一种是最近新出的 Pandacss
Pandacss
的作者Segun Adebayo
是一个尼日利亚(Nigeria
)黑人小哥,也是Chakra UI
的作者,真是太强了!
Linaria
一想到 linaria
我就想到一个字,牛逼!
,两个字,太酷啦!
,它对 babel ast
的处理简直是教科书级别的出神入化,令人着迷。
是的,它值得这么高的评价,只要你看过它 babel
插件的实现。这里我们不深入探讨,你只要知道它会去计算 css
模板字符串里的值和表达式(eval
流程),并把里面的计算结果提取成一个个 css
节点,然后在原先的 js
文件中 css
模板字符串处留下对应的 css selector
的值。
不过这种方式生成的 class
选择器名称,虽然可以自定义,但是不能很好的代表生效样式的内容。因为一个 linaria css template string
只对应一个 css
节点,所以说它不够原子化。
这点不如 tailwindcss
直观,例如,你一瞅见类名是 relative flex items-start
,你甚至都不需要打开 Chrome devtool
就知道里面的样式是什么样的。
Pandacss
而 pandacss
就是另外一种方案,它是原子化的,这意味着一个css()
方法会生成多个 css
节点,还有就是它和 [jt]sx?
文件相结合的写法灵活而又令人印象深刻。
另外它的实现其实很像 tailwindcss
。
首先它们核心用法都是一个 postcss plugin
,其次,它们都是通过分析我们源码的内容,去进行提炼和生成css
原子类的。
不过不同的是,相比于 tailwindcss
这种基于文件内容进行 正则(regex) 匹配提取 token
的方式。pandacss
的提取器更为复杂。
它直接通过 ts-morph
,ts-pattern
,ts-evaluator
来评估和解析的所有 [jt]x?
文件内容,同时也会把 vue
/ svelte
文件转化成 tsx
进行提取。真是太酷了!
另外一个部分,是它的js
运行时和智能提示部分代码,也是通过 panda codegen
生成到我们项目里的,这会导致在配置上稍稍复杂一点,比如要额外配置 alias
和 tsconfig.json
等等。
总结
这 2
种 CSS-in-JS
方案各有它们出彩之处。目前我倾向于 pandacss
,只因 linaria
实在是太 hack
了! 它的 css
模板字符串,有自己的语法,有自己的词法分析器,就像在创造一个 Domain Specific Language
,真是太复杂了!但是它非常值得研究!
另外 pandacss
一个优势就是,它不会去修改我们的源代码,这带来了更少的出错的可能性,而本人在使用 linaria
的时候,配置稍复杂就有 fatal
的可能,这就需要我们花更多的时间去修复它。
它们各自有各自独一无二的优势,相互无法完全替代,看它们各自的源代码,就像在紫禁之巅欣赏叶孤城与西门吹雪的决战,令人赏心悦目的同时,受益匪浅!
所以我推荐 pandacss
给大家,另外 panda
也是我国的国宝,这预示着中国又赢了!
weapp-pandacss 介绍
接下来进入正题,我在使用了一段时间后的 pandacss
后,想在写 taro react
项目的时候也去使用它,于是我就写了这个 weapp-pandacss。
让我们快速来看看怎么使用它吧!
快速开始
pandacss 安装和配置
0. 安装和初始化 pandacss
首先我们需要把 @pandacss/dev
这些都安装和配置好,这里我们以 tarojs
项目为例:
npm install -D @pandacss/dev weapp-pandacss postcss # 或者 yarn / pnpm
npx panda init
此时会在当前目录生成一个 panda.config.ts
和一个包含大量文件的 styled-system
。
panda.config.ts
是pandacss
的配置文件,styled-system
文件夹里的是pandacss
的运行时js
。
把 styled-system
加入我们的 .gitignore
中去。
# .gitignore
+ styled-system
1. 配置 postcss
接着在根目录里,添加一个 postcss.config.cjs
文件,写入以下代码注册 pandacss
:
module.exports = {
plugins: {
'@pandacss/dev/postcss': {}
}
}
2. 检查你的 panda.config.ts
生成的配置文件大概长下面这样,尤其注意 include
是用来告诉 pandacss
从哪些文件中提取原子类的,所以这个配置一定要准确
import { defineConfig } from "@pandacss/dev"
export default defineConfig({
// 小程序不需要
preflight: process.env.TARO_ENV === 'h5',
// ⚠️注意这里,假如你使用 vue,记得把 vue 文件格式包括进来!!!
include: ["./src/**/*.{js,jsx,ts,tsx}"],
exclude: [],
theme: {
extend: {}
},
outdir: "styled-system",
})
3. 修改 package.json 脚本
然后,我们添加下方 prepare
脚本在我们的 package.json
的 scripts
块中:
{
"scripts": {
+ "prepare": "panda codegen",
}
}
这样我们每次重新 npm i / yarn /pnpm i
的时候,都会执行这个方法,重新生成 styled-system
,当然你也可以直接通过 npm run prepare
直接执行这个脚本。
4. 全局 css 注册 pandacss
然后在我们的全局样式文件 src/app.scss
中注册 pandacss
:
@layer reset, base, tokens, recipes, utilities;
配置好了之后,此时 pandacss
在 h5
平台已经生效了,你可以 npm run dev:h5
在 h5
平台初步使用了,但是为了开发体验,我们还有一些优化项要做。
5. 配置的优化与别名
来到根目录的 tsconfig.json
添加:
{
"compilerOptions": {
"paths": {
"@/*": [
"src/*"
],
+ "styled-system/*": [
+ "styled-system/*"
+ ]
}
},
"include": [
"./src",
"./types",
"./config",
+ "styled-system"
],
}
接着来到 config/index.ts
添加 alias
(参考链接):
import path from 'path'
{
alias: {
'styled-system': path.resolve(__dirname, '..', 'styled-system')
},
}
这样我们就不需要使用相对路径来使用 pandacss
了,同时 ts
智能提示也有了,你可以这样使用它:
import { View, Text } from "@tarojs/components";
import { css } from "styled-system/css";
const styles = css({
bg: "yellow.200",
rounded: "9999px",
fontSize: "90px",
p: "10px 15px",
color: "pink.500",
});
export default function Index() {
return (
<View className={styles}>
<Text>Hello world!</Text>
</View>
);
}
接下来进入 weapp-pandacss
的插件配置,不用担心,相比前面那些繁琐的步骤,这个可简单多了。
weapp-pandacss 配置
记得安装好
weapp-pandacss
!
0. 回到 postcss 进行注册
回到项目根目录的 postcss.config.cjs
注册 weapp-pandacss
,添加以下配置:
module.exports = {
plugins: {
'@pandacss/dev/postcss': {},
+ 'weapp-pandacss/postcss': {}
}
}
1. 回到 package.json 添加生成脚本
然后去 package.json
你添加 prepare
脚本的地方,加点代码
{
"scripts": {
- "prepare": "panda codegen",
+ "prepare": "panda codegen && weapp-panda codegen",
}
}
注意这里必须用
&&
而不能用&
,&
任务执行会并行不会等待,而&&
会等待前一个执行完成再执行后一条命令
然后,你再手动执行一下
npm run prepare
来重新生成 styled-system
, 此时你会发现 pandacss
的命令行输出中多了 2
行:
✔️ `src/styled-system/css`: the css function to author styles
✔️ `src/styled-system/tokens`: the css variables and js function to query your tokens
✔️ `src/styled-system/patterns`: functions to implement apply common layout patterns
✔️ `src/styled-system/jsx`: styled jsx elements for react
+ ✔️ `src/styled-system/weapp-panda`: the core escape function for weapp
+ ✔️ `src/styled-system/helpers.mjs`: inject escape function into helpers
这代表着小程序相关的转义逻辑已经被注入进去,此时 panda css
生成的类就兼容小程序平台啦,是不是很简单?
当然为了防止你配置失败,我也给出了参考项目: taro-react-pandacss-template 方便进行排查纠错。
跨平台注意事项
你可能同时开发 小程序
和 h5
平台,但是你发现使用 weapp-pandacss
之后,h5
平台似乎就不行了?
这时候你可以这样配置:
process.env.TARO_ENV === 'h5'
的时候,不去加载 weapp-pandacss/postcss
(根据环境变量动态加载 postcss
插件)
同时你也可以执行 weapp-panda rollback
把 css
方法进行回滚到最原始适配 h5
平台的状态。
当然你恢复到小程序版本也只需要执行 weapp-panda codegen
就会重新注入了。
小程序预览事项
当小程序预览时会出现 Error: 非法的文件,错误信息:invalid file: pages/index/index.js, 565:24, SyntaxError: Unexpected token . if (variants[key]?.[value])
错误。
这是因为 panda
生成的文件 cva.mjs
使用了 Optional chaining (?.)
语法,这个语法小程序原生不支持,这时候可以开启勾选 将JS编译成ES5
功能再进行预览,或者使用 babel
预先进行处理再上传预览。
参考示例
Show time
最后给自己的个人小程序打个小小的推广:
想要你的Github项目在微信内分享吗?快来试试 程序员名片
吧。
进入方式:微信内,搜索 程序员名片
,快速注册后添加名片,导入 Github
项目即可。
在使用过程中,如果遇到问题,或者你有好的意见和建议,欢迎与我讨论。
Demo short link
: 微信内访问 #小程序://程序员名片/c9lmHLg29GtN2PH
查看我的名片。