Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2104,9 +2104,15 @@ const UrlRewritePostcssPlugin: PostCSS.PluginCreator<{

return {
postcssPlugin: 'vite-url-rewrite',
OnceExit(root) {
OnceExit(_root, { result }) {
const promises: Promise<void>[] = []
root.walkDecls((declaration) => {
// walk `result.root` rather than the `root` argument: a plugin running
// earlier (e.g. postcss-lightningcss, @tailwindcss/vite) may hand back a
// freshly parsed tree by reassigning `result.root`, and PostCSS keeps
// passing the pre-reassignment root to the OnceExit hooks that run after
// it. Rewriting urls on that discarded tree leaves the emitted CSS
// pointing at the original source-relative paths.
result.root.walkDecls((declaration) => {
const importer = declaration.source?.input.file
if (!importer) {
opts.logger.warnOnce(
Expand Down
17 changes: 17 additions & 0 deletions playground/css/__tests__/css.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,20 @@ test('postcss plugin that injects url() at OnceExit', async () => {
isBundled ? /base64/ : '/injected-source/injected-bg.png',
)
})

// a plugin that reassigns `result.root` (like postcss-lightningcss or
// @tailwindcss/vite) discards the tree Vite's url rewriter walked, so the
// rewritten urls must be read back off the live root
// (https://github.com/vitejs/vite/issues/23348)
test('postcss plugin that replaces the root at OnceExit', async () => {
await page.goto(viteTestUrl)
const replaced = await page.waitForSelector('.replace-root')
// the raw `./replaced-bg.png` must not survive: it is relative to the
// source file, not to wherever the emitted stylesheet ends up. The hash
// (bundled) and the source directory (served) are what prove it was rebased
expect(await getBg(replaced)).toMatch(
isBundled
? /\/replaced-bg-[-\w]+\.png/
: '/replace-root-source/replaced-bg.png',
)
})
4 changes: 4 additions & 0 deletions playground/css/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ <h1>CSS</h1>
<p class="inject-url-once-exit">
PostCSS plugin injecting at OnceExit: this should have a background image
</p>
<p class="replace-root">
PostCSS plugin replacing the root at OnceExit: this should have a background
image
</p>

<p class="sass">SASS: This should be orange</p>
<p class="sass-at-import">
Expand Down
1 change: 1 addition & 0 deletions playground/css/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import './less-plugin.less'
import './stylus.styl'
import './manual-chunk.css'
import './postcss-inject-url.css'
import './postcss-replace-root.css'

import urlCss from './url-imported.css?url'
appendLinkStylesheet(urlCss)
Expand Down
1 change: 1 addition & 0 deletions playground/css/postcss-replace-root.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@replace-root;
29 changes: 29 additions & 0 deletions playground/css/postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default {
testSourceInput,
testInjectUrl,
testInjectUrlOnceExit,
testReplaceRoot,
],
}

Expand Down Expand Up @@ -116,3 +117,31 @@ function testInjectUrlOnceExit() {
}
}
testInjectUrlOnceExit.postcss = true

/**
* A plugin for testing url() rewriting when a plugin replaces the whole tree
* at OnceExit by reassigning `result.root` (like postcss-lightningcss or
* @tailwindcss/vite do).
*/
function testReplaceRoot() {
return {
postcssPlugin: 'replace-root',
OnceExit(root, { result, postcss }) {
if (!root.some((node) => node.name === 'replace-root')) return
// re-parse from scratch and hand back a brand new tree instead of
// mutating `root`, the way postcss-lightningcss and @tailwindcss/vite
// return their own output. `from` points at the source file so the
// relative url has something to be rebased against.
result.root = postcss.parse(
'.replace-root { background: url(./replaced-bg.png) }',
{
from: path.join(
import.meta.dirname,
'replace-root-source/replaced.css',
),
},
)
},
}
}
testReplaceRoot.postcss = true
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.