Enable URL rewriting for PostCSS (#16965)

Fixes #16636 

This PR enables URL rebasing for PostCSS. Furthermore it fixes an issue
where transitive imports rebased against the importer CSS file instead
of the input CSS file. While fixing this we noticed that this is also
broken in Vite right now and that our integration test swallowed that
when testing because it did not import any Tailwind CSS code and thus
was not considered a Tailwind file.

## Test plan

- Added regression integration tests
- Also validated it against the repro of
https://github.com/tailwindlabs/tailwindcss/issues/16962:
  
<img width="1149" alt="Screenshot 2025-03-05 at 16 41 01"
src="https://github.com/user-attachments/assets/85396659-d3d0-48c0-b1c7-6125ff8e73ac"
/>

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This commit is contained in:
Philipp Spiess 2025-03-07 12:18:10 +01:00 • committed by GitHub
parent d18fed1dca
commit 225f3233b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 98 additions and 8 deletions

View file

@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Vite: Fix `url(…)` rebasing in transitively imported CSS files ([#16965](https://github.com/tailwindlabs/tailwindcss/pull/16965))
- PostCSS: Rebase `url(…)`s in imported CSS files ([#16965](https://github.com/tailwindlabs/tailwindcss/pull/16965))
- Ensure utilities are sorted based on their actual property order ([#16995](https://github.com/tailwindlabs/tailwindcss/pull/16995))
- Ensure strings in Pug and Slim templates are handled correctly ([#17000](https://github.com/tailwindlabs/tailwindcss/pull/17000))
- Ensure `}` and `{` are valid boundary characters when extracting candidates ([#17001](https://github.com/tailwindlabs/tailwindcss/pull/17001))

View file

@ -0,0 +1,74 @@
import { css, js, json, test } from '../utils'
test(
'can rewrite urls in production builds',
{
fs: {
'package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"tailwindcss": "workspace:^",
"@tailwindcss/postcss": "workspace:^"
}
}
`,
'postcss.config.js': js`
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
'src/index.css': css`
@reference 'tailwindcss';
@import './dir-1/bar.css';
@import './dir-1/dir-2/baz.css';
@import './dir-1/dir-2/vector.css';
`,
'src/dir-1/bar.css': css`
.test1 {
background-image: url('../../resources/image.png');
}
`,
'src/dir-1/dir-2/baz.css': css`
.test2 {
background-image: url('../../../resources/image.png');
}
`,
'src/dir-1/dir-2/vector.css': css`
@import './dir-3/vector.css';
.test3 {
background-image: url('../../../resources/vector.svg');
}
`,
'src/dir-1/dir-2/dir-3/vector.css': css`
.test4 {
background-image: url('./vector-2.svg');
}
`,
},
},
async ({ fs, exec, expect }) => {
await exec('pnpm postcss src/index.css --output dist/out.css')
expect(await fs.dumpFiles('dist/out.css')).toMatchInlineSnapshot(`
"
--- dist/out.css ---
.test1 {
background-image: url('../resources/image.png');
}
.test2 {
background-image: url('../resources/image.png');
}
.test4 {
background-image: url('./dir-1/dir-2/dir-3/vector-2.svg');
}
.test3 {
background-image: url('../resources/vector.svg');
}
"
`)
},
)

View file

@ -42,31 +42,36 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>
`,
'src/main.ts': ts``,
'src/app.css': css`
@reference 'tailwindcss';
@import './dir-1/bar.css';
@import './dir-1/dir-2/baz.css';
@import './dir-1/dir-2/vector.css';
`,
'src/dir-1/bar.css': css`
.bar {
.test1 {
background-image: url('../../resources/image.png');
}
`,
'src/dir-1/dir-2/baz.css': css`
.baz {
.test2 {
background-image: url('../../../resources/image.png');
}
`,
'src/dir-1/dir-2/vector.css': css`
.baz {
@import './dir-3/vector.css';
.test3 {
background-image: url('../../../resources/vector.svg');
}
`,
'src/dir-1/dir-2/dir-3/vector.css': css`
.test4 {
background-image: url('./vector-2.svg');
}
`,
'resources/image.png': binary(SIMPLE_IMAGE),
'resources/vector.svg': svg`
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
@ -76,6 +81,14 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
<circle cx="200" cy="100" r="80" fill="green" />
</svg>
`,
'src/dir-1/dir-2/dir-3/vector-2.svg': svg`
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="blue" />
<circle cx="200" cy="100" r="80" fill="green" />
<rect width="100%" height="100%" fill="red" />
<circle cx="200" cy="100" r="80" fill="pink" />
</svg>
`,
},
},
async ({ fs, exec, expect }) => {
@ -87,7 +100,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
await fs.expectFileToContain(files[0][0], [SIMPLE_IMAGE])
let images = await fs.glob('dist/**/*.svg')
expect(images).toHaveLength(1)
expect(images).toHaveLength(2)
await fs.expectFileToContain(files[0][0], [/\/assets\/vector-.*?\.svg/])
},

View file

@ -40,8 +40,8 @@ function createCompileOptions({
async loadModule(id: string, base: string) {
return loadModule(id, base, onDependency, customJsResolver)
},
async loadStylesheet(id: string, base: string) {
let sheet = await loadStylesheet(id, base, onDependency, customCssResolver)
async loadStylesheet(id: string, sheetBase: string) {
let sheet = await loadStylesheet(id, sheetBase, onDependency, customCssResolver)
if (shouldRewriteUrls) {
sheet.content = await rewriteUrls({

View file

@ -110,6 +110,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
DEBUG && I.start('Create compiler')
let compiler = await compileAst(ast, {
base: inputBasePath,
shouldRewriteUrls: true,
onDependency: (path) => {
context.fullRebuildPaths.push(path)
},