From d59f1b3e5d8c1489f0295e8b3ff18e7058ff2f8f Mon Sep 17 00:00:00 2001 From: philipp-spiess <458591+philipp-spiess@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:03:07 +0000 Subject: [PATCH] Vite: Fix issues when loading files via static asset queries (#14716) Fixes: #14558 This PR fixes an issue where our Vite plugin would crash when trying to load stylesheets via certain static asset query parameters: ```ts import raw from './style.css?raw' import url from './style.css?url' ``` The proper behavior for our extension is to _not touch these file at all_. The `?raw` identifier should never transform anything and the `?url` one will emit a module which points to the asset URL. However, if that URL is loaded as a stylesheet, another transform hook is called and the file is properly transformed. I verified this in the Vite setup and have added an integration test ensuring these two features work as expected. I've also greatly reduced the complexity of the Vite playground to make it easier to set up examples like this in the future. --- CHANGELOG.md | 1 + integrations/vite/index.test.ts | 61 +++++++++++++++++++++ packages/@tailwindcss-vite/src/index.ts | 11 +++- playgrounds/vite/package.json | 3 +- playgrounds/vite/src/animate.js | 1 - playgrounds/vite/src/app.tsx | 4 -- playgrounds/vite/src/bar.tsx | 7 --- playgrounds/vite/src/foo.tsx | 10 ---- playgrounds/vite/src/forms.js | 1 - playgrounds/vite/src/{app.css => index.css} | 1 - playgrounds/vite/src/index.html | 1 - playgrounds/vite/src/main.tsx | 2 + playgrounds/vite/src/plugin.js | 4 -- playgrounds/vite/src/typography.js | 1 - pnpm-lock.yaml | 55 +------------------ 15 files changed, 75 insertions(+), 88 deletions(-) delete mode 100644 playgrounds/vite/src/animate.js delete mode 100644 playgrounds/vite/src/bar.tsx delete mode 100644 playgrounds/vite/src/foo.tsx delete mode 100644 playgrounds/vite/src/forms.js rename playgrounds/vite/src/{app.css => index.css} (50%) delete mode 100644 playgrounds/vite/src/plugin.js delete mode 100644 playgrounds/vite/src/typography.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 13acdc3183d1..43725914687e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure color opacity modifiers work with OKLCH colors ([#14741](https://github.com/tailwindlabs/tailwindcss/pull/14741)) - Ensure changes to the input CSS file result in a full rebuild ([#14744](https://github.com/tailwindlabs/tailwindcss/pull/14744)) - Add `postcss` as a dependency of `@tailwindcss/postcss` ([#14750](https://github.com/tailwindlabs/tailwindcss/pull/14750)) +- Ensure loading stylesheets via the `?raw` and `?url` static asset query works when using the Vite plugin ([#14716](https://github.com/tailwindlabs/tailwindcss/pull/14716)) - _Upgrade (experimental)_: Migrate `flex-grow` to `grow` and `flex-shrink` to `shrink` ([#14721](https://github.com/tailwindlabs/tailwindcss/pull/14721)) - _Upgrade (experimental)_: Minify arbitrary values when printing candidates ([#14720](https://github.com/tailwindlabs/tailwindcss/pull/14720)) - _Upgrade (experimental)_: Ensure legacy theme values ending in `1` (like `theme(spacing.1)`) are correctly migrated to custom properties ([#14724](https://github.com/tailwindlabs/tailwindcss/pull/14724)) diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index f56575c1a8ec..89814ccc6168 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -504,3 +504,64 @@ test( }) }, ) + +test( + `does not interfere with ?raw and ?url static asset handling`, + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "devDependencies": { + "vite": "^5.3.5" + } + } + `, + 'vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' + + export default defineConfig({ + build: { cssMinify: false }, + plugins: [tailwindcss()], + }) + `, + 'index.html': html` + + + + `, + 'src/index.js': js` + import url from './index.css?url' + import raw from './index.css?raw' + `, + 'src/index.css': css`@import 'tailwindcss';`, + }, + }, + async ({ spawn, getFreePort }) => { + let port = await getFreePort() + await spawn(`pnpm vite dev --port ${port}`) + + await retryAssertion(async () => { + // We have to load the .js file first so that the static assets are + // resolved + await fetch(`http://localhost:${port}/src/index.js`).then((r) => r.text()) + + let [raw, url] = await Promise.all([ + fetch(`http://localhost:${port}/src/index.css?raw`).then((r) => r.text()), + fetch(`http://localhost:${port}/src/index.css?url`).then((r) => r.text()), + ]) + + expect(firstLine(raw)).toBe(`export default "@import 'tailwindcss';"`) + expect(firstLine(url)).toBe(`export default "/src/index.css"`) + }) + }, +) + +function firstLine(str: string) { + return str.split('\n')[0] +} diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index abb13a613f26..9571342cdbda 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -5,6 +5,8 @@ import { Features, transform } from 'lightningcss' import path from 'path' import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite' +const SPECIAL_QUERY_RE = /[?&](raw|url)\b/ + export default function tailwindcss(): Plugin[] { let servers: ViteDevServer[] = [] let config: ResolvedConfig | null = null @@ -261,9 +263,12 @@ function getExtension(id: string) { function isPotentialCssRootFile(id: string) { let extension = getExtension(id) let isCssFile = - extension === 'css' || - (extension === 'vue' && id.includes('&lang.css')) || - (extension === 'astro' && id.includes('&lang.css')) + (extension === 'css' || + (extension === 'vue' && id.includes('&lang.css')) || + (extension === 'astro' && id.includes('&lang.css'))) && + // Don't intercept special static asset resources + !SPECIAL_QUERY_RE.test(id) + return isCssFile } diff --git a/playgrounds/vite/package.json b/playgrounds/vite/package.json index 846b96252efd..a5a953a14cc8 100644 --- a/playgrounds/vite/package.json +++ b/playgrounds/vite/package.json @@ -19,7 +19,6 @@ "@types/react": "^18.3.9", "@types/react-dom": "^18.3.1", "bun": "^1.1.29", - "vite": "catalog:", - "vite-plugin-handlebars": "^2.0.0" + "vite": "catalog:" } } diff --git a/playgrounds/vite/src/animate.js b/playgrounds/vite/src/animate.js deleted file mode 100644 index 0a5617399262..000000000000 --- a/playgrounds/vite/src/animate.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('tailwindcss-animate') diff --git a/playgrounds/vite/src/app.tsx b/playgrounds/vite/src/app.tsx index 285bd41439de..8ec50298951f 100644 --- a/playgrounds/vite/src/app.tsx +++ b/playgrounds/vite/src/app.tsx @@ -1,11 +1,7 @@ -import { Foo } from './foo' - export function App() { return (

Hello World

- -
) } diff --git a/playgrounds/vite/src/bar.tsx b/playgrounds/vite/src/bar.tsx deleted file mode 100644 index 1efa2c004851..000000000000 --- a/playgrounds/vite/src/bar.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export function Bar() { - return ( -
-

Bar

-
- ) -} diff --git a/playgrounds/vite/src/foo.tsx b/playgrounds/vite/src/foo.tsx deleted file mode 100644 index 60ec68b5517e..000000000000 --- a/playgrounds/vite/src/foo.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Bar } from './bar' - -export function Foo() { - return ( -
-

Foo

- -
- ) -} diff --git a/playgrounds/vite/src/forms.js b/playgrounds/vite/src/forms.js deleted file mode 100644 index f288ddfa0763..000000000000 --- a/playgrounds/vite/src/forms.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@tailwindcss/forms') diff --git a/playgrounds/vite/src/app.css b/playgrounds/vite/src/index.css similarity index 50% rename from playgrounds/vite/src/app.css rename to playgrounds/vite/src/index.css index adbd8e19f9b0..d4b5078586e2 100644 --- a/playgrounds/vite/src/app.css +++ b/playgrounds/vite/src/index.css @@ -1,2 +1 @@ @import 'tailwindcss'; -@plugin "./plugin.js"; diff --git a/playgrounds/vite/src/index.html b/playgrounds/vite/src/index.html index d4b60a84a8fe..858e0cf2fccf 100644 --- a/playgrounds/vite/src/index.html +++ b/playgrounds/vite/src/index.html @@ -4,7 +4,6 @@ ≈ Playground -
diff --git a/playgrounds/vite/src/main.tsx b/playgrounds/vite/src/main.tsx index b8df681f3d23..6b81fa9158cb 100644 --- a/playgrounds/vite/src/main.tsx +++ b/playgrounds/vite/src/main.tsx @@ -2,6 +2,8 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { App } from './app' +import './index.css' + ReactDOM.createRoot(document.getElementById('app')!).render( diff --git a/playgrounds/vite/src/plugin.js b/playgrounds/vite/src/plugin.js deleted file mode 100644 index 0852126714ec..000000000000 --- a/playgrounds/vite/src/plugin.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = function ({ addVariant }) { - addVariant('inverted', '@media (inverted-colors: inverted)') - addVariant('hocus', ['&:focus', '&:hover']) -} diff --git a/playgrounds/vite/src/typography.js b/playgrounds/vite/src/typography.js deleted file mode 100644 index 711af27f791f..000000000000 --- a/playgrounds/vite/src/typography.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@tailwindcss/typography') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a13e857113a4..ba225832c5a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -435,9 +435,6 @@ importers: vite: specifier: 'catalog:' version: 5.4.0(@types/node@20.14.13)(lightningcss@1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4))(terser@5.31.6) - vite-plugin-handlebars: - specifier: ^2.0.0 - version: 2.0.0(@types/node@20.14.13)(lightningcss@1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4))(terser@5.31.6) packages: @@ -1941,11 +1938,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -2352,9 +2344,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - next@14.1.0: resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==} engines: {node: '>=18.17.0'} @@ -3007,11 +2996,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - uglify-js@3.19.1: - resolution: {integrity: sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==} - engines: {node: '>=0.8.0'} - hasBin: true - unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -3039,9 +3023,6 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-plugin-handlebars@2.0.0: - resolution: {integrity: sha512-+J3It0nyhPzx4nT1I+fnWH+jShTEXzm6X0Tgsggdm9IYFD7/eJ6a3ROI13HTe0CVoyaxm/fPxH5HDAKyfz7T0g==} - vite@5.4.0: resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3133,9 +3114,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -4739,15 +4717,6 @@ snapshots: graceful-fs@4.2.11: {} - handlebars@4.7.8: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.1 - has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -5088,8 +5057,6 @@ snapshots: natural-compare@1.4.0: {} - neo-async@2.6.2: {} - next@14.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.1.0 @@ -5486,7 +5453,8 @@ snapshots: source-map: 0.6.1 optional: true - source-map@0.6.1: {} + source-map@0.6.1: + optional: true source-map@0.8.0-beta.0: dependencies: @@ -5761,9 +5729,6 @@ snapshots: typescript@5.5.4: {} - uglify-js@3.19.1: - optional: true - unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 @@ -5805,20 +5770,6 @@ snapshots: - supports-color - terser - vite-plugin-handlebars@2.0.0(@types/node@20.14.13)(lightningcss@1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4))(terser@5.31.6): - dependencies: - handlebars: 4.7.8 - vite: 5.4.0(@types/node@20.14.13)(lightningcss@1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4))(terser@5.31.6) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - terser - vite@5.4.0(@types/node@20.14.13)(lightningcss@1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4))(terser@5.31.6): dependencies: esbuild: 0.21.5 @@ -5920,8 +5871,6 @@ snapshots: word-wrap@1.2.5: {} - wordwrap@1.0.0: {} - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0