Fall back to the plugin base when PostCSS has no from option#19980
Fall back to the plugin base when PostCSS has no from option#19980rebasecase wants to merge 1 commit intotailwindlabs:mainfrom
base when PostCSS has no from option#19980Conversation
When PostCSS invokes the plugin without `result.opts.from` (some bundlers,
including Turbopack, do this for certain CSS inputs), `inputFile` defaults
to `''`, and `path.dirname(path.resolve(''))` resolves to the *parent* of
`process.cwd()` rather than CWD. That made `@import 'tailwindcss'` walk
above the project root and fail with "Can't resolve 'tailwindcss' in
'<parent of CWD>'".
The plugin already computes `base = opts.base ?? process.cwd()`. Use it as
the fallback so the project root is searched when `from` is missing.
WalkthroughThis change addresses a regression in the Tailwind PostCSS plugin's base path derivation when PostCSS is invoked without a 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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 |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/@tailwindcss-postcss/src/index.test.ts (1)
143-157: Optional: strengthen the regression assertion.
expect(result.css.length).toBeGreaterThan(0)would also pass if the plugin silently emitted, say, a comment or an unprocessed@importline. Asserting that a known utility from theexample-projectfixture appears (e.g..underline, which is referenced in the fixture'sindex.html) more directly proves that@import 'tailwindcss'resolved against the configuredbaseand that scanning ran. It also guards against future regressions where the import silently no-ops.♻️ Suggested assertion
- let result = await processor.process(`@import 'tailwindcss'`) - - expect(result.css.length).toBeGreaterThan(0) + let result = await processor.process(`@import 'tailwindcss'`) + + expect(result.css.length).toBeGreaterThan(0) + expect(result.css).toContain('.underline')Also, organizationally this case fits naturally inside the existing
describe('processing without specifying a base path', ...)block (or a sibling describe), but that's purely cosmetic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@tailwindcss-postcss/src/index.test.ts around lines 143 - 157, Update the test "processing input without a `from` option falls back to the plugin `base`" to assert a concrete utility from the example-project fixture rather than only checking result length: replace the loose assertion expect(result.css.length).toBeGreaterThan(0) with a check that the generated CSS contains a known class (for example assert result.css includes ".underline" or another utility present in the fixtures) so the test verifies that the import resolved and scanning ran; optionally move this test into the existing describe('processing without specifying a base path', ...) block for organization.packages/@tailwindcss-postcss/src/index.ts (1)
185-206: Optional: avoid forcing a full rebuild on every no-frominvocation.When
inputFile === '', line 189 still pushes the empty string intofiles. Then at line 192,fs.statSync('', { throwIfNoEntry: false })yieldsnull, thefile === inputFilebranch matches, andrebuildStrategyis forced to'full'on every subsequent invocation — defeating the mtime-based cache for exactly the Turbopack/no-frompath this PR enables. Pre-existing, but now reachable in a supported flow.♻️ Proposed tweak
- files.push(inputFile) + if (inputFile) files.push(inputFile)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@tailwindcss-postcss/src/index.ts around lines 185 - 206, The code currently pushes inputFile (which can be the empty string for no-`from` invocations) into files and then treats a stat failure of that empty path as forcing rebuildStrategy = 'full'; update the logic in the block that builds "files" (and/or just before iterating) to exclude empty/falsey filenames so that inputFile === '' is not pushed or processed: either only push inputFile when it is a non-empty string, or filter files to remove '' before the fs.statSync loop, leaving the existing file === inputFile branch unchanged for real files; reference symbols: files, inputFile, result.messages, context.mtimes, rebuildStrategy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/`@tailwindcss-postcss/src/index.test.ts:
- Around line 143-157: Update the test "processing input without a `from` option
falls back to the plugin `base`" to assert a concrete utility from the
example-project fixture rather than only checking result length: replace the
loose assertion expect(result.css.length).toBeGreaterThan(0) with a check that
the generated CSS contains a known class (for example assert result.css includes
".underline" or another utility present in the fixtures) so the test verifies
that the import resolved and scanning ran; optionally move this test into the
existing describe('processing without specifying a base path', ...) block for
organization.
In `@packages/`@tailwindcss-postcss/src/index.ts:
- Around line 185-206: The code currently pushes inputFile (which can be the
empty string for no-`from` invocations) into files and then treats a stat
failure of that empty path as forcing rebuildStrategy = 'full'; update the logic
in the block that builds "files" (and/or just before iterating) to exclude
empty/falsey filenames so that inputFile === '' is not pushed or processed:
either only push inputFile when it is a non-empty string, or filter files to
remove '' before the fs.statSync loop, leaving the existing file === inputFile
branch unchanged for real files; reference symbols: files, inputFile,
result.messages, context.mtimes, rebuildStrategy.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 2c32dbe5-6989-47a4-bd56-a1acedd1cd4f
📒 Files selected for processing (2)
packages/@tailwindcss-postcss/src/index.test.tspackages/@tailwindcss-postcss/src/index.ts
Summary
@tailwindcss/postcssderivesinputBasePathfromresult.opts.from:When PostCSS calls the plugin without
from(some bundlers, including Turbopack, do this for certain CSS inputs),inputFileis'',path.resolve('')returnsprocess.cwd(), andpath.dirname(...)therefore returns the parent of CWD. The downstreamcompileAst({ base: inputBasePath })call then asks the resolver to findtailwindcssfrom one level above the project root, which fails with:The plugin already computes
base = opts.base ?? process.cwd()near the top. Reusing that as the fallback gives a sensible default (CWD) and respects an explicitopts.basewhen set.Test plan
Added a test in
packages/@tailwindcss-postcss/src/index.test.tsthat processes@import 'tailwindcss'viaprocessor.process(input)with nofromoption. Before the fix, this throwsError: Can't resolve 'tailwindcss' in '<parent of CWD>'; after the fix, the import resolves and the processor returns non-empty CSS.I wasn't able to run the suite locally —
pnpm buildrequirescargofor@tailwindcss/oxideand I don't have a Rust toolchain set up — so the test has been written to match existing conventions inindex.test.ts(vitest, plainpostcss([tailwindcss({...})]).process(...)), and I'm relying on CI to verify.