Skip to content

Commit cef9525

Browse files
committed
Support Vite v5.1.0's .css?url imports
1 parent 5bfd7ad commit cef9525

File tree

8 files changed

+110
-43
lines changed

8 files changed

+110
-43
lines changed

.changeset/shaggy-sheep-cheer.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/dev": patch
3+
---
4+
5+
Vite: Support Vite v5.1.0's `.css?url` imports

docs/future/vite.md

+31-9
Original file line numberDiff line numberDiff line change
@@ -657,11 +657,34 @@ If a route's `links` function is only used to wire up `cssBundleHref`, you can r
657657
- ];
658658
```
659659

660-
#### Fix up CSS imports
660+
#### Add `?url` to regular CSS imports
661661

662-
In Vite, CSS files are typically imported as side effects.
662+
<docs-warning>
663+
664+
**This feature is not supported in Vite v5.0.x.**
665+
666+
Vite v5.0.x and earlier has a [known issue with `.css?url` imports][vite-css-url-issue] that causes them to break in production builds. If you'd like to use this feature immediately, support for `.css?url` imports is currently available in the [Vite v5.1.0 beta][vite-5-1-0-beta].
667+
668+
If you'd prefer to avoid running a beta version of Vite, you can either wait for Vite v5.1.0 or [convert your CSS imports to side-effects.](#optionally-convert-regular-css-imports-to-side-effect-imports)
663669

664-
During development, [Vite injects imported CSS files into the page via JavaScript,][vite-css] and the Remix Vite plugin will inline imported CSS alongside your link tags to avoid a flash of unstyled content. In the production build, the Remix Vite plugin will automatically attach CSS files to the relevant routes.
670+
</docs-warning>
671+
672+
If you were using [Remix's regular CSS support][regular-css], you'll need to update your CSS import statements to use [Vite's explicit `?url` import syntax.][vite-url-imports]
673+
674+
👉 **Add `?url` to regular CSS imports**
675+
676+
```diff
677+
-import styles from "~/styles/dashboard.css";
678+
+import styles from "~/styles/dashboard.css?url";
679+
```
680+
681+
#### Optionally convert regular CSS imports to side-effect imports
682+
683+
<docs-info>Any existing side-effect imports of CSS files in your Remix application will work in Vite without any code changes.</docs-info>
684+
685+
Rather than [migrating regular CSS imports to use Vite's explicit `.css?url` import syntax](#add-url-to-regular-css-imports) — which requires either waiting for Vite v5.1.0 or running the [v5.1.0 beta][vite-5-1-0-beta] — you can instead convert them to side-effect imports. You may even find that this approach is more convenient for you.
686+
687+
During development, [Vite injects CSS side-effect imports into the page via JavaScript,][vite-css] and the Remix Vite plugin will inline imported CSS alongside your link tags to avoid a flash of unstyled content. In the production build, the Remix Vite plugin will automatically attach CSS files to the relevant routes.
665688

666689
This also means that in many cases you won't need the `links` function export anymore.
667690

@@ -672,10 +695,10 @@ Since the order of your CSS is determined by its import order, you'll need to en
672695
```diff filename=app/dashboard/route.tsx
673696
- import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
674697

675-
- import dashboardStyles from "./dashboard.css?url";
676-
- import sharedStyles from "./shared.css?url";
677-
+ // ⚠️ NOTE: The import order has been updated
678-
+ // to match the original `links` function!
698+
- import dashboardStyles from "./dashboard.css";
699+
- import sharedStyles from "./shared.css";
700+
+ // NOTE: The import order has been updated
701+
+ // to match the original `links` function.
679702
+ import "./shared.css";
680703
+ import "./dashboard.css";
681704

@@ -685,8 +708,6 @@ Since the order of your CSS is determined by its import order, you'll need to en
685708
- ];
686709
```
687710

688-
<docs-warning>While [Vite supports importing static asset URLs via an explicit `?url` query string][vite-url-imports], which in theory would match the behavior of the existing Remix compiler when used for CSS files, there is a [known Vite issue with `?url` for CSS imports][vite-css-url-issue]. This may be fixed in the future, but in the meantime you should exclusively use side effect imports for CSS.</docs-warning>
689-
690711
#### Optionally scope regular CSS
691712

692713
If you were using [Remix's regular CSS support][regular-css], one important caveat to be aware of is that these styles will no longer be mounted and unmounted automatically when navigating between routes during development.
@@ -1256,3 +1277,4 @@ We're definitely late to the Vite party, but we're excited to be here now!
12561277
[cloudflare-proxy-caches]: https://github.com/cloudflare/workers-sdk/issues/4879
12571278
[how-fix-cjs-esm]: https://www.youtube.com/watch?v=jmNuEEtwkD4
12581279
[presets]: ./presets
1280+
[vite-5-1-0-beta]: https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#510-beta0-2024-01-15

integration/helpers/vite-template/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@types/react-dom": "^18.2.7",
2424
"eslint": "^8.38.0",
2525
"typescript": "^5.1.6",
26-
"vite": "^5.0.0",
26+
"vite": "5.1.0-beta.6",
2727
"vite-tsconfig-paths": "^4.2.1"
2828
},
2929
"engines": {

integration/vite-css-test.ts

+48-18
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,35 @@ import {
1313
EXPRESS_SERVER,
1414
} from "./helpers/vite.js";
1515

16+
const js = String.raw;
17+
const css = String.raw;
18+
1619
const PADDING = "20px";
1720
const NEW_PADDING = "30px";
1821

1922
const files = {
20-
"app/entry.client.tsx": `
23+
"postcss.config.js": js`
24+
export default ({
25+
plugins: [
26+
{
27+
// Minimal PostCSS plugin to test that it's being used
28+
postcssPlugin: 'replace',
29+
Declaration (decl) {
30+
decl.value = decl.value
31+
.replace(
32+
/NEW_PADDING_INJECTED_VIA_POSTCSS/g,
33+
${JSON.stringify(NEW_PADDING)},
34+
)
35+
.replace(
36+
/PADDING_INJECTED_VIA_POSTCSS/g,
37+
${JSON.stringify(PADDING)},
38+
);
39+
},
40+
},
41+
],
42+
});
43+
`,
44+
"app/entry.client.tsx": js`
2145
import "./entry.client.css";
2246
2347
import { RemixBrowser } from "@remix-run/react";
@@ -33,7 +57,7 @@ const files = {
3357
);
3458
});
3559
`,
36-
"app/root.tsx": `
60+
"app/root.tsx": js`
3761
import { Links, Meta, Outlet, Scripts } from "@remix-run/react";
3862
3963
export default function Root() {
@@ -51,55 +75,55 @@ const files = {
5175
);
5276
}
5377
`,
54-
"app/entry.client.css": `
78+
"app/entry.client.css": css`
5579
.entry-client {
5680
background: pink;
5781
padding: ${PADDING};
5882
}
5983
`,
60-
"app/styles-bundled.css": `
84+
"app/styles-bundled.css": css`
6185
.index_bundled {
6286
background: papayawhip;
6387
padding: ${PADDING};
6488
}
6589
`,
66-
"app/styles-linked.css": `
67-
.index_linked {
90+
"app/styles-postcss-linked.css": css`
91+
.index_postcss_linked {
6892
background: salmon;
69-
padding: ${PADDING};
93+
padding: PADDING_INJECTED_VIA_POSTCSS;
7094
}
7195
`,
72-
"app/styles.module.css": `
96+
"app/styles.module.css": css`
7397
.index {
7498
background: peachpuff;
7599
padding: ${PADDING};
76100
}
77101
`,
78-
"app/styles-vanilla-global.css.ts": `
102+
"app/styles-vanilla-global.css.ts": js`
79103
import { createVar, globalStyle } from "@vanilla-extract/css";
80104
81105
globalStyle(".index_vanilla_global", {
82106
background: "lightgreen",
83107
padding: "${PADDING}",
84108
});
85109
`,
86-
"app/styles-vanilla-local.css.ts": `
110+
"app/styles-vanilla-local.css.ts": js`
87111
import { style } from "@vanilla-extract/css";
88112
89113
export const index = style({
90114
background: "lightblue",
91115
padding: "${PADDING}",
92116
});
93117
`,
94-
"app/routes/_index.tsx": `
118+
"app/routes/_index.tsx": js`
95119
import "../styles-bundled.css";
96-
import linkedStyles from "../styles-linked.css?url";
120+
import postcssLinkedStyles from "../styles-postcss-linked.css?url";
97121
import cssModulesStyles from "../styles.module.css";
98122
import "../styles-vanilla-global.css";
99123
import * as stylesVanillaLocal from "../styles-vanilla-local.css";
100124
101125
export function links() {
102-
return [{ rel: "stylesheet", href: linkedStyles }];
126+
return [{ rel: "stylesheet", href: postcssLinkedStyles }];
103127
}
104128
105129
export default function IndexRoute() {
@@ -108,7 +132,7 @@ const files = {
108132
<input />
109133
<div id="entry-client" className="entry-client">
110134
<div id="css-modules" className={cssModulesStyles.index}>
111-
<div id="css-linked" className="index_linked">
135+
<div id="css-postcss-linked" className="index_postcss_linked">
112136
<div id="css-bundled" className="index_bundled">
113137
<div id="css-vanilla-global" className="index_vanilla_global">
114138
<div id="css-vanilla-local" className={stylesVanillaLocal.index}>
@@ -241,7 +265,7 @@ async function pageLoadWorkflow({ page, port }: { page: Page; port: number }) {
241265
await Promise.all(
242266
[
243267
"#css-bundled",
244-
"#css-linked",
268+
"#css-postcss-linked",
245269
"#css-modules",
246270
"#css-vanilla-global",
247271
"#css-vanilla-local",
@@ -274,20 +298,26 @@ async function hmrWorkflow({
274298
await expect(input).toHaveValue("stateful");
275299

276300
let edit = createEditor(cwd);
277-
let modifyCss = (contents: string) => contents.replace(PADDING, NEW_PADDING);
301+
let modifyCss = (contents: string) =>
302+
contents
303+
.replace(PADDING, NEW_PADDING)
304+
.replace(
305+
"PADDING_INJECTED_VIA_POSTCSS",
306+
"NEW_PADDING_INJECTED_VIA_POSTCSS"
307+
);
278308

279309
await Promise.all([
280310
edit("app/styles-bundled.css", modifyCss),
281-
edit("app/styles-linked.css", modifyCss),
282311
edit("app/styles.module.css", modifyCss),
283312
edit("app/styles-vanilla-global.css.ts", modifyCss),
284313
edit("app/styles-vanilla-local.css.ts", modifyCss),
314+
edit("app/styles-postcss-linked.css", modifyCss),
285315
]);
286316

287317
await Promise.all(
288318
[
289319
"#css-bundled",
290-
"#css-linked",
320+
"#css-postcss-linked",
291321
"#css-modules",
292322
"#css-vanilla-global",
293323
"#css-vanilla-local",

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
"unified": "^10.1.2",
134134
"unist-util-remove": "^3.1.0",
135135
"unist-util-visit": "^4.1.1",
136-
"vite": "^5.0.0",
136+
"vite": "5.1.0-beta.6",
137137
"wait-on": "^7.0.1"
138138
},
139139
"engines": {

packages/remix-dev/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"msw": "^1.2.3",
9292
"strip-ansi": "^6.0.1",
9393
"tiny-invariant": "^1.2.0",
94-
"vite": "^5.0.0",
94+
"vite": "5.1.0-beta.6",
9595
"wrangler": "^3.24.0"
9696
},
9797
"peerDependencies": {

packages/remix-dev/vite/plugin.ts

+1
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => {
909909
...(viteCommand === "build" && {
910910
base: ctx.remixConfig.publicPath,
911911
build: {
912+
cssMinify: viteUserConfig.build?.cssMinify ?? true,
912913
...(!viteConfigEnv.isSsrBuild
913914
? {
914915
manifest: true,

yarn.lock

+22-13
Original file line numberDiff line numberDiff line change
@@ -10158,7 +10158,7 @@ mz@^2.7.0:
1015810158
object-assign "^4.0.1"
1015910159
thenify-all "^1.0.0"
1016010160

10161-
nanoid@^3.3.3, nanoid@^3.3.6:
10161+
nanoid@^3.3.3, nanoid@^3.3.6, nanoid@^3.3.7:
1016210162
version "3.3.7"
1016310163
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
1016410164
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
@@ -10955,7 +10955,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
1095510955
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
1095610956
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
1095710957

10958-
postcss@^8.0.9, postcss@^8.3.6, postcss@^8.4.19, postcss@^8.4.27, postcss@^8.4.31:
10958+
postcss@^8.0.9, postcss@^8.3.6, postcss@^8.4.19, postcss@^8.4.27:
1095910959
version "8.4.31"
1096010960
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
1096110961
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
@@ -10964,6 +10964,15 @@ postcss@^8.0.9, postcss@^8.3.6, postcss@^8.4.19, postcss@^8.4.27, postcss@^8.4.3
1096410964
picocolors "^1.0.0"
1096510965
source-map-js "^1.0.2"
1096610966

10967+
postcss@^8.4.33:
10968+
version "8.4.33"
10969+
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
10970+
integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
10971+
dependencies:
10972+
nanoid "^3.3.7"
10973+
picocolors "^1.0.0"
10974+
source-map-js "^1.0.2"
10975+
1096710976
preferred-pm@^3.0.0:
1096810977
version "3.0.3"
1096910978
resolved "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz"
@@ -13479,6 +13488,17 @@ vite-tsconfig-paths@^4.2.2:
1347913488
globrex "^0.1.2"
1348013489
tsconfck "^2.1.0"
1348113490

13491+
13492+
version "5.1.0-beta.6"
13493+
resolved "https://registry.npmjs.org/vite/-/vite-5.1.0-beta.6.tgz#2fd554818ec3cc888d336d24d5f0994153a06523"
13494+
integrity sha512-Tnham+O97w9GAQfeYyh1wZF2iePQdr/MgU+8k23O8aa+DtUbAPTmg09CsFgIi4eMta2utRa0pOjSqtYIMcUKbQ==
13495+
dependencies:
13496+
esbuild "^0.19.3"
13497+
postcss "^8.4.33"
13498+
rollup "^4.2.0"
13499+
optionalDependencies:
13500+
fsevents "~2.3.3"
13501+
1348213502
"vite@^3.0.0 || ^4.0.0", vite@^4.1.4:
1348313503
version "4.4.10"
1348413504
resolved "https://registry.npmjs.org/vite/-/vite-4.4.10.tgz#3794639cc433f7cb33ad286930bf0378c86261c8"
@@ -13490,17 +13510,6 @@ vite-tsconfig-paths@^4.2.2:
1349013510
optionalDependencies:
1349113511
fsevents "~2.3.2"
1349213512

13493-
vite@^5.0.0:
13494-
version "5.0.0"
13495-
resolved "https://registry.npmjs.org/vite/-/vite-5.0.0.tgz#3bfb65acda2a97127e4fa240156664a1f234ce08"
13496-
integrity sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==
13497-
dependencies:
13498-
esbuild "^0.19.3"
13499-
postcss "^8.4.31"
13500-
rollup "^4.2.0"
13501-
optionalDependencies:
13502-
fsevents "~2.3.3"
13503-
1350413513
w3c-xmlserializer@^4.0.0:
1350513514
version "4.0.0"
1350613515
resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073"

0 commit comments

Comments
 (0)