-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite urls in CSS files when using Vite (#14877)
Fixes #14784 This is an alternative to #14850 in which we actually perform url rewriting / rebasing ourselves. We ported a large portion of the URL-rewriting code from Vite (with attribution) to use here with some minor modifications. We've added test cases for the url rewriting so verifying individual cases is easy. We also wrote integration tests for Vite that use PostCSS and Lightning CSS that verify that files are found and inlined or relocated/renamed as necessary. We also did some manual testing in the Playground to verify that this works as expected across several CSS files and directories which you can see a screenshot from here: <img width="1344" alt="Screenshot 2024-11-05 at 10 25 16" src="https://github.com/user-attachments/assets/ff0b3ac8-cdc9-4e26-af79-36396a5b77b9"> --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
- Loading branch information
1 parent
75eeed8
commit e82b316
Showing
9 changed files
with
479 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { describe, expect } from 'vitest' | ||
import { binary, css, html, svg, test, ts, txt } from '../utils' | ||
|
||
const SIMPLE_IMAGE = `iVBORw0KGgoAAAANSUhEUgAAADAAAAAlAQAAAAAsYlcCAAAACklEQVR4AWMYBQABAwABRUEDtQAAAABJRU5ErkJggg==` | ||
|
||
for (let transformer of ['postcss', 'lightningcss']) { | ||
describe(transformer, () => { | ||
test( | ||
'can rewrite urls in production builds', | ||
{ | ||
fs: { | ||
'package.json': txt` | ||
{ | ||
"type": "module", | ||
"dependencies": { | ||
"tailwindcss": "workspace:^" | ||
}, | ||
"devDependencies": { | ||
${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} | ||
"@tailwindcss/vite": "workspace:^", | ||
"vite": "^5.3.5" | ||
} | ||
} | ||
`, | ||
'vite.config.ts': ts` | ||
import tailwindcss from '@tailwindcss/vite' | ||
import { defineConfig } from 'vite' | ||
export default defineConfig({ | ||
plugins: [tailwindcss()], | ||
build: { | ||
assetsInlineLimit: 256, | ||
cssMinify: false, | ||
}, | ||
css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, | ||
}) | ||
`, | ||
'index.html': html` | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<link rel="stylesheet" href="./src/app.css" /> | ||
</head> | ||
<body> | ||
<div id="app"></div> | ||
<script type="module" src="./src/main.ts"></script> | ||
</body> | ||
</html> | ||
`, | ||
'src/main.ts': ts``, | ||
'src/app.css': css` | ||
@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 { | ||
background-image: url('../../resources/image.png'); | ||
} | ||
`, | ||
'src/dir-1/dir-2/baz.css': css` | ||
.baz { | ||
background-image: url('../../../resources/image.png'); | ||
} | ||
`, | ||
'src/dir-1/dir-2/vector.css': css` | ||
.baz { | ||
background-image: url('../../../resources/vector.svg'); | ||
} | ||
`, | ||
'resources/image.png': binary(SIMPLE_IMAGE), | ||
'resources/vector.svg': svg` | ||
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg"> | ||
<rect width="100%" height="100%" fill="red" /> | ||
<circle cx="200" cy="100" r="80" fill="green" /> | ||
<rect width="100%" height="100%" fill="red" /> | ||
<circle cx="200" cy="100" r="80" fill="green" /> | ||
</svg> | ||
`, | ||
}, | ||
}, | ||
async ({ fs, exec }) => { | ||
await exec('pnpm vite build') | ||
|
||
let files = await fs.glob('dist/**/*.css') | ||
expect(files).toHaveLength(1) | ||
|
||
await fs.expectFileToContain(files[0][0], [SIMPLE_IMAGE]) | ||
|
||
let images = await fs.glob('dist/**/*.svg') | ||
expect(images).toHaveLength(1) | ||
|
||
await fs.expectFileToContain(files[0][0], [/\/assets\/vector-.*?\.svg/]) | ||
}, | ||
) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { expect, test } from 'vitest' | ||
import { rewriteUrls } from './urls' | ||
|
||
const css = String.raw | ||
|
||
test('URLs can be rewritten', async () => { | ||
let root = '/root' | ||
|
||
let result = await rewriteUrls({ | ||
root, | ||
base: '/root/foo/bar', | ||
// prettier-ignore | ||
css: css` | ||
.foo { | ||
/* Relative URLs: replaced */ | ||
background: url(./image.jpg); | ||
background: url(../image.jpg); | ||
background: url('./image.jpg'); | ||
background: url("./image.jpg"); | ||
/* External URL: ignored */ | ||
background: url(http://example.com/image.jpg); | ||
background: url('http://example.com/image.jpg'); | ||
background: url("http://example.com/image.jpg"); | ||
/* Data URI: ignored */ | ||
/* background: url(data:image/png;base64,abc==); */ | ||
background: url('data:image/png;base64,abc=='); | ||
background: url("data:image/png;base64,abc=="); | ||
/* Function calls: ignored */ | ||
background: url(var(--foo)); | ||
background: url(var(--foo, './image.jpg')); | ||
background: url(var(--foo, "./image.jpg")); | ||
/* Fragments: ignored */ | ||
background: url(#dont-touch-this); | ||
/* Image Sets - Raw URL: replaced */ | ||
background: image-set( | ||
image1.jpg 1x, | ||
image2.jpg 2x | ||
); | ||
background: image-set( | ||
'image1.jpg' 1x, | ||
'image2.jpg' 2x | ||
); | ||
background: image-set( | ||
"image1.jpg" 1x, | ||
"image2.jpg" 2x | ||
); | ||
/* Image Sets - Relative URLs: replaced */ | ||
background: image-set( | ||
url('image1.jpg') 1x, | ||
url('image2.jpg') 2x | ||
); | ||
background: image-set( | ||
url("image1.jpg") 1x, | ||
url("image2.jpg") 2x | ||
); | ||
background: image-set( | ||
url('image1.avif') type('image/avif'), | ||
url('image2.jpg') type('image/jpeg') | ||
); | ||
background: image-set( | ||
url("image1.avif") type('image/avif'), | ||
url("image2.jpg") type('image/jpeg') | ||
); | ||
/* Image Sets - Function calls: ignored */ | ||
background: image-set( | ||
linear-gradient(blue, white) 1x, | ||
linear-gradient(blue, green) 2x | ||
); | ||
/* Image Sets - Mixed: replaced */ | ||
background: image-set( | ||
linear-gradient(blue, white) 1x, | ||
url("image2.jpg") 2x | ||
); | ||
} | ||
/* Fonts - Multiple URLs: replaced */ | ||
@font-face { | ||
font-family: "Newman"; | ||
src: | ||
local("Newman"), | ||
url("newman-COLRv1.otf") format("opentype") tech(color-COLRv1), | ||
url("newman-outline.otf") format("opentype"), | ||
url("newman-outline.woff") format("woff"); | ||
} | ||
`, | ||
}) | ||
|
||
expect(result).toMatchInlineSnapshot(` | ||
".foo { | ||
background: url(./foo/bar/image.jpg); | ||
background: url(./foo/image.jpg); | ||
background: url('./foo/bar/image.jpg'); | ||
background: url("./foo/bar/image.jpg"); | ||
background: url(http://example.com/image.jpg); | ||
background: url('http://example.com/image.jpg'); | ||
background: url("http://example.com/image.jpg"); | ||
background: url('data:image/png;base64,abc=='); | ||
background: url("data:image/png;base64,abc=="); | ||
background: url(var(--foo)); | ||
background: url(var(--foo, './image.jpg')); | ||
background: url(var(--foo, "./image.jpg")); | ||
background: url(#dont-touch-this); | ||
background: image-set(url(./foo/bar/image1.jpg) 1x, url(./foo/bar/image2.jpg) 2x); | ||
background: image-set(url('./foo/bar/image1.jpg') 1x, url('./foo/bar/image2.jpg') 2x); | ||
background: image-set(url("./foo/bar/image1.jpg") 1x, url("./foo/bar/image2.jpg") 2x); | ||
background: image-set(url('./foo/bar/image1.jpg') 1x, url('./foo/bar/image2.jpg') 2x); | ||
background: image-set(url("./foo/bar/image1.jpg") 1x, url("./foo/bar/image2.jpg") 2x); | ||
background: image-set(url('./foo/bar/image1.avif') type('image/avif'), url('./foo/bar/image2.jpg') type('image/jpeg')); | ||
background: image-set(url("./foo/bar/image1.avif") type('image/avif'), url("./foo/bar/image2.jpg") type('image/jpeg')); | ||
background: image-set(linear-gradient(blue, white) 1x, linear-gradient(blue, green) 2x); | ||
background: image-set(linear-gradient(blue, white) 1x, url("./foo/bar/image2.jpg") 2x); | ||
} | ||
@font-face { | ||
font-family: "Newman"; | ||
src: local("Newman"), url("./foo/bar/newman-COLRv1.otf") format("opentype") tech(color-COLRv1), url("./foo/bar/newman-outline.otf") format("opentype"), url("./foo/bar/newman-outline.woff") format("woff"); | ||
} | ||
" | ||
`) | ||
}) |
Oops, something went wrong.