Skip to content

Commit

Permalink
Support Vite v5.1.0's .css?url imports
Browse files Browse the repository at this point in the history
  • Loading branch information
markdalgleish committed Feb 5, 2024
1 parent 5bfd7ad commit cef9525
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-sheep-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/dev": patch
---

Vite: Support Vite v5.1.0's `.css?url` imports
40 changes: 31 additions & 9 deletions docs/future/vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -657,11 +657,34 @@ If a route's `links` function is only used to wire up `cssBundleHref`, you can r
- ];
```

#### Fix up CSS imports
#### Add `?url` to regular CSS imports

In Vite, CSS files are typically imported as side effects.
<docs-warning>

**This feature is not supported in Vite v5.0.x.**

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].

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)

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.
</docs-warning>

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]

👉 **Add `?url` to regular CSS imports**

```diff
-import styles from "~/styles/dashboard.css";
+import styles from "~/styles/dashboard.css?url";
```

#### Optionally convert regular CSS imports to side-effect imports

<docs-info>Any existing side-effect imports of CSS files in your Remix application will work in Vite without any code changes.</docs-info>

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.

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.

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

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

- import dashboardStyles from "./dashboard.css?url";
- import sharedStyles from "./shared.css?url";
+ // ⚠️ NOTE: The import order has been updated
+ // to match the original `links` function!
- import dashboardStyles from "./dashboard.css";
- import sharedStyles from "./shared.css";
+ // NOTE: The import order has been updated
+ // to match the original `links` function.
+ import "./shared.css";
+ import "./dashboard.css";

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

<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>

#### Optionally scope regular CSS

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.
Expand Down Expand Up @@ -1256,3 +1277,4 @@ We're definitely late to the Vite party, but we're excited to be here now!
[cloudflare-proxy-caches]: https://github.com/cloudflare/workers-sdk/issues/4879
[how-fix-cjs-esm]: https://www.youtube.com/watch?v=jmNuEEtwkD4
[presets]: ./presets
[vite-5-1-0-beta]: https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#510-beta0-2024-01-15
2 changes: 1 addition & 1 deletion integration/helpers/vite-template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@types/react-dom": "^18.2.7",
"eslint": "^8.38.0",
"typescript": "^5.1.6",
"vite": "^5.0.0",
"vite": "5.1.0-beta.6",
"vite-tsconfig-paths": "^4.2.1"
},
"engines": {
Expand Down
66 changes: 48 additions & 18 deletions integration/vite-css-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,35 @@ import {
EXPRESS_SERVER,
} from "./helpers/vite.js";

const js = String.raw;
const css = String.raw;

const PADDING = "20px";
const NEW_PADDING = "30px";

const files = {
"app/entry.client.tsx": `
"postcss.config.js": js`
export default ({
plugins: [
{
// Minimal PostCSS plugin to test that it's being used
postcssPlugin: 'replace',
Declaration (decl) {
decl.value = decl.value
.replace(
/NEW_PADDING_INJECTED_VIA_POSTCSS/g,
${JSON.stringify(NEW_PADDING)},
)
.replace(
/PADDING_INJECTED_VIA_POSTCSS/g,
${JSON.stringify(PADDING)},
);
},
},
],
});
`,
"app/entry.client.tsx": js`
import "./entry.client.css";
import { RemixBrowser } from "@remix-run/react";
Expand All @@ -33,7 +57,7 @@ const files = {
);
});
`,
"app/root.tsx": `
"app/root.tsx": js`
import { Links, Meta, Outlet, Scripts } from "@remix-run/react";
export default function Root() {
Expand All @@ -51,55 +75,55 @@ const files = {
);
}
`,
"app/entry.client.css": `
"app/entry.client.css": css`
.entry-client {
background: pink;
padding: ${PADDING};
}
`,
"app/styles-bundled.css": `
"app/styles-bundled.css": css`
.index_bundled {
background: papayawhip;
padding: ${PADDING};
}
`,
"app/styles-linked.css": `
.index_linked {
"app/styles-postcss-linked.css": css`
.index_postcss_linked {
background: salmon;
padding: ${PADDING};
padding: PADDING_INJECTED_VIA_POSTCSS;
}
`,
"app/styles.module.css": `
"app/styles.module.css": css`
.index {
background: peachpuff;
padding: ${PADDING};
}
`,
"app/styles-vanilla-global.css.ts": `
"app/styles-vanilla-global.css.ts": js`
import { createVar, globalStyle } from "@vanilla-extract/css";
globalStyle(".index_vanilla_global", {
background: "lightgreen",
padding: "${PADDING}",
});
`,
"app/styles-vanilla-local.css.ts": `
"app/styles-vanilla-local.css.ts": js`
import { style } from "@vanilla-extract/css";
export const index = style({
background: "lightblue",
padding: "${PADDING}",
});
`,
"app/routes/_index.tsx": `
"app/routes/_index.tsx": js`
import "../styles-bundled.css";
import linkedStyles from "../styles-linked.css?url";
import postcssLinkedStyles from "../styles-postcss-linked.css?url";
import cssModulesStyles from "../styles.module.css";
import "../styles-vanilla-global.css";
import * as stylesVanillaLocal from "../styles-vanilla-local.css";
export function links() {
return [{ rel: "stylesheet", href: linkedStyles }];
return [{ rel: "stylesheet", href: postcssLinkedStyles }];
}
export default function IndexRoute() {
Expand All @@ -108,7 +132,7 @@ const files = {
<input />
<div id="entry-client" className="entry-client">
<div id="css-modules" className={cssModulesStyles.index}>
<div id="css-linked" className="index_linked">
<div id="css-postcss-linked" className="index_postcss_linked">
<div id="css-bundled" className="index_bundled">
<div id="css-vanilla-global" className="index_vanilla_global">
<div id="css-vanilla-local" className={stylesVanillaLocal.index}>
Expand Down Expand Up @@ -241,7 +265,7 @@ async function pageLoadWorkflow({ page, port }: { page: Page; port: number }) {
await Promise.all(
[
"#css-bundled",
"#css-linked",
"#css-postcss-linked",
"#css-modules",
"#css-vanilla-global",
"#css-vanilla-local",
Expand Down Expand Up @@ -274,20 +298,26 @@ async function hmrWorkflow({
await expect(input).toHaveValue("stateful");

let edit = createEditor(cwd);
let modifyCss = (contents: string) => contents.replace(PADDING, NEW_PADDING);
let modifyCss = (contents: string) =>
contents
.replace(PADDING, NEW_PADDING)
.replace(
"PADDING_INJECTED_VIA_POSTCSS",
"NEW_PADDING_INJECTED_VIA_POSTCSS"
);

await Promise.all([
edit("app/styles-bundled.css", modifyCss),
edit("app/styles-linked.css", modifyCss),
edit("app/styles.module.css", modifyCss),
edit("app/styles-vanilla-global.css.ts", modifyCss),
edit("app/styles-vanilla-local.css.ts", modifyCss),
edit("app/styles-postcss-linked.css", modifyCss),
]);

await Promise.all(
[
"#css-bundled",
"#css-linked",
"#css-postcss-linked",
"#css-modules",
"#css-vanilla-global",
"#css-vanilla-local",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
"unified": "^10.1.2",
"unist-util-remove": "^3.1.0",
"unist-util-visit": "^4.1.1",
"vite": "^5.0.0",
"vite": "5.1.0-beta.6",
"wait-on": "^7.0.1"
},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
"msw": "^1.2.3",
"strip-ansi": "^6.0.1",
"tiny-invariant": "^1.2.0",
"vite": "^5.0.0",
"vite": "5.1.0-beta.6",
"wrangler": "^3.24.0"
},
"peerDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/remix-dev/vite/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => {
...(viteCommand === "build" && {
base: ctx.remixConfig.publicPath,
build: {
cssMinify: viteUserConfig.build?.cssMinify ?? true,
...(!viteConfigEnv.isSsrBuild
? {
manifest: true,
Expand Down
35 changes: 22 additions & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10158,7 +10158,7 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"

nanoid@^3.3.3, nanoid@^3.3.6:
nanoid@^3.3.3, nanoid@^3.3.6, nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
Expand Down Expand Up @@ -10955,7 +10955,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==

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

postcss@^8.4.33:
version "8.4.33"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.0.2"

preferred-pm@^3.0.0:
version "3.0.3"
resolved "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz"
Expand Down Expand Up @@ -13479,6 +13488,17 @@ vite-tsconfig-paths@^4.2.2:
globrex "^0.1.2"
tsconfck "^2.1.0"

vite@5.1.0-beta.6:
version "5.1.0-beta.6"
resolved "https://registry.npmjs.org/vite/-/vite-5.1.0-beta.6.tgz#2fd554818ec3cc888d336d24d5f0994153a06523"
integrity sha512-Tnham+O97w9GAQfeYyh1wZF2iePQdr/MgU+8k23O8aa+DtUbAPTmg09CsFgIi4eMta2utRa0pOjSqtYIMcUKbQ==
dependencies:
esbuild "^0.19.3"
postcss "^8.4.33"
rollup "^4.2.0"
optionalDependencies:
fsevents "~2.3.3"

"vite@^3.0.0 || ^4.0.0", vite@^4.1.4:
version "4.4.10"
resolved "https://registry.npmjs.org/vite/-/vite-4.4.10.tgz#3794639cc433f7cb33ad286930bf0378c86261c8"
Expand All @@ -13490,17 +13510,6 @@ vite-tsconfig-paths@^4.2.2:
optionalDependencies:
fsevents "~2.3.2"

vite@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/vite/-/vite-5.0.0.tgz#3bfb65acda2a97127e4fa240156664a1f234ce08"
integrity sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==
dependencies:
esbuild "^0.19.3"
postcss "^8.4.31"
rollup "^4.2.0"
optionalDependencies:
fsevents "~2.3.3"

w3c-xmlserializer@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073"
Expand Down

0 comments on commit cef9525

Please sign in to comment.