Skip to content

[Tracking]: weapp-tailwindcss/vite 与 @tailwindcss/vite 的完整差异及 Web 收敛边界 #1107

Description

@sonofmagic

背景与结论

weapp-tailwindcss/vite 与 @tailwindcss/vite 目前不是同一层级的插件:

  • @tailwindcss/vite 是面向标准 Vite Web CSS module graph 的 Tailwind 编译器适配层。
  • weapp-tailwindcss/vite 同时拥有 Tailwind 生成、小程序候选收集、class 转义、模板/JS/CSS 转译、框架差异、分包样式归属、产物收尾和跨框架 HMR。

因此,小程序路径不应该机械追求与官方插件相同的 hook 数量;但 Generic Vite Web 的目标输出就是标准 Web CSS,不需要支付全部小程序 adaptor 成本。建议用本 issue 记录完整差异、明确哪些能力必须保留,并为 Generic Web 建立可量化的收敛边界。

相关执行项:

对照环境

weapp-tailwindcss: 5.3.4
@tailwindcss/vite: 4.3.3
tailwindcss: 4.3.3
Vite: 8.2.2
Rolldown: 1.2.5

复现项目可使用 weapp-vite/packages/dashboard。它是包含 Vue SFC、Iconify、Monaco 和 ECharts 的 Generic Vite Web SPA。

完整差异矩阵

维度 @tailwindcss/vite weapp-tailwindcss/vite 判断
主要职责 编译 Tailwind Web CSS 生成 Tailwind CSS,并适配小程序模板、JS、CSS、产物和框架生命周期 小程序能力必须保留;Generic Web 应裁剪
支持目标 标准 Web/SSR Vite 环境 Web、小程序及 uni-app、uni-app x、Taro、weapp-vite 等框架分支 产品边界不同
Tailwind 版本 与包版本严格同步的 Tailwind 4 v5 新生成管线只支持 Tailwind 4,并通过 runtime/generator 封装 当前主版本一致
用户 API 只有 optimize?: boolean | { minify? } appType、generator、cssEntries、tailwindcssBasedir、CSS/JS/template、分包、style injector、生命周期等跨 bundler 配置 高级能力合理;Web 默认值需要收敛
返回的 Vite 插件 固定 3 个:scan、generate:serve、generate:build Generic Web 当前返回 9 个插件;其他 framework 还会增加 extra plugins Web 分支可按 capability 构造
framework 选择 不做框架猜测 factory 阶段根据 cwd/basedir/env 提前选择 generic/Taro/uni/weapp-vite 分支 当前会在 Vite root 可用前冻结,见 #1106
target 选择 天然是 Web 根据 framework/env/显式 generator target 推断;Generic Vite 无平台 env 时回退小程序 Generic browser 默认应为 Web,见 #1106
Vite root configResolved 后使用真实 root factory 先使用 basedir/cwd,configResolved 再对齐 root framework plugin 数组无法在对齐后重选
CSS root 从进入 transform 的 CSS 和 Tailwind directive 建立 compiler 支持显式 cssEntries、自动 CSS source、placeholder、真实 import、分包入口 多入口能力合理;唯一 Vite root CSS 应自动发现
扫描模型 每个 environment + CSS id 持有 compiler/scanner,Oxide 扫描 compiler root/sources source collector 扫源码和 transform module,generator 内部还使用 Tailwind scanner Generic Web 存在重复扫描,见 #1105
candidate 用途 只用于生成 CSS 还用于验证并精确转译 JS/template class,维护分包 scope 和 runtime class set 小程序不能直接删除 candidate 层
CSS 生成阶段 build/serve 都在 CSS transform 中完成 Web 可在 CSS transform 生成;复杂框架还会延后、回放或在 bundle 阶段补全 Web 应稳定走 transform 主路径
hook filter CSS transform 使用 Vite/Rolldown 原生 transform.filter 5.3.4 的 Generic Web transform 在 handler 内判断;非 CSS 模块仍跨入 JS hook 可直接优化,见 #1105
hook 面 3 个插件,核心为 configResolved/configureServer/transform/hotUpdate 9 个 Generic Web 插件覆盖 config/resolveId/load/buildStart/transform/watchChange/handleHotUpdate/generateBundle/closeBundle 多数为适配能力,不应全部进入纯 Web 热路径
source/output relation 依赖 Vite module graph 和 scanner files 为每个子插件包装相同的 watchChange/handleHotUpdate 以维护 source-output relation 当前一次变更会重复进入多组包装 hook,可集中所有权
HMR 按 environment/id 缓存;非 CSS 扫描源变化时失效并触发 reload 维护 candidate diff、append/full regeneration、删除 class 保留策略、补充 CSS update 和 framework fallback reload Web 语义应对齐;小程序增强保留
删除 class dev scanner candidate set 为增量集合 默认 hmr.preserveDeletedCss: true,也支持完整重生成 默认语义接近,但实现不同
Vite Environment API 状态按 this.environment.name 隔离;resolver 区分 client/SSR 主要持有单个 ResolvedConfig 和共享 runtime/cache,没有 environment 级隔离 SSR/multi-environment 需要明确支持契约
resolver 使用 Vite CSS/JS resolver,按 environment 处理 client/SSR generator/source resolver + placeholder alias + CSS import rewrite 小程序需要增强;Generic Web 可复用 Vite resolver 语义
官方插件冲突 不处理其他 Tailwind owner generator mode 会移除 @tailwindcss/vite 和 @tailwindcss/postcss,并 alias Tailwind import,保证单一生成所有者 正确的安全边界,应保留
CSS 兼容处理 Tailwind 编译后按 optimize 使用官方 optimize/minify 还会执行小程序 selector、单位、preflight、变量、cascade layer、平台 CSS 和 Web compat 处理 小程序必要;Web 只保留明确的 Web compat
JS/template 转译 不处理 通过已验证 classNameSet 转义 JS、WXML/模板及运行时包名 小程序核心能力;Web 应完全跳过加载和注册
bundle 输出 不直接接管最终 bundle asset 可处理 CSS/JS/HTML asset、主样式注入、分包、root import shell、finalizer 和 style injector 小程序核心能力;Generic Web 已有 fast path,可继续前移门控
CSS code splitting 交给 Vite 维护 source-output relation、entry scope、共享/主 CSS 归属 多入口/分包需要;普通 Web 应交回 Vite
sourcemap css.devSourcemap 时返回 Tailwind compiler source map 多个 transform 返回 map: null,另有 bundle/source relation 与 sourcemap asset 处理 不能直接判定最终错误,但需要真实 build/dev parity 门禁
optimize/minify optimize 默认开启;是否 minify 跟随 build.cssMinify,可独立关闭 没有等价的顶层 Vite option,生成后还经过 style handler 和 Vite CSS pipeline 应明确 Web option 映射和输出阶段
cache 按 environment + CSS id 缓存 compiler/scanner/build dependencies compiler session、runtime class set、source collector、CSS memory、processed registry、LRU 和 bundle cache 小程序状态更复杂;Web 应减少重复状态
日志 默认只在 Tailwind DEBUG instrumentation 下输出 默认 info,普通 Vite Web 为保持安静需 logLevel: 'silent' 应跟随 Vite logger,见 #1106
package contract 声明 Vite ^5.2 || ^6 || ^7 || ^8 peer ./vite 有导出,但 package 未声明 Vite peer;Node 要求 ^22.18.0 || >=24.11.0 建议声明 optional Vite peer 和实际支持矩阵
依赖与冷启动 主要加载 Tailwind node/oxide 和 Vite adapter 入口静态加载 Babel/OXC、PostCSS adaptor、template、CSS、HMR、bundle 等完整依赖图 Generic Web import 慢约 195ms,见 #1105

当前 Generic Web 插件/hook 实测

@tailwindcss/vite

@tailwindcss/vite:scan
  configResolved, configureServer
@tailwindcss/vite:generate:serve
  transform(filter), hotUpdate
@tailwindcss/vite:generate:build
  transform(filter)

weapp-tailwindcss/vite

rewrite-css-imports
  resolveId, transform, watchChange, handleHotUpdate
source-candidates
  load, transform, buildStart, generateBundle, watchChange, handleHotUpdate
generate:serve
  transform, watchChange, handleHotUpdate
generate:build
  transform, watchChange, handleHotUpdate
generate:serve-hmr
  transform, watchChange, handleHotUpdate
js:serve
  transform, watchChange, handleHotUpdate
watch-css-cache
  configResolved, watchChange, handleHotUpdate
post
  config, configResolved, generateBundle, watchChange, handleHotUpdate
css-finalizer
  generateBundle, closeBundle, closeWatcher, watchChange, handleHotUpdate

这里不是要求将 9 个插件强行压缩成 3 个,而是希望 Generic Web profile 不注册确定不会工作的 JS/template/小程序产物 hook,并避免多个插件重复观察同一个 source change。

已确认的正确性与性能边界

建议的收敛架构

P0:定义 runtime capability profile

在真实 Vite config/root/environment 可用后,一次性解析以下能力,而不是由各插件重复判断:

interface ViteTailwindCapabilityProfile {
  framework: 'generic' | 'taro' | 'uni-app' | 'uni-app-x' | 'weapp-vite'
  target: 'web' | 'mini-program' | 'native-app'
  command: 'serve' | 'build'
  watch: boolean
  needsSourceCandidateGraph: boolean
  needsJsTransform: boolean
  needsTemplateTransform: boolean
  needsBundleFinalizer: boolean
  needsStyleInjection: boolean
}

由 profile 构造实际插件数组。Generic Web production 默认只保留 CSS root discovery、Tailwind generation 和必要的 Web CSS finalize;显式高级能力再打开对应插件。

P0:为 Generic Web 建立单一扫描所有者

二选一并通过 profile 验证:

  1. Web generator 直接复用 Tailwind compiler/scanner 的 candidates 与 dependencies。
  2. 纯 Generic Web 不建立 source candidate collector,只让 Tailwind scanner 扫描;需要 JS/template 转译的 framework 才保留 collector。

不要继续维护两套独立 scanner 再在末端比较结果。候选来源、依赖文件和 HMR invalidation 应有单一所有者。

P1:提供官方插件形状的 Web 入口

import tailwindcss from 'weapp-tailwindcss/vite/web'

plugins: [tailwindcss()]

建议该入口支持官方的 optimize option,并固定 Generic + Web profile。实现上可以复用官方插件、共享 Tailwind compiler adapter,或提供语义等价的轻量实现;关键是不要静态加载 JS/template/mini-program finalizer 依赖。

主 WeappTailwindcss() 在可靠推断为 Generic Web 后,也应收敛到同一内部 profile,避免维护两套 Web 实现。

P1:采用 Vite 原生能力

  • 为 CSS-only transform 增加原生 transform.filter。
  • 按 this.environment.name 隔离 compiler/scanner/cache,明确 client、SSR 和未来 environment 行为。
  • 使用 environment-aware resolver,避免 root alias 和内部 resolver 出现不同解析语义。
  • 将 source-output relation 的 watchChange/handleHotUpdate 观察集中到一个 owner plugin,避免对子插件重复包装。

P1:明确 Web 输出契约

  • selector、declaration、layer、custom variant、plugin、@source 和 dependency watch 与官方一致。
  • optimize=false 时比较结构化 CSS;开启 optimize/minify 后分别验证语义和字节稳定性。
  • 明确 css.devSourcemap、build.sourcemap、CSS code splitting、SSR 与 library mode 的支持范围。
  • 默认日志与 Vite 对齐,debug/计时由显式开关启用。

P2:补齐 package contract

  • 为 ./vite 声明 optional Vite peerDependency,并给出实际支持范围。
  • 对 Web 专用入口建立 export-size/import-time regression report,不设固定毫秒阈值,但持续记录依赖和初始化阶段。
  • 将 Node 版本差异写入 Web migration 文档,明确只影响维护构建环境,不影响静态产物消费者。

兼容边界

  • 不删除 cssEntries、分包 scope、main CSS matcher、style injector 或 framework extra plugins。
  • 不把小程序 JS/template class 转译改成启发式匹配;继续只处理 Tailwind 验证过的 classNameSet。
  • 不在同一受管构建中同时运行两个 Tailwind generator;Web 入口若内部复用官方实现,仍必须保持单一所有者。
  • 显式 appType、generator.target、platform、framework env 和高级配置始终优先于自动 profile。
  • uni-app、uni-app x、Taro、weapp-vite、watch build 和小程序 HMR 继续保留当前增强路径。
  • Generic Web fast path 遇到多入口歧义、显式 style injection 或无法证明安全时,允许回退完整管线。

建议回归矩阵

  1. Generic Vue/React Vite Web:build、dev、HMR、新增/删除 class。
  2. client + SSR/multi-environment:状态和 resolver 不串环境。
  3. 单 CSS root、多 CSS root、CSS code splitting、library mode。
  4. @plugin、@source、source(none)、自定义 variant、任意值和 Iconify 动态 utility。
  5. optimize on/off、cssMinify on/off、dev/build sourcemap。
  6. monorepo Web 子包,上层含 weapp-vite/Taro/uni-app 依赖时不得误判。
  7. Taro/uni-app/weapp-vite 的 Web target 与小程序 target 分别回归。
  8. 分包、独立分包、main CSS injection、watch build 和 framework HMR 不退化。

比较规则建议使用结构化 CSS AST/selector/declaration 集合,避免把压缩变量名、规则顺序或完整 minified snapshot 当作唯一断言。

完成标准

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions