Repository navigation
Ensure @tailwindcss/postcss rebuilds when the input CSS changes but its mtime is unchanged - #20310
Conversation
… its mtime is unchanged
f08c9b8 to
70ccac2
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughThis 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
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
Related issues: None specified. Related PRs: None specified. Suggested labels: bug, postcss Suggested reviewers: None specified. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
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. Comment |
Confidence Score: 5/5This looks safe to merge.
Reviews (4): Last reviewed commit: "rename `cachedInputCss` to `inputCss`" | Re-trigger Greptile |
There was a problem hiding this comment.
🧹 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#cssreliably holds the original raw source text passed topostcss.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 valueTest 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 (sincecontext.compiler ??= createCompiler()is a no-op once set, and mtime alone wouldn't trigger a full rebuild), so.secondwould 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
📒 Files selected for processing (3)
CHANGELOG.mdpackages/@tailwindcss-postcss/src/index.test.tspackages/@tailwindcss-postcss/src/index.ts
|
Pushed a follow-up for both nitpicks, swapped |
This information is already scoped to a `CacheEntry`
|
do the webpack and turbopack loaders need the same change? looks like they only check mtime |
…input CSS changes (#20582) This PR fixes an issue where `next dev` with the `@tailwindcss/turbopack` loader keeps serving the CSS of the previous edit after you edit the CSS file. Only a restart of the dev server shows the latest version. Fixes #20581 The loader caches a compiler for each CSS file. It only does a full rebuild when the mtime of the input file (or of one of its dependencies) changes. After a save, Turbopack can run the loader two times: 1. With the previous version of the CSS file. The file on disk already has the new mtime, so we did a full rebuild from the old source, and stored the new mtime. 2. With the new version of the CSS file. The mtime did not change since the first call, so we did an incremental build with the stale compiler of the first call. The loader registers the folders that it scans as context dependencies, and the CSS file is in one of them (e.g. `app/`). So one save invalidates the loader in two ways, and the first run can still get the previous content of the file. `@tailwindcss/postcss` had the same problem with Turbopack (#17554), and it compares the input CSS with the input CSS of the last build since #20310. The loader now does the same: it stores the source on the cache entry, and does a full rebuild when the source changes. `@tailwindcss/turbopack` is a copy of `@tailwindcss/webpack`, so the webpack loader gets the same fix. With webpack, the problem shows up when a loader that runs before ours changes the CSS, but not the input file. For example, `sass-loader` inlines a partial, and you edit that partial. The matrix of the integration tests in CI had no `turbopack` entry, so the tests in `integrations/turbopack` never ran in CI. This PR adds it. ## Test plan - Added an integration test to `integrations/turbopack/loader.test.ts`. It edits `app/globals.css` three times during `next dev`, and checks that the served CSS has each edit. On `main` (with Next.js 16.4.1), the served CSS still has `.custom-1` after the first edit. With this change, it passes. - Added an integration test to `integrations/webpack/loader.test.ts`, with a loader before `@tailwindcss/webpack` that inlines `src/partial.css`. In watch mode, it edits that partial. On `main`, the output keeps `.partial-1`. With this change, it passes. - Called the built loaders directly, like Turbopack does: first with the previous source after the file changed on disk, then with the new source. On `main`, the second call returns the CSS of the previous version. With this change, each call returns the CSS of the source that it gets. - `pnpm run build` passes, and all tests in `integrations/turbopack/loader.test.ts` and `integrations/webpack/loader.test.ts` pass. <sub>Sent by an AI assistant on behalf of @RobinMalfait.</sub>
Summary
@tailwindcss/postcsschooses between an incremental and a full rebuild by comparing the mtimes of the entry file and its resolved@import/@config/@plugingraph. 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 viaprocess(), it can change while thefromfile'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
packages/@tailwindcss-postcss/src/index.test.tsthat compiles two different inputs for the same on-diskfromfile (unchanged mtime) and asserts the second compile reflects the new CSS.main(the second compile returns the stale first output) and passes with the fix.@tailwindcss/postcsspackage tests (all green) and checked formatting with Prettier.