diff --git a/.changeset/shaggy-sheep-cheer.md b/.changeset/shaggy-sheep-cheer.md new file mode 100644 index 00000000000..bf46165257e --- /dev/null +++ b/.changeset/shaggy-sheep-cheer.md @@ -0,0 +1,5 @@ +--- +"@remix-run/dev": patch +--- + +Vite: Support Vite v5.1.0's `.css?url` imports diff --git a/docs/future/vite.md b/docs/future/vite.md index 61a3c464879..a358d213a7e 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -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. + + +**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. + + +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 + +Any existing side-effect imports of CSS files in your Remix application will work in Vite without any code changes. + +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. @@ -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"; @@ -685,8 +708,6 @@ Since the order of your CSS is determined by its import order, you'll need to en - ]; ``` -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. - #### 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. @@ -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 diff --git a/integration/helpers/vite-template/package.json b/integration/helpers/vite-template/package.json index 96b017221a0..6262abc5c2d 100644 --- a/integration/helpers/vite-template/package.json +++ b/integration/helpers/vite-template/package.json @@ -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": { diff --git a/integration/vite-css-test.ts b/integration/vite-css-test.ts index c63ddf93ee2..3d1bd7c598a 100644 --- a/integration/vite-css-test.ts +++ b/integration/vite-css-test.ts @@ -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"; @@ -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() { @@ -51,31 +75,31 @@ 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", { @@ -83,7 +107,7 @@ const files = { 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({ @@ -91,15 +115,15 @@ const files = { 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() { @@ -108,7 +132,7 @@ const files = {
-
+
@@ -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", @@ -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", diff --git a/package.json b/package.json index 1d132e231d6..5c16fd73082 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index d218e3e0335..d12f3be651a 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -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": { diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts index a5382c18d1e..769569d283e 100644 --- a/packages/remix-dev/vite/plugin.ts +++ b/packages/remix-dev/vite/plugin.ts @@ -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, diff --git a/yarn.lock b/yarn.lock index 02cf635324d..22ea0fa9110 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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== @@ -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== @@ -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" @@ -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" @@ -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"