👁 79 次阅读
Tailwind Scanner globs 为何仍遍历 gitignore 目录
来源:https://github.com/tailwindlabs/tailwindcss/issues/20533现象描述
issue #20533 报告的是@tailwindcss/oxide 中 Scanner 的一处不一致行为:自动源检测在构建 scanner.files 时会正确遵守 .gitignore,但在计算 scanner.globs 时却会走进被 gitignore 忽略的目录。返回的 globs 结果是正确的(被忽略的目录并不包含在内),问题纯粹出在遍历过程本身。
报告者给出的复现版本是 @tailwindcss/oxide 4.3.3,并说明在 4.1.14 中表现相同。构建工具使用 @tailwindcss/vite,同时也在 JetBrains Tailwind CSS 插件(其 oxide-helper.js)以及直接调用 Scanner API 时复现。环境为 Node.js v24.18.1,操作系统 Linux(btrfs)。
报告者提供了一个自包含的复现脚本,无需完整项目:
// repro.mjs — run: strace -f -e trace=statx,openat -o trace.log node repro.mjs && grep -c 'repo/var/cache' trace.log
import { createRequire } from 'node:module';
import { mkdirSync, writeFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
const require = createRequire(import.meta.url);
const { Scanner } = require('@tailwindcss/oxide');
const root = '/tmp/oxide-repro/repo';
mkdirSync(${root}/var/cache/profiler/aa, { recursive: true });
mkdirSync(${root}/templates, { recursive: true });
writeFileSync(${root}/.gitignore, '/var/\n');
writeFileSync(${root}/templates/page.html, '<div class="flex"></div>');
writeFileSync(${root}/var/cache/profiler/aa/bb1234, '<div class="grid"></div>');
execSync('git init -q', { cwd: root });
const scanner = new Scanner({ sources: [{ base: root, pattern: '**/*', negated: false }] });
console.log('files:', scanner.files); // correct: only templates/page.html
console.log('globs:', scanner.globs); // correct result, but computing it walks var/
从 strace 输出可以观察到两条路径的差异:
files遍历会打开var/,检查var/.gitignore、var/.ignore与var/.git/info/exclude,然后停止,行为正确。globs遍历则会依次打开var/、var/cache、var/cache/profiler、var/cache/profiler/aa,并对其中每个文件执行 stat,整个过程中没有读取任何 ignore 文件。
背景原因
在大型被忽略目录存在时,这种多余的遍历代价很高。报告者描述其项目里有一个 Symfony 的var/cache/dev/profiler 目录,包含约 1300 万个小文件:
vite build在read_extent_buffer_pages处挂起超过 20 分钟,单线程阻塞在主线程上,位置正是读取globs。- JetBrains Tailwind helper 每个进程持续占用 15%–25% CPU。该 helper 总会附加
{ base: <project root>, pattern: '**/*' },因此无论 CSS 内容如何,这次遍历都会发生。
main 分支上已不再复现。参与者 akasakariko 表示已对此进行调查,在 v4.3.3 上复现了报告描述的 globs 遍历进入被忽略的 var/cache 子目录的行为,但在当前 main 上同样的测试不再进入这些目录。该参与者将修复归因于 PR #20408(标题为 “Fix slow Vite rebuilds in projects with large gitignored directories”,合并于 2026-08-12),并称 scanner 的 71 个集成测试在 main 上全部通过。
这里需要区分两个说法:代码合并进主干与修复随某个已发布版本分发,是两件不同的事。截至 issue 讨论,可核实的信息是 PR #20408 已被合并到 main,而包含该修复的稳定版本号在讨论中并未给出。
当前处理路径与局限
该 issue 目前为 open 状态,官方尚未在本 issue 下给出实质回应与修复版本方案。讨论中的实质内容来自社区侧的三条评论,可归纳为以下两点:- 代码已进主干,但未进入已发布二进制。 据 issue 讨论,akasakariko 称在 v4.3.3 上复现了问题,在
main上不再复现,并指向已合并的 PR #20408。这意味着使用版本化二进制分发的工具(IDE 插件等)仍会遇到该行为。 - 发布渠道需要补丁版本。 FluffyDiscord 的回应是,如果该说法成立,需要一个 hotfix release,让 IDE 和其他使用版本化二进制的工具能够拿到,并强调这不是小问题:它可以让项目构建变得极慢,在 dev build 中甚至可能无法完成构建。
参数或对比表格
| 项目 | files 遍历 | globs 遍历 |
| 是否读取 ignore 文件 | 是(.gitignore、.ignore、.git/info/exclude) | 否 |
| 是否进入 gitignore 目录 | 否 | 是(在受影响版本上) |
| 返回结果正确性 | 正确 | 正确,仅遍历过程有问题 |
| 受影响版本 | — | v4.3.3、4.1.14(据 issue 讨论) |
| 主干状态 | — | main 上不再复现(据 issue 讨论,关联 PR #20408) |
常见错误排查
1. 看到构建长时间卡住且 CPU 单核占满。 报告者的观测是vite build 在 read_extent_buffer_pages 处挂起超过 20 分钟,单线程阻塞在主线程上,位置在读取 globs。这属于可观察现象,具体成因需结合本地项目结构判断。
2. IDE 插件进程持续占用 CPU。 报告者观测到 JetBrains Tailwind helper 每个进程 15%–25% CPU,并指出该 helper 总会附加 { base: <project root>, pattern: '**/*' },因此遍历与 CSS 内容无关。
3. 怀疑 globs 结果本身有误。 报告者明确说明返回的 globs 是正确的,被忽略的目录不在其中,问题仅在遍历过程。排查时不应把方向放在结果错误上。
4. 在 4.3.3 上确认是否仍存在。 据 issue 讨论,该行为在 v4.3.3 可复现;若使用版本化二进制,需要确认所用工具实际加载的 oxide 版本。
5. 在 main 上验证是否已消失。 据 issue 讨论,当前 main 上同样的测试不再进入被忽略目录,scanner 的 71 个集成测试全部通过。但 main 上的状态不等同于已发布版本的状态。
完整解决方案(含代码)
截至 issue 讨论,本 issue 处于 open 状态,官方尚未在本条目下合入方案或给出修复版本号。据 issue 讨论,修复已通过 PR #20408 进入main,但该修复尚未反映到 v4.3.3 这类已发布版本中。在官方发布包含该修复的版本之前,可行的处理路径如下。
使用 main 之外的渠道获取修复。 由于修复已进主干但未进入版本化二进制,依赖已发布包的工具无法直接受益。是否需要切换分支或等待 hotfix release,取决于使用方对稳定性的要求。
核实所依赖工具的 oxide 版本。 报告者复现的版本为 4.3.3,并说明 4.1.14 表现相同。对于 IDE 插件,需要确认其内置或加载的 oxide 二进制版本。
复现脚本可用于本地确认。 报告者提供的 repro.mjs 不依赖完整项目,可直接用于判断本地环境是否触发 globs 遍历进入被忽略目录。脚本中的配置为 { sources: [{ base: root, pattern: '**/*', negated: false }] },通过对比 scanner.files 与 scanner.globs 的输出以及 strace 中是否出现 repo/var/cache 计数来判断。
在项目侧减少被忽略目录的文件数量。 报告者描述的痛点是 var/cache/dev/profiler 下约 1300 万个小文件。在修复版本可用之前,控制此类目录的规模是缓解遍历代价的直接手段。该方向属于应用层缓解,不改变 scanner 行为本身。
实践建议
- 在受影响版本上,避免在项目根目录放置包含大量文件的被忽略目录,尤其是缓存与 profiler 类目录。
- 对使用
@tailwindcss/vite或其他封装构建工具的项目,构建卡顿需要同时排查 oxide 的files与globs两条遍历路径,而不是只看 CSS 配置。 - 对 IDE 插件,注意其可能无条件附加
**/*模式,因此即使项目 CSS 未显式声明扫描范围,遍历仍可能发生。 - 关注 PR #20408 的发布状态,并通过 GitHub API 查询 PR 状态以核实其是否已进入目标版本,而不是仅依据 “已在 main 修复” 的说法。
- 若在较新版本上仍复现,按 issue 讨论的惯例,应记录复现的版本号并向对应渠道上报。
FAQ
问:这个问题的根因是什么? 答:自动源检测在构建scanner.files 时遵守 .gitignore,但在计算 scanner.globs 时不读取任何 ignore 文件,直接走进被忽略目录。返回的 globs 结果本身正确,问题在遍历过程。
问:哪些版本受到影响?
答:据 issue 讨论,复现版本为 @tailwindcss/oxide 4.3.3,4.1.14 表现相同。
问:main 分支上是否已修复?
答:据 issue 讨论,akasakariko 称在 main 上同样测试不再进入被忽略目录,并归因于已合并的 PR #20408,scanner 的 71 个集成测试在 main 上全部通过。该说法属于社区参与者的调查结论。
问:是否已经有包含修复的正式版本?
答:截至 issue 讨论,没有给出包含该修复的稳定版本号。讨论中有参与者提出需要 hotfix release,以便使用版本化二进制的工具能够获取。
问:在修复版本发布前可以做什么?
答:报告者提供的复现脚本可用于本地确认是否触发该遍历;同时在项目侧控制被忽略目录的文件规模,尤其是缓存与 profiler 类目录,可以缓解遍历带来的代价。
🔗 相关阅读(你可能还想看)
📄 Outlook 节点创建草稿添加附件为何报 UnableToDeserializePostBody?
📄 Flowise 点击市场或聊天页为何整页白屏?
📄 如何解决 Azure AI Search 向量存储的 $filter 递归深度超限错误?
📄 如何解决 shadcn/ui Sidebar 与 Header 重叠的问题?
📄 Next.js 开发服务器为何会 JavaScript heap out of memory?
📄 Nuxt 偶发 Cannot read properties of undefined 报错是怎么回事
📄 如何解决「Visual Studio Code for ipad」相关的问题?
📄 如何在AutoGen中实现企业级代理治理的加密行动收据(AAR)?
📄 记忆内容未清洗直接注入系统提示词,如何防范间接提示注入?
crewAI LiteAgent 将记忆内容直接拼接到系统提示词,存在间接提示注入风险。社区提出分层防御方案,但 iss