Fall back to the plugin base when PostCSS has no from option#19980
Conversation
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.
WalkthroughThe PostCSS plugin now falls back to the configured 🚥 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
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/@tailwindcss-postcss/src/index.test.ts (1)
143-151: Consider strengthening the assertion to lock in the actual fix.
expect(result.css.length).toBeGreaterThan(0)is a fairly weak guard for this regression: it would still pass if the plugin silently emitted partial output without resolvingtailwindcss. Asserting that the@importwas actually replaced (e.g. contains a known token from the generated reset/preflight or no longer contains the raw@import 'tailwindcss') more directly verifies thatinputBasePathcorrectly fell back tobaseand the resolver foundtailwindcss.♻️ Example: tighten the assertion
let result = await processor.process(`@import 'tailwindcss'`) - expect(result.css.length).toBeGreaterThan(0) + expect(result.css.length).toBeGreaterThan(0) + // Ensure the import was actually resolved (not left as-is) using `base` as fallback. + expect(result.css).not.toContain(`@import 'tailwindcss'`)🤖 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 - 151, The current assertion only checks result.css.length > 0 which is weak; update the test "fallback to `base` directory when `result.opts.from` is not provided" to assert that the import was actually resolved by checking result.css no longer contains the literal "@import 'tailwindcss'" (or that it contains a known token from Tailwind’s generated output such as the preflight reset like "*, ::before, ::after" or "preflight" content); modify the assertion on the result produced by processor.process(...) (variables: processor, result, tailwindcss) to use a more specific expect (e.g., expect(result.css).not.toContain("@import 'tailwindcss'") or expect(result.css).toContain("*, ::before, ::after")) so the test verifies the resolver fell back to the provided base directory.
🤖 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-151: The current assertion only checks result.css.length > 0
which is weak; update the test "fallback to `base` directory when
`result.opts.from` is not provided" to assert that the import was actually
resolved by checking result.css no longer contains the literal "@import
'tailwindcss'" (or that it contains a known token from Tailwind’s generated
output such as the preflight reset like "*, ::before, ::after" or "preflight"
content); modify the assertion on the result produced by processor.process(...)
(variables: processor, result, tailwindcss) to use a more specific expect (e.g.,
expect(result.css).not.toContain("@import 'tailwindcss'") or
expect(result.css).toContain("*, ::before, ::after")) so the test verifies the
resolver fell back to the provided base directory.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d1fee6f7-0f9a-45b9-ba68-a4da39211cef
📒 Files selected for processing (3)
CHANGELOG.mdpackages/@tailwindcss-postcss/src/index.test.tspackages/@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 (1)
- packages/@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.