经历了一番自虐式开发后,weapp-tailwindcss
v4.0 终于发布了!
在清理了一些自己当初写的屎山代码后,我终于实现了心心念念的两个大功能:
[email protected]
版本tailwind-merge
因为
tailwindcss@4
直接变成了一个样式预处理器,定位上类似Sass
/Less
,所以相关的改动还是挺大的。目前weapp-tailwindcss@4
版本也同时兼容tailwindcss 4+3+2(jit)
三个版本了。
想快速上手集成?欢迎访问 weapp-tailwindcss 官网! 如果你想进一步了解细节,下面有个示例,看看就懂了。
这里我们以 uni-app
(Vue3 + Vite) 为例。
npm install -D tailwindcss @tailwindcss/vite weapp-tailwindcss
然后,把下面这段脚本加入 package.json
的 scripts
字段里:
{
"scripts": {
// highlight-next-line
"postinstall": "weapp-tw patch"
}
}
这个补丁是为了让
tailwindcss@4
认识rpx
单位,否则它会以为rpx
是个颜色单位,导致text-[40rpx]
这样的样式翻车。
vite.config.ts
import uni from '@dcloudio/vite-plugin-uni'
import { defineConfig } from 'vite'
import { UnifiedViteWeappTailwindcssPlugin } from 'weapp-tailwindcss/vite'
export default defineConfig(async () => {
// 这里必须这样引用,因为 uni 只提供 cjs 版本,而 @tailwindcss/vite 只提供 esm 版本
const { default: tailwindcss } = await import('@tailwindcss/vite')
return {
plugins: [
uni(),
tailwindcss(),
UnifiedViteWeappTailwindcssPlugin({ rem2rpx: true })
],
}
})
在项目目录下创建 main.css
,然后添加以下内容:
@import 'weapp-tailwindcss';
接着在 main.js
里引用这个文件作为全局样式,然后直接运行:
npm run dev:mp-weixin
在微信开发者工具中导入这个项目,在项目中使用 tailwindcss
原子类,即可看到效果。
tailwindcss@4
的样式兼容性问题tailwindcss@4
生成的样式广泛使用了 @layer
、color-mix
、oklch
等新特性,现代浏览器兼容性都不咋地,更别说小程序了。
所以无论是 H5 还是小程序,都需要 postcss-preset-env
来降级这些样式的生成结果。降级的程度取决于你的 browserslist
配置。
tailwind-merge
样式合并问题tailwind-merge
是一个 tailwindcss
运行时样式合并工具,它能自动合并重复的样式。不过,小程序端用 tailwind-merge
有点挑战,因为:
weapp-tailwindcss
在编译时 对 tailwindcss
样式做转义。tailwind-merge
在运行时 处理样式合并。这导致 tailwind-merge
处理已转义的 class
时,误以为它们是普通 class
,而不是 tailwindcss
的原子类。
我研究了一下 tailwind-merge
的源码,尝试写一个 plugin
来解决这个问题,结果发现路走不通,因为:
tailwind-merge
内部有一些硬编码规则,比如 !
代表 !important
,无法通过 plugin
自定义。于是,我换了个思路,采用编译时感应、忽略转义,运行时合并转义的方式 解决了这个问题,这就是 @weapp-tailwindcss/merge
诞生的原因。
我利用 babel
的 scope
(作用域) 和 binding
(绑定) 增强了识别能力
scope
简而言之就是 js
基础里的大括号, binding
就是一个变量真正的绑定
比如:
const b = 'after:xx'
const a = `${b} text-[#123456]`
cn(a, 'xx', 'yy')
这段代码的作用域是全局,然后 cn
中 a
的绑定是一个 VariableDeclaration
它的 init
中,又引用了 b
,它的绑定也是一个 VariableDeclaration
初始化是一个 StringLiteral
于是,在编译时,我们可以从 cn
递归向上找到 a
和 b
,识别出完整的样式链。
当然,另一种方式是 自上而下查找,通过 import
语句分析模块依赖关系,甚至可以利用打包工具的模块分析图进行静态分析。
当然这块的实现被用在了 @weapp-tailwindcss/merge
中,详见 tailwind-merge 快速开始。
在开发的时候,一定要把可能影响结果的方式,拆成多多阶段运行,比如在修改 js ast
的时候,不应该一边 traverse
,一边再用 MagicString
去修改,而是应该把需要修改的地方,先全部收集出来,然后在一个阶段统一去修改。
另外鉴别一个 ast
工具的成熟度,也应该考虑一下,作用域和绑定的关系,能否被解析出来。比如我们都知道 js
里面大概有 5
种行为会产生作用域:
{}
包裹,例如 if、for、while 中的块,在 ES6 中支持 let 和 const)。而绑定本身也是通过 scope
和它的 parent
依次向上去找的。
转眼我也到了而立之年,在这个行情下,毕业(失业)随时可能发生,当然也不敢毕业,毕竟上有老下有小,还有各种贷款。
技术学得越多,越觉得是个无底洞。而且掌握的东西多了,好像也没什么收益。更何况前端技术和那种 AI 技术相比,就像是过家家一样的。
开源呢?搞了半天也没啥产出,想成为开源明星,但自己技术水平也没到那个级别。
最近,我开始深入学习 云原生,因为我意识到,国内这种国情下 "技术是工具,业务才是目的"。
这导致我们技术人员的地位是非常低的,这点只要还在国内混就无法改变。
所以我们自己也不要对自己或者团队写的东西,搞得有多好多完美的似的。够用就行,多尝试,先快速搞他个 100 个应用,然后挑没死的去进行进一步的开发。
举个例子,现在让我从 0 到 1 搭建一个完整的业务系统,我可以全程自主搞定:
Vue
/ React
Node.js
(任意框架)MongoDB
/ PostgreSQL
Docker
/ Serverless
但问题是——开发成本和时间成本太高了!
这种情况,我要自己去设计数据库,写 openapi.yml
,然后实现接口,还要自己写前端,和自己对接。写的差不多了还要测试,又是一堆 bug
。
而且还可能遇到技术难点,之前我在 Node.js
中实现 PostgreSQL
的 GraphQL
方案,就折腾了好久。这种情况,为什么不直接上 pg_graphql
这种成熟的中间件呢?
所以,从商业角度来看:
所以,从一个程序员的视角出发:
这是最好的时代,也是最坏的时代。