评论

Panda 编译时原子化 CSS-in-JS 框架的跨平台方案

Panda 编译时原子化 CSS-in-JS 框架的跨平台方案

Panda 编译时原子化 CSS-in-JS 框架的跨平台方案

对编译时原子化CSS框架的思考

笔者对目前市面上,一些流行的 编译时 动态生成css的解决方案还算有一点研究,比如 tailwindcssunocss,( 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-morphts-patternts-evaluator 来评估和解析的所有 [jt]x? 文件内容,同时也会把 vue / svelte 文件转化成 tsx 进行提取。真是太酷了!

另外一个部分,是它的js运行时和智能提示部分代码,也是通过 panda codegen 生成到我们项目里的,这会导致在配置上稍稍复杂一点,比如要额外配置 aliastsconfig.json 等等。

总结

2CSS-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.tspandacss 的配置文件,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.jsonscripts 块中:

{
  "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;

配置好了之后,此时 pandacssh5 平台已经生效了,你可以 npm run dev:h5h5 平台初步使用了,但是为了开发体验,我们还有一些优化项要做。

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>
  );
}

此部分参考的官方链接 https://panda-css.com/docs/installation/postcss

接下来进入 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 rollbackcss 方法进行回滚到最原始适配 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 预先进行处理再上传预览。

参考示例

taro-react-pandacss-template


Show time

最后给自己的个人小程序打个小小的推广:

想要你的Github项目在微信内分享吗?快来试试 程序员名片 吧。

进入方式:微信内,搜索 程序员名片,快速注册后添加名片,导入 Github 项目即可。

在使用过程中,如果遇到问题,或者你有好的意见和建议,欢迎与我讨论。

Demo short link: 微信内访问 #小程序://程序员名片/c9lmHLg29GtN2PH 查看我的名片。

点赞 0
收藏
评论
登录 后发表内容