Closes #15138 This PR changes the postcss client to run in the `Once` hook instead of `OnceExit`. This makes sure the postcss order in v4 matches that of v3. Conceptually this also makes more sense, since we expect tailwindcss to be run as one of the first plugins in the pipeline (where `OnceExit` would run it almost at the end). To make sure it's still possible to use `postcss-import` before and have it resolve to the right paths, we also needed to change the `postcss-fix-relative-paths` plugin to run in the `Once` order (`postcss-import` also uses `Once` order so the order). ## Test Plan This issue had many ways in which it can manifest. I added a unit test to ensure the plugin order works but here's a concrete example when using the postcss plugin in Vite. ### Before Image `url()`s were not properly handled since the postcss plugin to transform these was run before Tailwind CSS could generate the class for it: <img width="2532" alt="Screenshot 2024-12-02 at 14 55 42" src="https://github.com/user-attachments/assets/2f23b409-1576-441d-9ffe-6f24ad6e7436"> ### After <img width="2529" alt="Screenshot 2024-12-02 at 14 53 52" src="https://github.com/user-attachments/assets/b754c3d8-1af1-4aeb-87da-0bfc3ffecdb7"> --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me>
74 lines
2 KiB
TypeScript
74 lines
2 KiB
TypeScript
import { normalizePath } from '@tailwindcss/node'
|
|
import path from 'node:path'
|
|
import type { AtRule, Plugin } from 'postcss'
|
|
|
|
const SINGLE_QUOTE = "'"
|
|
const DOUBLE_QUOTE = '"'
|
|
|
|
export default function fixRelativePathsPlugin(): Plugin {
|
|
// Retain a list of touched at-rules to avoid infinite loops
|
|
let touched: WeakSet<AtRule> = new WeakSet()
|
|
|
|
function fixRelativePath(atRule: AtRule) {
|
|
let rootPath = atRule.root().source?.input.file
|
|
if (!rootPath) {
|
|
return
|
|
}
|
|
|
|
let inputFilePath = atRule.source?.input.file
|
|
if (!inputFilePath) {
|
|
return
|
|
}
|
|
|
|
if (touched.has(atRule)) {
|
|
return
|
|
}
|
|
|
|
let value = atRule.params[0]
|
|
|
|
let quote =
|
|
value[0] === DOUBLE_QUOTE && value[value.length - 1] === DOUBLE_QUOTE
|
|
? DOUBLE_QUOTE
|
|
: value[0] === SINGLE_QUOTE && value[value.length - 1] === SINGLE_QUOTE
|
|
? SINGLE_QUOTE
|
|
: null
|
|
if (!quote) {
|
|
return
|
|
}
|
|
let glob = atRule.params.slice(1, -1)
|
|
|
|
// Handle eventual negative rules. We only support one level of negation.
|
|
let negativePrefix = ''
|
|
if (glob.startsWith('!')) {
|
|
glob = glob.slice(1)
|
|
negativePrefix = '!'
|
|
}
|
|
|
|
// We only want to rewrite relative paths.
|
|
if (!glob.startsWith('./') && !glob.startsWith('../')) {
|
|
return
|
|
}
|
|
|
|
let absoluteGlob = path.posix.join(normalizePath(path.dirname(inputFilePath)), glob)
|
|
let absoluteRootPosixPath = path.posix.dirname(normalizePath(rootPath))
|
|
|
|
let relative = path.posix.relative(absoluteRootPosixPath, absoluteGlob)
|
|
|
|
// If the path points to a file in the same directory, `path.relative` will
|
|
// remove the leading `./` and we need to add it back in order to still
|
|
// consider the path relative
|
|
if (!relative.startsWith('.')) {
|
|
relative = './' + relative
|
|
}
|
|
|
|
atRule.params = quote + negativePrefix + relative + quote
|
|
touched.add(atRule)
|
|
}
|
|
|
|
return {
|
|
postcssPlugin: 'tailwindcss-postcss-fix-relative-paths',
|
|
Once(root) {
|
|
root.walkAtRules(/source|plugin|config/, fixRelativePath)
|
|
},
|
|
}
|
|
}
|