# weapp-tailwindcss 文档索引 > Tailwind CSS 小程序适配方案,覆盖 uni-app、taro、rax、mpx、原生等场景的官方文档合集。 完整文档合辑,适合离线或单文件加载: - quick-start/* 给出接入步骤与常见框架示例,options/*、api* 提供配置与 API 细节,ai/* 收录提示词与工作流。 - 内容按上手优先排序,并保留关键 frontmatter 供模型摘要与索引。 ## 简介 ## 总览 由于小程序运行时,本身有自己的一套 **独特的** 技术规范标准。这导致你无法使用 `web` 开发中的很多的特性, 你也无法 **直接** 使用像 [`tailwindcss`](https://www.tailwindcss.com/) 这种原子化 `css` 生成器来提升你的开发效率。 而 `weapp-tailwindcss` 就能让你,在小程序开发中使用 `tailwindcss` **大部分** 特性。 它支持目前上所有使用 `webpack` 和 `vite` 的主流多端小程序框架和使用 `webpack` / `gulp` 的原生小程序打包方式。 你可以很容易在各个框架,或原生开发中集成 `tailwindcss`。 现在,就让我们开始使用吧! :::info 从本质上讲,它是一个字符串转义器。它负责把 `tailwindcss` 中,所采集的类名,以及生成的结果,转化成小程序中可以接受的方式。 ::: ## Why `weapp-tailwindcss`? - ✅ 自动处理所有文件:以微信小程序为例,不但可以处理和转义 `wxml` / `wxss`,还能处理 `js` 和 `wxs` 产物 - ✅ 支持最原生的小程序开发,也支持许多框架如 `taro` , `uni-app` , `mpx` , `rax` 等等.. - ✅ 提供多种使用方式,方便项目集成:包括 `webpack` / `vite` / `gulp` 插件和直接的 `nodejs api` - ✅ 生态好,解决方案丰富,提供大量现成模板,可以利用许多 `tailwindcss` 现有的生态来构建小程序。 - ✅ 高效的解析和缓存机制,热更新响应时间快 - ✅ 贴合 `tailwindcss` 的设计思路,智能提示友好 ## 快速开始 :rocket: ### 🔥 Tailwind CSS @3.x `weapp-tailwindcss` 主要提供了 `3` 种使用方式: #### 👉 [1. 框架类( `taro` , `uni-app` , `mpx` )小程序开发的快速开始](/docs/quick-start/install) #### 👉 [2. 原生小程序开发的快速开始](/docs/quick-start/native/install) #### 👉 [3. 可直接使用的各个框架的小程序模板](/docs/community/templates) ### 🧪 Tailwind CSS @4.x #### 👉 [Tailwindcss@4.x 快速开始](/docs/quick-start/v4) ## 演示视频 ## 另外特别感谢 [舜岳同学](https://space.bilibili.com/475498258) 为 `weapp-tailwindcss` 制作的视频 --- ## 小程序多主题方案 ## 自由的 web 方案 对于 `web` 来说,多主题色的需求是非常常见的,比如 `暗黑模式` 就是一个极其常见的需求, `web` 上的解决方案无非就是,通过动态切换 `css` 变量的值达成效果,或者通过 `.dark / [data-theme]` 选择器,包裹暗黑模式下页面和组件的样式,通过增加选择器的优先级,来覆盖默认的样式等等... 那么小程序的方案应该怎么去实现呢? 答案就是有 [page-meta 页面属性配置节点](https://developers.weixin.qq.com/miniprogram/dev/component/page-meta.html) 的情况下,优先使用它的 `page-style` 属性,进行 `css` 变量的切换 没有 `page-meta` 页面属性配置节点的情况下,我们只能通过配置单个 `view` 组件的样式变量,来进行主题色的切换 ## 方案的设计和实现 切换多主题主要依赖 `css` 变量切换,所以我们只要依照这个设计去实现即可 ### 1. 页面属性配置节点 page-meta 利用 `page-meta` 页面属性配置节点的 `page-style` 属性,来进行 `css` 变量的切换 另外我们也可以通过 `page-meta` 组件的,来切换一些原生的样式 [page-meta 页面属性配置节点](https://developers.weixin.qq.com/miniprogram/dev/component/page-meta.html) ### 2. 自己实现 css 变量切换组件 首先既然我们无法利用**根**节点的变量切换来达成效果,但是我们可以通过组件的特性,即数据的响应式和插槽来达成效果。 我们可以设计一个 `ConfigProvider` 组件,它拥有一个`dom`节点,内部是一个插槽 其中那个`dom`节点就是我们主题相关变量寄居在的节点,而这个组件往往会作为一个根组件,在每个页面中被使用,去包裹我们真正的业务页面 甚至我们可以再设计一个 `BaseLayout` 这样的组件,去包含每个页面公共的部分,再在其中去引用 `ConfigProvider`,然后做一层插槽的透传即可。 #### 实现 > 这里我以 `vue` 的语法作为示例,因为我个人认为它比 `react` 和 `原生` 更容易让新手看懂 `ConfigProvider`的实现: ```html ``` 其中,`mode` 这个 `prop` 用来模拟实现了 `` 的效果,而 `vars` 则用来模拟实现 `js api` 设置 `css` 变量的效果。 通过这 `2` 个 `props`,你既可以通过 `mode` 的切换,把多个主题以及对应的变量值全部给写在你自己的 `css`中,然后通过切换`mode`,触发样式的覆盖来切换主题,这种是为静态的切换。 又可以通过设置 `vars`的值去动态的覆盖和切换,比如从服务端获取`css`变量的值,然后`set`进组件中,这显然是非常灵活的,这种是为动态的切换。 现在有了这个组件,我们就可以用它去包裹每一个页面了。 然后下一步,自然是要我们的页面和组件,都去应用那些我们设计的 `css` 变量了。 这一块可以参考下方链接中的`动态调整系统主题色(4)`中的`CssVar`方案,里面也有和 `tailwindcss` 相结合的部分,也欢迎阅读`动态调整web系统主题` 系列文章,并与在下进行探讨。 ## 动态调整主题参考链接 1. [动态调整web系统主题? 看这一篇就够了](https://icebreaker.top/articles/2021/12/18-flexible-theme) 2. [动态调整web主题(2) 萃取篇](https://icebreaker.top/articles/2022/1/15-custom-theme-2) 3. [动态调整web主题(3): 基于tailwindcss插件的主题色生成方案](https://icebreaker.top/articles/2022/9/26-custom-theme-3) 4. [动态调整系统主题色(4): CssVar 与 Variant 方案的探索](https://icebreaker.top/articles/2023/10/5-custom-theme-4) ## 参考示例 微信上搜索 `tailwind`(未交 `30` 元个人资质费用,已无法搜索),进入小程序即可,小程序码: ![tailwind](./frameworks/img/tailwind-mp-qrcode.jpg) 实现源代码详见: [weapp-tailwindcss/tailwindcss-weapp](https://github.com/sonofmagic/weapp-tailwindcss/tree/main/tailwindcss-weapp) --- ## 构建以及引入外部组件 ## 前言 我们在日常的开发中,经常会去使用和封装各种各样的组件库。有些是开源的,第三方开发的UI库,有些是我们开发人员给自己的特定的业务封装的UI库。其中很多情况其实是以流行的 `开源UI库(或者fork的改版)` + `自己封装的业务组件为主的` `开源UI库` 它们的样式相对来说是独立于整套系统的,比如它们的样式都是 `ant-`,`el-` 开头的,一般引入之后不会和原先系统里的样式产生冲突。而 `自己封装的业务组件`,由于往往和系统高度绑定也没有这样的问题。 那么如何用 `tailwindcss` 来构建/发布和引入自己封装的业务组件呢? ## 构建组件 ### 核心思想 首先我必须重点把本篇文章的核心思想预先抛出: `tailwindcss` 只是一个`css`生成器,它只是帮你按照一定的规则,从你的源代码中匹配字符串去生成`css`。所以在用它去构建组件的时候,一定要去思考你用 `tailwindcss` 生成的 `css` 的影响范围,因为大部分用 `tailwindcss` 都是默认全局应用的。但是你在组件里面的自定义样式很多情况下,是没有必要的。 根据这个核心思想,我们就可以知道在封装组件时可行和不可行的方式了,大致如下: ### 可行方案 1. `custom css selector` + `Functions & Directives` 2. `add prefix` (添加前缀) 3. `add scoped` (像 `vue` 的 `scoped` 一样添加 data-v-[hash] 类似的自定义属性,然后去修改css选择器) 4. 不打包方案 (不构建产物,直接发布,然后在项目里安装,再提取 `node_modules` 里制定的文本重新生成。) ### 不可行方案 1. module css 这会去修改 css 选择器。 ## 可行方案详解 这里我写了2个`demo`分别是 `react` 和 `vue`,其中下方代码以 `vue` 为示例,`react`示例见下方的 `构建demo链接` ### custom css selector + Functions & Directives 这种方案其实非常的传统,仅仅使用到了 `tailwindcss` 中 `@apply` 和 `theme` 等等指令的功能。 比如我们有个组件 `ApplyButton.vue`,它的模板,样式和独立的 `tailwind.config.js` 分别如下所示: ```html ``` ```css @config 'tailwind.config.js'; @tailwind utilities; .apply-button { @apply text-white p-4 rounded; background-color: theme("colors.sky.600") } ``` ```js const path = require('node:path') /** @type {import('tailwindcss').Config} */ export default { content: [path.resolve(__dirname, './index.vue')], // ... } ``` 然后在打包的时候,以这个文件或者导出文件(`index.ts`) 为打包入口即可。 这样它的产物css中,选择器由于是你自己定义的,就能尽可能保证它是独一无二的。 它对应的`css`产物为: ```css .apply-button { border-radius: 0.25rem; --tw-bg-opacity: 1; background-color: rgb(2 132 199 / var(--tw-bg-opacity)); padding: 1rem; --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); } ``` ### add prefix 这个也很好理解,前缀嘛,各个UI库都是这样搞的,我们就可以创建出以下的代码: ```html ``` ```js const path = require('node:path') /** @type {import('tailwindcss').Config} */ export default { prefix: 'ice-', content: [path.resolve(__dirname, './index.vue')], } ``` 它对应的`css`产物为: ```css .ice-rounded { border-radius: 0.25rem; } .ice-bg-sky-600 { --tw-bg-opacity: 1; background-color: rgb(2 132 199 / var(--tw-bg-opacity)); } .ice-p-4 { padding: 1rem; } .ice-text-white { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); } ``` ### add scoped 这个就是通过同时添加html标签属性和修改css选择器来做的了: ```html ``` 这里仅仅给 `style` 加了一个 `scoped` 属性 ```js const path = require('node:path') /** @type {import('tailwindcss').Config} */ export default { content: [path.resolve(__dirname, './index.vue')], } ``` `css` 生成结果为: ```css .rounded[data-v-10205a53] { border-radius: 0.25rem; } .bg-sky-600[data-v-10205a53] { --tw-bg-opacity: 1; background-color: rgb(2 132 199 / var(--tw-bg-opacity)); } .p-4[data-v-10205a53] { padding: 1rem; } .text-white[data-v-10205a53] { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); } ``` ### 不打包 以上三种方式总结一下,都是通过在选择器上下功夫来制作组件库的,而且它们都有一个打包的过程,即 `src`->`dist` 然后发布 `dist` 可是这第四种方案就不怎么一样了: 核心就是 `不打包` 即我们写好组件之后,直接把 `npm`的入口文件,指向 `src` ,然后直接把里面的组件发布(比如直接发布 `vue`组件) 这种情况下,你需要让你在 `node_modules` 里的组件再次经受一遍 `js` 的处理,比如 `vue sfc compiler`,`babel`,`swc`等等。 同时你也需要配置你项目里的 `tailwind.config.js` 去提取你 `node_modules` 里的组件源代码内容: ```diff module.exports = { content: [ './index.html', './src/**/*.{html,js,ts,jsx,tsx,vue}', + './node_modules/mypkg/src/components/**/*.{html,js,ts,jsx,tsx,vue}' ] } ``` 这样才能重新提取生成 `css` 在项目主`css chunk`里。 ## 构建demo链接 ## 相关 issues --- ## CSS 单位转化 ## rem 转 rpx (或 px) 在 [rem 转 rpx (或 px)](/docs/quick-start/rem2rpx) 章节,我们做了 `CSS` 中 `rem` 转化成 `px` / `rpx` 的方式。 但是除了 [rem 转 rpx (或 px)](/docs/quick-start/rem2rpx),我们可能也有 `px 转 rpx` 的需求,这种情况实际上也很容易就能做到。 ## px 转 rpx ### 4.3.0 以后 从 `weapp-tailwindcss@4.3.0` 开始,我们内置了 [`postcss-pxtransform`](https://www.npmjs.com/package/postcss-pxtransform) 插件,提供了开箱即用的 `px` 转 `rpx` 的方式。 只需传入配置项: ```js // vite UnifiedViteWeappTailwindcssPlugin({ // ...other-options px2rpx: true }) // webpack const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') new UnifiedWebpackPluginV5({ // ...other-options px2rpx: true }) ``` 只需传入一个 `true`,你写的所有的 `px` 都会被 `1:1` 的转换成 `rpx`,比如 `10px` -> `10rpx` 当然这个选项也可以传入一个 `object`, 这个默认值和参数,可以参考 [`postcss-pxtransform`](https://www.npmjs.com/package/postcss-pxtransform) 插件。 ### 4.3.0 以前 这里我们使用 [`postcss-pxtransform`](https://www.npmjs.com/package/postcss-pxtransform) 这个 `postcss` 插件来做。 > [`postcss-pxtransform`](https://www.npmjs.com/package/postcss-pxtransform) 由京东团队出品,应该是目前质量最高的 `px` 转 `rpx` 插件,而且已经被内置在了 `tarojs` 框架内 #### 安装插件 ```bash npm2yarn npm i -D postcss-pxtransform ``` #### 注册到 postcss 配置中 ```js title="postcss.config.js" module.exports = { plugins: { 'tailwindcss': {}, 'autoprefixer': {}, // highlight-start // 下方为 px 转 rpx 区域 'postcss-pxtransform': { platform: 'weapp', // 根据你的设计稿宽度进行配置 // 可以传入一个 function // designWidth (input) { // if (input.file.replace(/\\+/g, '/').indexOf('@nutui/nutui-taro') > -1) { // return 375 // } // return 750 // }, designWidth: 750, // 可以设置为 375 等等来应用下方的规则, deviceRatio: { 640: 2.34 / 2, // 此时应用到的规则,代表 1px = 1rpx 750: 1, 828: 1.81 / 2, // 假如你把 designWidth 设置成 375 则使用这条规则 1px = 2rpx 375: 2 / 1, }, }, // highlight-end }, } ``` 这样就能进行转化了,此时假如你写 `w-[20px]` 这种 `class` 它最终生效的样式会经过 `postcss-pxtransform` 转化,转变为 `width: 20rpx`, 当然这取决于你传入插件的配置,比如设计稿宽度 (`designWidth`) 你可以在 [taro 官网的设计稿及尺寸单位章节内](https://docs.taro.zone/docs/size) 查看这个插件的所有用法。 另外,假如你要禁止单个文件 `px` 转 `rpx`,可以在样式表文件内头部,添加 `/*postcss-pxtransform disable*/` 这样的注视,禁用该文件 `px` 转 `rpx`。 --- ## Nodejs API > 版本 2.11.0+ , 此为高阶 `api`,使用起来有难度,不适合新手,假如你不清楚你在做什么,请使用 `webpack/vite/gulp` 插件 有时候,我们不一定会使用 `webpack/vite/gulp`,可能是直接使用 `nodejs` 去构建应用,或者封装更高阶的工具,这时候可以使用`api`去转义你的应用。 ## 如何使用 ```js // mjs or // cjs const { createContext } = require('weapp-tailwindcss/core') async function main(){ // createContext 可传入参数,类型为 UserDefinedOptions const ctx = createContext() // 3.1.0 开始 api 都是异步的,为 rust 工具链做准备 const wxssCode = await ctx.transformWxss(rawWxssCode) const wxmlCode = await ctx.transformWxml(rawWxmlCode) const jsCode = await ctx.transformJs(rawJsCode) // 传入参数和输出结果均为 字符串 string // 然后你就可以根据结果去复写你的文件了 } main() ``` :::tip 有一点要特别注意,在使用 `ctx.transformJs` 的时候,一定要确保 `tailwindcss` 已经执行完毕了!也就是说对应的 `postcss` 执行完毕。 因为 `js` 的转义依赖 `tailwindcss` 的执行结果,然后根据它,再去从你的代码中找到 `tailwindcss` 提取出的字符串,再进行处理的。 假如此时 `tailwindcss` 还没有执行,则插件就只能获取到一个 **空的** 提取字符串集合,这就无法进行匹配,从而导致你写在 `js` 里的类名转义失效。 比如这种情况: ```js // index.js const classNames = ['mb-[1.5rem]'] ``` 另外使用此种方式,编译缓存需要自行处理,且暂时没有类名的压缩与混淆功能 ::: --- ## uni-app HbuilderX 使用方式 ## 默认使用方式 > 配置会稍微复杂一些,这里推荐直接使用或者参考模板: [uni-app-vue3-tailwind-hbuilder-template](https://github.com/sonofmagic/uni-app-vue3-tailwind-hbuilder-template) 或者 [若依移动端 (Gitee 地址)](https://gitee.com/sonofmagic/RuoYi-App) ### tailwind.config.js 注意: 在使用 `hbuilderx` 进行开发时,由于目录结构和启动项的不同,你必须要给你 `tailwind.config.js` 传入**绝对路径**: ```js title="tailwind.config.js" const path = require("path"); const resolve = (p) => { return path.resolve(__dirname, p); }; /** @type {import('tailwindcss').Config} */ module.exports = { // 注意此处,一定要 `path.resolve` 一下, 传入绝对路径 // 你要有其他目录,比如 components,也必须在这里,添加一下 content: ["./index.html", "./pages/**/*.{html,js,ts,jsx,tsx,vue}"].map(resolve), // ... corePlugins: { // 跨多端可以 h5 开启,小程序关闭 preflight: false, }, }; ``` ### vite.config.[tj]s 另外使用 `vite.config.[tj]s` 中注册 `tailwindcss` 时,也要传入绝对路径: ```js title="vite.config.[tj]s" // 注意: 打包成 h5 和 app 都不需要开启插件配置 const isH5 = process.env.UNI_PLATFORM === "h5"; const isApp = process.env.UNI_PLATFORM === "app"; const WeappTailwindcssDisabled = isH5 || isApp; const resolve = (p) => { return path.resolve(__dirname, p); }; export default defineConfig({ plugins: [ uni(), uvwt({ rem2rpx: true, disabled: WeappTailwindcssDisabled, // 由于 hbuilderx 会改变 process.cwd 所以这里必须传入当前目录的绝对路径 tailwindcssBasedir: __dirname }) ], css: { postcss: { plugins: [ require("tailwindcss")({ // 注意此处,手动传入你 `tailwind.config.js` 的绝对路径 config: resolve("./tailwind.config.js"), }), require("autoprefixer"), ], }, }, }); ``` `hbuilderx` 正式版本的 `vue2` 项目,由于使用 `webpack4` 和 `postcss7`,所以只能使用本插件的 `weapp-tailwindcss/webpack4` 版本, 详见[uni-app-vue2-tailwind-hbuilder-template](https://github.com/sonofmagic/uni-app-vue2-tailwind-hbuilder-template) 或者下方也有一种 `Hack hbuilderx vue2 Way` 来在 `hbuilderx` `vue2` 项目中,使用 `webpack5` 和 `postcss8` ## Hbuilderx 与 uni-app cli 环境汇总 首先,你需要知道你的项目究竟使用的是什么打包工具,截止今天 `2023/12/18` 目前如下所示: | | webpack | vite | postcss | | ---------------- | -------- | ---- | -------- | | hbuilderx vue2 | webpack4 | x | postcss7 | | uni-app cli vue2 | webpack5 | x | postcss8 | | hbuilderx vue3 | x | √ | postcss8 | | uni-app cli vue3 | x | √ | postcss8 | 也就是说,目前 `hbuilderx vue2` 的项目是最老的,无法使用最新版本的 `tailwindcss`,其他都可以使用。 ## hbuilderx vue2 webpack4 postcss7 版本模板 如果你实在必须在 `hbuilderx vue2` 的项目中使用 `tailwindcss`,那么你可以使用下面的方法来使用 `tailwindcss` 详见 [uni-app-vue2-tailwind-hbuilder-template](https://github.com/icebreaker-trash/uni-app-vue2-tailwind-hbuilder-template) ## Hack hbuilderx vue2 Way :::caution 以下方式为全局 Hack, 可能会在 `hbuilderx` 升级后出现问题 ::: `hbuilderx` 和 `hbuilderx alpha` 新建的 `vue2` 项目,发现它们的 `webpack` 版本被锁死在了 **`4`** ,我又用 `cli` 创建了一个 `vue2` 项目,发现已经是 `webpack5` 了,看起来只有 `cli` 创建的项目,会被默认升级 `webpack5`。 当然这并不意味着 `hbuilderx` 创建的 `vue2` 项目无法使用最新的这个插件,我们可以强行升级 `HBuilderX/plugins/uniapp-cli` 中的依赖,使得它适配 `webpack5` > Macos uniapp-cli 路径在 /Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli > > Windows 的路径应该也在类似的地方,记得要先下载 vue2 的编译器,这个文件夹才有 来到 `uniapp-cli` 这个项目路径,执行 `yarn upgradeInteractive --latest` 升级项目依赖,重点升级 `@vue/cli-*` 相关包到 `5` 这时候 `webpack` 已经被升级到 `5` 版本了,然后你升级其他的 `loader` 到适配 `webpack5` 的版本(通常是最新版本) 再安装 `postcss` 和 `postcss-loader` 的最新版本,这时候你就把整个 `uni-app vue2` 项目的 `hbuilderx` 内置 `cli` 从 `webpack4`,`postcss7`变为了 `webpack5`,`postcss8` 了 不过代价是什么呢?那就是,这项改动是全局的! 你要想恢复设置,那只有重新安装 `uni-app vue2` 编译插件,或者重新安装整个 `hbuilderx`,所以这里还是推荐使用 `cli` 方式去创建项目,保证一个项目一个编译模式,你要节省空间就用 `pnpm`, 想用什么版本编译就用什么版本。 ## 视频演示 --- ## mpx (原生增强) 在 `vue.config.js` 中注册: ```js title="vue.config.js" const { defineConfig } = require('@vue/cli-service') const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') module.exports = defineConfig({ // other options configureWebpack(config) { config.plugins.push( new UnifiedWebpackPluginV5({ rem2rpx: true, }) ) } }) ``` ## 引入 Tailwind CSS 样式 Tailwind 的底层样式需要显式导入。不同主版本的包结构略有差异: ### Tailwind CSS v3.x 仍然可以直接引用 Tailwind 提供的底层样式文件(带 `.css` 扩展名): ```html title="src/app.mpx" ``` ### Tailwind CSS v4.x v4 官方改用了全新的 CSS 包结构,推荐直接引入 `weapp-tailwindcss` 提供的聚合样式文件: ```html title="src/app.mpx" ``` 这样即可获得预置的 base / components / utilities,同时避免 `postcss-import` 误解析到 JS 入口导致构建报错。 ## mpx 中的 vscode tailwindcss 智能提示缺失设置 我们知道 `tailwindcss` 最佳实践,是要结合 `vscode`/`webstorm`提示插件一起使用的。 假如你遇到了,在 `vscode` 的 `mpx` 文件中,编写 `class` 没有出智能提示的情况,可以参考以下步骤。 这里我们以 `vscode` 为例: 接着找到 `Tailwind CSS IntelliSense` 的 `扩展设置` 在 `include languages`,手动标记 `mpx` 的类型为 `html` ![如图所示](./img/vscode-tailwindcss.png) 保存设置,再去`mpx`文件里写`class`的时候,智能提示就出来啦。 --- ## 原生开发(打包方案) :::warning 注意!这是原生开发(**打包方案**),假如你需要纯原生方案,请查看 [快速开始(纯原生)](/docs/quick-start/native/install) ::: > 由于原生小程序没有 `webpack/vite/gulp` 工具链暴露出来,所以我们要添加这一套机制,来整个前端社区接轨,以此来实现更强大的功能。 :::tip 给原生小程序加入编译时这块 `webpack/vite/gulp` 等等工具,思路都是一样的,然而实现起来比较复杂,损耗精力,在此不提及原理。 更改模板工具链流程前,请确保你比较熟悉工具链开发(到我这样的水平就差不多了)。 另外这些模板,只需要稍微改一下产物后缀,添加 `tailwind.config.js` 的 `content` 就可以适配百度,头条,京东...各个平台。 ::: ## gulp 模板 模板项目 [weapp-tailwindcss-gulp-template(gulp打包)](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/tree/main/demo/gulp-app) ## webpack5 模板 模板项目 [weapp-native-mina-tailwindcss-template(webpack打包)](https://github.com/sonofmagic/weapp-native-mina-tailwindcss-template) ## 组件样式的隔离性 :::tip 发现很多用户,在使用原生开发的时候,经常会问,为什么样式不生效。 这可能有以下几个原因: 1. 代码文件不在 `tailwind.config.js` 的 `content` 配置内 2. 原生小程序组件是默认开启 **组件样式隔离** 的,默认情况下,自定义组件的样式只受到自定义组件 wxss 的影响。而 `tailwindcss` 生成的工具类,都在 `app.wxss` 这个全局样式文件里面。不属于组件内部,自然不生效。 这时候可以使用: ```js /* 组件 custom-component.js */ Component({ options: { addGlobalClass: true, } }) ``` 来让组件应用到 `app.wxss` 里的样式。 [微信小程序相关开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E7%BB%84%E4%BB%B6%E6%A0%B7%E5%BC%8F%E9%9A%94%E7%A6%BB) ::: ## vscode tailwindcss 智能提示设置 我们知道 `tailwindcss` 最佳实践,是要结合 `vscode`/`webstorm`提示插件一起使用的。 假如你遇到了,在 `vscode` 的 `wxml` 文件中,编写 `class` 没有出智能提示的情况,可以参考以下步骤。 这里我们以 `vscode` 为例: 1. 安装 [`WXML - Language Services 插件`](https://marketplace.visualstudio.com/items?itemName=qiu8310.minapp-vscode)(一搜 wxml 下载量最多的就是了) 2. 安装 [`Tailwind CSS IntelliSense 插件`](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 接着找到 `Tailwind CSS IntelliSense` 的 `扩展设置` 在 `include languages`,手动标记 `wxml` 的类型为 `html` ![如图所示](./img/vscode-setting.png) 智能提示就出来了: ![智能提示](./img/wxml-i.png) --- ## Rax (react) 在根目录下创建一个 `build.plugin.js` 文件,然后在 `build.json` 中注册: ```json title="build.json" { "plugins": [ "./build.plugin.js" ], } ``` 回到 `build.plugin.js` ```js title="build.plugin.js" const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') module.exports = ({ context, onGetWebpackConfig }) => { onGetWebpackConfig((config) => { config.plugin('UnifiedWebpackPluginV5').use(UnifiedWebpackPluginV5, [ { rem2rpx: true, }, ]); }); }; ``` --- ## Taro (所有框架) 目前 Taro v4 同时支持了 `Webpack` 和 `Vite` 进行打包编译,`weapp-tailwindcss` 这 `2` 者都支持,但是配置有些许的不同 :::caution 假如你写了 `tailwindcss` 工具类不生效,可能是由于微信开发者工具默认开启了 `代码自动热重载` 功能,关闭它即可生效。 假如你和 `NutUI` 一起使用,或者启用了 `@tarojs/plugin-html` 插件,请一定要查看这个[注意事项](/docs/issues/use-with-nutui)! ::: 下列配置同时支持 `taro` 的 `react` / `preact` / `vue2` / `vue3` 所有框架 ## 使用 Webpack 作为打包工具 ### 注册插件 在项目的配置文件 `config/index` 中注册: ```js title="config/index.[jt]s" const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') // 假如你使用 ts 配置,则使用下方 import 的写法 // import { UnifiedWebpackPluginV5 } from 'weapp-tailwindcss/webpack' { // 找到 mini 这个配置 mini: { // postcss: { /*...*/ }, // 中的 webpackChain, 通常紧挨着 postcss webpackChain(chain, webpack) { // 复制这块区域到你的配置代码中 region start // highlight-start chain.merge({ plugin: { install: { plugin: UnifiedWebpackPluginV5, args: [{ // 这里可以传参数 rem2rpx: true, }] } } }) // highlight-end // region end } } } ``` 然后正常运行项目即可,相关的配置可以参考模板 [taro-react-tailwind-vscode-template](https://github.com/sonofmagic/taro-react-tailwind-vscode-template) :::info `weapp-tailwindcss/webpack` 对应的插件 `UnifiedWebpackPluginV5` 对应 `webpack@5` `weapp-tailwindcss/webpack4` 对应的插件 `UnifiedWebpackPluginV4` 对应 `webpack@4` 在使用 `Taro` 时,检查一下 `config/index` 文件的配置项 `compiler`,来确认你的 `webpack` 版本,推荐使用 `'webpack5'` 另外假如你使用了 [`taro-plugin-compiler-optimization`](https://www.npmjs.com/package/taro-plugin-compiler-optimization) 记得把它干掉。因为和它一起使用时,它会使整个打包结果变得混乱。详见 [issues/123](https://github.com/sonofmagic/weapp-tailwindcss/issues/123) [issues/131](https://github.com/sonofmagic/weapp-tailwindcss/issues/131) 还有 `taro` 的 `prebundle` 功能老是出错,最近更新之后,由于 `prebundle` 默认开启,有时候连 `taro cli` 初始化的模板项目都跑不起来,假如遇到问题找不到原因,可以尝试关闭这个配置。 ::: ## 使用 Vite 作为打包工具 由于 `taro@4` 的 `vite` 版本,目前加载 `postcss.config.js` 配置是失效的,所以我们目前暂时只能使用内联 `postcss` 插件的写法 ### 在 `config/index.ts` 中注册插件 ```ts title="config/index.[jt]s" const baseConfig: UserConfigExport<'vite'> = { // ... 其他配置 // highlight-start compiler: { type: 'vite', vitePlugins: [ { // 通过 vite 插件加载 postcss, name: 'postcss-config-loader-plugin', config(config) { // 加载 tailwindcss if (typeof config.css?.postcss === 'object') { config.css?.postcss.plugins?.unshift(tailwindcss()) } }, }, uvtw({ // rem转rpx rem2rpx: true, // 除了小程序这些,其他平台都 disable disabled: process.env.TARO_ENV === 'h5' || process.env.TARO_ENV === 'harmony' || process.env.TARO_ENV === 'rn', // 由于 taro vite 默认会移除所有的 tailwindcss css 变量,所以一定要开启这个配置,进行css 变量的重新注入 injectAdditionalCssVarScope: true, }) ] as Plugin[] // 从 vite 引入 type, 为了智能提示 }, // highlight-end // ... 其他配置 } ``` `tailwindcss` 即可注册成功,正常使用了 这段代码的意思为,在 `vite` 里注册 `postcss` 插件和 `vite` 插件 > `vite.config.ts` 只有在运行小程序的时候才会加载,`h5` 不会,所以只能通过这种方式进行 `小程序` + `h5` 双端兼容 ## 视频演示 --- ## uni-app cli vue3 vite :::warning 这是 `uni-app cli` 创建的项目的注册方式,如果你使用 `HbuilderX`,应该查看 [uni-app HbuilderX 使用方式](/docs/quick-start/frameworks/hbuilderx) ::: ## 注册插件 创建完成后,快速上手中的准备工作都完成之后,就可以便捷的注册了: ```js title="vite.config.[jt]s" export default defineConfig({ // uni 是 uni-app 官方插件, uvtw 一定要放在 uni 后,对生成文件进行处理 plugins: [uni(),uvwt()], css: { postcss: { plugins: [ // require('tailwindcss')() 和 require('tailwindcss') 等价的,表示什么参数都不传,如果你想传入参数 // require('tailwindcss')({} <- 这个是postcss插件参数) require('tailwindcss'), require('autoprefixer') ], }, }, }); ``` 这里只列举了插件的注册,包括`postcss`配置完整的注册方式,参考配置项文件链接: ## 创建项目参考 `uni-app vite` 版本是 `uni-app` 最新的升级,它使用 `vue3` 的语法。 你可以通过 `cli` 命令创建项目 ([参考官网文档](https://uniapp.dcloud.net.cn/quickstart-cli.html)): - 创建以 javascript 开发的工程(如命令行创建失败,请直接访问 gitee 下载模板) ```bash npx degit dcloudio/uni-preset-vue#vite my-vue3-project ``` - 创建以 typescript 开发的工程(如命令行创建失败,请直接访问 gitee 下载模板) ```bash npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project ``` > gitee 地址见上方的 `参考官网文档` 链接,点击跳转到 uni-app 官网即可 ## 视频演示 --- ## 🔥 uni-app x 目前 `weapp-tailwindcss` 从 `4.2.0` 版本开始,插件已经支持 `uni-app-x` 同时构建包括 `web`,`小程序`, `安卓`,`IOS`,`鸿蒙` 五端项目 ## 创建工具类 在项目中创建 `shared.js` 文件,用于存放一些工具函数: ```js title="shared.js" const path = require('node:path') function r(...args) { return path.resolve(__dirname, ...args) } module.exports = { r, } ``` ## 注册插件 创建 `vite.config.ts` 文件,注册插件: > 这里特别注意 `uniAppX` 是从 `weapp-tailwindcss/presets` 这个预设中导出的 ```js title="vite.config.[jt]s" export default defineConfig({ plugins: [ uni(), UnifiedViteWeappTailwindcssPlugin( uniAppX({ base: __dirname, rem2rpx: true, }), ), ], css: { postcss: { plugins: [ tailwindcss({ config: r('./tailwind.config.js'), }), ] } } }); ``` ## 更改 tailwindcss 配置 使用绝对路径,包括所有的提取文件 ```js title="tailwind.config.js" const { r } = require('./shared') /** @type {import('tailwindcss').Config} */ module.exports = { content: [ r('./pages/**/*.{uts,uvue}'), r('./components/**/*.{uts,uvue}') ], corePlugins: { preflight: false, }, } ``` ## 现成模板可以直接使用或者参考 https://github.com/icebreaker-template/uni-app-x-hbuilderx 使用方式详见项目中的 `README.md` --- ## uni-app cli vue2 webpack :::warning 这是 `uni-app cli` 创建的项目的注册方式,如果你使用 `HbuilderX`,应该查看 [uni-app HbuilderX 使用方式](/docs/quick-start/frameworks/hbuilderx) ::: :::tip 截止到 (2023/09/08),目前所有的 `uni-app vue2 cli` 项目的 `webpack` 版本,已经切换到了 `webpack@5`,`@vue/cli@5`,`postcss@8` 了 另外如果你有旧有的 `uni-app webpack4` 项目需要迁移到 `webpack5`,可以看这篇 [旧有uni-app项目升级webpack5指南](/docs/upgrade/uni-app) ::: ```js title="vue.config.js" const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') /** * @type {import('@vue/cli-service').ProjectOptions} */ const config = { // some option... // highlight-start configureWebpack: (config) => { config.plugins.push( new UnifiedWebpackPluginV5({ rem2rpx: true, }) ) } // highlight-end // other option... } module.exports = config ``` 这样所有的配置便完成了!赶紧启动你的项目试试吧! --- ## tailwindcss 多上下文与独立分包 你看过动漫《百兽王》吗?《百兽王》的主人公是五个飞行员,他们分别驾驶黑、红、青、黄、绿五头机器狮,它们平时可以单独进行作战,遇到强敌时,也能进行五狮合体,成为巨大机器人“百兽王”。 同样,在日常开发中,我们经常遇到这样的问题,一个很大的程序,它有很多个独立的部分组成,每一个部分可以单独运行,也有独立的入口,相互之间没有任何的依赖,但是它们在同一个项目/任务里进行构建。 在这种场景下,去使用 `tailwindcss` 就往往需要去创建多个上下文,让这些上下文各自去管理我们程序中的指定的一块区域。 当然我写到这,相信大家也啥都没看懂,于是我搬出一个小程序中,独立分包的示例,来让大家理解这种思想。 ## 什么是独立分包 独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。 独立分包属于分包的一种。普通分包的所有限制都对独立分包有效。独立分包中插件、自定义组件的处理方式同普通分包。此外,使用独立分包时要注意: 1. 独立分包中不能依赖主包和其他分包中的内容,包括 js 文件、template、wxss、自定义组件、插件等(使用 分包异步化 时 js 文件、自定义组件、插件不受此条限制) 2. 主包中的 `app.wxss` 对独立分包无效,应避免在独立分包页面中使用 `app.wxss` 中的样式; 3. App 只能在主包内定义,独立分包中不能定义 App,会造成无法预期的行为; 4. 独立分包中暂时不支持使用插件。 > 更多信息详见 [微信独立分包官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/independent.html) --- 这里要特别注意第二条: **主包中的 `app.wxss` 对独立分包是无效的!!!** 在我之前提供的`tailwindcss`小程序模板的示例中,所有 `tailwindcss` 生成的 `wxss` 工具类都是在主包里共用的 (`app.wxss`),这在大部分情况下运转良好,然而这在独立分包场景下,是不行的!因为主包的样式无法影响到独立分包。 那么应该怎么做才能解决这个问题呢? ## 创建与配置示例 这里笔者先以 `taro@3.6.7` 和 `weapp-tailwindcss@2.5.2` 版本的项目作为示例。 首先配置好 `weapp-tailwindcss` 的配置,然后在 `config/index.js` 中关闭 `prebundle` 功能,因为这在独立分包场景下会报一些未知的错误: ```js const config = { compiler: { prebundle: { enable: false, }, type: 'webpack5' }, // ..... } ``` 其次关闭插件对 `tailwindcss css var` 主块的寻址行为: ```js chain.merge({ plugin: { install: { plugin: UnifiedWebpackPluginV5, args: [{ // 方法1: 不要传 appType // 注释掉 appType : 'taro' // 或者方法2: 让所有css chunk 都是 main chunk // mainCssChunkMatcher: ()=> true // 2 种选其一即可 }] } } }) ``` 接下来我们就可以创建一个独立分包 `moduleA`,在里面新建一个 `"pages/index"` 页面,并写入一个只属于 `moduleA` 的独一无二的 `tailwindcss class`,然后在 `app.config.ts` 里注册它: ```js subpackages: [ { root: "moduleA", pages: [ "pages/index", ], // 下方这个标志位,声明独立分包 independent: true }, ] ``` 到这里,准备工作就完成了,接下来就可以设计方案了。 ## 单 `tailwindcss` 上下文的方案(不完美不推荐) 这个方案是一个不完美的方案,在这里写出来是为了促进大家对 `tailwindcss` 的理解。 首先在独立分包中,也创建一个 `index.scss` 内容为: ```css @import 'tailwindcss/base'; @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; ``` 然后在所有独立分包中的页面引用它,这样打包之后,独立分包里的 `tailwindcss` 样式也就生效了。 然而这种方式有一个巨大的问题,就是它会带来严重的 `css` 冗余。 因为此时 `tailwindcss` 上下文有且仅有一个,它会把在这个项目中,所有提取出来的 `css` 工具类,全部注入到所有的 `@tailwind` 指令里去。`@import 'tailwindcss/utilities'` 这个引入(本质实际上是`@tailwind`指令)一下子膨胀了起来。 这导致了,主包里的 `app.wxss` 里,会包含主包里所有的 `class` + 独立分包里所有的 `class`,而独立分包里的 `index.scss` 里,也包含主包里所有的 `class` + 独立分包里所有的 `class`! 这显然是不可接受的,因为主包是没有必要包含独立分包的 `class`,而独立分包里,也没有必要包含主包里的 `class`! 这只会白白增大打包后`wxss`文件的体积。 所以这个方案需要改进! ## 多 `tailwindcss` 上下文的方案 由于上面那个方案的问题,我们开始改进,就必须要创建多个 `tailwindcss` 上下文。 那么第一步就是要 **`↓`** ### 创建多个 `tailwind.config.js` 比如说我们只有一个独立分包,所以我们创建了2个 `tailwind.config.js`: 1. `tailwind.config.js` 用于主包以及相互依赖的子包 2. `tailwind.config.sub.js` 用于 `moduleA` 这个独立分包 内容如下: #### 独立分包的上下文配置 ```js // `moduleA` 这个独立分包的 tailwind.config.sub.js /** @type {import('tailwindcss').Config} */ module.exports = { // 这里只提取 moduleA 这个独立分包下的文件内容 content: ["./src/moduleA/**/*.{html,js,ts,jsx,tsx}"], // .... corePlugins: { preflight: false } } ``` #### 主包以及相互依赖的子包的上下文配置 ```js // 主包以及相互依赖的子包的 tailwind.config.js /** @type {import('tailwindcss').Config} */ module.exports = { // https://github.com/mrmlnc/fast-glob // 这里需要限定范围,不去提取 moduleA 这个独立分包下的文件内容 // 所以后面跟了一个 `!` 开头的路径 content: [ "./src/**/*.{html,js,ts,jsx,tsx}", // 不提取独立分包里的 class "!./src/moduleA/**/*.{html,js,ts,jsx,tsx}"], // .... corePlugins: { preflight: false } } ``` 这样 `2` 个配置文件创建好了,接下来就要通过配置让它们各自在打包中生效。 ### `postcss.config.js` 配置 这是非常重要的一块配置,我们需要把 `postcss.config.js` 的配置变成一个函数,这样才能把构建时的上下文传入进来: ```js const path = require('path') module.exports = function config(loaderContext) { // moduleA 下面的所有 scss 文件,都是独立模块的,应用不同的 tailwindcss 配置 const isModuleA = /moduleA[/\\](?:\w+[/\\])*\w+\.scss$/.test( loaderContext.file ) // 多个独立子包同理,加条件分支即可 if (isModuleA) { return { plugins: { tailwindcss: { config: path.resolve(__dirname, 'tailwind.config.sub.js') }, autoprefixer: {}, } } } return { plugins: { // 不传默认取 tailwind.config.js tailwindcss: {}, autoprefixer: {}, } } } ``` 通过这种方式,我们成功的创建了 `2` 个不同的 `tailwindcss` 上下文,此时你进行打包之后,会发现 主包里的 `app.wxss` 和独立分包里的 `index.wxss`,里面的内容就已经各归各了,不再相互包含了。 ## 尾言 当然,上面只是一种方案,达到这样的目的方式有很多种,比如你可以在运行时去修改 `postcss-loader` 对它进行劫持,或者拆成多个项目,分开构建。 我这篇文章只是抛砖引玉,相信聪明的你们一定可以举一反三的。 ## 参考示例 示例见: --- ## 1. 安装与配置 tailwindcss > 请确保你的 `nodejs` 版本 `^18.17.0 || >=20.5.0`。目前低于 `18` 的长期维护版本(`偶数版本`) 都已经结束了生命周期,建议安装 `nodejs` 的 `LTS` 版本,详见 [nodejs/release](https://github.com/nodejs/release)。 > > 假如你安装的 `nodejs` 太新,可能会出现安装包不兼容的问题,这时候可以执行安装命令时,使用 `--ignore-engines` 参数进行 `nodejs` 版本的忽略 。 首先安装本插件前,我们需要把 `tailwindcss` 对应的环境和配置安装好。 这里我们参考 `tailwindcss` 官网中 `postcss` 的使用方式进行安装 ([参考链接](https://tailwindcss.com/docs/installation/using-postcss)) ## 1. 使用包管理器安装 `tailwindcss` ```bash npm2yarn # 安装 tailwindcss@3 版本的依赖 npm i -D tailwindcss@3 postcss autoprefixer ``` ```bash npm2yarn # 初始化 tailwind.config.js 文件 npx tailwindcss init ``` :::info `tailwindcss` 最新版本(`3.x`)对应的 `postcss` 大版本为 `8`,假如你使用像 `uni-app` 或 `taro` 这样的跨端框架,大概率已经内置了 `postcss` 和 `autoprefixer` ::: ## 2. 在项目目录下创建 `postcss.config.js` 并注册 `tailwindcss` > 注意:这只是比较普遍的注册方式,各个框架很有可能是不同的! 比如 `uni-app vue3 vite` 项目就必须要内联注册 `postcss` 选项! 详见下方的注意事项 ```js title="postcss.config.js" // 假如你使用的框架/工具不支持 postcss.config.js 配置文件,则可以使用内联的写法 module.exports = { plugins: { tailwindcss: {}, // 假如框架已经内置了 `autoprefixer`,可以去除下一行 autoprefixer: {}, } } ``` :::tip 注意事项 `uni-app vite vue3` 项目,必须在`vite.config.ts` 文件中,使用 `postcss` 内联的写法注册插件。相关写法可以参考我的这个模板项目: [uni-app-vite-vue3-tailwind-vscode-template](https://github.com/sonofmagic/uni-app-vite-vue3-tailwind-vscode-template)。 而 `uni-app vue webpack5` 项目中的 `postcss.config.js`,在默认情况下,已经预置很多插件在里面,配置比较繁杂,可以参考这个文件 [uni-app-webpack5/postcss.config.js](https://github.com/sonofmagic/weapp-tailwindcss/blob/main/demo/uni-app-webpack5/postcss.config.js) ::: ## 3. 配置 `tailwind.config.js` `tailwind.config.js` 是 `tailwindcss` 的配置文件,我们可以在里面配置 `tailwindcss` 的各种行为。 ```js title="tailwind.config.js" /** @type {import('tailwindcss').Config} */ module.exports = { // 这里给出了一份 uni-app /taro 通用示例,具体要根据你自己项目的目录结构进行配置 // 不在 content 包括的文件内,你编写的 class,是不会生成对应的css工具类的 content: ['./public/index.html', './src/**/*.{html,js,ts,jsx,tsx,vue}'], // 其他配置项 // ... corePlugins: { // 小程序不需要 preflight,因为这主要是给 h5 的,如果你要同时开发小程序和 h5 端,你应该使用环境变量来控制它 preflight: false } } ``` ## 4. 引入 `tailwindcss` 在你的项目入口引入 `tailwindcss` 使它在小程序全局生效 ### uni-app 比如 `uni-app` 的 `App.vue` 文件: ```html title="App.vue" ``` :::warning 千万不要在 `uni.scss` 中去引入 `tailwindcss`, `uni.scss` 本质上走的是 `scss.additionalData`, 它会在每一个 `scss` 文件的开头,都去添加 `uni.scss` 里的文件内容 所以这相当于你每个 `scss`/ `vue` 文件里面,都加了 `tailwindcss` 引入,那 `css` 体积就爆炸了 ::: ### Taro 又或者 `Taro` 的 `app.scss` 文件: ```scss title="app.scss" @import 'tailwindcss/base'; @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; // sass 版本1.25+ 使用 @use // @use 'tailwindcss/base'; // @use 'tailwindcss/components'; // @use 'tailwindcss/utilities'; // 非 scss 的纯 css 文件,下列写法也是可以生效的 // @tailwind base; // @tailwind components; // @tailwind utilities; ``` 然后在 `app.ts` 里引入这个样式文件即可。 这样 `tailwindcss` 的安装与配置就完成了,接下来让我们进入第二个环节:安装 `weapp-tailwindcss`。 ## 参考链接 [`tailwindcss` 官方配置项](https://tailwindcss.com/docs/configuration) --- ## IDE 智能提示设置 ## VS Code > 首先,确保你已经安装 [`Tailwind CSS IntelliSense 插件`](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) ### 让 `Tailwind CSS IntelliSense` 识别 weapp-tailwindcss v4 `tailwindcss-intellisense` 在 v4 中必须看到 `@import "tailwindcss"` 才会将工作区视为 Tailwind 根文件。从 v4.7.10 起,`weapp-tailwindcss` 默认会在构建阶段把这些 `@import 'tailwindcss'` 自动改写成 `@import 'weapp-tailwindcss'`(可通过 `rewriteCssImports: false` 关闭)。这意味着你可以直接在项目入口写 `@import 'tailwindcss';` 以获得 IntelliSense,而插件会在最早的 PostCSS 流程中替换成小程序可用的样式。 如果仍希望与源码解耦,也可以使用 CLI 为 VS Code 生成一个仅供扩展使用的辅助 CSS: ```bash npm2yarn npx weapp-tailwindcss vscode-entry --css src/app.css ``` - 默认输出在 `.vscode/weapp-tailwindcss.intellisense.css`,其中包含 `@import 'tailwindcss';`、常见的 `@source` globs 以及你传入的 CSS 入口(例如 `src/app.css`)。 - 该文件只用于激活 IntelliSense,不需要、也不应该被打包流程引用。 - 若需自定义文件名或额外的 `@source`,可通过 `--output`、`--source`、`--force` 等参数调整,运行 `npx weapp-tailwindcss vscode-entry --help` 查看全部选项。 - 如果不想生成独立文件,可以直接在真实入口写 `@import 'tailwindcss';`,默认启用的 `rewriteCssImports` 会让 webpack/vite 在 CSS 解析阶段把它映射到 `weapp-tailwindcss`(只影响样式导入,JS/TS `import 'tailwindcss'` 不会被修改)。 保存/重载任意文件后 VS Code 会检测到该辅助文件,从而让 `@import 'weapp-tailwindcss';` 的项目享受到完整的补全、悬浮和跳转体验。 ### wxml 的智能提示 我们知道 `tailwindcss` 最佳实践,是要结合 `vscode`/`webstorm`提示插件一起使用的。 假如你遇到了,在 `vscode` 的 `wxml` 文件中,编写 `class` 没有出智能提示的情况,可以参考以下步骤。 这里我们以 `vscode` 为例: 安装 [`WXML - Language Services 插件`](https://marketplace.visualstudio.com/items?itemName=qiu8310.minapp-vscode)(一搜 wxml 下载量最多的就是了) 然后下方提供了 `2` 种方式, `全局设置` 和 `工作区设置`, 根据你的需求仍选其一即可 #### 全局设置 点击 `vscode` 左下角的设置图标里,通过搜索关键词 `tailwindcss` ,找到 `Tailwind CSS IntelliSense` 插件的 `扩展设置` 在 `include languages`,手动标记 `wxml` 的类型为 `html` ![如图所示](./frameworks/img/vscode-setting.png) 智能提示就出来了: ![智能提示](./frameworks/img/wxml-i.png) #### 工作区设置 在你的打开的工作区目录的根目录里,创建 `.vscode` 文件夹,然后添加 `settings.json` 内容如下: ```json { "tailwindCSS.includeLanguages": { "wxml": "html" } } ``` 这样就通过你工作区的 `vscode` 设置,去覆盖了你全局的 `vscode` 设置,也能够达到上图中的效果。 ### js,jsx,ts,tsx,vue...这类文件的智能提示 #### 场景 在安装配置好插件后,我们在写代码时,写到那些标签中的 `class=`,`className=`,这种场景时,智能提示一下子就可以出来。 然而我们在写 `js` 代码的时候,很多时候是直接在代码里,去写 `tailwindcss` 字符串字面量,比如: ```jsx const clsName = 'bg-[#123456] text-[#654321]' return ``` 写这种字符串是没有任何的智能提示的,怎么办呢? #### 解决方案 这里给出一种基于插件的解决方案: 1. 安装 `clsx`: ```bash npm2yarn npm i clsx ``` 2. 进入你的 `vscode` 设置的 [`settings.json`](https://code.visualstudio.com/docs/getstarted/settings) 在里面加入下方的配置: ```json { "tailwindCSS.experimental.classRegex": [ [ "clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)" ] ] } ``` 这样配置之后,智能提示就出来了: ![智能提示](./frameworks/img/js-intelliSense.png) [Refer link](https://github.com/lukeed/clsx#tailwind-support) #### 存在问题 这种原理也是依赖正则匹配,即 `Tailwind CSS IntelliSense 插件` 匹配到了当前 `vscode` 活动的文本域中,存在着 `clsx()` 方法这个关键词,所以就把智能提示给注入进去。 所以你这样写就不会生效: ```js const btn = AAA('') ``` 另外,你可以依据这个特性,修改/添加 `"tailwindCSS.experimental.classRegex"` 里的正则,然后自行封装一个方法,用来进行 `tailwindcss` 的智能提示。 ## WebStorm > 和 `vscode` 方式类似,同样使用 `clsx` 函数 1. 确保你的版本大于等于 [WebStorm 2023.1](https://www.jetbrains.com/webstorm/whatsnew/#version-2023-1-tailwind-css-configuration) 2. 打开设置,前往 [Languages and Frameworks | Style Sheets | Tailwind CSS](https://www.jetbrains.com/help/webstorm/tailwind-css.html#ws_css_tailwind_configuration) 3. 添加以下的配置: ```json { "experimental": { "classRegex": ["clsx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] } } ``` > 如果你使用 `class-variance-authority` 的 `cva` 函数,只需再添加 `"cva\\(([^)]*)\\)"` 正则即可。 ## HbuilderX --- ## 1. 安装与配置 tailwindcss(Native) ## 前言 很荣幸,我们在 `weapp-tailwindcss@3.2.0` 版本开始,引入了微信小程序原生支持的能力。 (其他平台的原生小程序开发,也非常容易兼容) 接下来让我们看看,如何进行使用吧! 本教程演示的是,使用微信开发者工具创建的原生 `js` 小程序,以及原生 `js` `skyline` 小程序使用 `tailwindcss` 的方式 ### 运行环境 请确保你的 `nodejs` 版本 `>=16.6.0`。目前低于 `16` 的长期维护版本(`偶数版本`) 都已经结束了生命周期,建议安装 `nodejs` 的 `LTS` 版本,详见 [nodejs/release](https://github.com/nodejs/release)。 假如你安装的 `nodejs` 太新,可能会出现安装包不兼容的问题,这时候可以执行安装命令时,使用 `--ignore-engines` 参数进行 `nodejs` 版本的忽略 。 ## 创建项目 打开微信开发者工具, 点击 `+` 创建一个项目,依次选择: 0. `AppID` 使用测试号 1. 开发模式: `小程序` 2. 后端服务: `不使用云服务` 3. 模板选择: 第二项选择 `基础` 4. 选择 `JS 基础模板` ![](/img/create-project.png) > 使用 JS 基础模板创建的项目,依然可以使用 `Typescript` 首先安装本插件前,我们需要把 `tailwindcss` 对应的环境和配置安装好。 ## 0. 初始化 `package.json` 首先,假如你使用原生的 JS 模板创建的项目。 在创建的项目目录下,是没有 `package.json` 文件 (`原生的 TS 模板有这个文件`), 你需要执行命令: `npm init -y`,快速创建一个 `package.json` 文件在你的项目下 ## 1. 使用包管理器安装 `tailwindcss` 然后执行: ```bash npm2yarn # 安装 tailwindcss@3 版本的依赖 npm i -D tailwindcss@3 postcss autoprefixer ``` ```bash npm2yarn # 初始化 tailwind.config.js 文件 npx tailwindcss init ``` 这样 `tailwindcss` 就被安装到你项目本地了 ## 2. 配置 `tailwind.config.js` `tailwind.config.js` 是 `tailwindcss` 的配置文件,我们可以在里面配置 `tailwindcss` 的各种行为。 这里给出了一份 `JS微信小程序` 通用示例,具体要根据你自己项目的目录结构进行配置 ```js title="tailwind.config.js" /** @type {import('tailwindcss').Config} */ module.exports = { content: [ // 添加你需要提取的文件目录 'components/**/*.{wxml,js,ts}', 'pages/**/*.{wxml,js,ts}', // 不要使用下方的写法,这会导致 vite 开发时监听文件数量爆炸 // '**/*.{js,ts,wxml}', '!node_modules/**', '!dist/**' ], // 假如你使用 ts 模板,则可以使用下方的配置 // content: ['miniprogram/**/*.{ts,js,wxml}'], corePlugins: { // 小程序不需要 preflight 和 container,因为这主要是给 h5 的,如果你要同时开发小程序和 h5 端,你应该使用环境变量来控制它 preflight: false, container: false, } } ``` ## 3. 在项目目录下创建 `postcss.config.js` 并注册 `tailwindcss` 内容如下: ```js title="postcss.config.js" module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, } } ``` > 这个文件和 `tailwind.config.js` 平级 ## 4. 引入 `tailwindcss` 在你的小程序项目入口 `app.wxss` 文件中,引入 `tailwindcss` 使它在小程序全局生效 ```css @tailwind base; @tailwind components; @tailwind utilities; ``` 在 `app.wxss` 加入这一段代码之后,微信开发者工具会报错。不用担心,这是因为我们还没有完全配置好。 接下来,赶紧进入下一步,安装 `weapp-tailwindcss` 并运行吧! --- ## 2. 安装这个插件并运行 ## 安装插件 在项目目录下,执行: ```bash npm2yarn npm i -D weapp-tailwindcss weapp-vite ``` 这样 `weapp-tailwindcss` 和 `weapp-vite` 就被安装在你的本地了 ## 执行初始化命令 在命令行中运行 ```sh npx weapp-vite init ``` 这个命令会对现有的原生小程序项目,进行 `weapp-vite` 的初始化 执行后,会发现主要有许多文件改动,`CLI` 主要做了 `3` 件事情: - 创建 `vite.config.ts` 文件,这个是 `weapp-vite` 和 `vite` 的配置文件 - 修改 `package.json`, 添加 `dev` 和 `build` 开发和构建脚本,还有构建 `npm` 和打开微信开发者工具 - 修改 `project.config.json` 内容,来适配构建产物 - 添加适配 vite 的 `dts` 和 `tsconfig.json` ## 安装所有的依赖包 在执行完成 `weapp-vite init` 初始化命令之后,我们需要在项目里执行一下安装命令: ```bash npm2yarn npm i ``` ## 注册插件 给 `package.json` 添加下列脚本: ```json title="package.json" { "scripts": { "postinstall": "weapp-tw patch" } } ``` 然后在你的 `vite.config.ts` 里对插件进行注册: ```ts title="vite.config.ts" export default defineConfig({ // highlight-start plugins: [ uvwt({ rem2rpx: true, }), ], // highlight-end }) ``` ## 开始运行 使用 `npm run dev` 进入开发模式, 此模式带有热更新的,主要用于开发 使用 `npm run build` 进行构建 不论是 `npm run dev` 还是 `npm run build`, 他们的构建产物,都在工程目录下的 `dist` 目录 使用微信开发者工具,直接导入工程目录,然后即可预览效果! > 注意不是导入 `dist` 目录,是你工程的根目录! 通常是 `dist` 的父级目录,不要搞错了! ## 配置好的模板 假如你配置不成功,你可以参考以下模板进行配置文件对比: [weapp-vite-tailwindcss-template](https://github.com/weapp-vite/weapp-vite/tree/main/apps/weapp-vite-tailwindcss-template) 或者直接执行命令: ```bash npm2yarn npx weapp-vite create my-app ``` 此命令会在当前目录下,创建一个目录名为 `my-app` 的 `weapp-vite` + `weapp-tailwindcss` 集成模板 {/* [vite-native](https://github.com/sonofmagic/weapp-tailwindcss/tree/main/apps/vite-native) */} {/* [native-weapp-tailwindcss-template](https://github.com/sonofmagic/native-weapp-tailwindcss-template) */} ## 原生组件样式的隔离性 :::tip 发现很多用户,在使用原生开发的时候,经常会问,为什么 `tailwindcss` 样式对自定义组件不生效。 这可能有以下几个原因: 1. 代码文件不在 `tailwind.config.js` 的 `content` 配置内 2. 原生小程序组件是默认开启 **组件样式隔离** 的,默认情况下,自定义组件的样式只受到自定义组件 `wxss` 的影响。而 `tailwindcss` 生成的工具类,都在 `app.wxss` 这个全局样式文件里面。不属于组件内部,自然不生效。 这时候可以在你组件的 `json` 文件配置中,设置下面一行 `styleIsolation` 来开启样式共享: ```json title="custom-component.json" { "styleIsolation": "apply-shared" } ``` > `apply-shared` 表示页面 `wxss` 样式将影响到自定义组件,但自定义组件 `wxss` 中指定的样式不会影响页面; 来让组件应用到 `app.wxss` 里的样式。 更多的文档详见: [微信小程序相关开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E7%BB%84%E4%BB%B6%E6%A0%B7%E5%BC%8F%E9%9A%94%E7%A6%BB) ::: ## 想了解更多 weapp-vite 更多场景和配置,请查看 [weapp-vite 文档网站](https://vite.icebreaker.top/) --- ## 4. rem 转 rpx (或 px) > 此为可选步骤,根据你自己的需求进行配置。通常此配置的值,是由你拿到的设计稿尺寸决定的。 ## 为什么要配置 rem 转 rpx 呢? 这是因为 `tailwindcss` 里面工具类的长度单位,默认都是 `rem`,比如: ```css .m-4 { margin: 1rem; } .h-4 { height: 1rem; } /*......*/ ``` `rem`这个单位在 `h5` 环境下自适应良好,但小程序环境下,我们大部分都是使用 `rpx` 这个 `wxss` 单位来进行自适应,所以就需要把默认的 `rem` 单位转化成 `rpx`。 ## 三种转化方式(根据你的需求选其一即可) ## 插件内置 rem 转 rpx 功能 (推荐) 在 `^3.0.0` 版本中,所有插件都内置了 `rem2rpx` 参数,默认不开启,要启用它只需将它设置成 `true` 即可 ```js // vite.config.js UnifiedViteWeappTailwindcssPlugin({ // ...other-options // highlight-next-line rem2rpx: true }) // webpack const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') new UnifiedWebpackPluginV5({ // ...other-options // highlight-next-line rem2rpx: true }) ``` 设置为 `true` 相当于 `rem2rpx` 传入下方这样一个配置对象: ```js { // 32 意味着 1rem = 16px = 32rpx rootValue: 32, // 默认所有属性都转化 propList: ['*'], // 转化的单位,可以变成 px / rpx transformUnit: 'rpx' } ``` :::tip 为什么 `rootValue` 默认值是 `32`? 这是因为开发微信小程序时, 设计师基本都使用 `iPhone6` 作为视觉稿的标准,此时 `1px = 2rpx`。 然后默认情况下 `1rem = 16px`,所以 `1rem = 16px = 32rpx`。 详见 [WXSS 尺寸单位](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html#%E5%B0%BA%E5%AF%B8%E5%8D%95%E4%BD%8D) 章节, ::: 当然你也可以自行传入一个 `object` 来进行更多配置,具体的配置项见 [postcss-rem-to-responsive-pixel](https://www.npmjs.com/package/postcss-rem-to-responsive-pixel) ### 优势 这种方式 **最简单**,和插件集成度高,传入一个配置就好了。 ## 外置 postcss 插件 首先我们安装 [postcss-rem-to-responsive-pixel](https://www.npmjs.com/package/postcss-rem-to-responsive-pixel) ```bash npm2yarn npm i -D postcss-rem-to-responsive-pixel ``` 安装好之后,把它注册进你的 `postcss.config.js` 即可: ```js title="postcss.config.js" module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, 'postcss-rem-to-responsive-pixel': { // 32 意味着 1rem = 32rpx rootValue: 32, // 默认所有属性都转化 propList: ['*'], // 转化的单位,可以变成 px / rpx transformUnit: 'rpx' // postcss-rem-to-responsive-pixel@6 版本添加了 disabled 参数,用来禁止插件的转化 // disabled: process.env.TARO_ENV === 'h5' || process.env.TARO_ENV === 'rn' } } } ``` :::tip 一些用户在使用 `tarojs` 开发的时候,错误的把 `tailwindcss` 配置在了 `config/index.js` 的 `postcss` 里,导致不生效。 原因实际上是因为 `config/index.js` 的 `postcss`这个配置,只是用来配置 `tarojs` **内置** `postcss` 插件的参数。 要使用 `tailwindcss`,你需要在项目根目录,新建一个 `postcss.config.js`,然后把上面的代码写入进去。 ::: ### 优势 这种方式 **最灵活**,你可以自由的决定 `postcss` 插件的加载顺序,也可以按你自己的策略按需加载插件 (比如特定目录下的样式才接受这个插件的转化) ## 外置 tailwindcss 插件 你想缩小一下范围,只把 `tailwindcss` 生成的工具类的单位,从 `rem` 转变为 `rpx`,那么我写的 `tailwindcss preset`: [tailwindcss-rem2px-preset](https://www.npmjs.com/package/tailwindcss-rem2px-preset) 适合你。 同样我们安装它: ```bash npm2yarn npm i -D tailwindcss-rem2px-preset ``` 然后在 `tailwind.config.js` 中,注册这个预设: ```js title="tailwind.config.js" module.exports = { presets: [ require('tailwindcss-rem2px-preset').createPreset({ // 32 意味着 1rem = 32rpx fontSize: 32, // 转化的单位,可以变成 px / rpx unit: 'rpx' }) ] // ... } ``` 这样即可完成 `tailwindcss` 默认工具类的 `rem` 转 `rpx` 的配置了。 ### 优势 这种方式受影响范围 **最小**,因为 `preset` 方式处理 `tailwindcss`,不会把你写的其他样式里的 `rem` 转化成 `rpx` ## px 转 rpx 如果你也有 [`px 转 rpx`](/docs/quick-start/css-unit-transform) 的需求,你可以查看 [CSS 单位转化](/docs/quick-start/css-unit-transform) 这个章节。 --- ## 2. 安装 weapp-tailwindcss 在项目目录下,执行: ```bash npm2yarn npm i -D weapp-tailwindcss # 假如 tailwindcss 在 weapp-tailwindcss 之后安装,可以手动执行一下 patch 方法 # npx weapp-tw patch ``` 然后把下列脚本,添加进你的 `package.json` 的 `scripts` 字段里: ```json title="package.json" "scripts": { // highlight-next-line "postinstall": "weapp-tw patch" } ``` :::caution 使用 pnpm@10+ `pnpm@10` 默认只允许 `onlyBuiltDependencies` 中的包运行生命周期脚本。安装完本插件后,请执行 `pnpm approve-builds weapp-tailwindcss` 将其加入白名单,避免 `postinstall` 的 `weapp-tw patch` 被跳过。 ::: :::info 执行 `weapp-tw patch` 主要是做2件事情 ### 1. 给当前你本地的 `tailwindcss` 打上支持 `rpx` 的补丁 (小程序特有单位,非 `web` 标准)。 否则你一旦使用像 `text-[14.43rpx]` 这样的任意值写法,生成出来的 `css` 样式就是 `color: 14.43rpx;`,显然是错误的。 它会被 `tailwindcss` 认为是一种颜色,从而导致生成错误,样式不生效。 详见 [rpx 任意值颜色或长度单位二义性与解决方案](/docs/issues/rpx-ambiguities) ### 2. 暴露 `tailwindcss` 运行上下文给 `webpack`/`vite`/`glup` 插件。 这样就能够在 `js` 中,和 `postcss` 插件进行通信,从而达到共享上下文的效果。 --- 而添加上面一段 `npm scripts` 的用途是,利用 `npm hook`, 每次安装包后,都会自动执行一遍 `weapp-tw patch` 这个脚本。 这样即使 `tailwindcss` 更新了版本导致了补丁失效,也会在重新下载后,第一时间被打上。 如果你希望在补丁前“强制刷新 `tailwindcss-patch` 的缓存目录(如 `node_modules/.cache/tailwindcss-patch`)”,可以在命令后附加 `--clear-cache`: ```json title="package.json" "scripts": { "postinstall": "weapp-tw patch --clear-cache" } ``` 默认不清理缓存,更加保守稳定;仅当怀疑缓存导致补丁未生效或版本不一致时再开启该参数。 ::: 我们已经完成了这些步骤了,最后就是注册这个插件,到各个不同的框架里去,马上就好! --- ## uni-app 条件编译语法糖插件 > 版本需求 2.10.0+ ## 这是什么玩意? 在 `uni-app` 里,存在一种类似宏指令的[样式条件编译写法](https://uniapp.dcloud.net.cn/tutorial/platform.html#%E6%A0%B7%E5%BC%8F%E7%9A%84%E6%9D%A1%E4%BB%B6%E7%BC%96%E8%AF%91): ```css /* #ifdef %PLATFORM% */ 平台特有样式 /* #endif */ ``` > uni-app `%PLATFORM%` 的所有取值可以参考这个[链接](https://uniapp.dcloud.net.cn/tutorial/platform.html#preprocessor) 在 `weapp-tailwindcss@2.10.0+` 版本中内置了一个 `css-macro` 功能,可以让你的 `tailwindcss` 自动生成带有条件编译的样式代码,来辅助你进行多平台的适配开发,效果类似如下方式: ```html Web和微信小程序平台蓝色背景 非MP-WEIXIN平台红色背景 微信小程序为蓝色,不是微信小程序为红色 微信小程序为蓝色,不是微信小程序为红色 头条小程序蓝色 ``` 或者这样的条件样式代码: ```css /*只在 H5 和 MP-WEIXIN, 背景为蓝色,否则为红色 */ .apply-test-0 { @apply ifdef-[H5||MP-WEIXIN]:bg-blue-400 ifndef-[H5||MP-WEIXIN]:bg-red-400; } /* 自定义 */ .apply-test-1 { @apply mv:bg-blue-400 -mv:bg-red-400 wx:text-blue-400 -wx:text-red-400; } ``` 让我们看看如何使用吧! ## 如何使用 这里需要同时配置 `tailwindcss` 和 `postcss` 的配置文件才能起作用,其中 `tailwindcss` 配置修改的方式大体类似, `uni-app` `vue2/3` `postcss`插件的注册方式,有些许不同: ### tailwind.config.js 注册 首先在你的 `tailwind.config.js` 注册插件 `cssMacro`: #### Tailwind CSS 3.x 配置 ```js const cssMacro = require('weapp-tailwindcss/css-macro'); /** @type {import('tailwindcss').Config} */ module.exports = { // ... plugins: [ /* 这里可以传入配置项,默认只包括 ifdef 和 ifndef */ cssMacro(), ], }; ``` #### Tailwind CSS 4.x 配置 > v4 推荐直接在入口 CSS 中通过 `@plugin` 引入。 ```css /* tailwind.css */ @import "tailwindcss"; @plugin "weapp-tailwindcss/css-macro"; /* 可选:为常用平台创建语义别名 */ @utility platform-weixin:(value) { @apply ifdef-[MP-WEIXIN]:$(value); } @utility not-alipay:(value) { @apply ifndef-[MP-ALIPAY]:$(value); } ``` 若需要自定义更多静态变体,可额外保留一个 `tailwind.config.ts` 以传入参数: ```ts export default { plugins: { cssMacro: cssMacro({ variantsMap: { wx: 'MP-WEIXIN', '-wx': { value: 'MP-WEIXIN', negative: true }, }, }), }, } ``` > [!TIP] > `cssMacro` 的动态变体(`ifdef:` / `ifndef:`)依赖 Tailwind 内置的 `matchVariant`,请确保 Tailwind 版本 ≥ 3.2;在 v4 中该 API 同样可用。 ### postcss 插件注册 对应的 `postcss` 插件位置为 `weapp-tailwindcss/css-macro/postcss` 值得注意的是,你必须把这个插件,注册在 `tailwindcss` 之后和 `@dcloudio/vue-cli-plugin-uni/packages/postcss` 之前。 > `@dcloudio/vue-cli-plugin-uni/packages/postcss` 为 vue2 cli项目特有,vue3不用管。 注册在 `tailwindcss` 之后很好理解,我们在针对 `tailwindcss` 的产物做修改,自然要在它执行之后处理,注册在 `@dcloudio/vue-cli-plugin-uni/packages/postcss` 之前则是因为 `uni-app` 样式的条件编译,靠的就是它。假如在它之后去处理不久已经太晚了嘛。 > 这里提一下 postcss 插件的执行顺序,假如注册是数组,那就是按照顺序执行,如果是对象,那就是从上往下执行,详见[官方文档](https://www.npmjs.com/package/postcss-load-config#ordering) #### uni-app vite vue3 ```diff // vite.config.ts 文件 // postcss 插件配置 const postcssPlugins = [require('autoprefixer')(), require('tailwindcss')()]; // ... 其他省略 + postcssPlugins.push(require('weapp-tailwindcss/css-macro/postcss')); // https://vitejs.dev/config/ export default defineConfig({ plugins: vitePlugins, css: { postcss: { plugins: postcssPlugins, }, }, }); ``` > 可以参考这个项目的配置 [demo/uni-app-vue3-vite](https://github.com/sonofmagic/weapp-tailwindcss/tree/main/demo/uni-app-vue3-vite) #### uni-app vue2 vue2 cli 项目默认会带一个 `postcss.config.js` 我们之间直接在里面注册即可: ```diff const webpack = require('webpack') const config = { parser: require('postcss-comment'), plugins: [ // ... require('tailwindcss')({ config: './tailwind.config.js' }), // ... + require('weapp-tailwindcss/css-macro/postcss'), require('autoprefixer')({ remove: process.env.UNI_PLATFORM !== 'h5' }), + // 注意在 tailwindcss 之后和 这个之前 require('@dcloudio/vue-cli-plugin-uni/packages/postcss') ] } if (webpack.version[0] > 4) { delete config.parser } module.exports = config ``` > 可以参考这个项目的配置 [demo/uni-app](https://github.com/sonofmagic/weapp-tailwindcss/tree/main/demo/uni-app) ### 配置完成 现在配置好了这2个地方,目前你就可以直接使用 `ifdef` 和 `ifndef` 的条件编译写法了! ```html Web和微信小程序平台蓝色背景 非MP-WEIXIN平台红色背景 微信小程序为蓝色,不是微信小程序为红色 微信小程序为蓝色,不是微信小程序为红色 头条小程序蓝色 ``` 不过你肯定会觉得这种默认写法很烦!要写很多,不要紧,我还为你提供了自定义的方式,接下来来看看配置项吧! ## 配置项 这里提供了一份示例, > uni-app `%PLATFORM%` 的所有取值可以参考这个[链接](https://uniapp.dcloud.net.cn/tutorial/platform.html#preprocessor) ```js const cssMacro = require('weapp-tailwindcss/css-macro'); /** @type {import('tailwindcss').Config} */ module.exports = { // ... plugins: [ /* 这里可以传入配置项,默认只包括 ifdef 和 ifndef */ cssMacro({ // 是否包含 ifdef 和 ifndef,默认为 true // dynamic: true, // 传入一个 variantsMap variantsMap: { // wx 对应的 %PLATFORM% 为 'MP-WEIXIN' // 有了这个配置,你就可以使用 wx:bg-red-300 wx: 'MP-WEIXIN', // -wx,语义上为非微信 // 那就传入一个 obj 把 negative 设置为 true // 就会编译出 ifndef 的指令 // 有了这个配置,你就可以使用 -wx:bg-red-300 '-wx': { value: 'MP-WEIXIN', negative: true }, mv: { // 可以使用表达式 value: 'H5 || MP-WEIXIN' }, '-mv': { // 可以使用表达式 value: 'H5 || MP-WEIXIN', negative: true } } }), ], }; ``` ## IDE智能提示 只要你使用 `vscode`/`webstorm` 这类IDE,加上安装了 `tailwindcss` 的官方插件。 智能提示会根据你对 `cssMacro` 这个插件的配置,直接生成出来! > 假如没有下方的智能提示出现,有可能是 `tailwindcss` 插件挂了,这时候可以改好配置之后 **重启** `vscode` 以重新运行插件 这里我们以上面 `配置项` 为例: ### 动态提示: ifdef-[] 和 ifndef-[] ![macro-tip0](./img/macro-tip0.png) ### 配置的静态提示: wx 和 -wx ![macro-tip1](./img/macro-tip1.png) --- ## Tailwindcss 2.x 目前,有些用户由于现有的项目,已经是 `webpack 4`, `postcss 7.x` 且无法往上升级,但是又想要使用 `tailwindcss`, 所以写了这个文档作为参考,在现有版本的情况下,**不推荐**任何的新项目使用 ## 安装 在这种条件下,只能使用 `tailwindcss 2.x` 版本。 参考 https://v2.tailwindcss.com/docs/installation#post-css-7-compatibility-build 中的安装方式 安装好之后,一定要打开 `jit` 模式, https://github.com/icebreaker-trash/uni-app-vue2-tailwind-hbuilder-template/blob/master/tailwind.config.js 具体更多的细节,详见下方模板代码。 ## vue2 hbuilderx 参考模板 注意,一定要在开发环境中设置 ```js process.env.TAILWIND_MODE = "watch" ``` 才能正常热更新 模板源代码地址: https://github.com/icebreaker-template/uni-app-vue2-tailwind-hbuilder-template --- ## 初始化 package.json ## 1. 安装 ```bash npm2yarn # 初始化 package.json npm init # 安装包 npm install -D tailwindcss @tailwindcss/postcss weapp-tailwindcss ``` ## 2. 添加 `vite.config.ts` ```ts title="vite.config.ts" export default defineConfig({ plugins: [ uni(), UnifiedViteWeappTailwindcssPlugin({ rem2rpx: true, tailwindcssBasedir: __dirname, cssEntries: [ // 你 @import "weapp-tailwindcss"; 那个文件绝对路径 path.resolve(__dirname, './src/app.css'), ], }), ], css: { postcss: { plugins: [ tailwindcss({ base: __dirname }) ] } } }); ``` > tailwindcss@4 必须配置 `cssEntries` 并且使用绝对路径,否则 `tailwindcss` 生成的类名不会参与转译。 ## 3. 添加样式 现在,在你的页面里面去随意的编写样式,比如 `bg-[#123456] text-[#654321]`, 然后运行到微信开发者工具即可 ## 参考模板 - [uni-app-x-hbuilderx 模板](https://github.com/icebreaker-template/uni-app-x-hbuilderx) - [uni-app-hbuilderx 模板](https://github.com/icebreaker-template/uni-app-hbuilderx-tailwindcss-v4) --- ## UniappCliStyle 在 `src/app.css` 中引入 `weapp-tailwindcss`,这个文件会作为 `cssEntries` 的入口: ```css title="src/app.css" @import "weapp-tailwindcss"; ``` 为了解决 IDE 智能提示问题,再额外创建一个 `main.css` 并引入 `weapp-tailwindcss/css`。 ```css title="src/main.css" @import "weapp-tailwindcss/css"; @source not "dist"; ``` 在项目目录下的 `App.vue` 中,添加以下内容: ```html title="App.vue" ``` :::warning 千万不要在 `uni.scss` 中去引入 `tailwindcss`, `uni.scss` 本质上走的是 `scss.additionalData`, 它会在每一个 `scss` 文件的开头,都去添加 `uni.scss` 里的文件内容 所以这相当于你每个 `scss`/ `vue` 文件里面,都加了 `tailwindcss` 引入,那 `css` 体积就爆炸了 ::: --- ## UniappHbuilderStyle 在 `src/app.css` 中引入 `weapp-tailwindcss`,这个文件会作为 `cssEntries` 的入口: ```css title="src/app.css" @import "weapp-tailwindcss"; ``` 为了 IDE 智能提示,再额外创建一个 `main.css` 并引入 `weapp-tailwindcss/css`。 ```css title="src/main.css" @import "weapp-tailwindcss/css"; @source not "unpackage"; ``` 在项目目录下的 `App.vue` / `App.uvue`(uni-app x 项目) 然后添加以下内容: ```html title="App.vue" ``` > 添加 `@source not "unpackage";` 是为了避免 `HBuilderX` 差量编译死循环问题 :::warning 千万不要在 `uni.scss` 中去引入 `tailwindcss`, `uni.scss` 本质上走的是 `scss.additionalData`, 它会在每一个 `scss` 文件的开头,都去添加 `uni.scss` 里的文件内容 所以这相当于你每个 `scss`/ `vue` 文件里面,都加了 `tailwindcss` 引入,那 `css` 体积就爆炸了 ::: --- ## Mpx ## 安装 ```bash npm2yarn npm install -D tailwindcss @tailwindcss/postcss postcss weapp-tailwindcss ``` ## 配置 更改 `mpx.config.js` 注册 `weapp-tailwindcss` ```js title="mpx.config.js" const { defineConfig } = require('@vue/cli-service') const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') const path = require('node:path') const tailwindPostcss = require('@tailwindcss/postcss') module.exports = defineConfig({ outputDir: `dist/${process.env.MPX_CURRENT_TARGET_MODE}`, pluginOptions: { mpx: { plugin: { postcssInlineConfig: { // tailwindcss@4 需要在此处注册 @tailwindcss/postcss,详见 templates/mpx-tailwindcss-v4/mpx.config.js ignoreConfigFile: true, plugins: [tailwindPostcss()] }, srcMode: 'wx', hackResolveBuildDependencies: ({ files, resolveDependencies }) => { const path = require('path') const packageJSONPath = path.resolve('package.json') if (files.has(packageJSONPath)) files.delete(packageJSONPath) if (resolveDependencies.files.has(packageJSONPath)) { resolveDependencies.files.delete(packageJSONPath) } } }, loader: {} } }, configureWebpack(config) { // 添加的代码在这里 // highlight-start config.plugins.push( new UnifiedWebpackPluginV5({ appType: 'mpx', rem2rpx: true, cssEntries: [ // 你 @import "weapp-tailwindcss"; 那个文件绝对路径 path.resolve(__dirname, './src/app.css'), ], }) ) // highlight-end } }) ``` > tailwindcss@4 必须配置 `cssEntries` 并且使用绝对路径,否则 `tailwindcss` 生成的类名不会参与转译。 > tailwindcss@4 下必须在 `mpx.config.js` 里用 `postcssInlineConfig` 注册 `@tailwindcss/postcss`(同仓库 `templates/mpx-tailwindcss-v4/mpx.config.js` 的写法),否则插件不会生效,无需再单独维护 `postcss.config.js`。 ## 添加样式 在 `src/app.css` 中引入 `weapp-tailwindcss`: ```css title="src/app.css" @import "weapp-tailwindcss"; ``` > 💡 如果你希望在任意样式文件中直接引用包内样式,请使用 `@import "weapp-tailwindcss/index.css";`。这样可以确保 `postcss-import` 解析到的是真实的 CSS 资源,避免出现 “Unknown word "use strict"” 这一类由于误解析到 JS 文件导致的构建报错。 ### Tailwind CSS 样式引用指引 - **Tailwind CSS v3.x**:沿用旧模块名即可(例如 `@import 'tailwindcss/base.css'`、`@import 'tailwindcss/components.css'`、`@import 'tailwindcss/utilities.css'`)。 - **Tailwind CSS v4.x**:推荐直接引入 `weapp-tailwindcss` 提供的聚合样式: ```html title="src/app.mpx" ``` 这样可以一次性获得 base / components / utilities,并规避 `postcss-import` 误解析 JS 入口的报错。如果你是从 v3 升级,只需将原来的三个 `@import` 换成这条语句即可。 然后在项目目录下,小程序全局的 `app.mpx` 中,通过 `@import` 引入该文件: ```html title="app.mpx" ``` 更改好配置之后,直接启动即可 ## 参考模版 https://github.com/icebreaker-template/mpx-tailwindcss-v4 --- ## Patch 然后把下列脚本,添加进你的 `package.json` 的 `scripts` 字段里: ```json title="package.json" "scripts": { // highlight-next-line "postinstall": "weapp-tw patch" } ``` :::caution 使用 pnpm@10+ `pnpm@10` 默认只允许 `onlyBuiltDependencies` 中的包运行生命周期脚本。安装完本插件后,请执行 `pnpm approve-builds weapp-tailwindcss` 将其加入白名单,避免 `postinstall` 的 `weapp-tw patch` 被跳过。 ::: 这是为了给 `tailwindcss@4` 打上支持 `rpx` 单位的补丁,否则它会把 `rpx` 认为是一种颜色 如需在补丁前强制刷新 `tailwindcss-patch` 的缓存,可改为: ```json title="package.json" "scripts": { "postinstall": "weapp-tw patch --clear-cache" } ``` 默认不清理缓存;只有当你怀疑缓存导致补丁未生效或目标不一致时,才需要添加 `--clear-cache`。*** --- ## 开发参考手册 :::warning 由于 `tailwindcss@4.x` 本身还在快速的开发迭代中,即使是小版本也可能带有一些意外的 `Breaking Change` 所以以下内容可能会经常变更,如果发现已经过时,请提 `issue` 或者直接修复提 `pr` ::: 所以假如你要兼容更多的手机机型,请使用 `tailwindcss@3.x`。 ## 定位的变化: 样式预处理器 相对于 `tailwindcss@3` 版本, `tailwindcss@4` 存在定位的重大变更 它直接变成了一个样式预处理器,和原生 `css` 已经它的规范相结合,相辅相成。 所以你在 `4.x` 版本中,不应该让 `tailwindcss` 和 `sass`,`less`,`stylus` 一起使用 详见: https://tailwindcss.com/docs/compatibility#sass-less-and-stylus ## 集成选择 `tailwindcss` 集成上提供了多种选择 (`cli`,`vite`,`postcss`),这里我们主要选择 `@tailwindcss/postcss`,原因如下: 1. `@tailwindcss/postcss` 兼容性更好,开发打包器使用 `vite` 和 `webpack` 的都能用,而 `@tailwindcss/vite` 这里只有 `vite` 能用。 2. `@tailwindcss/vite` 很容易和其他的 `vite` 插件起冲突,尤其是和 `uni-app` / `taro` 一起使用的时候,依赖注册的顺序和编译 `hook` 注册的顺序 3. `uni-app`/`taro` 这种框架,默认都是 `cjs` 加载的,而 `@tailwindcss/vite` 只提供了 `esm` 的版本,所以集成上可能会遇到问题 4. `tailwindcss@3.x` 是 `postcss` 插件,`@tailwindcss/postcss` 也是 `postcss` 插件,所以选择它,项目迁移升级的成本会更低。 所以,综合考虑下来,我们主要选择 `@tailwindcss/postcss`。 当然,你也完全可以使用 `uni-app vite vue3` + `@tailwindcss/vite` 这种组合。从编译速度出发, `@tailwindcss/vite` 会更快,但是可能需要一些额外的配置,行为也有可能和 `tailwindcss@3.x` 不一致。 ## 小程序样式引入 `tailwindcss` 不同点 在小程序的样式文件中,引入 `tailwindcss` 的时候,需要把官方文档上写的 `@import "tailwindcss"` 替换为 `@import "weapp-tailwindcss"`。 ```diff - @import "tailwindcss"; + @import "weapp-tailwindcss"; ``` ### 有什么区别? `@import "weapp-tailwindcss"` 相比 `@import "tailwindcss"` 的主要区别是: 1. `"weapp-tailwindcss"` 没有 `"tailwindcss"` 中 `h5` `preflight` 的类(这些都是给 `h5` 用的,小程序用不到) 2. `"weapp-tailwindcss"` 中,不使用 `tailwindcss` 默认的 `@layer` 来控制样式优先级。这是因为小程序本身不支持 `css` `@layer` 这个特性,强行启用会造成一些样式难以覆盖的问题。 ### 多端开发 假如你需要进行多端的开发,那么可以使用对应框架的样式条件编译写法,比如 `uni-app`: ```css /* #ifdef H5 */ @import "tailwindcss"; /* #endif */ /* #ifndef H5 */ @import "weapp-tailwindcss"; /* #endif */ ``` 详见 https://uniapp.dcloud.net.cn/tutorial/platform.html ## css 作为配置文件 由于在 `tailwindcss@4` 中,配置文件默认为一个 `css` 文件,所以你需要显式的告诉 `weapp-tailwindcss` 你的入口 `css` 文件的绝对路径。 来让 `weapp-tailwindcss` 和 `tailwindcss` 保持一致的处理模式 > `cssEntries` 为一个数组,就是你 @import "weapp-tailwindcss"; 那些文件,可以有多个 ```ts { cssEntries: [ // 就是你 @import "weapp-tailwindcss"; 那个文件 // 比如 tarojs path.resolve(__dirname, '../src/app.css') // 比如 uni-app (没有 app.css 需要先创建,然后让 `main` 入口文件引入) // path.resolve(__dirname, './src/app.css') ], } ``` 假如不添加这个,会造成 `tailwindcss` 插件生成的样式,转义不了的问题。 > 插件会自动根据已安装的 Tailwind 版本开启 v4 模式。只有在调试自定义 `tailwindcss` 目录或多版本共存时,才需要在 `tailwindcss` 配置里手动指定 `version`。 ## 使用 @apply 如果你想在 页面或者组件独立的 `CSS` 模块中使用 `@apply` 或 `@variant`,你需要使用 `@reference` 指令,来导入主题变量、自定义工具和自定义变体,以使这些值在该上下文中可用。 ```css /* 到你引入 weapp-tailwindcss 的 css 相对路径 */ @reference "../../app.css"; /* 如果你只使用默认主题,没有自定义,你可以直接 reference weapp-tailwindcss */ @reference "weapp-tailwindcss"; ``` 详见: https://tailwindcss.com/docs/functions-and-directives#reference-directive ## @layer 在小程序的降级方案 `tailwindcss@4` 使用原生的 `@layer` 去控制样式的优先级 > 如果你不知道什么是 `@layer`,你可以阅读这篇文档 https://developer.mozilla.org/zh-CN/docs/Web/CSS/@layer 但是像 `uni-app` / `taro` 这种框架,默认都是直接引入很多内置样式的。 于是就会出现下方尴尬的情况: 优先级 `(0,1,0)` 的 `class` 选择器样式无法覆盖 `(0,0,1)` 的标签选择器样式: ![](./tailwindcss-v4-uniapp-layer.png) 这种情况,你就非常需要兼容性降级方案,即使用 [`postcss-preset-env`](https://www.npmjs.com/package/postcss-preset-env) (`weapp-tailwindcss` 已经内置了这个插件了,你可以直接使用它的配置,详见 [cssPresetEnv](/docs/api/interfaces/UserDefinedOptions#csspresetenv)) 这在开发需要兼容低版本移动端 h5 的时候很重要。 ## 使用 pnpm 默认使用 `pnpm` 的时候,由于 `pnpm` 是无法使用幽灵依赖的 但是 `uni-app`/`taro` 出于一些历史原因,是需要幽灵依赖的,这时候可以在项目下创建 `.npmrc` 添加内容如下 ```txt title=".npmrc" shamefully-hoist=true ``` 然后重新执行 `pnpm i` 安装包即可运行 ## 智能提示 目前 `tailwindcss@4` 的 `vscode` 插件,会扫描目录下的 `css` 来获取 `tailwindcss` 的配置。 但是这里有个非常坑的点是,它不会去自动的扫描 `.vue` 文件里面的 `tailwindcss` 引入 这就导致,我们假如想在 `vue` 项目(比如 `uni-app`) 中获得智能提示,必须再随便创建一个 `main.css`,然后通过 `App.vue` 文件引入它 ```css title="main.css" @import "weapp-tailwindcss/css"; @source not "dist"; ``` ```html title="App.vue" ``` ## 如何去除 preflight 样式 在引入 `@import "weapp-tailwindcss"` 时,默认会引入 `preflight` 样式。 ### 什么是 preflight 样式 一些全局的 `reset` 样式,用来让一些标签行为统一的,比如你在你的样式中,看到的: ```css view,text,::before,::after,::backdrop { box-sizing: border-box; margin: 0; padding: 0; border: 0 solid; } ``` 类似这样的就是 `weapp-tailwindcss` 给你的应用注入的 `preflight` 样式 ### 解决方案 `@import "weapp-tailwindcss"` 本质上由三个部分组成: ```css @import 'weapp-tailwindcss/theme.css'; @import 'weapp-tailwindcss/preflight.css'; @import 'weapp-tailwindcss/utilities.css'; ``` 所以想要去除 `preflight` 样式,只需像下面一样写即可 ```diff - @import "weapp-tailwindcss"; + @import 'weapp-tailwindcss/theme.css'; + @import 'weapp-tailwindcss/utilities.css'; ``` ## 使用大写单位 (h-[100PX]) 无效问题 默认情况下,在 `process.env.NODE_ENV === 'production'` 的时候, `tailwindcss` 会自动进入优化模式 它会进行 `CSS` 单位的校准,比如把大写的 `PX` 转化为小写的 `px`,你要禁用这个行为可以这样传入。 ```js export default { plugins: { "@tailwindcss/postcss": { optimize: false }, } } ``` 详见: [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/blob/7779d3d080cae568c097e87b50e4a730f4f9592b/packages/%40tailwindcss-postcss/src/index.ts#L73C35-L73C72) --- ## Taro vite ## 安装 ```bash npm2yarn npm install -D tailwindcss @tailwindcss/postcss postcss weapp-tailwindcss ``` > 这里不使用 `@tailwindcss/vite` 是由于 `taro` 配置没法加载纯 `esm` 不是 `cjs` 的包, 会爆错误 `No "exports" main defined` ## 配置 ```js title="config/index.ts" { compiler: { type: 'vite', vitePlugins: [ { name: 'postcss-config-loader-plugin', config(config) { // 加载 tailwindcss if (typeof config.css?.postcss === 'object') { config.css?.postcss.plugins?.unshift(tailwindcss()) } }, }, UnifiedViteWeappTailwindcssPlugin({ rem2rpx: true, cssEntries: [ // 你 @import "weapp-tailwindcss"; 那个文件绝对路径 path.resolve(__dirname, '../src/app.css'), ], }), ] }, } ``` > tailwindcss@4 必须配置 `cssEntries` 并且使用绝对路径,否则 `tailwindcss` 生成的类名不会参与转译。 > 这里使用 `vite` 插件直接去加载 `tailwindcss`,这是由于 `taro4 vite` 不会自动去加载项目下的 `postcss.config.js`,所以只能定义这个 `postcss-config-loader-plugin` ## 添加样式 在项目目录下的 `src/app.css` 中,添加以下内容: ```css title="src/app.css" @import "weapp-tailwindcss"; ``` 更改好配置之后,直接运行启动项目,微信开发者工具导入这个项目,即可看到效果。 ## 参考模板 https://github.com/icebreaker-template/taro-vite-tailwindcss-v4 --- ## Taro webpack ## 安装 ```bash npm2yarn npm install -D tailwindcss @tailwindcss/postcss postcss weapp-tailwindcss ``` ## 配置 ### 在你的根目录创建 `postcss.config.mjs` ```js title="postcss.config.mjs" export default { plugins: { "@tailwindcss/postcss": {}, } } ``` ### 在你的 `app.css` 里面添加 ```css @import "weapp-tailwindcss"; ``` ### 注册插件 在项目的配置文件 `config/index` 中注册: ```js title="config/index.[jt]s" // 假如你使用 js 配置,则使用下方 require 的写法 // const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') // const path = require('node:path') { // 找到 mini 这个配置 mini: { // postcss: { /*...*/ }, // 中的 webpackChain, 通常紧挨着 postcss webpackChain(chain, webpack) { // 复制这块区域到你的配置代码中 region start // highlight-start chain.merge({ plugin: { install: { plugin: UnifiedWebpackPluginV5, args: [{ // 这里可以传参数 rem2rpx: true, cssEntries: [ // 你 @import "weapp-tailwindcss"; 那个文件绝对路径 path.resolve(__dirname, '../src/app.css'), ], }], }, }, }) // highlight-end // region end } } } ``` > tailwindcss@4 必须配置 `cssEntries` 并且使用绝对路径,否则 `tailwindcss` 生成的类名不会参与转译。 ## 运行 然后执行命令发布到微信小程序 ```bash npm2yarn npm run dev:weapp ``` 微信开发者工具导入这个项目,即可看到效果 ## 参考模板 https://github.com/icebreaker-template/taro-webpack-tailwindcss-v4 --- ## 高阶篇:性能、兼容与团队协作 Tailwind CSS 4 带来了更强大的原生语法,但在小程序环境中仍需平衡兼容性与团队协作。本篇从工程化视角出发,帮助你在真实项目中稳定地落地、优化与维护。 ## 1. 处理 `@layer` 与兼容性 小程序运行时目前对 CSS Cascade Layers 支持有限,当你引用第三方组件或自定义样式时可能出现覆盖关系错乱。`weapp-tailwindcss` 内置的 `postcss-preset-env` 可将 `@layer` 转译成传统写法来提升兼容性。 ```ts title="vite.config.ts" export default defineConfig({ plugins: [ UnifiedViteWeappTailwindcssPlugin({ cssEntries: [ /* ... */ ], cssPresetEnv: { stage: 1, features: { 'cascade-layers': true, }, }, }), ], }) ``` > 如果你只在微信小程序调试,可使用开发者工具的「自定义编译」观察处理前后的差异;若仍存在覆盖问题,可结合传统的 `!important` 或布局拆分策略。 额外提示: - `cssSelectorReplacement.root` 默认包含 `['page', '.tw-root']`。当页面外的容器(例如自定义 tab bar、弹出层根节点等)需要承载 Tailwind 注入的 `--tw-*` 变量时,只需在容器上加上 `class="tw-root"` 即可复用整套预设,无需额外配置。下面是一个在自定义 tab bar 中注入主题变量的示例: ```xml title="custom-tab-bar/index.mpx" ``` 搭配 `cssPreflight` 注入的变量或自定义 `@theme`,可以把 tab bar 的主题色、渐变背景等统一交给 Tailwind 管理。若你需要覆盖其他容器名称,仍可以通过配置把 `cssSelectorReplacement.root` 扩展为更多选择器。 - `cssPresetEnv` 会参与最终构建,请在发布前执行一次 `pnpm build:apps` 或对应的 `pnpm build` 命令确认产物 ## 2. 多端共存与按需构建 团队常同时维护小程序与 H5 版本,此时可以通过多个 `@source` 区分模板范围,并结合条件编译实现按需打包: ```css title="src/app.css" @source "../src/**/*.{vue,wxml}"; @source not "../src/**/*.h5.*"; /* 排除只用于 H5 的模板 */ /* H5 用 Tailwind 官方包,小程序继续用 weapp-tailwindcss */ /* #ifdef H5 */ @import "tailwindcss"; /* #endif */ /* #ifndef H5 */ @import "weapp-tailwindcss"; /* #endif */ ``` 当需要拆分体积时,可以把不同业务域的样式写到各自的 `entry.css`,再将路径加入 `cssEntries`: ```ts UnifiedViteWeappTailwindcssPlugin({ cssEntries: [ path.resolve(import.meta.dirname, './src/app.css'), path.resolve(import.meta.dirname, './src/features/order/app.css'), ], }) ``` 这样 Tailwind 只会为实际引用的模板生成原子类,避免冗余。 ## 3. 产物体积与性能优化 - **控制扫描范围**:`@source` 支持 `not` 语法,排除 `dist`、`node_modules` 等目录能显著加快增量编译 - **合理使用自定义工具类**:把相同组合提炼成 `@utility`,既减少模板体积,也方便统一调整 - **按需开启 `rem2rpx` / `px2rpx`**:在仅小程序端需要 rpx 的情况下,可以在多端构建中动态开启 - **缓存管理**:Tailwind 会在 `.tailwind` 写入缓存。CI 环境可缓存该目录以提升构建速度,发布前若需彻底清理运行 `pnpm exec tailwindcss --config tailwind.config.js --clean` 或直接删除缓存目录 ## 4. 调试与质量保障 - **可视化定位**:使用 `outline` 类临时标记组件边界,例如在调试时加上 `outline outline-1 outline-dashed outline-brand/60` - **断言样式存在**:核心组件可引入快照测试或 DOM 断言,结合 Vitest + @testing-library 验证关键类名 - **lint 约束**:在 `eslint-plugin-tailwindcss` 或团队约定的 lint 规则中,限制自定义类名必须通过 `@utility` - **回归验证**:Tailwind 升级时执行 `pnpm test`, `pnpm e2e` 确认核心链路稳定,同时在主流机型(尤其是低版本安卓)上真机预览 ## 5. 升级与维护策略 1. **版本对齐**:Tailwind 4 迭代频繁,升级前先在 `CHANGELOG` 与 GitHub Release 查 Breaking Change,再在测试分支试跑。 2. **切分 Changeset**:对外发布库时遵循 [Changesets](https://github.com/changesets/changesets) 约定,确保依赖者知道何时需要手动介入。 3. **文档同步**:团队内部记录 `@utility`、`@theme` 的设计规范,推荐把核心样式写进 Storybook 或内部组件示例库。 4. **遇到问题及时回馈**:`weapp-tailwindcss` 社区对 Tailwind 4 的需求反馈快速,遇到兼容问题可通过 Issue 或 PR 参与共建。 至此,你已经掌握了 Tailwind CSS 4 在小程序中的完整落地思路:从环境搭建、组件实践到性能与协同。结合现有的框架集成文档,就能为新成员提供一套体系化的学习路径。 --- ## 入门篇:快速认识 Tailwind CSS 4 与 weapp-tailwindcss Tailwind CSS 4 重新定义了“原子化”样式的组织方式:配置文件换成了 `CSS`,主题变量与自定义工具也变成了原生语法。这一变化对小程序生态同样生效,`weapp-tailwindcss` 已经适配了新的编译流程。本篇入门指南帮助你在 30 分钟内从零搭好一套 `tailwindcss@4` + `weapp-tailwindcss` 的基础开发环境,并理解最核心的概念。 ## 本篇能学到什么 - 搭建一套可运行的 Tailwind CSS 4 + 小程序项目骨架 - 分清核心配置(`cssEntries`、`@source`、`@reference` 等)各自负责的事情 - 理解 `@tailwindcss/postcss`、`@tailwindcss/vite` 与编译插件的差异 - 掌握验证样式是否生效的最小闭环,避免“写了没生效”的常见坑 如果你还未接触过 `weapp-tailwindcss`,建议先浏览 [新手快速上手(Tailwind CSS 3.x)](/docs/quick-start/install) 了解插件能解决的问题,再回来完成 4.x 的配置。 ## 环境准备 | 名称 | 说明 | | --- | --- | | Node.js ≥ 18 | Tailwind CSS 4 要求更高的 Node 版本,建议使用 LTS 20 | | pnpm ≥ 8 | Monorepo 与文档项目统一使用 `pnpm` | | 小程序框架 | 任选 `weapp-vite`、`uni-app`、`taro` 等,推荐使用现成模板 | | 代码编辑器 | VS Code 并安装 `Tailwind CSS IntelliSense` 插件 | 初始化项目后,执行一次 `pnpm install` 以及框架 CLI 的初始化命令(如 `pnpm create @tarojs/cli`)。随后即可进入 Tailwind CSS 配置步骤。 ## 步骤一:安装依赖 在项目根目录执行: ```bash pnpm add -D tailwindcss@latest @tailwindcss/postcss postcss weapp-tailwindcss ``` > `@tailwindcss/postcss` 兼容 Vite、Webpack、Rspack 等大多数构建链路;对于纯 Vite 项目也可以酌情改用 `@tailwindcss/vite`,但在小程序场景下 `postcss` 方案更稳定。 ## 步骤二:注册 weapp-tailwindcss 插件 以 `weapp-vite` 为例(不同框架请对照对应的 [集成指南](/docs/quick-start/v4)): ```ts title="vite.config.ts" export default defineConfig({ plugins: [ UnifiedViteWeappTailwindcssPlugin({ // 常用内置能力:开启 px 自动转换 rem2rpx: true, cssEntries: [ // 声明 Tailwind 的入口 CSS,必须是绝对路径 path.resolve(import.meta.dirname, './src/app.css'), ], }), ], }) ``` `cssEntries` 对 `tailwindcss@4` 至关重要,它告诉 `weapp-tailwindcss` 哪些 CSS 需要参与编译与转义。漏掉该配置将导致产物中缺失样式。 ## 步骤三:配置 PostCSS ```js title="postcss.config.js" export default { plugins: { '@tailwindcss/postcss': {}, }, } ``` Tailwind CSS 4 已内置 Autoprefixer,无需手动补充。若项目仍需其他 PostCSS 插件,保持旧的写法即可。 ## 步骤四:创建入口 CSS 在 `src/app.css` 中写入: ```css title="src/app.css" @import "weapp-tailwindcss"; /* 声明模板扫描路径,确保原子类能被收集 */ @source "../src/**/*.{vue,tsx,jsx,svelte,wxml}"; /* 需要自定义设计令牌时可在此编写 */ @theme { --color-brand: oklch(67% 0.2 264); --spacing-safe: clamp(12px, 1.2vw + 8px, 24px); } ``` `@source` 是 Tailwind 4 的新写法,用于替代旧版本的 `content` 配置。路径请根据项目结构调整。若你需要在局部样式文件使用 `@apply`,在对应文件顶部添加 `@reference "./app.css";`。 ## 步骤五:验证类名是否生效 创建一个最小页面(以 `src/pages/index/index.vue` 为例): ```html title="src/pages/index/index.vue" ``` > `py-safe` 是我们在上文 `@theme` 中声明的自定义变量(通过 `--spacing-safe` 导出),可验证主题自定义是否生效。 运行框架命令(如 `pnpm dev:weapp` 或 `pnpm run build -- --watch`),查看开发者工具中的页面是否渲染出预期样式。如果没有: - 检查 `cssEntries` 是否指向了正确路径 - 确认 `@source` 是否包含当前文件扩展名 - 若在单文件组件内使用 `@apply`,确保添加了 `@reference` ## 常见下一步 - 阅读 [Tailwind CSS 4 官方文档](https://tailwindcss.com/docs/installation) 了解更多原生指令 - 针对不同框架的集成细节,请查阅「🧪Tailwind CSS @4.x」分类中的各篇文档 - 想理解 `@layer` 在小程序下的降级方案,可继续阅读本教程的 [进阶篇](/docs/quick-start/v4/tutorial/workflow) 与 [高阶篇](/docs/quick-start/v4/tutorial/advanced) 完成本篇后,你已经拥有了稳定的基础开发环境。接着让我们通过真实业务组件把 Tailwind CSS 4 的能力串联起来。 --- ## 进阶篇:用 Tailwind CSS 4 构建真实页面 基础环境搭建完成后,真正的挑战来自“如何把抽象的原子类应用到真实业务”。本篇精选小程序常见的页面模块,结合 `tailwindcss@4` 的新指令,总结出一套从设计拆解到代码落地的流程。 ## 工具优先的心智模型 Tailwind CSS 的核心不在于背公式,而是把 UI 拆成「布局」「排版」「状态」三个维度,然后使用类名组合快速拼装。Tailwind 4 进一步引入 `@theme`、`@utility` 等指令,让自定义能力与项目约束保持同步: - `@theme` 管理设计令牌:颜色、间距、字号、阴影等 - `@utility` 声明可复用的工具类,代替传统的 `.btn { ... }` - `@variant` / `@custom-variant` 管理状态类(如 `active:`、`dark:`) 掌握这些指令后,就可以在不离开 CSS 语法的前提下扩展 Tailwind。 ## 1. 拆解一个卡片组件 以「订单卡片」为例,先在 `src/components/order-card/index.vue` 写出骨架: ```html title="src/components/order-card/index.vue" ``` 接下来在 `src/components/order-card/index.css` 中使用 Tailwind 原子类重构: ```css title="src/components/order-card/index.css" @reference "../../app.css"; /* 使用 @utility 提升可读性 */ @utility order-card { @apply block rounded-3xl bg-white shadow-lg shadow-slate-200/70 p-5 space-y-4; } @utility order-card__header { @apply flex items-center justify-between gap-3; } @utility order-card__title { @apply text-base font-semibold text-slate-900; } @utility order-card__meta { @apply flex items-center justify-between text-sm text-slate-500; } @utility order-card__status { @apply inline-flex items-center gap-1 rounded-full px-3 py-1 text-xs font-medium uppercase tracking-[0.28em]; } /* 通过 @variant 声明业务状态 */ @custom-variant status-pending (&[data-status="pending"]); @custom-variant status-finished (&[data-status="finished"]); @custom-variant status-failed (&[data-status="failed"]); .order-card__status { @apply status-pending:bg-amber-100 status-pending:text-amber-600; @apply status-finished:bg-emerald-100 status-finished:text-emerald-600; @apply status-failed:bg-rose-100 status-failed:text-rose-600; } ``` 关键点: - 使用 `@reference` 让局部样式文件继承入口 CSS 中的主题与工具 - `@utility` 让类名语义化,同时还能继续 `@apply` 原子类 - `@custom-variant`(Tailwind 4 新增)能让业务状态转换成语义化前缀 最后在组件实例上应用: ```html title="src/pages/index/index.vue" ``` ## 2. 构建可复用的设计令牌 Tailwind 4 把主题抽象为原生 CSS 变量。你可以在 `app.css` 中集中维护令牌,再通过 `@theme` 输出: ```css title="src/app.css" {2-7} @import "weapp-tailwindcss"; @theme { --color-brand: oklch(66% 0.21 268); --color-brand-muted: oklch(80% 0.04 268); --radius-xl: 24px; --shadow-elevated: 0 18px 40px -20px rgb(99 102 241 / 45%); } ``` 然后在业务样式中直接引用这些变量,或结合 Tailwind 的 `bg-[...]` 写法: ```css .order-card { @apply shadow-[var(--shadow-elevated)]; } .order-card__status { @apply status-finished:bg-[var(--color-brand-muted)] status-finished:text-[var(--color-brand)]; } ``` 得益于 CSS 变量特性,你还可以在不同页面覆写 `:root` 或对应容器的变量,以实现主题切换。 ## 3. 管理复杂布局与响应式 小程序虽然没有传统意义上的宽度断点,但我们仍可以借助媒体查询、`min()` / `max()` 等函数实现容错布局: ```css @layer utilities { @responsive { @media (min-width: 560px) { .md\\:card-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(16px, 4vw, 36px); } } } } ``` 在页面中配合默认的原子类即可: ```html ``` 对于需要横向滚动的卡片,Tailwind 4 提供的 `snap-x`, `snap-mandatory`, `snap-center` 等类仍旧适用,不同端的处理逻辑交给 `weapp-tailwindcss`。 ## 4. 多文件协作与团队约定 当项目迎来多人协作时,建议遵循以下约定: 1. **入口 CSS 只做聚合**:集中维护 `@import`、`@source` 与 `@theme`,其余逻辑拆到独立文件。 2. **组件目录内固定 `index.css`**:组件内声明 `@utility`、`@apply`,并写在组件同名文件夹下,便于按需引用。 3. **公共工具抽成包**:例如 `src/styles/utilities/forms.css`,内部声明所有表单相关的 `@utility`。 4. **lint 与提示配置**:确保团队成员的 VS Code `tailwindCSS.experimental.classRegex` 包含 `.wxml`、`.vue` 等模板。 ## 5. 调试与性能提示 - Tailwind 4 的编译沿用增量模式,`pnpm run:watch` 时生成的原子类会写入缓存。需要彻底清理时删除 `.tailwind`、`node_modules/.cache/tailwind`。 - 小程序开发者工具不支持 `@layer`,若遇到覆盖问题,可以开启 `cssPresetEnv` 的降级行为,详情见 [高阶篇](/docs/quick-start/v4/tutorial/advanced)。 - 借助 `pnpm dev:h5`(部分框架提供)可快速在浏览器预览,调试完后再回到真机验证。 完成本篇后,你应该可以把 Tailwind 应用到实际业务模块,并形成一套可复用的组件写法。接下来,我们会关注性能优化、跨端适配与团队协作等更高阶的话题。 --- ## HBuilderX --- ## uni-app cli vue3 vite(V4) ## 1. 安装 ```bash npm2yarn npm install -D tailwindcss @tailwindcss/postcss weapp-tailwindcss ``` ## 2. 配置 `vite.config.ts` ```ts title="vite.config.ts" export default defineConfig({ plugins: [ uni(), UnifiedViteWeappTailwindcssPlugin({ rem2rpx: true, cssEntries: [ // 你 @import "weapp-tailwindcss"; 那个文件绝对路径 path.resolve(import.meta.dirname, './src/app.css'), ], }), ], css: { postcss: { plugins: [ tailwindcss(), ], }, }, }); ``` > tailwindcss@4 必须配置 `cssEntries` 并且使用绝对路径,否则 `tailwindcss` 生成的类名不会参与转译。 ## 3. 添加样式 接着直接运行 `npm run dev:mp-weixin`, 微信开发者工具导入这个项目,即可看到效果 {/* :::warning 这里必须创建一个额外的 `css` 文件,而不是直接在 `App.vue` 里的 `style` 标签下直接写, 这是因为 `@tailwindcss/vite` 目前只转化 `.css` 文件。后续可能会支持更多格式的文件,比如 `vue` 编译的中间文件 详见 http://github.com/tailwindlabs/tailwindcss/blob/main/packages/%40tailwindcss-vite/src/index.ts#L122 中的 `isCssFile` 判断 ::: */} ## 参考模板 [uni-app-tailwindcss-v4](https://github.com/icebreaker-template/uni-app-tailwindcss-v4) --- ## uni-app cli vue2 webpack(V4) ## 安装 ```bash npm2yarn npm install -D tailwindcss @tailwindcss/postcss postcss weapp-tailwindcss ``` ## 配置 ### 创建 `vue.config.js` ```js title="vue.config.js" const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') const path = require('node:path') /** * @type {import('@vue/cli-service').ProjectOptions} */ const config = { // some option... configureWebpack: (config) => { config.plugins.push( new UnifiedWebpackPluginV5({ rem2rpx: true, cssEntries: [ // 你 @import "weapp-tailwindcss"; 那个文件绝对路径 path.resolve(__dirname, './src/app.css'), ], }) ) } // other option... } module.exports = config ``` > tailwindcss@4 必须配置 `cssEntries` 并且使用绝对路径,否则 `tailwindcss` 生成的类名不会参与转译。 ## 配置 `postcss.config.js` ```js title="postcss.config.js" const path = require('path') const webpack = require('webpack') const config = { parser: require('postcss-comment'), plugins: [ // highlight-next-line require('@tailwindcss/postcss')(), // 只添加这一行 require('postcss-import')({ resolve (id, basedir, importOptions) { if (id.startsWith('~@/')) { return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3)) } else if (id.startsWith('@/')) { return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2)) } else if (id.startsWith('/') && !id.startsWith('//')) { return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1)) } return id } }), require('autoprefixer')({ remove: process.env.UNI_PLATFORM !== 'h5' }), require('@dcloudio/vue-cli-plugin-uni/packages/postcss') ] } if (webpack.version[0] > 4) { delete config.parser } module.exports = config ``` ## 添加样式 然后直接运行到小程序,微信开发者工具导入这个项目,即可看到效果 ## 参考模板 https://github.com/icebreaker-template/uni-app-webpack-tailwindcss-v4 --- ## uni-app x :::warning tailwindcss@4 版本,暂时只支持 hbuilderx uni-app x 跨 `h5` 和 `小程序` 平台, 原生安卓和IOS平台无法支持,因为 uni-app x 目前对 css 各种现代语法的兼容较差,兼容不了 tailwindcss@4 生成的样式 未来此功能的适配,取决于 uni-app x 团队对现代 css 的兼容程度 (2025-07-27) ::: --- ## Weapp-vite ## 安装 ```bash npm2yarn npm install -D tailwindcss @tailwindcss/postcss postcss weapp-tailwindcss ``` ## 配置 更改 `vite.config.ts` 注册 `weapp-tailwindcss` ```js title="vite.config.ts" export default defineConfig({ plugins: [ UnifiedViteWeappTailwindcssPlugin({ rem2rpx: true, cssEntries: [ // app.css 的路径 path.resolve(import.meta.dirname, './app.css'), ], }), ], }) ``` 添加 `postcss.config.js` 注册 `@tailwindcss/postcss` ```js title="postcss.config.js" export default { plugins: { '@tailwindcss/postcss': {}, }, } ``` ## 添加样式 在项目目录下,小程序全局的 `app.css` 中,添加以下内容: ```css title="app.css" @import "weapp-tailwindcss"; ``` 更改好配置之后,直接启动即可 --- ## wxs的转义与处理 :::info `2.5.2+` 版本中,已经添加了对 `wxs`,`sjs`等视图层运行 `js` 的转义和处理,默认关闭 具体见配置项: - [`wxsMatcher`](/docs/api/interfaces/UserDefinedOptions#wxsmatcher) - [`inlineWxs`](/docs/api/interfaces/UserDefinedOptions#inlinewxs) ::: --- ## weapp-tw CLI 使用指南 `weapp-tailwindcss` 自带一个 `weapp-tw` 命令行工具,负责提前给 Tailwind CSS 打补丁、生成类名缓存以及收集 token。以下内容介绍常用命令及最佳实践。 ## 常见场景 | 场景 | 命令 | 说明 | | --- | --- | --- | | 给 Tailwind CSS 打补丁、注入 `rpx` 支持 | `npx weapp-tw patch` | 运行一次即可,推荐写在 `postinstall`;支持 `--cwd` 指向子包目录。 | | 强制刷新补丁缓存后再打补丁 | `npx weapp-tw patch --clear-cache` | 默认不会清理缓存;仅在怀疑缓存导致补丁未生效或版本不一致时使用。 | | 在 monorepo 针对子包补丁 | `pnpm --filter exec weapp-tw patch --cwd .` | `pnpm` 场景建议显式传 `--cwd .`,每个子包只写入自己的缓存,避免 hoist 后的 `node_modules` 不一致。 | | 一键扫描 workspace 并补丁 | `weapp-tw patch --workspace --cwd ` | 自动读取 `pnpm-lock.yaml`/`pnpm-workspace.yaml` 批量补丁,跳过未安装 Tailwind 的包,并输出每个子包状态。 | | 收集 Tailwind 产出的类名缓存 | `npx weapp-tw extract --css src/app.css` | 适用于 Tailwind v3/v4,v4 需传入 `--css`,生成 `.tw-patch/tw-class-cache.*`,支持 `--output`/`--format`。 | | 输出 Tailwind token 和定位信息 | `npx weapp-tw tokens --format grouped-json` | 调试 `content` 配置时非常有用,可与 `--no-write` 一起纯输出。 | ## `patch` 子命令 ### 功能 - 扫描当前工作目录所依赖的 `tailwindcss`,给其打上 `rpx` 单位补丁。 - 暴露 Tailwind 运行时上下文,供 `webpack`/`vite`/`gulp` 插件复用。 - 检测到未安装 Tailwind 时会输出中文警告。 ### 常用参数 | 参数 | 默认值 | 含义 | | --- | --- | --- | | `--cwd ` | `WEAPP_TW_PATCH_CWD` \| `INIT_CWD` \| `npm_config_local_prefix` \| 当前工作目录 | 指定要补丁的子包目录,等价于先 `cd`。默认会按顺序读取上述环境变量,避免在 `pnpm`/CI 里被 wrapper 覆盖成根目录。 | | `--record-target` | `true` | 默认记录本次打补丁对应的 `tailwindcss` 根路径到子包专属的 `node_modules/.cache/weapp-tailwindcss//tailwindcss-target.json`,包含 `cwd`、Tailwind 路径、补丁版本与时间戳;如需关闭请传 `--record-target false`。 | | `--clear-cache` | `false` | 按需清理 `tailwindcss-patch` 缓存目录后再执行补丁。默认不清理,避免不必要的 IO 和 CI 侧效应。 | | `--workspace` | `false` | 批量扫描 workspace(`pnpm-lock.yaml`/`pnpm-workspace.yaml`/`workspaces`),逐个子包执行 patch 并输出“补丁/跳过/失败”状态。 | 默认会记录补丁目标,运行日志类似: ```bash $ pnpm --filter demo/native-mina weapp-tw patch weapp-tw patch 绑定 Tailwind CSS -> ./node_modules/tailwindcss (v3.4.18) 记录 weapp-tw patch 目标 -> ./node_modules/.cache/weapp-tailwindcss//tailwindcss-target.json Tailwind CSS 运行时补丁已完成。 ``` 随后每次启动构建工具,运行时会输出 `tailwindcss-patcher 绑定 Tailwind CSS -> ...`;若检测到与缓存记录不一致,会自动重打补丁并刷新记录,无需手动清理跨包告警。若不希望生成记录,可在命令后追加 `--record-target false`。补丁记录按子包(package.json 路径 hash)隔离,避免 `pnpm` hoist 或 `workspace:` 互相引用时互相覆盖。 ## `extract` 子命令 ```bash npx weapp-tw extract --css src/app.css --format lines ``` - `--css`:Tailwind 入口 CSS。v4 必填;v3 可省略以沿用 Tailwind 默认入口。 - `--output`:指定输出文件,默认写入 `.tw-patch/tw-class-cache.json`。 - `--format`:`json`/`lines`,配合 `--no-write` 可仅打印到终端。 - `--cwd` 与 `patch` 一致,也支持放在子包里执行。 生成的缓存可以让构建器快速读取 `tailwindcss` 编译结果,减少重复启动的开销。 ## `tokens` 子命令 ```bash npx weapp-tw tokens --format grouped-json --group-key absolute ``` - `--format`:`json`、`lines` 或 `grouped-json`。后一种会按文件分组,便于排查 `content` 匹配不到的问题。 - `--group-key`:`relative`(默认)/`absolute`,决定输出路径的基准。 - `--no-write`:终端预览,不落盘;默认写入 `.tw-patch/tw-token-report.json`。 输出的数据包含扫描到的文件、类名 token、出现位置(行列)、以及被跳过的文件列表。 ## 快速排查指引 1. **始终在子包目录执行 `weapp-tw patch`**:`pnpm --filter my-demo weapp-tw patch --cwd demo/my-demo`。 2. **如确实不需要追踪,可传 `--record-target false`**,避免生成额外 JSON。 3. **看到自动重补丁/记录迁移提示**:确认日志里的 Tailwind 路径与子包一致,`pnpm --filter ... exec weapp-tw patch --cwd .` 或 `weapp-tw patch --workspace --cwd ` 可避免 `INIT_CWD` 导致的跨包记录污染。 4. **类名缺失**: 用 `weapp-tw extract` 或 `weapp-tw tokens --no-write` 辅助定位是哪段代码没有被 Tailwind `content` 收录。 5. **需要强制指定根目录时**:在 monorepo 根执行 `WEAPP_TW_PATCH_CWD=$PWD weapp-tw patch --workspace`,可覆盖外层脚本设置的 `INIT_CWD`,确保一次性为所有子包补丁并写入各自的缓存记录。 配合这些命令,可以更直观地观察 Tailwind 依赖解析与补丁状态,减少“rpx 被当成颜色” 等由于上下游不一致导致的问题。 --- ## uni-app x 专题 跨端原子化样式方案 Tailwind CSS × uni-app x 一键使用模板项目(推荐) 或者按步骤手动集成 ## 这篇文档适合谁 - 想在 uni-app x 项目里使用 Tailwind CSS 的开发者 - 需要一套能同时覆盖 Web/小程序/Android/iOS/鸿蒙 的原子化样式方案 - 希望以最少的改动尽快起步,并有清晰的后续扩展路线 ## 你将收获什么 - 完整的从 0 到 1 集成流程(含运行与验证) - 可复用的模板项目与配置清单 - 多端开发的实践建议与避坑说明 :::info 支持范围 当前跨端集成仅支持 `tailwindcss@3.x`。 `tailwindcss@4.x` 生成的 CSS 依赖诸多现代特性,`uni-app x` 暂未全部覆盖,故不建议在该场景使用 v4。 ::: ## 最快开始 - 想最快跑起来:点击上方「一键使用模板项目」,按 README 步骤打开 HBuilderX 运行即可 - 想了解每一步:前往「手动集成」教程 → 快速集成 ## 开发建议(从用户出发) - 推荐编辑器协作:用 VS Code 写代码,用 HBuilderX 负责运行与构建 - 首选 Android 端调试:CSS 兼容度一般是 Web > 小程序 > App(Android/iOS/鸿蒙)。先用 Android 模拟器打通路径,跨端成本最低 - 组件语义注意:原生 App 端文字需放在 `` 标签,且文本样式需要直接作用在该元素上 - 渐进增强:若某些样式在 App 端受限,优先保证 Android 端体验,再按需做条件编译适配小程序/Web ## 为什么推荐先用 Android 1) 原生端样式约束更严格,例如文本必须使用 ``,许多 CSS 特性仍在补齐中 2) 如果你的页面在 Android 端无告警、展示正确,迁移到小程序与 Web 的心智负担会小很多 3) iOS/鸿蒙的调试门槛更高(设备/系统要求),多数团队以 Android 作为优先目标更务实 ## 常见问题 ### VS Code 对 uvue/uts 的高亮与跳转 安装官方语言服务插件: - ID: dcloud-ide.hbuilderx-language-services - 说明: 支持 uni-app x 项目的提示、悬浮、转到定义、查找引用、校验等 - 链接: https://marketplace.visualstudio.com/items?itemName=dcloud-ide.hbuilderx-language-services ### Tailwind CSS 智能提示 VS Code 原生不识别 `uvue/uts`,建议为 Tailwind 扩展加上语言映射。在项目根目录创建 `.vscode/settings.json`: ```json title=".vscode/settings.json" { "tailwindCSS.includeLanguages": { "uvue": "html", "uts": "javascript" } } ``` ### 运行方式 目前 `uni-app x` 尚无 CLI,需通过 HBuilderX 进行运行与构建。 建议配合 Android 模拟器进行开发调试。 ## 接下来 - 跟着步骤手动完成集成:快速集成 - 直接基于模板起步:uni-app x + Tailwind 模板(HBuilderX) --- ## 快速集成 ## 最简单:直接用模板项目(推荐) 立即上手而不纠结配置细节,点击下面链接即可: 使用模板一键开始 打开后按 README 步骤,用 HBuilderX 运行到 Android 模拟器或真机即可看到效果。 --- ## 手动集成(由浅入深) 你也可以按下面步骤,从零开始完成配置。 ### 前置条件 - 已安装最新 HBuilderX - 安装 Node.js(建议 ≥ 18) - 建议准备 Android 模拟器(或真机),便于快速验证 ### 1) 创建项目 在 HBuilderX 中创建一个全新的 `uni-app x` 项目;随后在项目根目录初始化 `package.json`: ```bash npm2yarn npm init -y ``` > 也可以手动创建 `package.json` 文件。 ### 2) 安装并引入 Tailwind CSS 3 ```bash npm2yarn # 安装 tailwindcss@3 所需依赖 npm i -D tailwindcss@3 postcss autoprefixer ``` ```bash npm2yarn # 初始化 Tailwind 配置文件 npx tailwindcss init ``` 在应用入口 `App.uvue` 中全局引入: ```html title="App.uvue" ``` ### 3) 安装 weapp-tailwindcss 并添加补丁脚本 ```bash npm2yarn npm i -D weapp-tailwindcss ``` 在 `package.json` 中增加 `postinstall` 脚本: ```json title="package.json" { "scripts": { // highlight-next-line "postinstall": "weapp-tw patch" } } ``` > 了解原因可阅读:weapp-tailwindcss 使用说明 ### 4) 在 uni-app x 中注册 weapp-tailwindcss 先创建一个简单的路径工具函数,方便在配置里引用绝对路径: ```js title="shared.js" const path = require('node:path') function r(...args) { return path.resolve(__dirname, ...args) } module.exports = { r } ``` 创建 `vite.config.ts` 并注册插件(注意 `uniAppX` 预设来自 `weapp-tailwindcss/presets`): ```js title="vite.config.ts" export default defineConfig({ plugins: [ uni(), UnifiedViteWeappTailwindcssPlugin( uniAppX({ base: __dirname, rem2rpx: true, }), ), ], css: { postcss: { plugins: [ tailwindcss({ config: r('./tailwind.config.js'), }), ], }, }, }) ``` ### 5) 配置 Tailwind 使用绝对路径包含所有需要提取原子类的文件: ```js title="tailwind.config.js" const { r } = require('./shared') /** @type {import('tailwindcss').Config} */ module.exports = { content: [ r('./pages/**/*.{uts,uvue}'), r('./components/**/*.{uts,uvue}'), ], corePlugins: { // 由 uni-app x 提供基础样式,这里关闭 preflight 避免冲突 preflight: false, }, } ``` 如需从更多目录提取样式,按需扩展 `content` 的 glob。 ### 6) 运行与验证 - 在 HBuilderX 中点击「运行」并选择目标平台(推荐 Android 模拟器/真机) - 新建/打开任意页面,写入示例类名验证: ```html Hello Tailwind on uni-app x ``` ### 7) 编辑器智能提示(可选) VS Code 配合插件体验最佳。为 Tailwind 扩展增加语言映射,让其识别 `uvue/uts`: ```json title=".vscode/settings.json" { "tailwindCSS.includeLanguages": { "uvue": "html", "uts": "javascript" } } ``` 同时安装官方语言服务插件(ID: dcloud-ide.hbuilderx-language-services)以获得完整语法支持。 --- ## 重要说明与限制 - 仅推荐在 `tailwindcss@3.x` 下跨端使用(v4 在该场景下暂不兼容) - 原生 App 端对文本与部分 CSS 特性有约束:文本使用 `` 元素,且样式需直接作用其上 - 如遇到 HBuilderX 控制台 CSS 警告,请按提示做兼容性调整或采用条件编译 --- ## weapp-tailwindcss ## 接口 - [TailwindcssPatchOptions](interfaces/TailwindcssPatchOptions.md) - [UserDefinedOptions](interfaces/UserDefinedOptions.md) --- ## 接口: TailwindcssPatchOptions 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:114 Root configuration consumed by the Tailwind CSS patch runner. ## 属性 ### cache? > `optional` **cache**: `boolean` \| `CacheUserOptions` 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:129 Cache configuration or boolean to enable/disable quickly. *** ### cwd? > `optional` **cwd**: `string` 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:119 Base directory used when resolving Tailwind resources. Defaults to `process.cwd()`. *** ### features? > `optional` **features**: `FeatureUserOptions` 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:125 Feature toggles for optional helpers. *** ### filter()? > `optional` **filter**: (`className`) => `boolean` 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:127 Optional function that filters final class names. #### 参数 ##### className `string` #### 返回 `boolean` *** ### output? > `optional` **output**: `OutputUserOptions` 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:131 Output configuration or boolean to inherits defaults. *** ### overwrite? > `optional` **overwrite**: `boolean` 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:121 Whether to overwrite generated artifacts (e.g., caches, outputs). *** ### tailwind? > `optional` **tailwind**: `TailwindUserOptions` 定义于: node\_modules/.pnpm/tailwindcss-patch@8.4.3\_magicast@0.5.1\_tailwindcss@4.1.18/node\_modules/tailwindcss-patch/dist/index.d.ts:123 Tailwind-specific configuration grouped by major version. --- ## 接口: UserDefinedOptions 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:15](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L15) ## 属性 ### replaceRuntimePackages? > `optional` **replaceRuntimePackages**: `boolean` \| `Record`\<`string`, `string`\> 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:94](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L94) **`Internal`** ## 0.重要配置 ### cssCalc? > `optional` **cssCalc**: `any` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:165](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L165) 预计算 CSS 变量或 `calc` 表达式的结果。 #### 添加于 ^4.3.0 #### 备注 解决部分机型对 `calc` 计算不一致的问题,可传入布尔值、选项对象或自定义匹配列表(支持正则)。在启用计算后,可通过 `includeCustomProperties` 指定需要保留的变量。 #### 示例 ```css // 原始输出 page, :root { --spacing: 8rpx; } .h-2 { height: calc(var(--spacing) * 2); } ``` ```css // 启用 cssCalc 后 .h-2 { height: 16rpx; height: calc(var(--spacing) * 2); } ``` ```js cssCalc: ['--spacing'] cssCalc: { includeCustomProperties: ['--spacing'] } ``` *** ### cssEntries? > `optional` **cssEntries**: `string`[] 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:264](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L264) 指定 tailwindcss@4 的入口 CSS。 #### 添加于 ^4.2.6 #### 备注 未配置时无法加载自定义插件,等价于设置 `tailwindcss.v4.cssEntries`。 *** ### cssPreflight? > `optional` **cssPreflight**: `any` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:123](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L123) 控制在视图节点上注入的 CSS 预设。 #### 参阅 https://github.com/sonofmagic/weapp-tailwindcss/issues/7 #### 备注 默认会向所有 `view`/`text` 元素注入 Tailwind 风格的基础样式,可通过此配置禁用、调整或补充规则,受 `cssPreflightRange` 影响。 #### 示例 ```js cssPreflight: { 'box-sizing': 'border-box', 'border-width': '0', 'border-style': 'solid', 'border-color': 'currentColor', } cssPreflight: false cssPreflight: { 'box-sizing': false, background: 'black', } ``` *** ### cssPreflightRange? > `optional` **cssPreflightRange**: `"all"` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:132](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L132) 控制 `cssPreflight` 注入的 DOM 选择器范围。 #### 参阅 https://github.com/sonofmagic/weapp-tailwindcss/pull/62 #### 备注 仅 `view`、`text` 及其伪元素会受影响。设置为 `'all'` 可以覆盖所有元素,此时需自行处理与宿主默认样式的冲突。 *** ### cssPresetEnv? > `optional` **cssPresetEnv**: `any` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:246](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L246) `postcss-preset-env` 的配置项。 #### 添加于 ^4.0.0 #### 参阅 - https://preset-env.cssdb.org/ - https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env#readme *** ### cssSelectorReplacement? > `optional` **cssSelectorReplacement**: `object` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:191](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L191) 控制 CSS 选择器的替换规则。 #### root? > `optional` **root**: `string` \| `false` \| `string`[] 将全局选择器 `:root` 替换为指定值。 ##### 默认值 `'page'` ##### 备注 设置为 `false` 时不再替换,可根据宿主环境(例如 RootPortal)传入数组值。 ##### 示例 ```ts root: ['page', 'wx-root-content'] ``` #### universal? > `optional` **universal**: `string` \| `false` \| `string`[] 将全局选择器 `*` 替换为指定值。 ##### 参阅 https://github.com/sonofmagic/weapp-tailwindcss/issues/81 ##### 默认值 `['view','text']` ##### 备注 小程序环境不支持 `*`,因此默认转换为 `view`、`text`;设置为 `false` 会留下原始选择器。 *** ### customAttributes? > `optional` **customAttributes**: `ICustomAttributes` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:60](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L60) 自定义 `wxml` 标签属性的转换规则。 #### 备注 默认会转换所有标签上的 `class` 与 `hover-class`。此配置允许通过 `Map` 或对象为特定标签指定需要转换的属性字符串或正则表达式数组。 - 使用 `'*'` 作为键可为所有标签追加通用规则。 - 支持传入 `Map` 以满足复杂匹配需求。 - 常见场景包括通过组件 `prop` 传递类名,或对三方组件的自定义属性做匹配,更多讨论见 [issue#129](https://github.com/sonofmagic/weapp-tailwindcss/issues/129#issuecomment-1340914688) 与 [issue#134](https://github.com/sonofmagic/weapp-tailwindcss/issues/134#issuecomment-1351288238)。 如果自定义规则已经覆盖默认的 `class`/`hover-class`,可开启 [`disabledDefaultTemplateHandler`](/docs/api/interfaces/UserDefinedOptions#disableddefaulttemplatehandler) 以关闭内置模板处理器。 #### 示例 ```js const customAttributes = { '*': [/[A-Za-z]?[A-Za-z-]*[Cc]lass/], 'van-image': ['custom-class'], 'ice-button': ['testClass'], } ``` *** ### customReplaceDictionary? > `optional` **customReplaceDictionary**: `Record`\<`string`, `string`\> 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:69](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L69) 自定义 class 名称的替换字典。 #### 备注 默认策略会将小程序不允许的字符映射为等长度的替代字符串,因此无法通过结果反推出原始类名。如需完全自定义,可传入 `Record`,只需确保生成的类名不会与已有样式冲突。示例参考 [dic.ts](https://github.com/sonofmagic/weapp-core/blob/main/packages/escape/src/dic.ts)。 #### 默认值 ```ts MappingChars2String ``` *** ### disabled? > `optional` **disabled**: `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:39](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L39) 是否禁用此插件。 #### 备注 在多平台构建场景下常用:小程序构建保持默认,非小程序环境(H5、App)传入 `true` 即可跳过转换。 #### 示例 ```ts // uni-app vue3 vite const isH5 = process.env.UNI_PLATFORM === 'h5' const isApp = process.env.UNI_PLATFORM === 'app' const disabled = isH5 || isApp uvtw({ disabled, }) ``` *** ### ignoreCallExpressionIdentifiers? > `optional` **ignoreCallExpressionIdentifiers**: (`string` \| `RegExp`)[] 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:89](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L89) 忽略指定调用表达式中的标识符。 #### 添加于 ^4.0.0 #### 备注 使用这些方法包裹的模板字符串或字符串字面量会跳过转义,常与 `@weapp-tailwindcss/merge` 配合(如 `['twMerge', 'twJoin', 'cva']`)。 *** ### ignoreTaggedTemplateExpressionIdentifiers? > `optional` **ignoreTaggedTemplateExpressionIdentifiers**: (`string` \| `RegExp`)[] 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:80](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L80) 忽略指定标签模板表达式中的标识符。 #### 添加于 ^4.0.0 #### 备注 当模板字符串被这些标识符包裹时,将跳过转义处理。 #### 默认值 ```ts ['weappTwIgnore'] ``` *** ### injectAdditionalCssVarScope? > `optional` **injectAdditionalCssVarScope**: `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:176](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L176) 是否额外注入 `tailwindcss css var scope`。 #### 添加于 ^2.6.0 #### 备注 当构建链路(例如 `@tarojs/plugin-html`)移除了包含 `*` 的选择器时,可启用该选项重新写入变量作用域,以避免渐变等功能失效。 #### 默认值 ```ts false ``` *** ### px2rpx? > `optional` **px2rpx**: `any` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:236](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L236) px 到 rpx 的转换配置。 #### 添加于 ^4.3.0 #### 备注 传入 `true` 启用默认映射(`1px = 1rpx`),或通过对象自定义更多行为。 *** ### rem2rpx? > `optional` **rem2rpx**: `any` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:227](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L227) rem 到 rpx 的转换配置。 #### 添加于 ^3.0.0 #### 备注 传入 `true` 使用默认配置,或提供 [postcss-rem-to-responsive-pixel](https://www.npmjs.com/package/postcss-rem-to-responsive-pixel) 支持的完整选项。 ```ts { rootValue: 32, propList: ['*'], transformUnit: 'rpx', } ``` *** ### rewriteCssImports? > `optional` **rewriteCssImports**: `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:185](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L185) 是否在 webpack/vite 阶段自动把 CSS 中的 `@import 'tailwindcss'` 映射为 `weapp-tailwindcss`。 #### 备注 开启后打包链路只会在处理样式时拦截 `tailwindcss` 的导入路径(JS/TS `import 'tailwindcss'` 不会被修改),让源码可以继续写 `@import 'tailwindcss';`,同时输出 weapp-tailwindcss 的样式。传入 `false` 可完全关闭该行为。 #### 默认值 ```ts true ``` *** ### tailwindcss? > `optional` **tailwindcss**: `TailwindUserOptions` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:254](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L254) 为不同版本的 Tailwind 配置行为。 #### 添加于 ^4.0.0 ## 1.文件匹配 ### cssMatcher()? > `optional` **cssMatcher**: (`name`) => `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:288](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L288) 匹配需要处理的 `wxss` 等样式文件。 #### 参数 ##### name `string` #### 返回 `boolean` *** ### htmlMatcher()? > `optional` **htmlMatcher**: (`name`) => `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:282](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L282) 匹配需要处理的 `wxml` 等模板文件。 #### 参数 ##### name `string` #### 返回 `boolean` *** ### jsMatcher()? > `optional` **jsMatcher**: (`name`) => `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:294](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L294) 匹配需要处理的编译后 `js` 文件。 #### 参数 ##### name `string` #### 返回 `boolean` *** ### mainCssChunkMatcher()? > `optional` **mainCssChunkMatcher**: (`name`, `appType?`) => `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:302](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L302) 匹配负责注入 Tailwind CSS 变量作用域的 CSS Bundle。 #### 参数 ##### name `string` ##### appType? `AppType` #### 返回 `boolean` #### 备注 在处理 `::before`/`::after` 等不兼容选择器时,建议手动指定文件位置。 ## 1.文件匹配 实验性质,有可能会改变 ### inlineWxs? > `optional` **inlineWxs**: `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:338](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L338) **`Experimental`** 是否转义 `wxml` 中的内联 `wxs`。 #### 备注 使用前同样需要在 `tailwind.config.js` 中声明 `wxs` 格式。 #### 示例 ```html // 我是内联wxs // 下方的类名会被转义 var className = "after:content-['我是className']" module.exports = { className: className } ``` #### 默认值 ```ts false ``` *** ### wxsMatcher()? > `optional` **wxsMatcher**: (`name`) => `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:313](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L313) **`Experimental`** 匹配各端的 `wxs`/`sjs`/`.filter.js` 文件。 #### 参数 ##### name `string` #### 返回 `boolean` #### 备注 配置前请确保在 `tailwind.config.js` 的 `content` 中包含对应格式。 #### 默认值 ```ts ()=>false ``` ## 2.生命周期 ### onEnd()? > `optional` **onEnd**: () => `void` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:372](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L372) 结束处理时触发。 #### 返回 `void` *** ### onLoad()? > `optional` **onLoad**: () => `void` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:348](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L348) 插件 `apply` 初始调用时触发。 #### 返回 `void` *** ### onStart()? > `optional` **onStart**: () => `void` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:354](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L354) 开始处理前触发。 #### 返回 `void` *** ### onUpdate()? > `optional` **onUpdate**: (`filename`, `oldVal`, `newVal`) => `void` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:366](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L366) 匹配并修改文件后触发。 #### 参数 ##### filename `string` ##### oldVal `string` ##### newVal `string` #### 返回 `void` ## 3.一般配置 ### appType? > `optional` **appType**: `AppType` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:400](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L400) 声明所使用的框架类型。 #### 备注 用于辅助定位主要的 CSS bundle,以便默认的 `mainCssChunkMatcher` 做出更准确的匹配,未传入时将尝试自动猜测变量注入位置。 *** ### arbitraryValues? > `optional` **arbitraryValues**: `IArbitraryValues` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:407](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L407) TailwindCSS 任意值的相关配置。 *** ### babelParserOptions? > `optional` **babelParserOptions**: `Partial`\<`Options`\> & `object` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:470](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L470) `@babel/parser` 的配置选项。 #### 类型声明 ##### cache? > `optional` **cache**: `boolean` ##### cacheKey? > `optional` **cacheKey**: `string` #### 添加于 ^3.2.0 *** ### cache? > `optional` **cache**: `boolean` \| `ICreateCacheReturnType` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:462](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L462) 控制缓存策略。 #### 添加于 ^3.0.11 *** ### cssChildCombinatorReplaceValue? > `optional` **cssChildCombinatorReplaceValue**: `string` \| `string`[] 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:487](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L487) 自定义 Tailwind 子组合器的替换值。 #### 备注 为兼容小程序缺乏 `:not([hidden])~` 支持的限制,默认会将 `.space-x-4` 等选择器替换为 `view + view`。可传入字符串或字符串数组以扩展适用标签。 ```css // 数组示例 .space-y-4>view + view,text + text{} // 字符串示例 .space-y-4>view,text,button,input ~ view,text,button,input{} ``` #### 默认值 ```ts 'view + view' ``` *** ### cssRemoveHoverPseudoClass? > `optional` **cssRemoveHoverPseudoClass**: `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:506](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L506) 是否移除 CSS 中的 `:hover` 选择器。 #### 添加于 ^3.2.1 #### 参阅 https://github.com/sonofmagic/weapp-tailwindcss/issues/293 #### 备注 小程序不支持 `:hover`,需要使用组件的 `hover-class`,因此默认删除相关节点。 #### 默认值 `true` *** ### cssRemoveProperty? > `optional` **cssRemoveProperty**: `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:516](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L516) 是否移除 `@property` 节点。 #### 添加于 ^4.1.2 #### 备注 微信小程序可识别 `@property`,但支付宝暂不支持,默认移除以避免构建失败。 #### 默认值 `true` *** ### disabledDefaultTemplateHandler? > `optional` **disabledDefaultTemplateHandler**: `boolean` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:428](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L428) 禁用默认的 `wxml` 模板替换器。 #### 添加于 ^2.6.2 #### 备注 启用后模板匹配完全交由 [`customAttributes`](/docs/api/interfaces/UserDefinedOptions#customattributes) 管理,需要自行覆盖默认的 `class` / `hover-class` 等匹配规则。 #### 默认值 ```ts false ``` *** ### jsPreserveClass()? > `optional` **jsPreserveClass**: (`keyword`) => `boolean` \| `undefined` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:417](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L417) 控制 JS 字面量是否需要保留。 #### 参数 ##### keyword `string` #### 返回 `boolean` \| `undefined` #### 添加于 ^2.6.1 #### 备注 当 Tailwind 与 JS 字面量冲突时,可通过回调返回 `true` 保留当前值,返回 `false` 或 `undefined` 则继续转义。默认保留所有带 `*` 的字符串字面量。 *** ### logLevel? > `optional` **logLevel**: `"info"` \| `"warn"` \| `"error"` \| `"silent"` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:531](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L531) 控制命令行日志输出级别。 #### 备注 默认 `info`,可设置为 `silent` 屏蔽全部输出。 *** ### postcssOptions? > `optional` **postcssOptions**: `any` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:495](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L495) `postcss` 的配置选项。 #### 添加于 ^3.2.0 *** ### supportCustomLengthUnitsPatch? > `optional` **supportCustomLengthUnitsPatch**: `boolean` \| `ILengthUnitsPatchOptions` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:391](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L391) 控制 Tailwind 自定义长度单位补丁。 #### 参阅 https://github.com/sonofmagic/weapp-tailwindcss/issues/110 #### 备注 TailwindCSS 3.2.0 起对任意值执行长度单位校验,会将未声明的 `rpx` 识别为颜色。本选项默认开启以注入 `rpx` 支持。当 Node.js 在插件执行前已缓存 `tailwindcss` 模块时,首轮运行可能未生效,可通过在 `postinstall` 中执行 `weapp-tw patch` 提前打补丁。 ```diff "scripts": { + "postinstall": "weapp-tw patch" } ``` *** ### tailwindcssBasedir? > `optional` **tailwindcssBasedir**: `string` 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:454](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L454) 指定用于获取 Tailwind 上下文的路径。 #### 添加于 ^2.9.3 #### 备注 在 linked 或 monorepo 场景下可手动指向目标项目的 `package.json` 所在目录。 *** ### tailwindcssPatcherOptions? > `optional` **tailwindcssPatcherOptions**: [`TailwindcssPatchOptions`](TailwindcssPatchOptions.md) 定义于: [packages/weapp-tailwindcss/src/typedoc.export.ts:523](https://github.com/sonofmagic/weapp-tailwindcss/blob/3f74f29bbba2815e37d9171fddec7566d76a5099/packages/weapp-tailwindcss/src/typedoc.export.ts#L523) 自定义 patcher 参数。 --- ## Class: UnifiedWebpackPluginV5 **`Name`** UnifiedWebpackPluginV5 **`Description`** webpack5 核心转义插件 **`Link`** https://tw.icebreaker.top/docs/intro ## Implements - [`IBaseWebpackPlugin`](../interfaces/IBaseWebpackPlugin.md) ## Constructors ### constructor • **new UnifiedWebpackPluginV5**(`options?`): [`UnifiedWebpackPluginV5`](UnifiedWebpackPluginV5.md) #### Parameters | Name | Type | | :-------- | :---------------------------------------------------------- | | `options` | [`UserDefinedOptions`](../interfaces/UserDefinedOptions.md) | #### Returns [`UnifiedWebpackPluginV5`](UnifiedWebpackPluginV5.md) #### Defined in [webpack/BaseUnifiedPlugin/v5.ts:24](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/webpack/BaseUnifiedPlugin/v5.ts#L24) ## Properties ### appType • `Optional` **appType**: [`AppType`](../#apptype) #### Implementation of [IBaseWebpackPlugin](../interfaces/IBaseWebpackPlugin.md).[appType](../interfaces/IBaseWebpackPlugin.md#apptype) #### Defined in [webpack/BaseUnifiedPlugin/v5.ts:22](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/webpack/BaseUnifiedPlugin/v5.ts#L22) --- ### options • **options**: `Required`<`Omit`<[`UserDefinedOptions`](../interfaces/UserDefinedOptions.md), `"customReplaceDictionary"` \| `"supportCustomLengthUnitsPatch"` \| [`GlobOrFunctionMatchers`](../#globorfunctionmatchers)\> & \{ `cssMatcher`: (`name`: `string`) => `boolean` ; `htmlMatcher`: (`name`: `string`) => `boolean` ; `jsMatcher`: (`name`: `string`) => `boolean` ; `mainCssChunkMatcher`: (`name`: `string`, `appType?`: [`AppType`](../#apptype)) => `boolean` ; `wxsMatcher`: (`name`: `string`) => `boolean` } & \{ `cache`: `ICreateCacheReturnType` ; `customReplaceDictionary`: `Record`<`string`, `string`\> ; `escapeMap`: `Record`<`string`, `string`\> ; `jsHandler`: [`JsHandler`](../#jshandler) ; `patch`: () => `void` ; `setMangleRuntimeSet`: (`runtimeSet`: `Set`<`string`\>) => `void` ; `styleHandler`: (`rawSource`: `string`, `options`: [`IStyleHandlerOptions`](../#istylehandleroptions)) => `Promise`<`string`\> ; `supportCustomLengthUnitsPatch`: `false` \| [`ILengthUnitsPatchOptions`](../interfaces/ILengthUnitsPatchOptions.md) ; `templateHandler`: (`rawSource`: `string`, `options?`: [`ITemplateHandlerOptions`](../interfaces/ITemplateHandlerOptions.md)) => `string` }\> #### Implementation of [IBaseWebpackPlugin](../interfaces/IBaseWebpackPlugin.md).[options](../interfaces/IBaseWebpackPlugin.md#options) #### Defined in [webpack/BaseUnifiedPlugin/v5.ts:21](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/webpack/BaseUnifiedPlugin/v5.ts#L21) ## Methods ### apply ▸ **apply**(`compiler`): `void` #### Parameters | Name | Type | | :--------- | :--------- | | `compiler` | `Compiler` | #### Returns `void` #### Implementation of [IBaseWebpackPlugin](../interfaces/IBaseWebpackPlugin.md).[apply](../interfaces/IBaseWebpackPlugin.md#apply) #### Defined in [webpack/BaseUnifiedPlugin/v5.ts:32](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/webpack/BaseUnifiedPlugin/v5.ts#L32) --- ## weapp-tailwindcss-webpack-plugin ## Classes - [UnifiedWebpackPluginV5](classes/UnifiedWebpackPluginV5.md) ## Interfaces - [IArbitraryValues](interfaces/IArbitraryValues.md) - [IBaseWebpackPlugin](interfaces/IBaseWebpackPlugin.md) - [ICommonReplaceOptions](interfaces/ICommonReplaceOptions.md) - [ILengthUnitsPatchDangerousOptions](interfaces/ILengthUnitsPatchDangerousOptions.md) - [ILengthUnitsPatchOptions](interfaces/ILengthUnitsPatchOptions.md) - [IPropValue](interfaces/IPropValue.md) - [ITemplateHandlerOptions](interfaces/ITemplateHandlerOptions.md) - [InternalCssSelectorReplacerOptions](interfaces/InternalCssSelectorReplacerOptions.md) - [InternalPatchResult](interfaces/InternalPatchResult.md) - [RawSource](interfaces/RawSource.md) - [UserDefinedOptions](interfaces/UserDefinedOptions.md) ## Type Aliases ### AppType Ƭ **AppType**: `"uni-app"` \| `"uni-app-vite"` \| `"taro"` \| `"remax"` \| `"rax"` \| `"native"` \| `"kbone"` \| `"mpx"` #### Defined in [types.ts:10](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L10) --- ### CreateJsHandlerOptions Ƭ **CreateJsHandlerOptions**: `Omit`<[`IJsHandlerOptions`](#ijshandleroptions), `"classNameSet"`\> #### Defined in [types.ts:502](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L502) --- ### CssPreflightOptions Ƭ **CssPreflightOptions**: \{ `[key: CssPresetProps]`: `string` \| `number` \| `boolean`; } \| `false` #### Defined in [types.ts:19](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L19) --- ### CssPresetProps Ƭ **CssPresetProps**: `string` #### Defined in [types.ts:17](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L17) ### GlobOrFunctionMatchers Ƭ **GlobOrFunctionMatchers**: `"htmlMatcher"` \| `"cssMatcher"` \| `"jsMatcher"` \| `"mainCssChunkMatcher"` \| `"wxsMatcher"` #### Defined in [types.ts:460](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L460) --- ### ICustomAttributes Ƭ **ICustomAttributes**: `Record`<`string`, [`ItemOrItemArray`](#itemoritemarray)<`string` \| `RegExp`\>\> \| `Map`<`string` \| `RegExp`, [`ItemOrItemArray`](#itemoritemarray)<`string` \| `RegExp`\>\> #### Defined in [types.ts:50](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L50) --- ### ICustomAttributesEntities Ƭ **ICustomAttributesEntities**: [`string` \| `RegExp`, [`ItemOrItemArray`](#itemoritemarray)<`string` \| `RegExp`\>][] #### Defined in [types.ts:52](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L52) --- ### IJsHandlerOptions Ƭ **IJsHandlerOptions**: `Object` #### Type declaration | Name | Type | | :----------------- | :------------------------------------------------------------------------------- | | `arbitraryValues?` | [`IArbitraryValues`](interfaces/IArbitraryValues.md) | | `classNameSet` | `Set`<`string`\> | | `escapeMap?` | `Record`<`string`, `string`\> | | `generateMap?` | `boolean` | | `jsPreserveClass?` | (`keyword`: `string`) => `boolean` \| `undefined` | | `minifiedJs?` | `boolean` | | `needEscaped?` | `boolean` | | `strategy?` | [`UserDefinedOptions`](interfaces/UserDefinedOptions.md)[``"jsEscapeStrategy"``] | #### Defined in [types.ts:54](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L54) --- ### IStyleHandlerOptions Ƭ **IStyleHandlerOptions**: [`RequiredStyleHandlerOptions`](#requiredstylehandleroptions) & \{ `ctx?`: `IContext` ; `postcssOptions?`: [`LoadedPostcssOptions`](#loadedpostcssoptions) ; `cssRemoveProperty?`: `boolean` ; `cssRemoveHoverPseudoClass?`: `boolean` ; `cssPresetEnv?`: [`PresetEnvOptions`](#presetenvoptions) ; `cssCalc?`: `boolean` \| [`CssCalcOptions`](#csscalcoptions) \| (`string` \| `RegExp`)[] ; `atRules?`: \{ `property?`: `boolean` ; `supports?`: `boolean` ; `media?`: `boolean` } ; `uniAppX?`: `boolean` ; `majorVersion?`: `number` } #### Defined in [types.ts:41](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L41) --- ### InternalPostcssOptions Ƭ **InternalPostcssOptions**: `Pick`<[`UserDefinedOptions`](interfaces/UserDefinedOptions.md), `"cssMatcher"` \| `"mainCssChunkMatcher"` \| `"cssPreflight"` \| `"replaceUniversalSelectorWith"` \| `"cssPreflightRange"` \| `"disabled"`\> #### Defined in [types.ts:479](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L479) --- ### InternalUserDefinedOptions Ƭ **InternalUserDefinedOptions**: `Required`<`Omit`<[`UserDefinedOptions`](interfaces/UserDefinedOptions.md), [`GlobOrFunctionMatchers`](#globorfunctionmatchers) \| `"supportCustomLengthUnitsPatch"` \| `"customReplaceDictionary"`\> & \{ [K in GlobOrFunctionMatchers]: K extends "mainCssChunkMatcher" ? Function : Function } & \{ `cache`: `ICreateCacheReturnType` ; `customReplaceDictionary`: `Record`<`string`, `string`\> ; `escapeMap`: `Record`<`string`, `string`\> ; `jsHandler`: [`JsHandler`](#jshandler) ; `patch`: () => `void` ; `setMangleRuntimeSet`: (`runtimeSet`: `Set`<`string`\>) => `void` ; `styleHandler`: (`rawSource`: `string`, `options`: [`IStyleHandlerOptions`](#istylehandleroptions)) => `Promise`<`string`\> ; `supportCustomLengthUnitsPatch`: [`ILengthUnitsPatchOptions`](interfaces/ILengthUnitsPatchOptions.md) \| `false` ; `templateHandler`: (`rawSource`: `string`, `options?`: [`ITemplateHandlerOptions`](interfaces/ITemplateHandlerOptions.md)) => `string` }\> #### Defined in [types.ts:462](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L462) --- ### ItemOrItemArray Ƭ **ItemOrItemArray**<`T`\>: `T` \| `T`[] #### Type parameters | Name | | :--- | | `T` | #### Defined in [types.ts:8](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L8) --- ### JsHandler Ƭ **JsHandler**: (`rawSource`: `string`, `set`: `Set`<`string`\>, `options?`: [`CreateJsHandlerOptions`](#createjshandleroptions)) => [`JsHandlerResult`](#jshandlerresult) #### Type declaration ▸ (`rawSource`, `set`, `options?`): [`JsHandlerResult`](#jshandlerresult) ##### Parameters | Name | Type | | :---------- | :-------------------------------------------------- | | `rawSource` | `string` | | `set` | `Set`<`string`\> | | `options?` | [`CreateJsHandlerOptions`](#createjshandleroptions) | ##### Returns [`JsHandlerResult`](#jshandlerresult) #### Defined in [types.ts:428](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L428) --- ### JsHandlerReplaceResult Ƭ **JsHandlerReplaceResult**: `Object` #### Type declaration | Name | Type | | :----- | :---------- | | `code` | `string` | | `map?` | `SourceMap` | #### Defined in [types.ts:46](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L46) --- ### JsHandlerResult Ƭ **JsHandlerResult**: [`JsHandlerReplaceResult`](#jshandlerreplaceresult) \| `GeneratorResult` #### Defined in [types.ts:48](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L48) --- ### RequiredStyleHandlerOptions Ƭ **RequiredStyleHandlerOptions**: \{ `cssInjectPreflight?`: `InjectPreflight` ; `escapeMap?`: `Record`<`string`, `string`\> ; `isMainChunk`: `boolean` } & `Pick`<[`UserDefinedOptions`](interfaces/UserDefinedOptions.md), `"cssPreflightRange"` \| `"cssChildCombinatorReplaceValue"` \| `"replaceUniversalSelectorWith"` \| `"injectAdditionalCssVarScope"` \| `"cssSelectorReplacement"`\> #### Defined in [types.ts:25](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L25) ### LoadedPostcssOptions `LoadedPostcssOptions` 是 `postcss-load-config` 导出的 `Result` 类型去掉 `file` 字段后再取 `Partial`。在插件内部用于接收已经过 `postcss-load-config` 解析的运行时配置,例如自定义插件、语法语义等设置。 #### 定义 ```ts type LoadedPostcssOptions = Partial> ``` #### 定义于 [packages/postcss/src/types.ts:14](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/packages/postcss/src/types.ts#L14) ### PresetEnvOptions `PresetEnvOptions` 直接复用自 `postcss-preset-env` 的 `pluginOptions`,用于控制 `stage`、`browsers`、`features` 等行为。传入该类型可以在保持 Docusaurus 生成文档一致的同时,自由扩展 CSS 预处理特性。 #### 定义 ```ts ``` #### 定义于 [packages/postcss/src/types.ts:8](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/packages/postcss/src/types.ts#L8) ### CssCalcOptions `CssCalcOptions` 基于 `@weapp-tailwindcss/postcss-calc` 的 `PostCssCalcOptions` 扩展而来,并额外提供 `includeCustomProperties`,用于声明哪些自定义属性需要参与 `calc` 计算或在输出中保留。 #### 定义 ```ts interface CssCalcOptions extends PostCssCalcOptions { includeCustomProperties?: (string | RegExp)[] } ``` #### 定义于 [packages/postcss/src/types.ts:45](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/packages/postcss/src/types.ts#L45) ## Functions ### UnifiedViteWeappTailwindcssPlugin ▸ **UnifiedViteWeappTailwindcssPlugin**(`options?`): `Plugin` \| `undefined` #### Parameters | Name | Type | | :-------- | :------------------------------------------------------- | | `options` | [`UserDefinedOptions`](interfaces/UserDefinedOptions.md) | #### Returns `Plugin` \| `undefined` **`Name`** UnifiedViteWeappTailwindcssPlugin **`Description`** uni-app vite vue3 版本插件 **`Link`** https://tw.icebreaker.top/docs/quick-start/frameworks/uni-app-vite #### Defined in [vite/index.ts:17](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/vite/index.ts#L17) --- ### createPlugins ▸ **createPlugins**(`options?`): `Object` #### Parameters | Name | Type | | :-------- | :------------------------------------------------------- | | `options` | [`UserDefinedOptions`](interfaces/UserDefinedOptions.md) | #### Returns `Object` | Name | Type | | :-------------- | :---------------- | | `transformJs` | () => `Transform` | | `transformWxml` | () => `Transform` | | `transformWxss` | () => `Transform` | **`Name`** weapp-tw-gulp **`Description`** gulp版本weapp-tw插件 **`Link`** https://tw.icebreaker.top/docs/quick-start/frameworks/native #### Defined in [gulp/index.ts:17](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/gulp/index.ts#L17) --- ## Interface: IArbitraryValues ## Properties ### allowDoubleQuotes • `Optional` **allowDoubleQuotes**: `boolean` 是否允许在类名里,使用双引号。 建议不要开启,因为有些框架,比如 `vue3` 它针对有些静态模板会直接编译成 `html` 字符串,此时开启这个配置很有可能导致转义出错 **`Example`** ```html ``` **`Default`** `false` #### Defined in [types.ts:114](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L114) --- ## Interface: IBaseWebpackPlugin ## Implemented by - [`UnifiedWebpackPluginV5`](../classes/UnifiedWebpackPluginV5.md) ## Properties ### appType • `Optional` **appType**: [`AppType`](../#apptype) #### Defined in [types.ts:488](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L488) --- ### apply • **apply**: (`compiler`: `any`) => `void` #### Type declaration ▸ (`compiler`): `void` ##### Parameters | Name | Type | | :--------- | :---- | | `compiler` | `any` | ##### Returns `void` #### Defined in [types.ts:490](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L490) --- ### options • **options**: `Required`<`Omit`<[`UserDefinedOptions`](UserDefinedOptions.md), `"customReplaceDictionary"` \| `"supportCustomLengthUnitsPatch"` \| [`GlobOrFunctionMatchers`](../#globorfunctionmatchers)\> & \{ `cssMatcher`: (`name`: `string`) => `boolean` ; `htmlMatcher`: (`name`: `string`) => `boolean` ; `jsMatcher`: (`name`: `string`) => `boolean` ; `mainCssChunkMatcher`: (`name`: `string`, `appType?`: [`AppType`](../#apptype)) => `boolean` ; `wxsMatcher`: (`name`: `string`) => `boolean` } & \{ `cache`: `ICreateCacheReturnType` ; `customReplaceDictionary`: `Record`<`string`, `string`\> ; `escapeMap`: `Record`<`string`, `string`\> ; `jsHandler`: [`JsHandler`](../#jshandler) ; `patch`: () => `void` ; `setMangleRuntimeSet`: (`runtimeSet`: `Set`<`string`\>) => `void` ; `styleHandler`: (`rawSource`: `string`, `options`: [`IStyleHandlerOptions`](../#istylehandleroptions)) => `Promise`<`string`\> ; `supportCustomLengthUnitsPatch`: `false` \| [`ILengthUnitsPatchOptions`](ILengthUnitsPatchOptions.md) ; `templateHandler`: (`rawSource`: `string`, `options?`: [`ITemplateHandlerOptions`](ITemplateHandlerOptions.md)) => `string` }\> #### Defined in [types.ts:487](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L487) --- ## Interface: ICommonReplaceOptions ## Hierarchy - **`ICommonReplaceOptions`** ↳ [`ITemplateHandlerOptions`](ITemplateHandlerOptions.md) ## Properties ### escapeMap • `Optional` **escapeMap**: `Record`<`string`, `string`\> #### Defined in [types.ts:442](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L442) --- ### keepEOL • `Optional` **keepEOL**: `boolean` #### Defined in [types.ts:441](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L441) --- ## Interface: ILengthUnitsPatchDangerousOptions ## Properties ### destPath • `Optional` **destPath**: `string` #### Defined in [types.ts:81](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L81) --- ### gteVersion • `Optional` **gteVersion**: `string` #### Defined in [types.ts:77](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L77) --- ### lengthUnitsFilePath • `Optional` **lengthUnitsFilePath**: `string` #### Defined in [types.ts:78](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L78) --- ### overwrite • `Optional` **overwrite**: `boolean` #### Defined in [types.ts:80](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L80) --- ### packageName • `Optional` **packageName**: `string` #### Defined in [types.ts:76](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L76) --- ### variableName • `Optional` **variableName**: `string` #### Defined in [types.ts:79](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L79) --- ## Interface: ILengthUnitsPatchOptions **`Deprecated`** ## Properties ### basedir • `Optional` **basedir**: `string` #### Defined in [types.ts:91](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L91) --- ### dangerousOptions • `Optional` **dangerousOptions**: [`ILengthUnitsPatchDangerousOptions`](ILengthUnitsPatchDangerousOptions.md) #### Defined in [types.ts:90](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L90) --- ### paths • `Optional` **paths**: `string`[] #### Defined in [types.ts:89](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L89) --- ### units • **units**: `string`[] #### Defined in [types.ts:88](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L88) --- ## Interface: IPropValue ## Properties ### prop • **prop**: `string` #### Defined in [types.ts:13](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L13) --- ### value • **value**: `string` #### Defined in [types.ts:14](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L14) --- ## Interface: ITemplateHandlerOptions ## Hierarchy - [`ICommonReplaceOptions`](ICommonReplaceOptions.md) ↳ **`ITemplateHandlerOptions`** ## Properties ### customAttributesEntities • `Optional` **customAttributesEntities**: [`ICustomAttributesEntities`](../#icustomattributesentities) #### Defined in [types.ts:447](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L447) --- ### disabledDefaultTemplateHandler • `Optional` **disabledDefaultTemplateHandler**: `boolean` #### Defined in [types.ts:456](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L456) --- ### escapeMap • `Optional` **escapeMap**: `Record`<`string`, `string`\> #### Overrides [ICommonReplaceOptions](ICommonReplaceOptions.md).[escapeMap](ICommonReplaceOptions.md#escapemap) #### Defined in [types.ts:451](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L451) --- ### inlineWxs • `Optional` **inlineWxs**: `boolean` #### Defined in [types.ts:453](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L453) --- ### jsHandler • `Optional` **jsHandler**: [`JsHandler`](../#jshandler) #### Defined in [types.ts:454](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L454) --- ### keepEOL • `Optional` **keepEOL**: `boolean` #### Inherited from [ICommonReplaceOptions](ICommonReplaceOptions.md).[keepEOL](ICommonReplaceOptions.md#keepeol) #### Defined in [types.ts:441](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L441) ### quote • `Optional` **quote**: `null` \| `string` #### Defined in [types.ts:457](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L457) --- ### runtimeSet • `Optional` **runtimeSet**: `Set`<`string`\> #### Defined in [types.ts:455](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L455) --- ## Interface: InternalCssSelectorReplacerOptions ## Properties ### escapeMap • `Optional` **escapeMap**: `Record`<`string`, `string`\> #### Defined in [types.ts:38](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L38) --- ## Interface: InternalPatchResult **`Description`** InternalPatchResult ## Properties ### dataTypes • `Optional` **dataTypes**: `string` #### Defined in [types.ts:497](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L497) --- ### plugin • `Optional` **plugin**: `string` #### Defined in [types.ts:499](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L499) --- ### processTailwindFeatures • `Optional` **processTailwindFeatures**: `string` #### Defined in [types.ts:498](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L498) --- ## Interface: RawSource ## Properties ### end • **end**: `number` #### Defined in [types.ts:67](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L67) --- ### raw • **raw**: `string` #### Defined in [types.ts:68](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L68) --- ### source • `Optional` **source**: `string` #### Defined in [types.ts:70](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L70) --- ### start • **start**: `number` #### Defined in [types.ts:66](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L66) --- ## Interface: UserDefinedOptions ## Properties ### appType • `Optional` **appType**: [`AppType`](../#apptype) **`Description`** 使用的框架类型(uni-app,taro...),用于找到主要的 `css bundle` 进行转化,这个配置会影响默认方法 `mainCssChunkMatcher` 的行为,不传会去猜测 `tailwindcss css var inject scope` 的位置 #### Defined in [types.ts:284](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L284) --- ### arbitraryValues • `Optional` **arbitraryValues**: [`IArbitraryValues`](IArbitraryValues.md) **`Description`** 针对 tailwindcss arbitrary values 的一些配置 #### Defined in [types.ts:301](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L301) --- ### cssChildCombinatorReplaceValue • `Optional` **cssChildCombinatorReplaceValue**: `string` \| `string`[] **`Description`** 用于控制 tailwindcss 子组合器的生效标签范围, 这里我们用一个例子来说明这个配置是干啥用的. 我们布局的时候往往会使用 `space-x-4` 那么实际上会生成这样的css选择器: ```css .space-x-4>:not([hidden])~:not([hidden]){} ``` 然而很不幸,这个选择器在小程序中是不支持的,写了会报错导致编译失败。 所以出于保守起见,我把它替换为了: ```css .space-x-4>view + view{} ``` 这同时也是默认值, 而这个选项就允许你进行自定义子组合器的行为 你可以传入一个 字符串,或者字符串数组 1. 传入字符串数组,比如 `['view','text']` 生成: ```css .space-y-4>view + view,text + text{} ``` 2. 传入一个字符串,此时行为变成了整个替换,比如 `'view,text,button,input ~ view,text,button,input'` 生成: ```css .space-y-4>view,text,button,input ~ view,text,button,input{} ``` **`Default`** ```ts 'view + view' ``` #### Defined in [types.ts:329](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L329) --- ### cssMatcher • `Optional` **cssMatcher**: `string` \| `string`[] \| (`name`: `string`) => `boolean` **`Description`** 匹配 `wxss` 等等样式文件的方法,支持 `glob` by [micromatch](https://github.com/micromatch/micromatch) #### Defined in [types.ts:125](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L125) --- ### cssPreflight • `Optional` **cssPreflight**: [`CssPreflightOptions`](../#csspreflightoptions) **`Issue`** **`Description`** 在所有 view节点添加的 css 预设,可根据情况自由的禁用原先的规则,或者添加新的规则。 详细用法如下: ```js // default 默认: cssPreflight: { 'box-sizing': 'border-box', 'border-width': '0', 'border-style': 'solid', 'border-color': 'currentColor' } // result // box-sizing: border-box; // border-width: 0; // border-style: solid; // border-color: currentColor // case 禁用所有 cssPreflight: false // result // none // case 禁用单个属性 cssPreflight: { 'box-sizing': false } // border-width: 0; // border-style: solid; // border-color: currentColor // case 更改和添加单个属性 cssPreflight: { 'box-sizing': 'content-box', 'background': 'black' } // result // box-sizing: content-box; // border-width: 0; // border-style: solid; // border-color: currentColor; // background: black ``` #### Defined in [types.ts:178](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L178) --- ### cssPreflightRange • `Optional` **cssPreflightRange**: `"view"` \| `"all"` **`Issue`** **`Description`** 全局`dom`选择器,只有在这个选择器作用范围内的`dom`会被注入 `cssPreflight` 的变量和默认样式。默认为 `'view'` 即只对所有的 `view` 和伪元素生效,想要对所有的元素生效,可切换为 `'all'`,此时需要自行处理和客户端默认样式的冲突 #### Defined in [types.ts:184](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L184) --- ### cssSelectorReplacement • `Optional` **cssSelectorReplacement**: `Object` #### Type declaration | Name | Type | Description | | :----------- | :------------------ | :------------------------ | | `root?` | `string` \| `false` | **`Default`** `ts 'page'` | | `universal?` | `string` \| `false` | **`Default`** `ts 'view'` | #### Defined in [types.ts:410](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L410) --- ### customAttributes • `Optional` **customAttributes**: [`ICustomAttributes`](../#icustomattributes) **`Description`** **这是一个重要的配置!** 它可以自定义`wxml`标签上的`attr`转化属性。默认转化所有的`class`和`hover-class`,这个`Map`的 `key`为匹配标签,`value`为属性字符串或者匹配正则数组。 如果你想要增加,对于所有标签都生效的转化的属性,你可以添加 `*`: `(string | Regexp)[]` 这样的键值对。(`*` 是一个特殊值,代表所有标签) 更复杂的情况,可以传一个 `Map`实例。 假如你要把 `className` 通过组件的`prop`传递给子组件,又或者想要自定义转化的标签属性时,需要用到此配置,案例详见:[issue#129](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/129#issuecomment-1340914688),[issue#134](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/134#issuecomment-1351288238) **`Example`** ```js const customAttributes = { // 匹配所有带 Class / class 相关的标签,比如 `a-class`, `testClass` , `custom-class` 里的值 '*': [ /[A-Za-z]?[A-Za-z-]*[Cc]lass/ ], // 额外匹配转化 `van-image` 标签上的 `custom-class` 的值 'van-image': ['custom-class'], 'ice-button': ['testClass'] } ``` 当然你可以根据自己的需求,定义单个或者多个正则/字符串。 甚至有可能你编写正则表达式,它们匹配的范围,直接包括了插件里自带默认的 `class`/`hover-class`,那么这种情况下,你完全可以取代插件的默认模板转化器,开启 [disabledDefaultTemplateHandler](/docs/api/interfaces/UserDefinedOptions#disableddefaulttemplatehandler) 配置项,禁用默认的模版匹配转化器。 #### Defined in [types.ts:251](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L251) --- ### customReplaceDictionary • `Optional` **customReplaceDictionary**: `Record`<`string`, `string`\> \| `"simple"` \| `"complex"` **`Description`** 自定义转化class名称字典,这个配置项用来自定义转化`class`名称字典,你可以使用这个选项来简化生成的`class` 插件中内置了`'simple'`模式和`'complex'`模式: - `'simple'`模式: 把小程序中不允许的字符串,转义为**相等长度**的代替字符串,这种情况不追求转化目标字符串的一比一绝对等价,即无法从生成结果,反推原先的`class` - `'complex'`模式: 把小程序中不允许的字符串,转义为**更长**的代替字符串,这种情况转化前后的字符串是等价的,可以从结果进行反推,缺点就是会生成更长的 `class` 导致 `wxml`和`wxss`这类的体积增大 当然,你也可以自定义,传一个 `Record` 类型,只需保证转化后 css 中的 `class` 选择器,不会和自己定义的 `class` 产生冲突即可,示例见[dic.ts](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/blob/main/src/dic.ts) **`Default`** ```ts 'simple' ``` #### Defined in [types.ts:263](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L263) ### disabled • `Optional` **disabled**: `boolean` **`Description`** 是否禁用此插件,一般用于构建到多平台时使用 #### Defined in [types.ts:196](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L196) --- ### disabledDefaultTemplateHandler • `Optional` **disabledDefaultTemplateHandler**: `boolean` **`Version`** `^2.6.2` **`Description`** 开启此选项,将会禁用默认 `wxml` 模板替换器,此时模板的匹配和转化将完全被 [`customAttributes`](/docs/api/interfaces/UserDefinedOptions#customattributes) 接管, 此时你需要自己编写匹配之前默认 `class`/`hover-class`,以及新的标签属性的正则表达式`regex` **`Default`** ```ts false ``` #### Defined in [types.ts:389](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L389) --- ### htmlMatcher • `Optional` **htmlMatcher**: `string` \| `string`[] \| (`name`: `string`) => `boolean` **`Description`** 匹配 `wxml`等等模板进行处理的方法,支持 `glob` by [micromatch](https://github.com/micromatch/micromatch) #### Defined in [types.ts:121](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L121) --- ### injectAdditionalCssVarScope • `Optional` **injectAdditionalCssVarScope**: `boolean` **`Version`** `^2.6.0` **`Description`** 是否注入额外的 `tailwindcss css var scope` 区域,这个选项用于这样的场景 比如 `taro vue3` 使用 [NutUI](https://nutui.jd.com), 需要使用 `@tarojs/plugin-html`,而这个插件会启用 `postcss-html-transform` 从而移除所有带 `*` 选择器 这会导致 `tailwindcss css var scope` 区域被移除导致一些样式,比如渐变等等功能失效 这种场景下,启用这个选项会再次重新注入整个 `tailwindcss css var scope` **`Default`** ```ts false ``` #### Defined in [types.ts:373](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L373) --- ### inlineWxs • `Optional` **inlineWxs**: `boolean` **`Experiment`** 实验性质,有可能会改变 **`Description`** 是否转义 `wxml` 中内联的 `wxs` > tip: 记得在 `tailwind.config.js` 中,把 `wxs` 这个格式加入 `content` 配置项,不然不会生效 **`Example`** ```html // 我是内联wxs // 下方的类名会被转义 var className = "after:content-['我是className']" module.exports = { className: className } ``` **`Default`** ```ts false ``` #### Defined in [types.ts:359](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L359) --- ### jsEscapeStrategy • `Optional` **jsEscapeStrategy**: `"regenerate"` \| `"replace"` **`Version`** `^2.7.0` **`Description`** js 字面量以及模板字符串的转义替换模式 - `regenerate` 模式,为需要转义的字面量,重新生成相同语义的字符串, (默认的传统模式) - `replace` 模式,为在原版本字符串上直接精准替换, (`2.7.0+` 新增) 如果用一个比喻来形容,那么 `regenerate` 类似于创造一个双胞胎,而 `replace` 模式就类似于一把精准的手术刀 > `replace` 模式已经在 `2.8.0` 版本中,成为默认模式,另外使用这个模式之后,生成相关的参数,比如 `minifiedJs` 就会失效了。 **`Default`** ```ts 'regenerate' ``` #### Defined in [types.ts:402](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L402) --- ### jsMatcher • `Optional` **jsMatcher**: `string` \| `string`[] \| (`name`: `string`) => `boolean` **`Description`** 匹配编译后 `js` 文件进行处理的方法,支持 `glob` by [micromatch](https://github.com/micromatch/micromatch) #### Defined in [types.ts:129](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L129) --- ### jsPreserveClass • `Optional` **jsPreserveClass**: (`keyword`: `string`) => `undefined` \| `boolean` #### Type declaration ▸ (`keyword`): `undefined` \| `boolean` ##### Parameters | Name | Type | | :-------- | :------- | | `keyword` | `string` | ##### Returns `undefined` \| `boolean` **`Version`** `^2.6.1` **`Description`** 当 `tailwindcss` 和 `js` 处理的字面量撞车的时候,配置此选项可以用来保留js字面量,不进行转义处理。返回值中,想要当前js字面量保留,则返回 `true`。想要转义则返回 `false/undefined` **`Default`** 保留所有带 `*` js字符串字面量 #### Defined in [types.ts:380](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L380) --- ### mainCssChunkMatcher • `Optional` **mainCssChunkMatcher**: `string` \| `string`[] \| (`name`: `string`, `appType?`: [`AppType`](../#apptype)) => `boolean` **`Description`** `tailwindcss css var inject scope` 的匹配方法,用于处理原始变量和替换不兼容选择器。可以不传,但是遇到某些 `::before/::after` 选择器注入冲突时,建议传入参数手动指定 css bundle 文件位置 #### Defined in [types.ts:134](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L134) ### minifiedJs • `Optional` **minifiedJs**: `boolean` **`Description`** 是否压缩 js (`process.env.NODE_ENV` 为 `production` 时默认开启) **`Default`** ```ts process.env.NODE_ENV === 'production' ``` #### Defined in [types.ts:290](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L290) --- ### onEnd • `Optional` **onEnd**: () => `void` #### Type declaration ▸ (): `void` ##### Returns `void` **`Description`** 结束处理时调用 #### Defined in [types.ts:222](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L222) --- ### onLoad • `Optional` **onLoad**: () => `void` #### Type declaration ▸ (): `void` ##### Returns `void` **`Description`** plugin apply 初调用 #### Defined in [types.ts:206](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L206) --- ### onStart • `Optional` **onStart**: () => `void` #### Type declaration ▸ (): `void` ##### Returns `void` **`Description`** 开始处理时调用 #### Defined in [types.ts:210](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L210) --- ### onUpdate • `Optional` **onUpdate**: (`filename`: `string`, `oldVal`: `string`, `newVal`: `string`) => `void` #### Type declaration ▸ (`filename`, `oldVal`, `newVal`): `void` ##### Parameters | Name | Type | | :--------- | :------- | | `filename` | `string` | | `oldVal` | `string` | | `newVal` | `string` | ##### Returns `void` **`Description`** 匹配成功并修改文件内容后调用 #### Defined in [types.ts:218](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L218) --- ### replaceUniversalSelectorWith • `Optional` **replaceUniversalSelectorWith**: `string` \| `false` **`Issue`** **`Default`** ```ts 'view' ``` **`Description`** 把`css`中的全局选择器 **`*`** 替换为指定值,默认替换为 `'view'`,设置为 `false` 时不进行替换,此时小程序会由于不认识`*`选择器而报错 #### Defined in [types.ts:191](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L191) --- ### supportCustomLengthUnitsPatch • `Optional` **supportCustomLengthUnitsPatch**: `boolean` \| [`ILengthUnitsPatchOptions`](ILengthUnitsPatchOptions.md) **`Deprecated`** **`Issue`** **`Description`** 自从`tailwindcss 3.2.0`对任意值添加了长度单位的校验后,小程序中的`rpx`这个`wxss`单位,由于不在长度合法名单中,于是被识别成了颜色,导致与预期不符,详见:[issues/110](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/110)。所以这个选项是用来给`tailwindcss`运行时,自动打上一个支持`rpx`单位的补丁。默认开启,在绝大部分情况下,你都可以忽略这个配置项,除非你需要更高级的自定义。 > 目前自动检索存在一定的缺陷,它会在第一次运行的时候不生效,关闭后第二次运行才生效。这是因为 nodejs 运行时先加载好了 `tailwindcss` 模块 ,然后再来运行这个插件,自动给 `tailwindcss` 运行时打上 `patch`。此时由于 `tailwindcss` 模块已经加载,所以 `patch` 在第一次运行时不生效,`ctrl+c` 关闭之后,再次运行才生效。这种情况可以使用: ```diff "scripts": { + "postinstall": "weapp-tw patch" } ``` 使用 `npm hooks` 的方式来给 `tailwindcss` 自动打 `patch` #### Defined in [types.ts:279](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L279) --- ### tailwindcssBasedir • `Optional` **tailwindcssBasedir**: `string` **`Version`** `^2.9.3` **`Description`** 用于指定路径来获取 tailwindcss 上下文,一般情况下不用传入,使用 linked / monorepo 可能需要指定具体位置,路径通常是目标项目的 package.json 所在目录 #### Defined in [types.ts:425](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L425) --- ### wxsMatcher • `Optional` **wxsMatcher**: `string` \| `string`[] \| (`name`: `string`) => `boolean` **`Experiment`** 实验性质,有可能会改变 **`Description`** 各个平台 `wxs` 文件的匹配方法,可以设置为包括微信的 .wxs,支付宝的 .sjs 和 百度小程序的 .filter.js > tip: 记得在 `tailwind.config.js` 中,把 `wxs` 这个格式加入 `content` 配置项,不然不会生效 **`Default`** ```ts ()=>false ``` #### Defined in [types.ts:337](https://github.com/sonofmagic/weapp-tailwindcss/blob/54db673b/src/types.ts#L337) --- ## 使用 arbitrary values `arbitrary values` 是 `tailwindcss v3` 的重要更新内容,幸运的是你使用了本插件。 使得你可以使用 `tailwindcss v3` 强大的 `arbitrary values` 功能。 比如: ```html bg-[#fafa00] bg-[#098765] p-[20px] -mt-2 mb-[-20px] margin的jit 不能这么写 -m-[20px] w-[300rpx] text-black text-opacity-[0.19] min-w-[300rpx] max-h-[100px] text-[20px] leading-[0.9] max-w-[300rpx] min-h-[100px] text-[#dddddd] Hello border-[10px] border-[#098765] border-solid border-opacity-[0.44] 1 2 3 ``` or `@apply` ```html ``` 详见 [tailwindcss/using-arbitrary-values 章节](https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values) --- ## js 中的精确转化与忽略 默认对所有 `jsx`、`js`、`wxml`、`wxss` 中出现的 `tailwindcss` 运行时工具类进行转化,如果不需要转化可以使用 `weappTwIgnore` 标识符来进行忽略: 例如: ```js classArray // weappTwIgnore 就是 String.raw ,所以它的结果就是后面字符串的结果 const weappTwIgnore = String.raw const classArray = [ 'text-[30rpx]', weappTwIgnore`bg-[#00ff00]` ] ``` 此时只有 `'text-[30rpx]'` 会被转化,`'bg-[#00ff00]'` 会被忽略。 > 默认情况下仅会忽略与 `weappTwIgnore` 有直接关系的标记模板,例如从包里导入后重命名、或在同一文件里一路别名过去的写法。简单的 `String.raw` 别名会继续参加转译,防止误杀。 如果需要自定义别名,可以包装一层函数并在配置里显式加入该别名,例如: ```js const alias = (...args) => String.raw(...args) // 在 ignoreTaggedTemplateExpressionIdentifiers 中加入 'alias' ``` 或者直接使用 `ignoreTaggedTemplateExpressionIdentifiers` 配置追加其它标识符。 --- ## weapp-tailwindcss 导出总览 > 本文根据 `packages/weapp-tailwindcss/package.json` 的 `exports` 字段整理,帮助你在不同框架/构建场景中快速定位合适的入口文件。 weapp-tailwindcss 同时提供 ESM 与 CommonJS 入口,并内置多个二级导出以适配 webpack、Vite、Gulp、Tailwind 宏等不同组合。下面按用途分类进行说明。 ## 核心插件入口 | 导出路径 | 说明 | 典型用法 | | --- | --- | --- | | `weapp-tailwindcss` | 聚合入口:`createPlugins`、`UnifiedWebpackPluginV5`、`UnifiedViteWeappTailwindcssPlugin` 等常用工厂齐备 | `import { UnifiedWebpackPluginV5 } from 'weapp-tailwindcss'` | `weapp-tailwindcss/webpack` | webpack@5 适配入口(uni-app CLI、mpx、原生小程序等) | `const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack')` | `weapp-tailwindcss/webpack4` | webpack@4 适配入口(旧版 Taro/uni-app 项目) | `import { UnifiedWebpackPluginV4 } from 'weapp-tailwindcss/webpack4'` | `weapp-tailwindcss/vite` | Vite 插件入口(Taro Vite、weapp-vite 等) | `import { UnifiedViteWeappTailwindcssPlugin } from 'weapp-tailwindcss/vite'` | `weapp-tailwindcss/core` | 暴露 `createContext` 等底层 API,可自定义 transform 流程 | `import { createContext } from 'weapp-tailwindcss/core'` ## 配置、工具与周边 | 导出路径 | 说明 | 场景 | | --- | --- | --- | | `weapp-tailwindcss/defaults` | 默认插件配置、补丁行为等常量 | 查看/复用默认选项 | `weapp-tailwindcss/presets` | 官方预设集合(差异化策略、Tailwind 配置等) | 扩展或组合默认行为 | [`weapp-tailwindcss/reset`](./reset) | 内置默认 `button` reset,可通过 `buttonReset` 选项禁用或改写选择器 | `@plugin 'weapp-tailwindcss/reset';` | `weapp-tailwindcss/types` | TypeScript 类型定义便捷入口 | `import type { UserDefinedOptions } from 'weapp-tailwindcss/types'` | `weapp-tailwindcss/escape` | `replaceWxml`、`isAllowedClassName` 等字符串处理工具 | 单独处理模板/字符串时复用 | `weapp-tailwindcss/postcss-html-transform` | 针对 HTML/WXML 的 PostCSS 转换器 | 自定义 PostCSS 流程 | `weapp-tailwindcss/css-macro` | Tailwind CSS 宏工具(JS/TS) | 在代码中动态生成原子类 | `weapp-tailwindcss/css-macro/postcss` | 宏工具的 PostCSS 版本 | 构建期宏替换 | `weapp-tailwindcss/gulp` | Gulp 插件入口 | 传统 Gulp 构建链集成 | `weapp-tailwindcss/package.json` | 包元数据 | 读取版本号等信息 ## 样式资源 | 导出路径 | 说明 | 引用示例 | | --- | --- | --- | | `weapp-tailwindcss/index.css` (`weapp-tailwindcss/index`) | 默认 runtime 样式(==推荐直接引用==) | `@import 'weapp-tailwindcss/index.css';` | `weapp-tailwindcss/preflight.css` (`weapp-tailwindcss/preflight`) | 小程序专用 Preflight | `@import 'weapp-tailwindcss/preflight.css';` | `weapp-tailwindcss/theme.css` (`weapp-tailwindcss/theme`) | 主题变量定义 | `@import 'weapp-tailwindcss/theme.css';` | `weapp-tailwindcss/utilities.css` (`weapp-tailwindcss/utilities`) | 原子类集合 | `@import 'weapp-tailwindcss/utilities.css';` | `weapp-tailwindcss/with-layer.css` (`weapp-tailwindcss/with-layer`) | layer 版样式,适配 Tailwind v4 layer 写法 | `@import 'weapp-tailwindcss/with-layer.css';` | `weapp-tailwindcss/uni-app-x.css` (`weapp-tailwindcss/uni-app-x`) | uni-app x 定制样式 | `@import 'weapp-tailwindcss/uni-app-x.css';` | `weapp-tailwindcss/css` | 指向 `css/index.css`,兼容旧目录结构 | `@import 'weapp-tailwindcss/css';` > ⚠️ 注意:部分构建工具(例如 `postcss-import`、mpx CLI)在解析 `@import 'weapp-tailwindcss'` 时可能回退到 JS 入口(`dist/index.js`),并抛出 “Unknown word "use strict"”。请显式写出 `index.css`,或在本地创建中转 `app.css` 后再导入。 ## 其他导出 - `weapp-tailwindcss/*`:保留通配符路径,兼容历史文件结构;建议优先使用上表列出的稳定入口。 - 所有 JS 模块同时提供 `import` 与 `require` 两种格式,TS 项目结合 `types` 入口即可获得完整声明。 想进一步了解各模块暴露的 API,可以查阅 [`weapp-tailwindcss` API 总览](../api/index.md) 或直接阅读对应类型定义与源码。 --- ## Index id: options/index slug: /options sidebar_position: 0 title: 选项概览 description: weapp-tailwindcss 可用配置概览 该章节的详细配置已经迁移至 [API / 配置项文档](/docs/api/interfaces/UserDefinedOptions)。 - [允许的类名写法](/docs/options/arbitrary-values) - [注释用法与忽略开关](/docs/options/comments) 如果你在升级过程中需要参考原先的配置写法,请参见上方链接。 --- ## `weapp-tailwindcss/reset` `weapp-tailwindcss/reset` 内置了一组常用组件的 reset 规则,默认会: - 清除所有 `button` 的原生样式(padding / 颜色 / border 等),让它的表现和 `view` / `text` 一致; - 将 `` / `` 统一为 `display: block`,并限制为 `max-width: 100%`、`height: auto`。 你可以通过选项控制是否注入这些规则、改写选择器 / 声明,甚至追加自定义 reset。 > ℹ️ 当你传入 `.class` / `#id` 作为选择器时,插件会自动转换为 `[class~="class"]` / `[id="id"]`,确保它们仍属于 base layer,不会破坏其他层级。 ## 可用选项 - `buttonReset?: false | ResetConfig` - `imageReset?: false | ResetConfig` - `extraResets?: ResetConfig[]` ```ts interface ResetConfig { selectors?: string[] // 支持元素 / 类名 / ID declarations?: Record pseudo?: Record // 注入到 ::after } ``` 设置为 `false` 即可关闭对应默认 reset;当提供类名 / ID 时会自动转换为 `[class~="foo"]` / `[id="bar"]`。 ## Tailwind CSS v3 用法 ```ts title="tailwind.config.ts" export default { plugins: [ // 默认注入 button/image reset reset(), // 完全自定义 reset({ buttonReset: { selectors: ['.wx-reset-btn', '#primary-btn'], declarations: { padding: '0', backgroundColor: 'transparent', }, pseudo: { border: 'none', }, }, imageReset: { selectors: ['.wx-reset-image'], declarations: { display: 'inline-block', borderWidth: '0', }, }, extraResets: [ { selectors: ['.wx-reset-view'], declarations: { display: 'block', borderWidth: '0', }, pseudo: { borderColor: 'transparent', }, }, ], }), ], } ``` 关闭默认 reset: ```ts reset({ buttonReset: false, imageReset: false, }) ``` ## Tailwind CSS v4 用法 在入口 CSS 中通过 `@plugin` 注册即可: ```css title="app.css" @plugin 'weapp-tailwindcss/reset'; @plugin 'weapp-tailwindcss/reset' ({ buttonReset: false, imageReset: { selectors: ['.wx-reset-image'], declarations: { display: 'inline-block', }, }, extraResets: [ { selectors: ['.list-reset'], declarations: { listStyle: 'none', margin: '0', padding: '0' }, }, ], }); @import 'tailwindcss/utilities'; ``` 同样可以通过 `buttonReset: false` / `imageReset: false` 精准控制需要的 reset。`extraResets` 允许你一次性追加多个自定义规则。*** --- ## 从 v1 迁移到 v2 在 `2.x` 版本中,可以把之前使用的 `webpack` 插件,全部更换为 `UnifiedWebpackPluginV5` 插件,不过 `vite` 插件的导出有一些小变化: `1.x`: ```js ``` `2.x`: ```js // UnifiedViteWeappTailwindcssPlugin 就是新的插件 ``` 另外新的 `UnifiedWebpackPluginV5` 可以直接从 `weapp-tailwindcss-webpack-plugin` 引入,同时在新的 `UnifiedWebpackPluginV5` 中,之前所有的配置项都被继承了过来,只需要用它直接替换原先插件即可。 另外不要忘记把: ```json "scripts": { + "postinstall": "weapp-tw patch" } ``` 添加进你的 `package.json` 里,然后清除原先的打包缓存之后重新打包运行。 --- ## 从 v2 迁移到 v3 v3 版本相比于 v2, 主要是删去一些过时的功能,配置项,同时会改变插件的默认值,使得整体插件变得更易用,更容易安装 假如你没有用到什么复杂自定义配置,那么完全可以平滑升级上来。 ## 配置项改动 ### 删除的配置项 - 删去 `replaceUniversalSelectorWith` 选项,使用 `cssSelectorReplacement.universal` 来代替,后者参数覆盖前者 - 删去 `minifiedJs` 选项,现在完全遵从用户的配置,用户压缩就压缩,反之亦然 - 删去 `jsEscapeStrategy` 选项,现在默认只有一种模式 `replace`/ 不再提供 `regenerate` 模式 - 删去 `customReplaceDictionary` 的 `complex` 模式,只内置 `simple` 模式 (你如果还要 `complex` 模式 ,可以从 `@weapp-core/escape` 引入,再传入 `customReplaceDictionary` 配置项即可) - `cssMatcher`/`htmlMatcher`/`jsMatcher`/ `mainCssChunkMatcher` / `wxsMatcher` 不再能够传入 `glob` 表达式(例如`**/*.html`),现在都是传入一个方法: `(name: string) => boolean`。要兼容原先的 `glob` 表达式,你可以通过 `minimatch` 把 `glob` 表达式转化成正则来兼容原先的配置 - `cssPreflightRange` 只存在一种模式,为 `all`, 之前的 `view` 选项交给 `cssSelectorReplacement.universal` 进行托管 ### 增加的配置项 - `rem2rpx` : 类型 `rem2rpx?: boolean | rem2rpxOptions` rem 转 rpx 配置,默认 **不开启**,可传入配置项,配置项见 这个配置项代表插件内置了 `postcss-rem-to-responsive-pixel` ,不过默认不开启,传入一个 `true` 相当于传入配置: ```js { // 32 意味着 1rem = 32rpx rootValue: 32, // 默认所有属性都转化 propList: ['*'], // 转化的单位,可以变成 px / rpx transformUnit: 'rpx', } ``` 当然你也可以传入 `rem2rpxOptions` 这样一个 `object` 进行自定义 #### 为什么默认不开启? 1. 为了从 `2.x` 版本可以平滑的过渡到 `3.x` 2. 从我的视角看,内置 `postcss` 插件功能,虽然整体集成度上更高了,但是对其他开发者可能不是那么自由,比如在 `2.x` 时候,由于 `postcss-rem-to-responsive-pixel` 是外置的,所以开发者可以自由的决定它的加载顺序和加载逻辑,但是内置之后都是我决定的。不过内置好处也有,就是开箱即用 ### 增强的配置项 - `cssChildCombinatorReplaceValue`, `cssSelectorReplacement.root`,`cssSelectorReplacement.universal` 现在都可以接受字符串数组了,它们可以自动展开,防止选择器格式化错误问题 ### 修改的默认值 - `cssPreflightRange` 从 `'view'` 变为 `undefined`, 现在 `all` 的作用变成了在 `tailwindcss` 变量注入区域的选择器,添加一个 `:not(not)` 的选择器作为全局选择器的替代 - `cssSelectorReplacement.universal` 从 `'view'` 变为 `['view', 'text']`, 这意味着 `*` 选择器会被展开成 `view,text` 以及对应方式 - `cssChildCombinatorReplaceValue` 从 `'view + view'` 变为 `['view']` - `replaceUniversalSelectorWith`,`jsEscapeStrategy`,`minifiedJs` 选项被删除,所以不再保留默认值 ### 现在选项合并,数组默认行为变为覆盖,原先是合并 ```js const options = getOptions(input,defaults) defaults: ['a','b'], input:['c'] // before: options == ['a','b','c'] // after: options == ['c'] ``` --- ## 从 v3 迁移到 v4 `tailwindcss@4` 改动较大,直接变成了一个样式预处理器,和 `sass` / `less` 类似,所以你不应该让 `tailwindcss@4` 和 `sass`, `less` 一起使用。 所以关于这方面的改动会比较多, 可能你需要把很多 `.scss`,`.less` 文件后缀改成 `.css` `v4` 版本相比于 `v3`, 影响功能的重大变动较少,假如你没有用到什么复杂自定义配置,那么完全可以平滑升级上来。 ## 重大变更 1. 移除 `jsAstTool` 的 `ast-grep` 支持,现在全部使用 `babel` 进行 `ast` 处理,假如你使用了这个配置,你可以保持不动,或者你可以把它删掉。 ## 特性更新 1. 添加 `@weapp-tailwindcss/merge` 包作为小程序版本的 `tailwind-merge` 1. 增加 `ignoreTaggedTemplateExpressionIdentifiers` 和 `ignoreCallExpressionIdentifiers` 配置,用于和 `@weapp-tailwindcss/merge` 结合起来使用 1. 在安装 `@weapp-tailwindcss/merge` 时自动设置 `ignoreCallExpressionIdentifiers` 为 `['twMerge', 'twJoin', 'cva']` 默认不进行转义里面的字面量 1. 更改 `cssChildCombinatorReplaceValue` 默认值从 `['view']` -> `['view', 'text']` 为了更好的小程序开发体验 ## 重构 1. 移除 `@babel/generator` 依赖 2. 去除 `weapp-tailwindcss/postcss` 导出,代替可直接安装使用 `@weapp-tailwindcss/postcss` 3. 增加 `weapp-tailwindcss/escape` 来取代 `weapp-tailwindcss/replace`, `weapp-tailwindcss/replace` 导出被移除 4. 项目 `monorepo` 区分包 5. 项目打包方式从 `rollup` 变为 `tsup` ## pnpm@10.x 假如你已经升级到了 `pnpm@10.x`,安装依赖后需要执行 `pnpm approve-builds weapp-tailwindcss` 将其加入 `onlyBuiltDependencies`,以便 `postinstall` 等 `npm hook` 能正常运行 --- ## css 中使用 @apply 警告问题 ## 解决方案 我们以 `vscode` 为例 1. 创建 `.vscode` 目录 然后在目录下创建 `settings.json` 和 `tailwind.json` 2. 修改 `settings.json` 添加 ```jsn { "css.customData": [".vscode/tailwind.json"] } ``` 3. 修改 `tailwind.json` ````json { "version": 1.1, "atDirectives": [ { "name": "@tailwind", "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind" } ] }, { "name": "@apply", "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#apply" } ] }, { "name": "@responsive", "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#responsive" } ] }, { "name": "@screen", "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#screen" } ] }, { "name": "@variants", "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#variants" } ] } ] } ```` 这样 `@apply` 就不会报错了。 ## 参考文档 https://github.com/tailwindlabs/tailwindcss/discussions/5258 --- ## 默认盒模型(box-sizing)问题 `Tailwindcss` 默认会把所有的元素的盒模型,设置为 `border-box` 但是一些组件库,比如 `wot-design-uni`,实现使用的是 `content-box` ,一切换到 `border-box` 高度塌陷了, 所以会导致部分显示效果错乱。 > `box-sizing: border-box;` 这行样式是在 'tailwindcss/base' 中的,所以你禁用这行代码,感觉上生效了,但是这样不是很好的解决方案。 假如你要从插件层面解决问题,只要做出如下修改: ```js uvtw({ // 添加这一行配置即可 cssPreflight: { 'box-sizing': false, }, }), ``` 这样就可以把 `box-sizing` 这个样式给去掉,但是你这样就要去评估原先那些依赖盒模型的样式是否会受到影响: 比如 `w-2`, `h-4` 都是盒子模型潜在的影响。 ## 参考文档 https://tw.icebreaker.top/docs/api/interfaces/UserDefinedOptions#csspreflight https://github.com/sonofmagic/weapp-tailwindcss/issues/604 --- ## CSS 变量失效问题 ## 问题的现象 在使用 `taro` 或者 `uni-app` 中,可能你会遇到 `CSS` 变量失效问题 具体表现,就是你在使用下列类名的时候,小程序的模拟器上,不会出来任何的背景颜色: ```jsx ``` ## 原因以及解决方案 导致这个的原因,是由于全局的 `tailwindcss` 变量丢失,没有注入进 `App` 引起的。参阅[什么是 `tailwindcss` 全局变量注入区域](#什么是全局-tailwindcss-变量注入区域)。 例如在 `taro` 中和 `@tarojs/plugin-html` 一起使用,就会出现这个问题,这是因为 `@tarojs/plugin-html` 把 `tailwindcss` 变量区域直接删除了。 遇到这样的问题,解决方案也很简单,只需要给插件传入 `injectAdditionalCssVarScope: true` 参数即可。 代码片段和配置详情详见[和 NutUI 一起使用](./use-with-nutui)。 ## 设置成功后的效果 设置成功之后的效果如下所示,可以观察一下左侧的效果,和右下角的 `inspect` 面板作为参考 ![小程序生效图片](./css-vars.jpg) ## 什么是全局 `tailwindcss` 变量注入区域 在你的 `app.wxss` 样式产物文件中(例如:`taro` 或 `uni-app` 的 `dist` 文件夹内),都有这样一块区域。 上面的这个问题,就是这块变量注入区域没了导致的。 ```css ::before,::after { --tw-content: ""; } view,text,::before,::after { --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; } ::backdrop { --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; } ``` 这块区域就是你 `@tailwind base;` 展开后, `tailwindcss` 注入的全局变量块。 丢失这块区域会导致 `bg-gradient-to-r` 这种依赖 `css` 变量的原子类失效。 --- ## 组件外部样式类(externalClasses)的支持 :::warning 快速结论 如果在自定义组件里写了 `my-class="bg-[#fafa00] text-[40px]"`,但调试器里看到变成了 `my-class="bg- #fafa00 text- 40px"` 并导致样式失效,请在插件配置中为 `customAttributes` 显式声明 `my-class`。 ::: ## 典型现象 在封装原生自定义组件时经常会用到外部样式类(`externalClasses`)。例如: ```js /* custom-component.js */ Component({ externalClasses: ['my-class'], }) ``` 在页面里直接使用 `tailwindcss` 工具类: ```html ``` 小程序开发者工具会把 `my-class` 中的样式拆开成 `bg- #fafa00 text- 40px`,最终导致样式全部失效。 ## 根本原因 插件默认只会转译 `class` 和 `hover-class`。外部样式类属于自定义属性,如果没有配置 [`customAttributes`](/docs/api/interfaces/UserDefinedOptions#customattributes),就不会被识别处理。 ## 解决方案 在插件选项里增加自定义属性的映射即可: ```js customAttributes: { '*': ['my-class'], } ``` - `*` 代表匹配所有标签,你也可以改成具体的标签名或正则表达式。 - 支持传入 `Object` 或 `Map`,用于灵活地映射标签与属性的关系。 :::tip 多个外部样式类 如果组件同时暴露 `['my-class', 'title-class']`,直接把它们都写进同一个数组即可。 ::: ## 扩展阅读 - 微信官方文档:[外部样式类](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#外部样式类) - 插件配置项说明:[customAttributes](/docs/api/interfaces/UserDefinedOptions#customattributes) > 使用正则进行自定义匹配标签时,需要传入一个 `Map`,其中正则作为 `key`,数组作为 `value`。 --- ## Tailwindcss 格式化 ## prettier 插件 > 这是官方提供的包, 使用并在 `prettier` 注册 [`prettier-plugin-tailwindcss`](https://www.npmjs.com/package/prettier-plugin-tailwindcss) ## eslint 插件 使用并在 `eslint` 注册 [`eslint-plugin-tailwindcss`](https://www.npmjs.com/package/eslint-plugin-tailwindcss) --- ## group 和 peer 使用限制 ## group 使用注意事项 在 `tailwindcss` 中,我们常常会这样写: ```html group tapped ``` 这样在最外层的 `div` 进入 `hover` 状态时,内部的子元素中的 `group-hover` 就会生效,从而改变样式。 然而,在小程序中,伪类 `:hover` 是不起作用的,取而代之的是 `hover-class` 这样一个属性,所以这种情况我们可以这么写: ```html group tapped ``` 这样在 `group` 进入 `hover` 状态时,`bg-yellow-400` 就会生效了。 相关 issue:[#14](https://github.com/sonofmagic/uni-app-vite-vue3-tailwind-vscode-template/issues/14) ## peer 使用注意事项 我们一般使用 `peer` 来标记一个元素,再使用各种 `peer-*` 来让它后续兄弟节点的样式生效。这些主要生成大量包含 `~` [后续兄弟选择器](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Subsequent-sibling_combinator) 的 `css` 代码。 然而很不幸,在小程序中 `~` 这个选择器非常容易报错,它前面只能跟 `class` 选择器,不能跟伪类,不然会报错: ```scss // 报错 // .xxx:invalid~.xxx-invalid:visible { // visibility: visible; // } // 不报错 .xxx~.xxx-invalid:visible { visibility: visible; } ``` 所以你要么就不使用 `peer` 特性,如果需要此特性请使用内嵌 `class` 的方式来使用: ```html ``` > 前一个方块按压后进入 `hover` 状态 ,后面那个就变成红色。 ## 出现 unexpected token "~" 错误 出现这个错误后,你应该把对应报错的相关 `peer`、`peer-*` 删掉,注意是删掉,请不要注释,因为 `tailwindcss` 中也会从注释中提取字符串,所以注释掉是没有效果的。 删掉之后,你需要重新启动一下你的应用(例如 `yarn dev:weapp`),不然导致错误的 `css` 还是会存在,导致项目崩溃。 --- ## 常见问题 :::info 组件外部样式类必读 自定义组件使用 `externalClasses` 时样式被拆分?请先查看《[组件外部样式类(externalClasses)的支持](/docs/issues/externalClasses)》,按照文档里的 `customAttributes` 配置即可解决。 ::: ## 为什么我更改了 class 保存重新打包的时候热更新失效? [[#93](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/93)] 目前微信开发者工具会默认开启代码自动热重载 `compileHotReLoad` 功能,这个功能在原生开发中表现良好,但在 `uni-app` 和 `taro` 等的框架中,存在一定的问题,参见 [issues#37](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/37),所以如果你遇到了此类问题,建议关闭 `compileHotReLoad` 功能。 ## `disabled:opacity-50` 这类的 `tailwindcss` 工具类不生效? 这是由于微信小程序 `wxss` 选择器的原生限制,无法突破。参见 [issue#33](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/33)。 ## `background-image` 为什么不能使用本地路径? 微信小程序在 `wxss` 中禁止 `background-image` 引用本地文件,解析时会直接报 `do-not-use-local-path` 错误。因此像 bg-[url('/images/homebg.png')] 这样的写法无法生效。请改用以下任一方式: - 使用线上可访问的远程图片地址,例如 `bg-[url('https://example.com/bg.png')]` - 将资源转成 `base64` 后内联到 `background-image` - 改用 `` 组件渲染背景效果 选择合适方案后再通过 `tailwindcss` 编写样式,即可避免编译报错。 ## 和原生组件一起使用注意事项 假如出现原生组件引入报错的情况,可以参阅 [issue#35](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/35),忽略指定目录下的文件,跳过插件处理,例如 `uni-app` 中的 `wxcomponents`。 如何更改?在传入的配置项 `cssMatcher`,`htmlMatcher` 这类配置,来过滤指定目录或文件。 ## 编译到 h5 / app 注意事项 有些用户通过 `uni-app` 等跨端框架,不止开发成各种小程序,也开发为 `H5`,然而 `tailwindcss` 本身就兼容 `H5` 了。此时你需要更改配置,我们以 `uni-app` 为例: ```js const isH5 = process.env.UNI_PLATFORM === "h5"; // vue3 版本构建到 app, UNI_PLATFORM 的值为 app // vue2 版本为 app-plus const isApp = process.env.UNI_PLATFORM === "app-plus"; const WeappTailwindcssDisabled = isH5 || isApp; // 然后在 h5 和 app 环境下把 webpack plugin 和 postcss for weapp 给禁用掉 // 我们以 uni-app-vue3-vite 这个 demo为例 // vite.config.ts // vite 插件配置 const vitePlugins = [uni(),uvtw({ disabled: WeappTailwindcssDisabled })]; export default defineConfig({ plugins: vitePlugins }); // 同理 postcss 配置 // 假如不起作用,请使用内联postcss const plugins = [require('autoprefixer')(), require('tailwindcss')()]; if (!WeappTailwindcssDisabled) { plugins.push( require('postcss-rem-to-responsive-pixel')({ rootValue: 32, propList: ['*'], transformUnit: 'rpx' }) ); } module.exports = { plugins }; ``` ## 报错 TypeError: Cannot use 'in' operator to search for 'CallExpression' in undefined 遇到这个问题是由于 `babel` 相关的包之间的版本产生了冲突导致的,这种时候可以删除掉 `lock` 文件(`yarn.lock`、`pnpm-lock.yaml`、`package-lock.json`),然后重新安装即可。 ## taro webpack5 环境下,这个插件和外置额外安装的 `terser-webpack-plugin` 一起使用,会导致插件转义功能失效 相关 issue:[#142](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/142) 例如:`.h-4/6`、`!w-full` 正常会转义为`.h-4s6`、`.iw-full`,本插件失效后小程序开发者工具报编译错误 `.h-4\/6`、`.\!w-full`。 请压缩代码并不要使用[链接](https://docs.taro.zone/docs/config-detail/#terserenable)中的方法,太老旧了。 使用 `taro` 配置项里的的 `terser` 配置项,参见 [`terser` 配置项](https://docs.taro.zone/docs/config-detail#terser)。 > `terser` 配置只在生产模式下生效。如果你正在使用 `watch` 模式,又希望启用 `terser`,那么则需要设置 `process.env.NODE_ENV` 为 `production`。 也就是说,直接在开发 `watch` 模式的时候,设置环境变量 `NODE_ENV` 为 `production` 就行。 另外也可以不利用 `webpack` 插件压缩代码,去使用微信开发者工具内部的压缩代码选项。 ## 为什么 space-y-1 这类写法不起作用? 相关 issue:[#108](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/108) 考虑到小程序的组件 `shadow root` 实现方式,默认情况下 `space` 这一类带有子选择器的,只对 `view` 元素生效。 即选择器变成了 `.space-y-1 > view + view` 这时候解决方案有 3 种: - 组件外层套一层 `` 元素。 - `virtualHost` 解决方案,在自定义组件中添加 `options: { virtualHost: true }` 即可解决此问题。 - [`cssChildCombinatorReplaceValue`](/docs/api/interfaces/UserDefinedOptions#csschildcombinatorreplacevalue) 配置项 ## 使用 uni-app vite vue 注册插件时,发行到 h5 环境出现: [plugin:vite-plugin-uni-app-weapp-tailwindcss-adaptor] 'import' and 'export' may appear only with 'sourceType: "module"' (1:0) 错误 解决方案: ```js // 注意:打包成 h5 和 app 都不需要开启插件配置 const isH5 = process.env.UNI_PLATFORM === "h5"; // vue3 版本构建到 app, UNI_PLATFORM 的值为 app // vue2 版本为 app-plus const isApp = process.env.UNI_PLATFORM === "app-plus"; const WeappTailwindcssDisabled = isH5 || isApp; const vitePlugins = [uni(), uvwt({ disabled: WeappTailwindcssDisabled })]; ``` 即 `h5` 环境和 `app` 环境都不开启我这个插件,因为本来这 2 个环境就是 `tailwindcss` 支持的环境,没必要开启插件转义。 ## 使用 pnpm@8 插件注册失败问题 pnpm 8 这个版本改变了一些默认值,其中 `resolution-mode` 默认值变成了 `lowest-direct`。 这会导致所有的依赖,会被安装成你在 `package.json` 里注册的最低版本,这可能会造成一些问题。如何解决? 目录下创建一个 `.npmrc`,设置 `resolution-mode` 为 `highest`,然后重新安装, 或者,使用 `pnpm up -Li` 升级一下你 `package.json` 里的依赖包版本到最新即可。 ## uni-app 在从v1升级到v2的过程中,如果使用了云函数相关功能,编译到小程序会出现问题 解决方案参见: 相关 issue:[#74](https://github.com/sonofmagic/weapp-tailwindcss/issues/74#issuecomment-1573033475) ## uni-app vue2 中的 css 使用 @import 引入其他 css,导致在 `rpx` 在H5下不生效 需要添加并配置 `postcss-import`,参见 [issues/75](https://github.com/sonofmagic/weapp-tailwindcss/issues/75#issuecomment-1574592907)。 你可以查看源码中 `demo/uni-app` 相关的示例来进行配置。 ## 为什么使用 taro 写 jsx,js 时候,转义不生效? 这是因为 [patch](/docs/quick-start/this-plugin) 方法没有生效,这个指令是用来在运行时暴露 `tailwindcss` 上下文的,只有暴露成功,我们写的 `js` 里的样式,才会变精准转义,否则就会出现在 `jsx` 里写 `className` 不生效的情况。 ## monorepo 项目中 arbitrary values 写法无效? 这可能是由于 tailwindcss 包被提升,导致项目获取不到正确的 tailwind 上下文,有两种解决方案。 - 配置 [tailwindcssBasedir](https://tw.icebreaker.top/docs/api/interfaces/UserDefinedOptions#tailwindcssbasedir) - 禁止 `tailwindcss` 包被提升,具体配置方法可以去查阅各包管理器的说明文档 --- ## 写在 js 中的 tailwindcss 任意值失效 `weapp-tailwindcss` 是允许你在 `js` 中编写任意值的,而且 `weapp-tailwindcss` 会自动帮你做好任意值的转译。 比如: ```js title="src/pages/index/index.js" const xs = { wrapper: 'px-[4px] h-[40px]', } ``` 那么在最终的产物中,编译结果会自动变为 ```js title="dist/pages/index/index.js" const xs = { wrapper: 'px-_4px_ h-_40px_', } ``` 但是你这个文件,必须被 `tailwindcss` 感知到,并从里面提取到这 `2` 个 `class`。`weapp-tailwindcss` 才能通过和 `tailwindcss` 的通信,来完成这 `2` 个 `class` 的转译。 所以你这个源文件必须在 `tailwindcss@3` 中的 `tailwind.config.js` 中被 `content` 配置包括。或者 `tailwindcss@4` 被 `@source` 包括到,那这个自动转译的流程才能走完。 否则就会出现 `js` 转译没有进行, 导致开发者工具中审查元素时,出现: ```html ``` 这种类名被切断的情况。 ## 解决方案 ### tailwindcss@3 检查你的 `tailwind.config.js` 中的 `content`,确认你出现类名被切断的源文件,被 `content` 包括。 文档地址: https://v3.tailwindcss.com/docs/configuration#content ### tailwindcss@4 检查你的 `@source`,确认你出现类名被切断的源文件,被 `@source` 包括。 文档地址: https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-registering-sources --- ## 在 monorepo 中使用 在 `monorepo` 由于存在 `hoist` 机制,可能会导致 `weapp-tailwindcss` 和 `tailwindcss` 通信受阻,这时候需要显式的去指定 `tailwindcss` 的路径 这里我们以 `taro@4` 的配置 `config/index.ts` 配置为例 ## Tailwindcss@3 ```ts const config = { webpackChain(chain) { chain.merge({ plugin: { install: { plugin: UnifiedWebpackPluginV5, args: [ { rem2rpx: true, // highlight-next-line tailwindcssBasedir: path.resolve(__dirname, '../'), }, ], }, }, }) }, } ``` ## Tailwindcss@4 ```ts const config = { webpackChain(chain) { chain.merge({ plugin: { install: { plugin: UnifiedWebpackPluginV5, args: [ { rem2rpx: true, // highlight-next-line cssEntries: [ // app.css 的路径 path.resolve(__dirname, '../src/app.css'), ], }, ], }, }, }) }, } ``` 使用这样的配置,就能在 `monorepo` 中使用了 --- ## 生成样式只作用于view和text标签 在微信小程序中,`darkMode` 设置为 `class`/ `selector` 后,`dark:className` 类选择器在 `button` 上无效,看生成样式只作用于 `view` 和 `text` 标签 这是由于小程序是不接受 `*` 这样一个选择器的。 默认情况下, `weapp-tailwindcss` 会把 `*` 选择器转化成 `view,text` 的选择器 这个配置可以通过 `cssSelectorReplacement.universal` 进行更改,从而适配更多标签。 详见 [cssSelectorReplacement.universal 文档](/docs/api/interfaces/UserDefinedOptions#universal) --- ## 原生头条小程序使用 TailWindCSS > 以下内容由使用 `weapp-tailwindcss` 的热心网友提供,十分感谢! ## 创建项目 创建项目 `test-miniapp`, 进入项目目录并初始化 `package.json` ```sh cd test-miniapp npm init -y ``` 新建小程序开发目录 `src`,对应的小程序代码,生成目标代码目录为 `dist` 此时目录结构如下所示: ``` test-miniapp -- src -- dist -- package.json ``` ## 安装 gulp 及插件 - 本地安装 `gulp` ```sh npm i -D gulp ``` - 安装 gulp 模块及插件 ```sh npm i -D gulp gulp-postcss gulp-plumber del@^6 ``` ## 安装与配置 tailwindcss - 安装 tailwindcss ```sh npm i -D tailwindcss@3 postcss autoprefixer ``` - 初始化 tailwindcss 配置文件 ```sh npx tailwindcss init ``` - 创建 `postcss.config.js` 并注册 `tailwindcss` ```js module.exports = { plugins: { tailwindcss: {}, // 假如框架已经内置了 `autoprefixer`,可以去除下一行 autoprefixer: {}, } } ``` - 配置 `tailwind.config.js` ```js /** @type {import('tailwindcss').Config} */ module.exports = { // 不在 content 包括的文件内的 class,不会生成对应的 css 工具类 content: ['./src/**/*.{ttml,js}'], theme: { extend: {}, }, plugins: [], } ``` - 代码引入 `tailwindcss`,打开 `src/app.ttss` ```css @tailwind base; @tailwind components; @tailwind utilities; ``` ## 配置 vscode 插件 ### Prettier - Code formatter 安装插件 ```sh npm i -D prettier prettier-plugin-tailwindcss ``` 配置 `prettier.config.js` ```js module.exports = { // 行尾加分号 semi: false, // 使用单引号 singleQuote: true, // 配置文件类型 overrides: [ { files: '*.ttml', options: { parser: 'html' }, }, { files: '*.ttss', options: { parser: 'css' }, }, ], plugins: ['prettier-plugin-tailwindcss'], } ``` 将小程序的文件包括进来,设置:`首选项->工作区->设置->扩展->Prettier->Prettier: Document Selectors` ```txt **/*.ttml **/*.ttss ``` - 字节小程序开发助手(微信小程序是这个:WXML - Language Service) ### Tailwind CSS IntelliSense 并配置:`首选项->工作区->设置->扩展->Tailwind CSS IntelliSense->Tailwind CSS: Include Languages` ``` 项:ttml,值:html ``` ### Gulp Tasks - Gulp Tasks 安装插件 ```sh npm i -D weapp-tailwindcss-webpack-plugin ``` 配置 `gulpfile.js`,需要注意的事,在面板执行 `serve` 后,即使后来停止了任务,程序里的监听 `watch` 也不会停,使得后续再启动 `serve` 后,会有多个监听 `watch` 和多个监听处理程序 `watchHandler`,重复处理文件。所以停止后再启动 `serve`,应该关闭 `vscode` 后重新打开 ```js const { src, dest, series, parallel, task, watch } = require('gulp') const postcss = require('gulp-postcss') const plumber = require('gulp-plumber') const path = require('path') const del = require('del') const tailwindcssGulp = require('weapp-tailwindcss-webpack-plugin/gulp') // 在 gulp 里使用, 先使用 postcss 转化 css, 触发 tailwindcss,然后转化 transformWxss,最后转化 transformJs, transformWxml const { transformJs, transformWxml: transformHtml, transformWxss: transformCss, } = tailwindcssGulp.createPlugins({ rem2rpx: true, }) const config = { srcDir: 'src', distDir: 'dist', cssExt: '.ttss', jsExt: '.js', htmlExt: '.ttml', } function transformCssFiles() { return src(`${config.srcDir}/**/*${config.cssExt}`) .pipe(plumber()) .pipe(postcss()) .pipe(transformCss()) .pipe(dest(`${config.distDir}`)) } function transformJsFiles() { return src(`${config.srcDir}/**/*${config.jsExt}`) .pipe(plumber()) .pipe(transformJs()) .pipe(dest(`${config.distDir}`)) } function transformHtmlFiles() { return src(`${config.srcDir}/**/*${config.htmlExt}`) .pipe(plumber()) .pipe(transformHtml()) .pipe(dest(`${config.distDir}`)) } function copyOtherFiles() { return src([ `${config.srcDir}/**/*`, `!${config.srcDir}/**/*${config.cssExt}`, `!${config.srcDir}/**/*${config.jsExt}`, `!${config.srcDir}/**/*${config.htmlExt}`, ]).pipe(dest(`${config.distDir}`)) } function promisify(task) { return new Promise((resolve, reject) => { if (task.destroyed) { resolve(undefined) return } task.on('finish', resolve).on('error', reject) }) } // type 取值: changed, added, deleted async function watchHandler(type, file) { if (type == 'deleted') { await del([ file.replace( `${config.srcDir}${path.sep}`, `${config.distDir}${path.sep}` ), ]) } else { const extName = path.extname(file) switch (extName) { case config.cssExt: await promisify(transformCssFiles()) break case config.jsExt: await promisify(transformCssFiles()) await promisify(transformJsFiles()) break case config.htmlExt: await promisify(transformCssFiles()) await promisify(transformHtmlFiles()) break default: await promisify(copyOtherFiles()) } } } function watchTask() { const watcher = watch([`${config.srcDir}/**/*`]) watcher .on('change', function (file) { console.log(`${file} is changed`) watchHandler('changed', file) }) .on('add', function (file) { console.log(`${file} is added`) watchHandler('added', file) }) .on('unlink', function (file) { console.log(`${file} is deleted`) watchHandler('deleted', file) }) } function clean() { return del(config.distDir, { force: true }) } const buildTasks = [ transformCssFiles, transformJsFiles, transformHtmlFiles, copyOtherFiles, ] // 注册服务任务 task('serve', series(...buildTasks, watchTask)) // 注册清除任务 task('clean', parallel(clean)) ``` --- ## rpx 任意值颜色或长度单位二义性与解决方案 ## 这是一个什么问题? 在不使用 `weapp-tailwindcss` 的情况下,你直接写这样的 `rpx` 写法: ```html ``` 最终它会生成这样的 `css`: ```css .text-\[32rpx\] { color: 32rpx; } ``` 为什么 `rpx` 这个好端端的长度单位,会变成颜色呢? 原因在于 `rpx` 不是一个标准的 `W3C` 规定的 `CSS` 长度单位,这是微信小程序自己定的 `WXSS` 单位。 ## 什么是 **二义性**? `tailwindcss` 中有些原子类具有 **二义性**,比如: - `text-[]` - `border-[]` - `bg-[]` - `outline-[]` - `ring-[]` 其中 `text-[]` 中的 `text-[16.16px]` 生成的 css 是 `font-size: 16.16px;`, 而 `text-[#123456]` 生成的 css 是 `color: #123456;`; 这就是原子类的 **二义性** --- 而 `tailwindcss` 在针对具有**二义性**的任意值写法: 这些会去**校验括号**内的任意值,是否为有效的 `CSS` 长度单位! 如果为 `true`,则生成出长度单位的 `css` 节点,反之则生成出颜色单位的 `css` 节点: ```css /* text-[16px] */ .text-\[16px\] { font-size: 16px } /* text-[#fafafa] */ .text-\[\#fafafa\] { --tw-text-opacity: 1; color: rgb(250 250 250 / var(--tw-text-opacity)) } ``` 那么问题来了,`rpx` 在单位校验的时候,由于不认识这个单位,导致单位无效所有被分到了颜色组。 ```css /* text-[32rpx] */ .text-\[32rpx\] { --tw-text-opacity: 1; color: 32rpx; } ``` 所以造成了这个问题!那么如何解决呢? ## 目前插件的解决方案 目前,我做了一个解决方案,我在 `weapp-tailwindcss` 植入了一个逻辑,使得插件可以通过分析 `tailwindcss` 运行时代码,来打上支持 `rpx` 单位的补丁,使得 `tailwindcss` 支持这样的写法,生成出长度单位的 `css`。 这也是为什么要让大家去执行 `weapp-tw patch` 的原因。 这个解决方案,最新的 `3.x` 和 `4.x` 版本,都是有效的。 ## 强制CSS单位的解决方案 我们可以在使用这些带有**二义性**的单位的时候,可以通过 `length` 或 `color` 这种的前缀来指定它应该是什么,例如: ```html ... ... ... ... ``` 这样就通过指定的方式,直接跳过了长度单位校验,生成出长度单位的 `css` 了! ```css .text-\[length\:22rpx\] { font-size: 22rpx } ``` 同样你可以使用这 2 个前缀来指定 `css` 变量的生成形式: ```html ... ... ``` ## 参见 - `tailwindcss` 中的[添加自定义样式](https://tailwindcss.com/docs/adding-custom-styles#resolving-ambiguities) - 相关 Issue:[#110](https://github.com/sonofmagic/weapp-tailwindcss/issues/110)、[#110](https://github.com/sonofmagic/weapp-tailwindcss/issues/109) --- ## `Tarojs` 中使用 `terser` 压缩代码 在 `taro` `webpack5` 环境下,这个插件和外置额外安装的 `terser-webpack-plugin` 一起使用,会导致插件转义功能失效 相关 issue:[#142](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/142) ## 现象 例如:`.h-4/6`、`!w-full` 正常会转义为`.h-4s6`、`.iw-full`,本插件失效后小程序开发者工具报编译错误 `.h-4\/6`、`.\!w-full`。 ## 解决方案 请压缩代码并不要使用[链接](https://docs.taro.zone/docs/config-detail/#terserenable)中的方法,太老旧了。 使用 `taro` 配置项里的的 `terser` 配置项,参见 [`terser` 配置项](https://docs.taro.zone/docs/config-detail#terser)。 > `terser` 配置只在生产模式下生效。如果你正在使用 `watch` 模式,又希望启用 `terser`,那么则需要设置 `process.env.NODE_ENV` 为 `production`。 也就是说,直接在开发 `watch` 模式的时候,设置环境变量 `NODE_ENV` 为 `production` 就行。 另外也可以不利用 `webpack` 插件压缩代码,去使用微信开发者工具内部的压缩代码选项。 ## 配置参考 ```ts title="config/index.ts" { terser: { enable: true, config: { // 相关配置项 }, }, } ``` 然后你想要在开发时,就生效,那就需要传入 `NODE_ENV=production` 环境变量,例如: ```json title="package.json" { "scripts":{ "dev:weapp": "cross-env NODE_ENV=production npm run build:weapp -- --watch", } } ``` `cross-env` 没有安装的可以安装一下 --- ## H5 端原生 toast 样式偏移问题 在使用 `tailwindcss` 的时候,编译到 `h5` 平台,使用 `uni.toast` / `taro.toast` 时,出现下列的效果 ![](./toast-svg-bug.jpg) `tailwindcss` 的 `base` 中的 `preflight` 影响这个 `uni.toast` 的样式 这是由于 `preflight.css` 中默认会添加下方的样式 ```css img, svg, video, canvas, audio, iframe, embed, object { display: block; /* 1 */ vertical-align: middle; /* 2 */ } ``` 这导致了 `svg` 变成了 `display: block;` 的状态 解决方案也非常的简单, 在 `app.wxss` 使用样式进行覆盖: ```scss .uni-toast{ svg { display: initial; // 重新初始化 uni-toast 里的样式进行覆盖 覆盖 } } ``` 假如你使用的是 `uni-app`,那么还可以使用样式条件编译的方式来做: ```scss /* #ifdef H5 */ svg { display: initial; } /* #endif */ ``` --- ## 和 NutUI 一起使用 `taro` 使用 [NutUI](https://nutui.jd.com) 的 `vue` 和 `react` 版本的共同注意点: 由于 [NutUI](https://nutui.jd.com) 必须要配合 `@tarojs/plugin-html` 一起使用。 然而 `@tarojs/plugin-html` 这个插件,默认情况下它会移除整个 `tailwindcss` 注入的 `css var` 区域块,这会造成所有 `tw-*` 相关变量找不到,导致样式大量挂掉的问题。例如(`drop-shadow-2xl`,`translate-1/2` 等样式)。 此时可以启用这个插件的 [`injectAdditionalCssVarScope`](/docs/api/interfaces/UserDefinedOptions#injectadditionalcssvarscope) 配置项,把它设为 `true`,这能在插件内部启用功能,来重新注入整个 `tailwindcss` 的 `css` 中的 `var` 区域块( `tailwindcss@3` )。 按照初始的配置,只需要添加一行即可,示例如下: ```diff const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack') { mini: { webpackChain(chain, webpack) { chain.merge({ plugin: { install: { plugin: UnifiedWebpackPluginV5, args: [{ rem2rpx: true, + injectAdditionalCssVarScope: true }] } } }) } } } ``` ## 可能有用但是过时的方案(部分 taro 版本有用) ~~需要去配置一下 `postcss-html-transform` 这个插件~~(实在找不到方法可以尝试一下) ```js // config/index.js config = { // ... mini: { // ... postcss: { htmltransform: { enable: true, // 设置成 false 表示 不去除 * 相关的选择器区块 // 假如开启这个配置,它会把 tailwindcss 整个 css 的 var 区域块直接去除掉 // 需要用 config 套一层,官方文档上是错的 config: { removeCursorStyle: false, } }, }, }, } ``` ## 参见 - [taro 官方文档](https://docs.taro.zone/docs/use-h5#插件-postcss-配置项) - 相关 Issue:[#155](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/155) --- ## 和 Taroify 一起使用 `taro` 使用 [Taroify](https://taroify.github.io/taroify.com/) 的共同注意点: 由于 [Taroify](https://taroify.github.io/taroify.com/) 引入后,会导致 `tailwindcss` 的样式被覆盖,[Taroify](https://taroify.github.io/taroify.com/) 样式的优先级会高于 `tailwindcss`。 ## 解决方案 ### 修改Taroify引入方式 按照[Taroify](https://taroify.github.io/taroify.com/) 修改引入方式,将 `taroify` 引入方式改成按需引入 ```bash npm2yarn # 安装插件 npm i babel-plugin-import ``` 修改Babel配置文件,修改组件和图标样式的引入方式为手动引入 ```js // babel.config.js module.exports = { plugins: [ [ 'import', { libraryName: '@taroify/core', libraryDirectory: '', // 这修改为false style: false, // style: false, }, '@taroify/core', ], [ 'import', { libraryName: '@taroify/icons', libraryDirectory: '', camel2DashComponentName: false, // 这里修改为false style: false, // style: () => "@taroify/icons/style", customName: name => name === 'Icon' ? '@taroify/icons/van/VanIcon' : `@taroify/icons/${name}`, }, '@taroify/icons', ], ], } ``` ### 修改引入样式顺序 修改根目录下的样式引入顺序,优先引入[Taroify](https://taroify.github.io/taroify.com/) 的样式,再引入Tailwindcss的样式 ```tsx // src/app.tsx // ... ``` ```scss // src/app.scss @use 'tailwindcss/base'; @use 'tailwindcss/components'; @use 'tailwindcss/utilities'; ``` ## 参见 - [Taroify 官方文档](https://taroify.github.io/taroify.com/) --- ## 和 wot-design-uni 一起使用 --- ## v1 版本插件常见问题,使用最新版本插件无须参考 ## 我在 `js` 里写了 `tailwindcss` 的任意值,为什么没有生效? 详见 [issue#28](https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/issues/28) A: 因为这个插件,主要是针对, `wxss`,`wxml` 和 `jsx` 进行转义的,`js` 里编写的 `string` 是不转义的。如果你有这样的需求可以这么写: ```js const cardsColor = reactive([ replaceJs('bg-[#4268EA] shadow-indigo-100'), replaceJs('bg-[#123456] shadow-blue-100') ]) ``` > 你不用担心把代码都打进来导致体积过大,我在 'weapp-tailwindcss-webpack-plugin/replace' 中,只暴露了2个方法,代码体积 1k左右,esm格式。 ## replaceJs 跨端注意点 就是在常见问题中的 `replaceJs` 这个方法原先是为小程序平台设计的,假如你一份代码,需要同时编译到小程序和 `h5` 平台,可以参考如下的封装: ```js // util.js // uni-app 的条件编译写法 export function replaceClass(str) { // #ifdef H5 return str // #endif return replaceJs(str) } // or 环境变量判断 export function replaceClass(str) { // 需要根据自己目标平台自定义,这里仅仅给一些思路 if(process.env.UNI_PLATFORM === 'h5'){ return str } return replaceJs(str) } // then other.js const cardsColor = reactive([ replaceClass('bg-[#4268EA] shadow-indigo-100'), replaceClass('bg-[#123456] shadow-blue-100') ]) ``` 这样就能在多端都生效了。 --- ## AI 生成小程序代码 ## 提升效率 本页面为使用 AI 快速构建小程序的专题,希望能够帮助大家不断的提升自己的开发效率 同时也希望大家一起讨论参与,快速的生成他个成百上千个小程序, APP, 和网站! ## 如何参与贡献 ### 前置环境 1. `nodejs@22` 2. `pnpm@10` 3. `Github` 账号 ### 开始 点击 [`fork weapp-tailwindcss`](https://github.com/sonofmagic/weapp-tailwindcss/fork), 然后 `git clone` 到本地在打开这个目录: 1. 执行 `pnpm i` 安装依赖 2. 执行 `pnpm build:pkg` 构建 `website` 的本地依赖包 3. 然后 `cd website && pnpm dev` (切换到 `website` 目录, 跑 `pnpm dev`,当然你也可以在 `vscode` 里面右键打开终端,然后 `pnpm dev` 运行) 4. 访问 `http://localhost:4000` 就是 `weapp-tailwindcss` 的官方文档网站了 然后,你可以在 `website/docs/ai` 目录下,新建 `md` / `mdx` 文件,进行写作,路由会自动映射到: `http://localhost:4000/docs/ai/{your_doc_name}` 路径中去 > 比如你创建一个 `v0.md`,你的路由就是 `http://localhost:4000/docs/ai/v0` > > 假如你创建一个 `index.md`,比如这个页面就是一个 `index.md` 这个页面访问路径为 http://localhost:4000/docs/ai 假如你有素材,可以放在 `website/docs/ai/assets/{your_doc_name}` 目录下,然后在 `md` 文件中,进行引用 ## 示例 ### 网站 1. https://v0.dev/ 2. https://docs.crewai.com/guides 3. https://bolt.new/ ### 上传图片 比如要实现 `网页云音乐`,就手机上打开 `网页云音乐`,然后截长图,上传到 `v0.dev` > 此处有截图 ### 提示词 然后提示词为 - `技术栈为 uni-app vue3 tailwindcss, 实现这个页面`(根据你的需求自定义) 然后复制代码即可 > 此处有截图 --- ## LLM 友好文档 (llms.txt) ## 生成方式 1. 在仓库根目录执行 `pnpm --filter @weapp-tailwindcss/website build`(或 `cd website && pnpm build`)。 2. 构建后,`website/build/` 会生成: - `llms.txt`(索引) - `llms-full.txt`(完整内容) - `llms-quickstart.txt`(上手/AI 工作流) - `llms-api.txt`(配置、API、迁移与问题) - 去除 MDX import 的纯 Markdown 文件,方便直接喂给模型。 ## 线上地址 - `https://tw.icebreaker.top/llms.txt` - `https://tw.icebreaker.top/llms-full.txt` - `https://tw.icebreaker.top/llms-quickstart.txt` - `https://tw.icebreaker.top/llms-api.txt` > 如果通过 GitHub Pages 访问,请注意前缀路径 `/weapp-tailwindcss/`。 ## 给 AI 的示例提示词 > 你可以从 https://tw.icebreaker.top/llms-quickstart.txt 和 https://tw.icebreaker.top/llms-api.txt 读取 weapp-tailwindcss 的入门与配置说明,回答时请引用相关链接。 ## 离线使用 - 下载 `llms-full.txt` 直接给模型。 - 或将生成阶段的 Markdown 文件整体打包后供模型上下文检索。 --- ## At Property 在 CSS 里,`@property` 是一个 **注册自定义属性(CSS Custom Properties)** 的新特性,它解决了原先 `--var: value` 那种“纯字符串变量”的一些缺陷。它主要提供了 **类型约束、初始值、继承控制** 等能力,让浏览器能更高效地处理这些变量,从而带来性能优化。 --- ## 1. `@property` 的基本作用 在传统 CSS 里,定义变量只能这样: ```css :root { --main-color: red; } ``` 但浏览器只把它当成字符串,无法提前知道这个变量是“颜色”还是“长度”。这会带来两个问题: - 无法做类型推断或插值优化(比如动画中间值计算)。 - 每次变量被使用时需要重新解析,性能开销更大。 而 `@property` 可以注册一个有类型的自定义属性: ```css @property --main-color { syntax: ''; inherits: false; initial-value: red; } ``` 这样浏览器就能: - 知道 `--main-color` 必须是一个颜色; - 有默认值 `red`; - 明确是否继承。 --- ## 2. 为什么它能优化性能 1. **类型约束 → 加快渲染** 浏览器不需要把字符串再解析成数值或颜色,而是直接使用已经注册好的类型。 2. **动画性能提升** 原本 CSS 变量无法参与动画插值,例如: ```css :root { --size: 10px; } div { width: var(--size); transition: width 1s; } ``` 这是无效的。但使用 `@property` 注册后,浏览器知道它是 ``,就能平滑插值: ```css @property --size { syntax: ''; inherits: false; initial-value: 10px; } ``` 3. **避免 FOUC/回退渲染** 有了 `initial-value`,在变量未设置时浏览器不用等到计算阶段才决定默认值,从而减少首次绘制闪烁。 --- ## 3. 示例场景 ### 动画数值 ```css @property --angle { syntax: ''; inherits: false; initial-value: 0deg; } .box { transform: rotate(var(--angle)); transition: --angle 1s; } .box:hover { --angle: 360deg; } ``` 👉 盒子会平滑旋转,而不是瞬间跳转。 --- ### 渐变颜色 ```css @property --gradient-color { syntax: ''; inherits: false; initial-value: blue; } .button { background: linear-gradient(var(--gradient-color), white); transition: --gradient-color 0.5s; } .button:hover { --gradient-color: red; } ``` 👉 按钮背景色在 hover 时会有渐变过渡。 --- ### 响应式尺寸 ```css @property --radius { syntax: ''; inherits: false; initial-value: 0px; } .card { border-radius: var(--radius); transition: --radius 0.3s; } .card:hover { --radius: 20px; } ``` 👉 hover 时圆角平滑过渡。 --- ## 4. 小结 - `@property` 是 **注册型 CSS 自定义属性**。 - 带来性能优化的核心原因:**浏览器能提前知道类型和初始值,减少解析和重绘开销**。 - 实用场景:**动画过渡、主题变量、响应式设计**。 --- 确实大多数人提到 `@property` 都会联想到 **transition / animation**,因为这是最直观的刚需。但其实它的用途远不止于动画,这里我给你整理一些 **非动画场景下的“刚需”案例**: --- ## 1. **防止变量未定义时的 FOUC(闪烁)** 普通自定义属性如果没定义,会导致样式不生效甚至回退为 `unset`,页面可能闪烁。 `@property` 可以通过 `initial-value` 提供一个强制的默认值,避免在首次渲染时出现意外。 ```css @property --page-bg { syntax: ''; inherits: true; initial-value: white; } body { background: var(--page-bg); } ``` 👉 即使主题系统的 JS 还没注入 `--page-bg`,页面一开始也会用 **white**,不会闪黑/闪透明。 --- ## 2. **保证变量输入合法性** 普通 CSS 变量是字符串,随便写都会被吃掉。 但注册后,浏览器会进行 **语法校验**,错误值会被忽略而回退到 `initial-value`。 ```css @property --spacing { syntax: ''; inherits: false; initial-value: 1rem; } .card { padding: var(--spacing); } ``` 👉 如果有人错误地设置了 `--spacing: "abc";`,不会让布局崩掉,而是安全地回退到 `1rem`。 --- ## 3. **在继承/不继承上的精细控制** 普通变量默认会继承,往往带来意料之外的问题。 例如定义主题色时,子元素继承了错误的值。 用 `@property` 就能控制是否继承。 ```css @property --border-color { syntax: ''; inherits: false; initial-value: gray; } .card { border: 1px solid var(--border-color); } ``` 👉 即使父元素设置了 `--border-color: red;`,子元素也不会继承,确保边框始终有稳定表现。 --- ## 4. **与容器查询(Container Query)结合** 当你写响应式时,可能用变量去驱动不同尺寸的布局。 如果变量是注册过的,浏览器能更高效地重新计算,而不是“字符串再解析”。 ```css @property --col-gap { syntax: ''; inherits: false; initial-value: 1rem; } @container (width > 800px) { .grid { --col-gap: 2rem; } } .grid { display: grid; gap: var(--col-gap); } ``` 👉 Gap 会随着容器变化自动稳定更新,而不会因为未定义或错误值导致渲染异常。 --- ## 5. **保证动态主题系统更鲁棒** 在“暗黑模式 / 多主题”里,用 `@property` 可以确保每个变量有定义,不会因为缺值导致奇怪的 UI。 ```css @property --theme-accent { syntax: ''; inherits: true; initial-value: #0066cc; } [data-theme="dark"] { --theme-accent: #ffcc00; } button { color: var(--theme-accent); } ``` 👉 即使某个主题忘了定义 `--theme-accent`,也能回退到默认蓝色,而不是无色。 --- ## ✅ 总结 除了 `transition`/`animation`,`@property` 在这些场景也算“刚需”: 1. **提供默认值,避免首屏闪烁** 2. **校验输入合法性,保证健壮性** 3. **控制继承,避免子元素样式污染** 4. **容器查询/响应式布局下提升渲染效率** 5. **多主题系统下保证 fallback 值** --- ## 咨询与定制服务 ## 一些碎碎念 国内像我这种开源,大部分都是为爱发电,除了个别朋友零星的打赏之外就再也没有人赞助了,当然打赏的钱也非常少,总的打赏金额比我一天的工资少多了。 诚然,我做开源的目的并不是为了钱,但是,我也知道一个开源项目,想要不断的持续维护,完善下去离不开钱的支持。 就像这个项目已经 `2` 年了,都是我自己维护代码,维护文档,花我自己的钱部署在国内的 `CDN` 上,总的贡献者加我一共 `2` 个人 (截止2024/1/12),日常维护也只有我一个人。 有些朋友加我,说我文档写的不行,读不懂,那你自己贡献一个易读的版本呀?下面那个编辑此页这么大的 `link` 看到没? --- ## 加入技术交流群 如果你在使用中遇到什么问题,也欢迎你进入交流群进行提问。 :::tip 添加时请注明来自 `Github: weapp-tw` 项目。 ::: 微信群 微信号 `SonOfMagic`,备注 “weapp-tw”。 QQ 二群 扫码加入最新 QQ 技术交流群。 QQ 一群(已满) 群号 `49262447` 暂无空位,可等待或加入二群。 --- ## 加载自定义字体 ## 微信小程序加载自定义字体 详见: https://github.com/sonofmagic/weapp-tailwindcss/discussions/637 --- ## cva 与 tailwind-variants 支持 `@weapp-tailwindcss/cva` 与 `@weapp-tailwindcss/variants` 对 `class-variance-authority`(简称 `cva`)和 `tailwind-variants` 进行了运行时封装,帮你在定义组件变体时自动处理类名冲突和小程序转义。 ## 安装 ```bash npm2yarn pnpm add @weapp-tailwindcss/cva pnpm add @weapp-tailwindcss/variants ``` > 运行时已经内置 `class-variance-authority` / `tailwind-variants`,无需再安装上游依赖。 ## cva() ```ts const button = cva( 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors', { variants: { tone: { primary: 'bg-[#2563EB] text-white hover:bg-[#1D4ED8]', outline: 'border border-border/60 bg-transparent', }, size: { sm: 'h-8 px-3', md: 'h-9 px-4', lg: 'h-10 px-6', }, }, defaultVariants: { tone: 'primary', size: 'md', }, }, ) button({ tone: 'outline', size: 'sm' }) // => 'inline-flex items-center ... border border-border_f60 bg-transparent h-8 px-3' ``` - 返回结果已经完成小程序转义,可直接用在 `class` 属性中; - 与原版 `cva` 的 API、推导出的 `VariantProps` 类型完全一致; - 如果需要自定义转义行为,可通过 `create` 获取隔离实例: ```ts const { cva: cvaForWeb } = create({ escape: false, unescape: false }) ``` ### Live Demo ```jsx live function () { return } ``` 可以动态切换 tone、size 以及是否关闭 escape/unescape,对比运行时返回的不同字符串。 ## tailwind-variants 直接从 `@weapp-tailwindcss/variants` 导入 `tv`/`cn`/`createTV` 等工具,即可在运行时获得与小程序兼容的类名字符串。封装会自动把合并结果转义,保持与编译期一致。 该包还提供了对 [`tailwind-variants`](https://tailwind-variants.org/) 的封装,导出项包括: | 导出 | 说明 | | --- | --- | | `tv` | 等价于默认的 `tailwind-variants` 工厂,但会自动执行小程序转义与合并。 | | `createTV` | 返回一个「预配置」的 `tv` 工厂,常用于库内定制。 | | `cn` | 将多个类名组合并(默认开启 `twMerge`),再进行小程序转义。 | | `cnBase` | 纯拼接工具,不做合并,仅处理转义。 | | `defaultConfig` | 直接透传上游默认配置。 | | `create` | 工厂函数,允许为 `tv` / `cn` 自定义转义行为。 | ### 示例:组合变体 ```ts const badge = tv({ base: 'inline-flex items-center rounded-full px-2 text-xs font-semibold', variants: { tone: { neutral: 'bg-[#F4F4F5] text-[#18181B]', success: 'bg-[#DCFCE7] text-[#166534]', danger: 'bg-[#FEE2E2] text-[#B91C1C]', }, soft: { true: 'bg-opacity-75', }, }, compoundVariants: [ { tone: 'danger', soft: true, class: 'bg-[#F87171] text-white', }, ], defaultVariants: { tone: 'neutral', }, }) badge({ tone: 'success' }) // => 'inline-flex ... bg-_b_hDCFCE7_B text-_b_h166534_B' ``` ### 自定义合并策略 `cn` 默认会调用 `twMerge` 去重。如果想关闭合并或覆盖其配置,可以使用第二个参数: ```ts const mergeLater = cn('text-[#ececec]', 'text-[#ECECEC]') mergeLater() // => 'text-_b_hECECEC_B' mergeLater({ twMerge: false }) // => 'text-_b_hececec_B text-_b_hECECEC_B' ``` 当需要精细化控制转义(例如 SSR 或混合应用中),同样可以结合 `create`: ```ts const { tv: tvForWeb, cn: cnForWeb } = create({ escape: false, unescape: false, }) ``` 这样生成的类名就会保持原样,方便在 Web 渲染时与已有代码复用。 ### Live Demo ```jsx live function () { return } ``` 示例中可切换 tone、soft、twMerge 以及 escape/unescape,观察 `tv` 与 `cn` 输出的差异。 --- ## 集成与排障指南 ## 与构建工具协作 ### 保留关键函数名 `weapp-tailwindcss` 会在编译阶段扫描源码中的 `twMerge`、`twJoin`、`cva`、`cn`、`tv`、`weappTwIgnore` 等标识符。当打包器在压缩环节改写了函数名(如将 `twMerge` 压成 `a`),扫描就会失败,导致运行时字符串无法还原。 - **esbuild / Vite** ```ts title="vite.config.ts" export default defineConfig({ build: { minify: 'esbuild', terserOptions: undefined, rollupOptions: {}, }, esbuild: { keepNames: true, }, }) ``` - **Terser / Webpack** ```js title="webpack.config.js" optimization: { minimize: true, minimizer: [ new TerserPlugin({ terserOptions: { mangle: { keep_classnames: true, keep_fnames: true, }, }, }), ], }, ``` 完整的配置示例可以参考仓库里的 [`packages/minify-preserve`](https://github.com/sonofmagic/weapp-tailwindcss/tree/main/packages/minify-preserve)。 ### 选择运行时版本 不同主版本的 Tailwind CSS 需要配套的运行时包: ```ts // tailwindcss v3 项目 // tailwindcss v4 项目 ``` ## 与 weapp-tailwindcss 插件联动 1. **同步依赖版本**:确保项目使用的 `weapp-tailwindcss` 主版本与文档中的说明一致,避免编译期和运行时的转义映射不匹配。 2. **配置忽略列表**:如果封装了新的工具函数来代理 `twMerge`/`cva` 等 API,请把这些名称添加到 [`ignoreCallExpressionIdentifiers`](../../api/interfaces/UserDefinedOptions.md#ignorecallexpressionidentifiers) 或 [`ignoreTaggedTemplateExpressionIdentifiers`](../../api/interfaces/UserDefinedOptions.md#ignoretaggedtemplateexpressionidentifiers)。 3. **联调第三方库**:当需要把类名传给第三方运行时代码(而不是直接渲染到模板上)时,可以结合 `weappTwIgnore` 手动控制是否保留原始字符串。 ## 调试建议 - **验证运行时版本**:在构建日志或控制台输出 `tailwindMergeVersion`,确认当前入口是否匹配预期。 - **确认转义链路**:可以在本地执行下面的脚本,检查 `twMerge` 是否正确完成「还原 ➜ 合并 ➜ 重新转义」: ```bash node - <<'NODE' const { twMerge } = require('@weapp-tailwindcss/merge') console.log(twMerge('text-[#ececec]', 'text-[#ECECEC]')) NODE ``` - **排查压缩产物**:打包后搜索产物中是否仍然存在 `twMerge`、`weappTwIgnore` 等关键字;若已经被替换,复查压缩配置。 - **生成快照**:对于依赖大量运行时字符串的项目,建议编写 Vitest 快照测试,记录 `twMerge`/`tv` 的输出,方便升级 Tailwind CSS 或迁移到 v4 时比对差异。 --- ## 概览 `@weapp-tailwindcss/merge` 是面向小程序生态的 [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) 运行时封装。它在保留原库全部 API 的基础上,对类名做了小程序合法化转义,并与 `weapp-tailwindcss` 编译期插件联动,提供稳定的跨端开发体验。 ## 为什么要使用 merge 运行时 - **动态类名更安全**:在组件里根据状态拼接类名时,`twMerge` 会自动剔除冲突项,确保最终样式一致。 - **小程序专属转义**:运行时会把诸如 `[#ececec]`、`[length:24rpx]` 等非法字符转成小程序可识别的占位符,并在编译期再还原。 - **覆盖多个 Tailwind CSS 主版本**:`@weapp-tailwindcss/merge` 针对 `tailwindcss@4`(基于 `tailwind-merge@3`),而 `@weapp-tailwindcss/merge-v3` 保持对 `tailwindcss@3` 的长期支持,两者 API 完全一致。 - **保留原生 API**:`twMerge`、`twJoin`、`extendTailwindMerge`、`createTailwindMerge` 等函数与原版完全一致,迁移成本低。 :::info 与原库的差异 为了适配小程序,`@weapp-tailwindcss/merge` 会在运行时做两件事: 1. 先调用 `@weapp-core/escape` 对字符串「去转义」,把已经被编译期处理过的值还原为标准 Tailwind 类名; 2. 调用原始的 `tailwind-merge` 完成合并后,再重新转义一次,交给小程序端运行。 如果你在纯 Web 项目中使用,请参考下方的「多端项目」章节配置。 ::: ## 运行时矩阵 | 包名 | 适用场景 | 说明 | | --- | --- | --- | | `@weapp-tailwindcss/runtime` | 共享依赖 | 封装 escape/unescape、`clsx`、`weappTwIgnore`,被下游包复用 | | `@weapp-tailwindcss/merge` | `tailwindcss@4` | 基于 `tailwind-merge@3`,默认入口 | | `@weapp-tailwindcss/merge-v3` | `tailwindcss@3` | 基于 `tailwind-merge@2`,专为老项目保留 | | `@weapp-tailwindcss/cva` | `class-variance-authority` | 自动 escape 的 `cva()` 运行时 | | `@weapp-tailwindcss/variants` | `tailwind-variants` | 提供 `tv`/`cn` 等扩展能力 | ## 安装 ```bash npm2yarn npm i @weapp-tailwindcss/merge ``` > 适用于 `tailwindcss@4` 的项目。若你仍在维护 `tailwindcss@3`,请改为安装 `@weapp-tailwindcss/merge-v3`: ```bash npm2yarn npm i @weapp-tailwindcss/merge-v3 ``` ## 快速上手 ```ts const buttonClass = twMerge( 'px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]' ) // => 'hovercbg-dark-red p-3 bg-_hB91C1C_' ``` 同样的写法在 Web 端会得到 `hover:bg-dark-red p-3 bg-[#B91C1C]`。在小程序里之所以出现 `_h`、`_` 等占位符,是因为运行时完成了转义。`weapp-tailwindcss` 插件会在编译阶段把这些占位符还原成合法类名。 ### 与 weapp-tailwindcss 的编译期配合 - `weapp-tailwindcss` 默认把 `twMerge`、`twJoin`、`cva` 标记为「无需再次转义」的调用点,如果你自定义了函数名(例如把 `twMerge` 重命名为 `cn`),需要在配置里补充 [`ignoreCallExpressionIdentifiers`](../../api/interfaces/UserDefinedOptions.md#ignorecallexpressionidentifiers)。 - 对于模板字符串,可以使用 `weappTwIgnore` 标签跳过编译期处理: ```ts const raw = weappTwIgnore`text-[#123456]` ``` 这个 helper 实际上就是 `String.raw`,但它可以让插件准确识别并跳过转义。 ### tailwind-variants 快速体验 `@weapp-tailwindcss/variants` 提供了 [`tailwind-variants`](https://tailwind-variants.org/) 的运行时封装,在小程序环境中同样可以使用 `tv`/`cn` 输出带转义的类名: ```ts const badge = tv({ base: 'inline-flex items-center rounded-full px-2 text-xs font-semibold', variants: { tone: { neutral: 'bg-[#F4F4F5] text-[#18181B]', success: 'bg-[#DCFCE7] text-[#166534]', danger: 'bg-[#FEE2E2] text-[#B91C1C]', }, }, defaultVariants: { tone: 'neutral', }, }) badge({ tone: 'success' }) // => 'inline-flex ... bg-_b_hDCFCE7_B text-_b_h166534_B' ``` 更多组合用法、`cn` 聚合器以及逃逸策略见后文的 cva 与 tailwind-variants 支持 章节。 ### 多端项目:按需控制转义 `create` 工厂允许你为不同运行时定制 escape 策略: ```ts const { twMerge: mergeForMiniProgram } = create() const { twMerge: mergeForWeb } = create({ escape: false, unescape: false, }) mergeForMiniProgram('text-[#ececec]', 'text-[#ECECEC]') // → 'text-_b_hECECEC_B' mergeForWeb('text-[#ececec]', 'text-[#ECECEC]') // → 'text-[#ECECEC]' ``` - `escape: false` 表示结果保持原样(适合 Web 或同构渲染)。 - `unescape: false` 可跳过运行前的「反转义」,用于确保输入不会被改写。 - 如果只想自定义映射表,传入 `escape: { map: { '#': '__hash__' } }` 即可;运行时会把自定义映射与默认映射合并,保证编译期和运行时的行为一致。 ### 在线体验 ```jsx live function () { return } ``` 上方示例允许切换「禁用 escape/unescape」,用来对比运行时转义前后的差异。 --- ## 运行时 API `@weapp-tailwindcss/runtime` 是所有运行时包的公共底座,统一提供 escape/unescape、`clsx` 封装、`weappTwIgnore` 以及 `createRuntimeFactory`。`@weapp-tailwindcss/merge`、`merge-v3`、`cva`、`variants` 都是基于它实现的。 ## 安装 ```ts pnpm add @weapp-tailwindcss/merge ``` 如果项目仍在使用 Tailwind CSS v3,请改用 `@weapp-tailwindcss/merge-v3`;而 `@weapp-tailwindcss/runtime` 在大部分场景下会作为间接依赖被自动安装。 ## Runtime 基本面 ```ts clsx, weappTwIgnore, resolveTransformers, createRuntimeFactory, } from '@weapp-tailwindcss/runtime' ``` - `clsx` / `ClassValue`:统一的类名聚合工具,避免各包重复安装 `clsx` - `weappTwIgnore`:`String.raw` 的别名,用于跳过 weapp 转义 - `resolveTransformers(createOptions)`:返回 `{ escape, unescape }` - `createRuntimeFactory(factoryOptions)`:将 `tailwind-merge`、`tailwind-variants` 等库注入 weapp 运行时 ## merge 运行时的标准导出 所有 API 都与 `tailwind-merge` 原版保持一致,只是在调用前后包裹了转义逻辑。你可以继续使用熟悉的导入路径: ```ts twMerge, twJoin, createTailwindMerge, extendTailwindMerge, getDefaultConfig, mergeConfigs, tailwindMergeVersion, weappTwIgnore, create, } from '@weapp-tailwindcss/merge' ``` > - `@weapp-tailwindcss/merge` 适配 `tailwindcss@4` > - `@weapp-tailwindcss/merge-v3` 适配 `tailwindcss@3` > > 两个包内部均依赖 `@weapp-tailwindcss/runtime`,API 完全一致。 ## twMerge(...classValues) 合并 Tailwind 工具类并移除冲突项,返回一个经过小程序转义的字符串。支持布尔值、数字、数组、嵌套数组等所有 `tailwind-merge` 支持的参数类型。 ```ts twMerge('px-2 py-1', ['px-6', { 'py-5': shouldExpand }]) // => 'px-6 py-5' twMerge('text-[#ececec]', flag && 'text-[#ECECEC]') // => 'text-_b_hECECEC_B' ``` ### 常见问题 - 如果输入已经是转义过的占位符(例如 `text-_b_hececec_B`),运行时会先调用 `unescape` 还原,再进行合并,因此不会出现重复。 - 返回结果永远是字符串;若想在 Web 端保持原样,需要使用下文的 `create` 禁用转义。 ## twJoin(...classValues) 单纯把类名拼接成一个字符串,不做冲突判断,但仍会执行「先 unescape、后 escape」的流程,保证多端结果一致。适合用于低成本地去除 `undefined` / `false`。 ```ts twJoin('text-[#ececec]', condition && 'bg-[#010101]') // => 'text-_b_hececec_B bg-_b_h010101_B' ``` ## createTailwindMerge(config) 与原版一致,可以基于自定义配置创建一个新的 `twMerge` 实例。区别在于返回值会自动带上小程序转义。 ```ts const mergeCard = createTailwindMerge({ classGroups: { card: ['card-default', 'card-primary'], }, }) mergeCard('card-default card-primary') // => 'card-primary' ``` 同一个实例在调用过程中仍然执行转义,因此你可以安全地混用转义前后的字符串。 ## extendTailwindMerge(config) 基于默认配置扩展新的合并规则,返回一个新的 `twMerge` 运行时。 ```ts const mergeWithCustomSpacing = extendTailwindMerge({ classGroups: { spacing: ['gap-safe', 'gap-huge'], }, }) mergeWithCustomSpacing('gap-huge gap-safe') // => 'gap-safe' ``` ## getDefaultConfig() 与 mergeConfigs(a, b) 两者直接透传自 `tailwind-merge`: - `getDefaultConfig()` 返回内部使用的原始配置对象; - `mergeConfigs(a, b)` 用于把两个配置合并成一个新的配置,常见于库内部复用。 ## tailwindMergeVersion 表示当前入口绑定的 `tailwind-merge` 主版本: - `2` ➜ 运行时基于 `tailwind-merge@2`,适配 `tailwindcss@3` - `3` ➜ 运行时基于 `tailwind-merge@3`,适配 `tailwindcss@4` 该值可用于调试或在运行时判断最终行为。 ## weappTwIgnore 模板字符串标签函数,本质是 `String.raw`。与 `weapp-tailwindcss` 的 `ignoreTaggedTemplateExpressionIdentifiers` 配置联动,用来标记「不要转义」的代码片段。 ```ts const raw = weappTwIgnore` bg-[#123456] before:content-['>'] ` ``` 当小程序端确实需要原始类名(例如传递给第三方库)时,可借助该标签避免被编译期改写。 ## create(options?) `create` 会根据传入的 `options` 返回一组隔离的运行时函数(`twMerge`、`twJoin`、`createTailwindMerge`、`extendTailwindMerge`),用于控制转义行为: ```ts const { twMerge: mergeForWeb } = create({ escape: false, unescape: false }) const { twMerge: mergeForMiniProgram } = create() ``` ### CreateOptions | 选项 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `escape` | `false` \| `EscapeOptions` | `{ map: 默认映射 }` | 控制输出时是否转义;传入对象可自定义映射表。 | | `unescape` | `false` \| `UnescapeOptions` | `{ map: 默认映射 }` | 控制在合并前是否还原占位符。 | - 传入 `false` 会彻底关闭对应阶段; - 当 `escape`/`unescape` 同时提供自定义 `map` 时,运行时会自动与默认映射 `MappingChars2String` 合并,确保和编译期保持一致; - 该工厂返回的实例不会影响默认导出的全局实例,可在多端方案里自由组合。 - `EscapeOptions`/`UnescapeOptions` 类型来自 `@weapp-core/escape` 包,与编译期所使用的转义逻辑保持一致。 ```ts const custom = create({ escape: { map: { '#': '__hash__', }, }, unescape: { map: { '#': '__hash__', }, }, }) custom.twMerge('text-[#ececec]') // => 'text-_b__hash__ececec_B' custom.twMerge('text-_b__hash__ececec_B') // => 'text-_b__hash__ececec_B' ``` ## 基于 runtime 的其他包 - `@weapp-tailwindcss/cva`:封装 `class-variance-authority`,详见 [cva 支持](/docs/community/merge/cva-and-variants#cva) - `@weapp-tailwindcss/variants`:封装 `tailwind-variants`,详见 [variants 支持](/docs/community/merge/cva-and-variants#tailwind-variants) - `@weapp-tailwindcss/merge-v3`:面向 `tailwindcss@3` 的长期支持分支 它们全部依赖 `@weapp-tailwindcss/runtime`,因此可以共享 escape 配置、`weappTwIgnore` 以及 `clsx`。 --- ## 适配的 `tailwindcss` 插件 虽然,相当一部分 `tailwindcss` 插件,都可以直接在 `weapp-tailwindcss` 里使用了。 但是小程序中 `wxss` 这种 `css` 子集,是原生不支持许多 `css` 的写法和选择器的,所以免不了在使用某些插件的时候会报错。 比如 `tailwindcss/typography`, `daisyui` 等等。 所以很多时候开发它们的迁移/阉割版本是不可避免。 比如 `tailwindcss/typography` 的小程序适配版本就是 `@weapp-tailwindcss/typography` 而 [`IceStack`](https://ui.icebreaker.top/zh-CN/docs/usage) 内部也包含了 `daisyui` 的微信小程序适配。 --- ## 🔥各个框架的模板 当你配置不对的时候,也可以参考参考, 其中带有 🔥 标志的为推荐使用版本 :::tip 假如你遇到网络问题,无法登陆 `Github` 的时候,你可以使用 `Gitee`。 登陆进 `Gitee` 之后,点击右上角的加号,有一个导入仓库功能。然后你想使用什么模板,你就右键下方模板的link,点击复制链接地址,然后把复制的url,粘贴到`Gitee`导入仓库的 `Git Repository URL` 中去,点击确定导入之后,过几秒仓库就能够同步完成,就可以下载了。 ::: ## uni-app --- ### 使用 `uni-app cli` 进行构建 `vscode` 开发 {/* ### 若依移动端 (`hbuilderx` 集成 `tailwindcss`) */} ### 使用 `hbuilderx` 进行构建和开发 ## tarojs --- ### 使用 `tarojs` 进行构建 `vscode` 开发 ## native --- ### 原生小程序开发模板 #### 下方的技术架构过于老旧,不推荐 ## `tailwindcss@4` 所有的 `tailwindcss@4` 模板都维护在下方的地址,标志为标题带有 `tailwindcss-v4` 字样: https://github.com/icebreaker-template ## 更多模板 你还可以在项目的根目录的 `demo` 文件夹中,找到各个框架的注册方式。 里面有 `gulp` , `mpx` , `webpack5` , `rax` , `taro-react` , `taro-vue2` , `taro-vue3` , `uni-app vue2 webpack5` , `uni-app vue3 vite` 的项目 --- ## @weapp-tailwindcss/typography 小程序 `@tailwindcss/typography` 富文本渲染方案 `@weapp-tailwindcss/typography` 是 `@tailwindcss/typography` 的小程序迁移版本,帮助你渲染美丽的富文本。 ## 介绍 在小程序中,我们往往使用 [rich-text](https://developers.weixin.qq.com/miniprogram/dev/component/rich-text.html) 组件,然后从后端请求到 `html` 字符串片段,然后放到小程序中去渲染,所示: ```html ``` 但是,使用它有很多的限制,比如自定义组件中使用 `rich-text` 组件,那么仅自定义组件的 `wxss` 样式对 `rich-text` 中的 `class` 生效。 所以针对这种 `case` 设计了 `@weapp-tailwindcss/typography` 来解决小程序渲染富文本的问题。 ## 如何使用? ### 安装 ```sh npm2yarn npm i -D @weapp-tailwindcss/typography ``` ### 注册 这里比较特殊,由于`rich-text` 组件的样式限制: 自定义组件中使用 `rich-text` 组件,那么仅自定义组件的 `wxss` 样式对 `rich-text` 中的 `class` 生效 #### 创建组件 这里以 `uni-app vue3 vite` 项目为例,比如此时我们目标组件为 `typography.vue`: ```html ``` #### 创建独立 tailwindcss 上下文 在当前 `typography.vue` 组件目录下,单独创建一个 `tailwind.typography.config.js` 来创建独立的 `tailwindcss` 上下文,单独处理它, ```js const path = require('node:path'); /** @type {import('tailwindcss').Config} */ module.exports = { content: [path.resolve(__dirname, './typography.vue')], plugins: [require('@weapp-tailwindcss/typography')], corePlugins: { preflight: false, }, }; ``` 此时渲染 `html` 就生效了。 #### 指定全局样式配置文件 但是这还没有结束,为了防止这个上下文,影响到你全局的 `tailwindcss` 上下文,你必须做一个显式指定。 此时要在你的引入 `tailwindcss` 的入口文件处(`App.vue`),声明它用的是根目录的 `tailwind.config.js` ```css @config "../tailwind.config.js"; @tailwind base; @tailwind components; @tailwind utilities; ``` 这样配置才最终完成。 > 使用 @import 要注意加载顺序是不同的,详见 ## 配置项 大体的配置项与 是相同的 额外添加了 `mode` 和 `classPrefix` ## 原理解释 `@weapp-tailwindcss/typography/transform` 这个方法是为你的 `html` 所有的元素给大上 `class` 属性,这样才能使用那些 `prose-headings:bg-red-100`,`prose-h5:text-green-400` 的写法,来覆盖原先富文本的样式。 而 `@weapp-tailwindcss/typography` 通过 `mode` 的配置,`mode` 为 `tag` 表示为原先默认的行为,`mode` 为 `class`,此时插件更改为对所有的 `class` 选择器生效,而不是对所有的标签生效。默认值为 `class` 假如你觉得 `@weapp-tailwindcss/typography/transform` 放在小程序端处理,体积太大了,你可以把它放在 `nodejs` 服务中,预先处理。 ## Demo --- ## 生态以及解决方案 ## 插件 ### [@weapp-tailwindcss/typography](./community/typography) ### [开箱即用的小程序icon解决方案](./icons) ### [uni-app 条件编译语法糖插件](./quick-start/uni-app-css-macro) ## 模板 ### [各个框架的模板](./community/templates) ## 解决方案 ### [小程序多主题方案](./quick-start/apply-themes) ### [构建以及引入外部组件](./quick-start/build-or-import-outside-components) ### [tailwindcss 多上下文与独立分包](./quick-start/independent-pkg) ### [wxs的转义与处理](./quick-start/wxs) ## 社群 ### [加入技术交流群](./community/group) --- ## 版权信息 --- ## 技术演进 目前 `weapp-tailwindcss` 使用: - `babel` 来处理 `js`/`wxs` - `htmlparser2` 来处理 `wxml` - `postcss` 来处理 `wxss` ## wxml 使用 `htmlparser2` 已经是 `v2` 版本后期的事情了 一开始使用的是 `@vivaxy/wxml` 这是一个 `wxml` 的 `ast` 工具 但是它已经很久没有更新了,在遇到内联的 `wxs` 的时候,会直接挂掉 后续使用正则来出来,但是 正则 同样是有问题的,比如这样一个 case: `` 由于 `2>1`的存在,它会提前匹配并进行返回,所以还是要使用 `ast` 工具才能做到精确。 而 `parse5` 对 html5 是严格的匹配,不怎么适用于 wxml 所以最终选择 `htmlparser2` 来处理 `wxml` ## babel 这里主要有1个演进原先是 `@babel/parser`->`@babel/traverse`->`@babel/generator` 但是这样,相当于重新生成了一遍用户的 js,同时 sourcemap 也会错乱 所以后续改成了 `@babel/parser`->`@babel/traverse`->`magic-string#replace` 的方式,做精确匹配 ## postcss 这里的演进比较多,也就是相当于加入了多个 postcss 插件进行转换。 --- ## 如何贡献 > 推荐阅读 [如何为开源做贡献?](https://opensource.guide/zh-hans/how-to-contribute/) ## 如何本项目做贡献? 其实非常简单,你不一定需要贡献代码,你提一个 `issue`,回答一个问题,写一篇相关的文章,这些都是为项目做贡献,无需拘泥于具体的形式。 无论你做什么,只要你对这个项目的发展,起到正向的帮助的,我们对你表示感谢 🙏! ## 贡献指南 首先,你必须 `fork` [`weapp-tailwindcss`](https://github.com/sonofmagic/weapp-tailwindcss) 这个项目到你自己的账号下,然后你再把它 `git clone` 到你的本地。 ### 文档贡献 目前本网站所有的文档都在 [weapp-tailwindcss/website](https://github.com/sonofmagic/weapp-tailwindcss/tree/main/website) 目录下, 你可以在里面任意添加文章,修改文章,删除文章,然后提交到你 `fork` 的分支,然后在 `pr` 到 `weapp-tailwindcss` 的 `main` 分支。 `website` 这个项目会被部署到 `https://tw.icebreaker.top` 这个域名下,作为 [`weapp-tailwindcss`](https://github.com/sonofmagic/weapp-tailwindcss) 的文档展示。 就项目而言,这是一个 `docusaurus@2` 的项目,和 `vuepress`/`vitepress` 类似,它也是一个开源的文档生成工具,不过它是 `react` 写的。 > `docusaurus` 相关的文档见 [docusaurus.io](https://docusaurus.io/) #### 目录介绍 - `docs`: `md`,`mdx` 文档所在位置 - `src`: 源代码,你可以在这里写 `jsx`,`tsx` - `static`: 静态资源所在位置 - `docusaurus.config.js`: `docusaurus` 配置,可在此处调配 `navbar` - `sidebars.js`: 调整所有 `sidebar` 的配置文件,当你添加一篇文档,你需要在这里声明它的位置 ### 代码贡献 #### 根目录介绍 - `assets`: 存放所有静态资源的地方,包括 `weapp-tailwindcss` 所有的 `logo`,还有对应的 `figma` 文件可以做二次设计开发 - `bin`: `cli` 入口文件 - `demo`: 存放所有 `demo` 的地方,里面包含各个框架的各种使用方式 - `demo-linked`: 存放部分 `demo` 的地方,不同的是,这里 `weapp-tailwindcss` 的注册方式都是本地 `linked` - `e2e`: 存放 `e2e` 测试的地方,测试对象就是 `demo` 下的那些项目 - `plugins`: 存放一些从 `web` 迁移到小程序的 `tailwindcss` 插件,目前包含 `@weapp-tailwindcss/typography` - `scripts`: 存放一些常用脚本的位置,比如 `readme.md` 生成脚本等等 - `src`: 源代码目录,待会细讲 - `test`: 单元测试和测试快照位置,一般出现一个 `bug`,我们都需要设计 1 到多个测试用例,测试通过后才能发布 - `website`: 文档网站 #### 使用技术介绍 - 单元测试和 `e2e` 测试现在完全使用了 `vitest` 过去曾经是 `jest` - 打包使用的是 `rollup` ### src 源代码介绍 目前 `weapp-tailwindcss` 使用: - `babel` 来处理 `js`/`wxs` - `htmlparser2` 来处理 `wxml` - `postcss` 来处理 `wxss` Why? #### wxml htmlparser2 使用 `htmlparser2` 已经是 `v2` 版本后期的事情了 一开始使用的是 `@vivaxy/wxml` 这是一个 `wxml` 的 `ast` 工具 但是它已经很久没有更新了,在遇到内联的 `wxs` 的时候,会直接挂掉,另外还有各种问题。 后续使用正则来处理 `wxml`,但是正则同样是有问题的,比如这样一个 `case`: `` 由于 `2>1`的存在,它会提前匹配并进行返回,所以还是要使用 `ast` 工具才能做到精确。 而 `parse5` 对 `html5` 是严格的匹配,不怎么适用于 `wxml` 所以最终选择 `htmlparser2` 来处理 `wxml` #### js/wxs babel 这里主要有1个演进原先是 `@babel/parser`->`@babel/traverse`->`@babel/generator` 但是这样,相当于重新生成了一遍用户的 js,同时 sourcemap 也会错乱 所以后续改成了 `@babel/parser`->`@babel/traverse`->`magic-string#replace` 的方式,做精确匹配 #### postcss 这里的演进比较多,也就是相当于加入了多个 postcss 插件进行转换。 ### src 目录介绍 - `babel`: `babel` 工具类 - `bundlers`: 存放使用方式,目前提供 `webpack`,`vite`,`gulp` 插件的使用方式 - `cache`: 缓存策略,用于解决在项目比较大时,热更新的速度问题,本质上是通过计算文件内容的 `hash` 值,如果没有发生改变就跳过 `ast` 的解析,直接返回结果的策略 - `css-macro`: `uni-app` 样式条件编译插件 - `debug`: `debug` 调试用 - `extractors`: 提取器,用于分割字符串 - `js`: 用于处理转译 `js` 结果的地方 - `postcss`: 和 `postcss` 相关,用于处理所有 `wxss` 结果的地方 - `tailwindcss`: 用于 `hack` `tailwindcss`,给它打补丁,强制让它支持小程序的一些特性 - `wxml`: 处理 `wxml` 的地方 - `*`: `src` 其他一些文件,大多是一些导出文件 想要知道各自做了什么事情,详见 [深入核心原理](./principle) ## 如何本地调试 你是否对我这种类型项目的本地调试感到棘手? 其实本地调试很简单,你怎么调试 `webpack`/`vite`/`gulp`/`postcss`插件,同样可以用这些的经验来调试这个项目。 ### 单元测试调试 目前这个项目里有大量的单元测试,来对各个模块进行测试。 目前我使用的使用 `vitest` 来进行进行单元测试 (之前使用 `jest` + `ts-jest` 但是它对 `esm`+`cjs`的混合引用的模式,支持不算很好) 所以你可以安装 `vscode` 的 `vitest` 插件,安装完成之后,在 `vscode` 左侧的测试管理器会看到大量的测试用例,这些都是它感应到了 `test` 项目里的单元测试。 此时,你可以选中一个,你就可以进行 `运行`,或者 `调试` ,或者 `打开测试文件` 的操作。 当然你也可以直接打开 `*.test.ts` 测试文件,此时,在 `describe`/`test`/`it` 在前面会出现一个运行的绿色 `icon`,直接点是 `运行`,右键选择 `调试测试` 即可 比如你要调试我的 `postcss` 插件,你就可以引用我的相关代码,在 `test` 文件里写一个用例: 比如 `console.log` 一下 `postcss([plugin]).process(rawSource).css` 结果 ,然后在我的 `postcss` 源代码里面打上断点,这时候再去进行 `调试` 操作,就能命中源码里面的断点了。 调试 `webpack`/`vite` 等等插件同理,你需要准备 `webpack`/`vite` 对应的配置,比如 `webpack/vite` 的 `api` 去引入插件,来进行构建,这样才能命中插件里的断点。 ## 源码映射调试 这里的核心是 `sourcemap`,你需要在本地构建的时候,把 `sourcemap` 打出来,然后把所有产物被对应的 `app` 应用(`uni-app`/`taro`)引入并注册 这样你在使用 `javascript` 调试终端,进行运行的时候,就能命中 `dist` 产物里的 `js`,然后再根据 `sourcemap` 命中到你的 `ts` 源码里去了。 当然,假如你生成的 `sourcemap` 不对,或者不生成,你也可以直接在 `dist` 产物里的 `js` 直接打断点进行调试,不过这需要你对源代码比较了解。 当然,每次都使用 `javascript` 调试终端太过于麻烦,为此我准备了 `.vscode/launch.json` 文件,帮助我们快速进行框架产物调试,这样才能获取第一手的 `uni-app` / `taro` 产物。 --- ## 小程序 `icon` 解决方案 这里介绍一种在小程序里,开箱即用的 icon 解决方案:[`iconify`](https://iconify.design/) 很高兴,我们已经有这样的能力,为小程序提供大量的 `icon`, 供我们开发者自己进行选择。 ## 立即开始 首先,在已经安装和配置完本插件之后,安装 [`@egoist/tailwindcss-icons`](https://www.npmjs.com/package/@egoist/tailwindcss-icons) ```sh npm2yarn npm i -D @egoist/tailwindcss-icons ``` :::tip **Iconify for Tailwind CSS** 其实给我们提供了两种选择: `@iconify/tailwind` 和 `@egoist/tailwindcss-icons` 个人用下来,感觉 `@egoist/tailwindcss-icons` 更好用一点,智能提示也更好一些。 因为它是直接把全部的 `icon` 生成在 `components` 里, 然后再交给 `Tailwind CSS` 从里面进行挑选再输出到我们的 `css` 文件中的。 而 `@iconify/tailwind` 走的是 `jit` 动态加载生成,但是没有智能提示加 `` 不好用啊,笑~。 详见 ::: 然后在 `tailwind.config.js` 注册插件: ```js const { iconsPlugin, getIconCollections } = require("@egoist/tailwindcss-icons") module.exports = { plugins: [ iconsPlugin({ // Select the icon collections you want to use collections: getIconCollections(["mdi", "lucide"]), }), ], } ``` 然后你还要安装你挑选的 `icon` 集合包,比如你选择了 `"mdi"` 和 `"lucide"` 那你就要安装: `@iconify-json/mdi` 和 `@iconify-json/lucide` (包名的规则就是:`@iconify-json/{collection_name}`) 当然你也可以直接安装 `@iconify/json`,这里面包含了所有的 `icon`,不过代价就是,这个包比较大(50MB+) 然后你回到你的页面,输入 `i-` 智能提示就出来了,然后就可以这么写了: `` > 假如不起作用,`Tailwindcss@3` 的话,请检查你的 `@tailwind components;` / `@import 'tailwindcss/components';`(scss) 是否在入口 `css/scss` 中引入 ## Tailwindcss v4 在 `Tailwindcss@4` 中,不会自动读取 `tailwind.config.js` 文件,所以你需要使用 [@config](https://tailwindcss.com/docs/functions-and-directives#config-directive) 指令,手动 引入 `tailwindcss` 的配置文件。 ```css @import "weapp-tailwindcss"; /* 添加下面这一行,路径为你创建的 tailwind.config.js 文件路径 */ /* highlight-next-line */ @config "../tailwind.config.js"; ``` 这样在 `tailwindcss@4` 中才能起效果 ## icon预览挑选网站 https://icon-sets.iconify.design/ ## 生成结果 我们以 `i-mdi-home` 这个类的`css` 生成结果为例: ```css .i-mdi-home{ display: inline-block; width: 1em; height: 1em; background-color: currentColor; -webkit-mask-image: var(--svg); mask-image: var(--svg); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; --svg: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 width=%2724%27 height=%2724%27%3E%3Cpath fill=%27black%27 d=%27M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5Z%27/%3E%3C/svg%3E"); } ``` 是的,它是通过 `css var` 的方式,把 `svg` 给内联了(`inline`)了进来,然后通过 `mask-image` 来把图形给显示出来的。 显然,这用多了 `icon` 之后会造成生成的 `css` 体积大的问题。 对此还有 `mask-image` 和 `css var` 在部分机器上不兼容的问题。 对此有解决方案吗?显然是有的,比如我们可以把它做成一个 `webfont`,更改每一个 `icon component class`,把里面换成字体,达到类似 `iconfont` 的效果,最后再生成一份 `ttf/woff` 等文件上传到自己的 `cdn`去,然后这里再引用即可。 这样小程序体积又小,兼容性也好,就是多了些网络请求罢了,我们自己取舍吧。 --- ## 💨跨多端开发CSS兼容 ## 何时开启插件 本插件主要作用于小程序环境,让开发者可以在小程序环境下可以使用 `tailwindcss` 的特性 然而在 `h5` 和 `app` 中,它们本来就是 `tailwindcss` 支持的环境,所以是没有必要开启本插件的。 所以你可以这样传入 `disabled` 选项。 ### uni-app 示例 比如 `uni-app`: ```js title="vite.config.[jt]s" const isH5 = process.env.UNI_PLATFORM === "h5"; // uni-app v2 // const isApp = process.env.UNI_PLATFORM === "app-plus"; // uni-app v3 const isApp = process.env.UNI_PLATFORM === "app"; // 只在小程序平台开启 weapp-tailwindcss 插件 // highlight-next-line const WeappTailwindcssDisabled = isH5 || isApp; const vitePlugins = [ uni(), uvwt({ // highlight-next-line disabled: WeappTailwindcssDisabled }) ]; ``` ### Taro 示例 ```js title="config/index.ts" const isH5 = process.env.TARO_ENV === "h5"; const isApp = process.env.TARO_ENV === "rn"; // highlight-next-line const WeappTailwindcssDisabled = isH5 || isApp; webpackChain(chain) { chain.merge({ plugin: { install: { plugin: UnifiedWebpackPluginV5, args: [ { // highlight-next-line disabled: WeappTailwindcssDisabled, rem2rpx: true } ] } } }); }, ``` 其他的框架,请自行在对应的文档中,发掘不同目标平台的环境变量判断方式。 ## uni-app 打包到 h5 svg icon 偏移问题 这是由于 `tailwindcss` 默认会把 `svg` 的 `display` 设置成 `block` 导致的,所以解决方案很简单 在你的全局样式,引入 `tailwindcss` 的地方下面加一行,进行样式覆盖: ```css @tailwind base; @tailwind components; @tailwind utilities; /* #ifdef H5 */ svg { display: initial; } /* #endif */ ``` ## uni-app 打包安卓 `rgb()` 颜色失效问题 这是由于 `uni-app` 打包成安卓中 `webview` 内核版本较低,无法兼容 `rgb(245 247 255 / var(--tw-bg-opacity))` 这样的 `css` 写法导致的 这时候我们需要把这个写法,转换为兼容写法: `rgba(245, 247, 255, var(--tw-bg-opacity))`,具体解决方案: ### 安装 `postcss-preset-env` ```bash npm2yarn npm i -D postcss-preset-env ``` ### 设置 `postcss.config.js` ```js module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, 'postcss-preset-env': { browsers: 'chrome >= 50', // configure a compatible browser version }, }, }; ``` 这样,所有的 `rgb` 和 `/` 写法就被转化成兼容写法了。 相关issue详见: ## CSS变量计算模式 在 `tailwindcss@4` 下,默认启用 CSS 变量计算模式。`tailwindcss@3` 默认不启用。 此模式下会去预编译所有的 `css` 变量和 `calc` 计算表达式。 比如 `tailwindcss@4` 下原先生成的样式为: ```css page, :root { --spacing: 8rpx; } .h-2 { height: calc(var(--spacing) * 2); } ``` 在CSS变量计算模式启动,进行预编译之后,现在的结果为: ```css page, :root { --spacing: 8rpx; } .h-2 { height: 16rpx; height: calc(var(--spacing) * 2); } ``` 这个模式可以解决很多手机机型 `calc` `rpx` 单位的兼容问题。\ 当处于 Tailwind CSS 模式(`tailwindcss@4` 及以上)时,插件会默认把 `--spacing` 注入到 `includeCustomProperties`,因此常见的 `space-x`/`space-y` 等工具类会被稳定计算。 > 可通过给插件,传入 `cssCalc` 配置项 `false` 来手动关闭这个功能 假如这时候你需要去除 CSS 变量的声明,你可以传入 ```js { cssCalc: ['--spacing'] } // 或者更精确的 { cssCalc: { includeCustomProperties: ['--spacing'] } } ``` > 你也可以传入正则表达式 这样生成的结果就是: ```css page, :root { --spacing: 8rpx; } .h-2 { height: 16rpx; } ``` 通过这种方式可以解决手机机型 `calc` `rpx` 单位的兼容问题 详见: https://tw.icebreaker.top/docs/api/interfaces/UserDefinedOptions#csscalc ### `includeCustomProperties` 进阶用法 `cssCalc` 提供多种写法来控制需要参与计算的自定义属性。下面结合「执行前 / 执行后」的示例说明差异。 **数组写法** ```ts cssCalc: ['--spacing', '--gap'] ``` - 执行前: ```css :root { --gap: 6rpx; --spacing: 8rpx; } .space-x-2>view+view { margin-left: calc(var(--gap) * (1 - var(--tw-space-x-reverse))); margin-right: calc(var(--spacing) * var(--tw-space-x-reverse)); } ``` - 执行后: ```css .space-x-2>view+view { margin-left: 6rpx; margin-right: 8rpx; } ``` **对象写法** ```ts cssCalc: { includeCustomProperties: ['--gap'], precision: 6, preserve: true, } ``` - 执行前: ```css :root { --gap: 12rpx; } .btn { margin-bottom: calc(var(--gap) * 2); } ``` - 执行后: ```css :root { --gap: 12rpx; } .btn { margin-bottom: 24rpx; margin-bottom: calc(var(--gap) * 2); } ``` **正则写法** ```ts cssCalc: [/^--(gap|spacing)$/] ``` - 执行前: ```css :root { --gap: 4rpx; --spacing: 10rpx; } .card + .card { margin-top: calc(var(--spacing) * 2); margin-left: calc(var(--gap) * 3); } ``` - 执行后: ```css .card + .card { margin-top: 20rpx; margin-top: calc(var(--spacing) * 2); margin-left: 12rpx; margin-left: calc(var(--gap) * 3); } ``` **Tailwind 统一变量写法** ```ts cssCalc: { includeCustomProperties: [/^--tw-/], preserve: true, } ``` - 执行前: ```css :root { --tw-space-y-reverse: 0; --tw-gradient-from: #38bdf8; --tw-gradient-stops: var(--tw-gradient-from), rgba(56,189,248,0); } .space-y-3>view+view { margin-top: calc(var(--spacing) * 3 * (1 - var(--tw-space-y-reverse))); } .from-sky-400 { background-image: linear-gradient(to right, var(--tw-gradient-stops)); } ``` - 执行后: ```css :root { --tw-space-y-reverse: 0; --tw-gradient-from: #38bdf8; --tw-gradient-stops: var(--tw-gradient-from), rgba(56,189,248,0); } .space-y-3>view+view { margin-top: 24rpx; margin-top: calc(var(--spacing) * 3 * (1 - var(--tw-space-y-reverse))); } .from-sky-400 { background-image: linear-gradient(to right, var(--tw-gradient-stops)); } ``` 以上示例可以自由组合使用,帮助你根据业务场景精确控制 `calc` 与自定义属性的计算范围。 --- ## tailwindcss in weapp 的原理 ## 2024-02-20 版本 ## 前言 转眼又是一年,感觉是时候再来修订一下 `tailwindcss in weapp 的原理` 这篇文章了, 放心,这次我写作核心就是要让大多数人看懂! ## 什么是 weapp-tailwindcss 如果下一个定义的话,我把 `weapp-tailwindcss` 定义成一个 **转义器**,在我看来它就做了一件事情。那就是把 `tailwindcss` 中,小程序不兼容的写法,转化成小程序兼容的写法,并尽量保持生成的 `CSS` 效果一致罢了。 更加细一点的解释,就是允许开发者像 `tailwindcss` `h5` 环境里那样去写小程序,插件在背后帮助你把 `wxml`,`js`,`wxss` 等等这类产物的小程序兼容转化给做了。 仅仅如此罢了,做了一点小小的努力和贡献。 ## 为什么需要 weapp-tailwindcss ? 因为小程序 `wxml`,`wxss` 等等文件/产物,本身是不支持 `Tailwindcss` 里面的一些特殊转义字符的,比如像 `[]`,`!`,`.` 等等。 经过测试后,`wxml` 中 `class` 属性,实际上只支持英文字母,数字,以及 `-` 和 `_` 这 `2` 个特殊字符,不支持的字符会被默认转化成**空格**,所以我们要做的就是把 `Tailwindcss` 选择器的写法,转化成小程序允许的方式。这也是核心包 [`@weapp-core/escape`](https://github.com/sonofmagic/weapp-core) 实现的功能。 插件大体的发展历程和方向,可以看下方以前的 `2023-03-19` 版本,在此不再叙述,接下来我们只来谈谈核心原理。 ## 核心原理 插件的核心就是转义,那么具体是怎么做的呢? 实际上就是插件去解析编译后的 `wxml`,`js`,`wxss` 这些产物,并对它们进行改写。 工具方面, `weapp-tailwindcss` 使用 `htmlparser2` 去解析改造 `wxml`,使用 `babel` 去解析改造 `js` 和 `wxs`,使用 `postcss` 去解析改造所有的 `wxss`。 ## `wxml` ### 为什么是 `htmlparser2` 其实,`weapp-tailwindcss` 一开始使用的是 `@vivaxy/wxml`,这是一个 `wxml` 的 `ast` 工具, 但是它已经很久没有更新了,在很多场景,比如遇到内联的 `wxs` 的时候,会直接挂掉,而本身我也没有技术能力,去修复和完善这个 `wxml` 自动机,所以后续放弃了它。后来自己实现了一套基于正则的模板匹配引擎,但是再使用一段时间后,发现 `正则` 同样是有问题的,比如这样一个 `case`: `` 由于 `2>1`的存在,它会与 ` ``` 这时候你就必须在匹配 `class` 属性值的情况下,对字符串进行转义,再对 `{{}}` 里包裹的 `js` 表达式,进行转义,使得结果变成: ```html ``` 那么怎么做呢?也很简单,我们通过 `htmlparser2` 匹配 `class` 属性,可以获取到 `w-[13px] {{flag?'h-[23px]':'h-[6px]'}} bg-[#123456] {{customClass}}` 这个字符串。 然后对这个字符串进行 `{{}}` 表达式匹配,在 `{{}}` 表达式匹配之外的字符串,直接进行转义。在 `{{}}` 表达式内的代码,使用 `babel` 进行解析,然后获取到所有的 `js` 字符串字面量,进行转义即可。 同时在进行匹配和转义的时候,实际上我们是可以获取到对应字符串 `start` 和 `end` 的下标的,这就为我们使用 `MagicString` 进行替换,提供了良好的基础。 ## js / wxs 我们同样要对所有的 `js` 里面的字符串,进行扫描,发现它是 `tailwindcss` 的类名,就需要进行转义。 我们还是以上方的代码片段为例: ```html ``` 在 `wxml` 的处理过程中,看似我们已经解决了大部分 `class` 内容的转义的,但是开发者也可能在 `customClass` 绑定的 `js` 字符串中,直接去编写 `tailwindcss` 的类名。比如: ```js Page({ data: { customClass: "bg-[url('https://xxx.com/xx.webp')] text-[#123456] text-[50px] bg-[#fff]", }, }) ``` 又或者直接在 `wxs` 里面去写类名,那么这时候怎么办呢?获取所有字符串字面量和模板字符串,进行转义即可吗? 显然这是不行的,一个应用程序里面,大部分的字符串字面量都是和 `tailwindcss` 无关的,假如全部转化只会把应用程序弄奔溃。 那么,我们是否有什么方式,去获取到 `tailwindcss` 的上下文,再从里面把它所有的类名提取出来,再和我们应用程序里面的字符串进行匹配,从而做到精确转义呢?可是 `tailwindcss` 只是一个 `postcss` 插件啊,怎么从 `webpack` / `vite` 插件里,把一个 `postcss` 插件里的内容取出来呢? 通过这种方式,我们可以在 `postcss` / `postcss-loader` 执行之后,把 `tailwindcss` 执行时的 1 或者多个上下文给取出来,交给插件进行使用,从而达到所有 `js` 字符串字面量筛选的效果,简单实用 `babel` 实现的代码如下: ```js let ast: ParseResult = parse(rawSource, { sourceType: 'unambiguous' }) const ms = new MagicString(rawSource) const ropt: TraverseOptions = { StringLiteral: { enter(p) { // set 为 tailwindcss 上下文里所有生效的 classnames if(set.has(p.node.value)){ // do escape const value = escape(p.node.value) ms.update(start, end, value) } }, }, } traverse(ast, ropt) const code = ms.toString() ``` 通过这种方式来精确的把 `js` / `wxs` 里面字符串字面量,给转义成小程序允许的方式。 这里我们使用 `babel` 作为 `js ast` 的工具,因为目前为止它也是最流行的这方面的包,但是出于效率上的考虑,未来将会使用 `swc` 并编写 `swc` 插件的方式,来取代 `babel`。 ## wxss 最后就是样式的处理了,在转义完 `wxml`,`js` 和 `wxs` 后,我们需要把 `wxss` 里 `tailwindcss` 生成的样式,转化成与之前的 `wxml`,`js`,`wxs` 中匹配的方式。这样才能做到类名的 11 对应,从而达到我们最终的目的。 那么怎么做呢?这里我们选用的工具,自然是 `postcss`,它是目前用 `js` 编写的最流行的一个 `css ast` 工具,它要比 `css-tree` 生态好很多,也有很多现成的稳定插件可以使用,帮助我们完成针对 `css` 各种各样的处理。 所以最后我们所要完成的工作,就是把 `tailwindcss` 的产物,转化成与 `wxml`,`js` 和 `wxs` 匹配,且小程序能够适配的样子了! 怎么做?也很简单,直接使用 `postcss` 扫描 `wxss` 里面所有的 `Rule` 节点,然后获取到里面的选择器,进行转义即可! ### postcss 对象简单介绍 什么是 `Rule` 节点?在 `postcss` 插件中,大致有这 `5` 类对象(其实还有一个 `Document` ): - `Root`: CSS tree 的根结点,通常可以代表整个 CSS 文件. - `AtRule`: 以 `@` 开始的 CSS 语句,比如 `@charset "UTF-8"` 或者 `@media (screen) {}`. - `Rule`: 普通的选择器节点,内部由 CSS 声明填充,比如 `.btn { /*decls*/ }`. - `Declaration`: key-value 键值对,代表 CSS 声明,比如 `color: black`; - `Comment`: CSS 注释. 详见 [writing-a-postcss-plugin](https://postcss.org/docs/writing-a-postcss-plugin#step-find-nodes) 通过这些对象,`postcss` 完成了对 `CSS` 的抽象,从而让我们可以通过操作这些对象来对 `CSS` 进行增删改查。 ### Tailwindcss 简单原理和运行表现 在转化所有的 `Rule` 节点之前,我们先分析一下 `Tailwindcss` 的原理,它也是一个 `postcss` 插件,它通过提取 `content` 配置里面的 `glob` 表达式 or `vfile` 对象,从里面提取到符合它规则的字符串,然后生成大量的 `AtRule` / `Rule` 对象保存起来。 然后再扫描到我们注册 `Tailwindcss` 的地方: ```css @tailwind base; @tailwind components; @tailwind utilities; ``` 把这些对象进行展开,替换掉原先的 `@tailwind xxx;`,从而达到了写什么, `CSS` 就自动生成什么的效果。 其中你可以把 `base` / `components` / `utilities` 理解成一个个 `layer` 标志位,不同的 `AtRule` / `Rule` 对象集合,会在它对应的 `layer` 出展开。比如 `base` 负责所有的 `css vars` 和 `preflight css` 注入,`utilities` 负责所有原子工具类的生成。 同时它们再通过 `@layer` 来控制它们的优先级,从而做到它们之间的 `CSS` 不会产生优先级相互覆盖冲突的问题。 > `@layer` 实际上是原生 `CSS` 的一个实验性功能,用来声明一个 级联层,从而让开发者更容易的控制自己 CSS 代码的优先级罢了。这里我们能使用 `@layer` 是因为这个功能被 `tailwindcss` 用它自己的方式实现了,使得我们可以在 `CSS` 里预先使用 `@layer` 罢了,最终产物里面是没有 `@layer` 的。相关文档详见 [MDN @layer](https://developer.mozilla.org/zh-CN/docs/Web/CSS/@layer) ### 开始转化 首先我们先简单声明一个 `postcss` 插件: ```ts const creator: PluginCreator = () => { return { postcssPlugin, Rule(rule) { ruleTransformSync(rule, options) }, } } creator.postcss = true ``` 这里我们把针对 `Rule` 的转化,封装进 `ruleTransformSync` 这个方法里: 这里我们需要用到 `postcss-selector-parser` 来对 `Rule` 中的选择器,做更加细致的转化。 因为 `postcss` 中的 `Rule` 对象的选择器千变万化,可以非常的简单,也可以非常的复杂,你单独把它当作字符串来处理,很容易出现问题。 所以我们需要 `postcss-selector-parser` 来解析 `Rule#selector` 然后进行修改再转化回字符串。 例如: ```ts export const ruleTransformSync = (rule: Rule, options: IStyleHandlerOptions) => { const transformer = selectorParser((selectors) => { selectors.walk((selector) => { // do something with the selector }) }) return transformer.transformSync(rule, { lossless: false, updateSelector: true }) } ``` 我们通过对 `selectors` 的 `walk`,再方法里面去筛选和修改节点,从而达到我们替换和转义 `Rule#selector` 的效果。 这样就可以去转义所有的 `Tailwindcss` 生成的所有节点了! ## 大功告成 最终在这 3 大部分完成之后,`weapp-tailwindcss` 的核心功能就完成了! --- ## 2023-03-19 版本 (2023-03-19) 版本,现在看了一下,已经比较老了,有空再改进改进。 ## 前言 笔者对 `tailwindcss` 这一类库的理解还算比较深刻。之前就已经撰写并发布过相关的许多包,比如像 [`tailwindcss-miniprogram-preset`](https://github.com/sonofmagic/tailwindcss-miniprogram-preset), [`weapp-tailwindcss-webpack-plugin`][weapp-link] [等等大堆的包](https://github.com/sonofmagic?tab=repositories&q=tailwindcss&type=&language=&sort=)。 最近我发布了 [`weapp-tailwindcss-webpack-plugin`][weapp-link] 的 `2.0.0`版本,增加了一些核心特性。想着现在也是时候,回顾总结一下这个插件的原理和历程了。回首`npm`版本发布历史,看到当年发布的第一个正式版,还是在 `2022/2/3` 号,到现在也已经过去了`1`年多的时间了,想想岁月真是如白驹过隙,等着等着我们就老了。 ## 这个插件是做什么的? 简单概括一下,就是把 `tailwindcss` 相关的特性,带入进小程序开发中。 为什么需要它呢?核心原因就是,小程序这个平台不像`h5`那么自由,有很多各式各样的束缚与非标准化的 `API`。我们以微信小程序为例: 同`css`相比,`wxss`有着更少的选择器支持,同`wxml`相比,`wxml`有着更少的字符集支持,`js` 和 `wxs` 的运行也是分离的(`wxs`语法像是一个低版本`js`)。 更不用说那些该有的全局对象一个都没有了,比如 `Blob`,`File`,`FormData`, `WebSocket`,`fetch`,`XmlHttpRequest`等等了,这导致许多浏览器包,安装下来无法运行。 > 举个例子,最近我在做服务端 graphql 改造,市面上主流的 `graphql client` 直接安装运行会立即报错,因为缺少全局对象,为了兼容它们,于是就写了 [`weapp-websocket`](https://www.npmjs.com/package/weapp-websocket) 和 [`weapp-fetch`](https://www.npmjs.com/package/weapp-graphql-request) 分别来实现 `subscriptions` 和 `query/mutation`。目前在项目里使用,运转良好。 总的来说,由于缺少许多特定的对象和语法上的限制,一些在 `h5` 上通用的流行库,很有可能在小程序里跑不起来。`tailwindcss` 便是其中之一,而这个插件就能帮助你在小程序里使用它。 ## 原理篇 ### utility-first CSS framework 其实吧,市面上的 `tailwindcss`/`windicss`/`unocss` 做的是一回事情。如果用比喻来形容,那么它们就是个带着滤网的字符串漏斗。它们对开发者编写的代码文件内容进行读取,并分割成海量的字符串放入漏斗中,然后经过滤网的过滤,符合条件的就去生成原子类,其余的"残渣"则忽略。 其中 `tailwindcss` 大多作为 `postcss plugin` 来使用的,它源码里自己实现了一个文件读取机制(也就是 `tailwind.config.js` 中的 `content` 配置项 ),来对我们编写的代码进行提取。 而 `windicss`/`unocss` 则是依赖 `webpack/rollup/vite` 这类的 `bundler`,在打包的过程中获取到 `Source / Asset / Chunk` 这类的对象,从而提取字符串的。虽然目前 `windicss`/`unocss` 都有对应的 `postcss plugin` 的实现,但是它们大多都是作为实验性质的,并不能很好的复刻它们打包插件的体验。 是什么造成了它们的不同呢? 这点其实就要说到 `unocss/windicss` 它们的优点了。目前 `tailwindcss postcss plugin` 实际上只有**读**的能力,它来读取我们写的代码,生成原子类。而 `windicss`/`unocss` 它们大多作为一个 `webpack/vite/rollup plugin` 来使用的,所以它们不但拥有读的能力,还拥有**修改**的能力。所以它们能写出这样的代码: ```html ``` 实际上就是在打包的插件内,展开覆写罢了,本质上还是个语法糖。 另外目前你选择这种原子类框架的时候,要不选择 `tailwindcss` 要不就 `unocss`,目前看起来 `windicss` 已经死了。 ### 小程序兼容 上面说了这么多,接下来来聊聊它们对小程序的兼容,事实上,市面这么多转义的插件,预设啥的,都是一回事。 思路大多就是把这些原子类`css`框架,生成的 `class` 通过重命名,转义,再覆写的方式,去兼容小程序环境。 说的具体一些,就是对打包后的产物进行修改,把其中的 `wxml` 里开发者撰写的 `className` 进行转义,把 `js` 里写的要应用到 `dom` 节点上的 `className` 也进行转义,同时还要对 `wxss` 里生成的 `css` 选择器进行转义。 而这些核心中的核心,便是转义后类名和选择器,一定要相互匹配!!不然生成的结果就是完全错误的。 ## 笔者的实现 前面说了这么多,接下来来讲讲我自己的实现。 实际上我一开始的实现也很简单,在第一版本初期,我选择写了这样一个 `webpack plugin`: 1. 它内部用 `wxml ast` 来解析所有的 `wxml`模板,以此来获取所有的 `className` 进行解析和替换 2. 使用 `postcss` 来解析所有的 `wxss`,以此对所有的`css`选择器进行修改 3. 使用 `babel` 来解析所有的 `js`/`jsx`,以此来动态修改 `jsx?` 里所以满足条件的字面量 (`StringLiteral`)。 然而理想很丰满,现实很骨感,在实现的过程中,困难一个一个浮现出来了: ### js里的字面量转义很容易误伤 一开始想着直接匹配并且替换掉符合要求的 `js` 的字面量,于是便兴致勃勃从 `tailwindcss` 源码里,拷贝来了解析提取器的正则,并在打包后进行匹配和替换。 然而这个方案失败了,原因是`tailwindcss`这个正则匹配有可能会把 `webpack` 它默认注入的一些`js code`的一些字面量,也给匹配进来,从而造成大面积误伤。这种误伤会导致 `js` 加载的失败,应用直接就挂了。所以暂时把 `js`字面量动态修改给去除掉了。同时导出了一个手动标记替换位置的方法: ```js const cardsColor = reactive([ replaceJs('bg-[#4268EA] shadow-indigo-100'), replaceJs('bg-[#123456] shadow-blue-100') ]) ``` 这样虽然解决了误伤的问题,但是带来了一些代码的入侵性,导致了开发体验不佳。不过这个问题在 `2.0.0` 解决了,请继续往下看。 ### 多框架兼容 > 源码 `framework` 部分 正如你看到的,我的这个 `plugin` 是兼容市面上,几乎所有还活着的主流开发框架的。在开始设计兼容方案的过程中,我发现这些框架编译成小程序的产物各有不同,一类是以 `uni-app`/`mpx`/`native(原生)`为例,它们就是按部就班的把类 `vue` 模板的写法,转义成小程序模板写法。另外一类是以 `taro`,`rax`,`remax` 为代表的 `jsx` 写法,这种写法的产物,最大的特点就是页面与组件的 `wxml` 里往往都是这样的结构: ```html