Skip to content

Ensure @tailwindcss/postcss rebuilds when the input CSS changes but its mtime is unchanged#20310

Merged
RobinMalfait merged 6 commits into
tailwindlabs:mainfrom
lazerg:fix/postcss-input-css-cache-invalidation
Jul 6, 2026
Merged

Ensure @tailwindcss/postcss rebuilds when the input CSS changes but its mtime is unchanged#20310
RobinMalfait merged 6 commits into
tailwindlabs:mainfrom
lazerg:fix/postcss-input-css-cache-invalidation

Conversation

@lazerg

@lazerg lazerg commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

@tailwindcss/postcss chooses between an incremental and a full rebuild by comparing the mtimes of the entry file and its resolved @import/@config/@plugin graph. It never looks at the input CSS itself, so when that CSS is produced by an upstream tool (e.g. Sass) and passed to the plugin via process(), it can change while the from file's mtime stays the same. The plugin then re-emits its previously cached output and silently drops the change.

This stores the input CSS per cache entry and takes the existing full rebuild path when it differs from the previous compile, mirroring the fix the CLI watcher already has for changed input files.

Fixes #20307

Test plan

  • Added a regression test in packages/@tailwindcss-postcss/src/index.test.ts that compiles two different inputs for the same on-disk from file (unchanged mtime) and asserts the second compile reflects the new CSS.
  • Confirmed it fails on main (the second compile returns the stale first output) and passes with the fix.
  • Ran the @tailwindcss/postcss package tests (all green) and checked formatting with Prettier.

@lazerg lazerg requested a review from a team as a code owner July 5, 2026 13:59
@lazerg lazerg force-pushed the fix/postcss-input-css-cache-invalidation branch from f08c9b8 to 70ccac2 Compare July 5, 2026 14:00
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9755ae0a-9938-41aa-a39b-c44d71d1b065

📥 Commits

Reviewing files that changed from the base of the PR and between 1490f5c and 1849cf9.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • packages/@tailwindcss-postcss/src/index.test.ts
  • packages/@tailwindcss-postcss/src/index.ts
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/@tailwindcss-postcss/src/index.ts
  • packages/@tailwindcss-postcss/src/index.test.ts

Walkthrough

This change adds input CSS content tracking to the Tailwind PostCSS plugin cache. The plugin stores the last processed input CSS string, compares it with the current CSS text during rebuild checks, and switches to a full rebuild when they differ. The tests add a reusable helper and cover both concurrent execution and stable-mtime input changes. A changelog entry records the fix.

Changes

Cohort / File(s) Summary
Input CSS diff-based rebuild detection
packages/@tailwindcss-postcss/src/index.ts
Adds inputCss, initializes it, and uses it to detect input CSS changes during rebuild selection.
packages/@tailwindcss-postcss/src/index.test.ts Adds the run helper, updates concurrent-build tests, and adds coverage for changing in-memory CSS with stable file mtime.
CHANGELOG.md Adds a Fixed note for recompilation when input CSS changes without a from mtime update.

Sequence Diagram(s)

sequenceDiagram
  participant Plugin
  participant CacheEntry
  Plugin->>Plugin: root.toString()
  Plugin->>CacheEntry: compare with inputCss
  CacheEntry-->>Plugin: mismatch detected
  Plugin->>Plugin: set rebuildStrategy = full
  Plugin->>CacheEntry: update inputCss
Loading

Related issues: None specified.

Related PRs: None specified.

Suggested labels: bug, postcss

Suggested reviewers: None specified.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: rebuilding when input CSS changes even if the file mtime does not.
Description check ✅ Passed The description directly explains the bug, fix, and test coverage for the changed rebuild behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Reviews (4): Last reviewed commit: "rename `cachedInputCss` to `inputCss`" | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/@tailwindcss-postcss/src/index.ts (1)

199-208: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

root.toString() re-serializes the input AST on every build.

This runs on every Once() invocation (i.e. every incremental rebuild, not just ones where the input actually changed), adding a full AST→string stringification cost to the hot rebuild path. For most entry files this is small, but for larger hand-authored CSS entry points it adds unnecessary work to every candidate-only rebuild in watch mode.

Consider comparing against the raw source text already retained by PostCSS's Input (root.source?.input.css) instead of re-stringifying the AST, which avoids the extra serialization work while still detecting the "input CSS changed but mtime didn't" case this PR targets.

⚡ Proposed optimization
-              let inputCss = root.toString()
+              let inputCss = root.source?.input.css ?? root.toString()
               if (context.cachedInputCss !== inputCss) {
                 rebuildStrategy = 'full'
                 context.cachedInputCss = inputCss
               }

Please confirm Input#css reliably holds the original raw source text passed to postcss.parse()/process() across the PostCSS version this package depends on, since this is an undocumented-adjacent internal property in some PostCSS API references.

packages/@tailwindcss-postcss/src/index.test.ts (1)

459-497: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test logic verified correct; minor duplication with existing helper.

Traced through the flow: without the fix, the second run() call would keep reusing the compiler/scanner built from the first input (since context.compiler ??= createCompiler() is a no-op once set, and mtime alone wouldn't trigger a full rebuild), so .second would not appear in the output — this test correctly exercises the regression.

The run() helper (Lines 469-477) duplicates the near-identical helper defined a few dozen lines earlier (Lines 425-433). Consider extracting a shared helper for both tests to reduce duplication.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c7015ee2-c2e6-4a71-9eee-319756345c87

📥 Commits

Reviewing files that changed from the base of the PR and between 67c745e and 70ccac2.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • packages/@tailwindcss-postcss/src/index.test.ts
  • packages/@tailwindcss-postcss/src/index.ts

@lazerg

lazerg commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up for both nitpicks, swapped root.toString() for root.source.input.css so it's not re-serializing the AST on every rebuild, and merged the two run() test helpers into one shared function.

@RobinMalfait RobinMalfait enabled auto-merge (squash) July 6, 2026 11:26

@RobinMalfait RobinMalfait left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@RobinMalfait RobinMalfait merged commit 9b0e8af into tailwindlabs:main Jul 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@tailwindcss/postcss: compiler cache ignores changed input CSS when from's mtime is unchanged — stale output with any preprocessor (Sass) pipeline

2 participants