From 6eedd6861d3695a50397f98203cd004e62efca8c Mon Sep 17 00:00:00 2001 From: Ruben Pascal Abel <4653214+dj-rabel@users.noreply.github.com> Date: Mon, 16 Sep 2024 03:33:23 +0200 Subject: [PATCH] Exclude `?inline` and `?raw` from critical styles in dev (#9910) Co-authored-by: Mark Dalgleish --- .changeset/unlucky-pigs-tap.md | 5 ++ contributors.yml | 1 + packages/remix-dev/__tests__/styles-test.ts | 60 +++++++++++++++++++++ packages/remix-dev/vite/styles.ts | 29 +++++++++- 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 .changeset/unlucky-pigs-tap.md create mode 100644 packages/remix-dev/__tests__/styles-test.ts diff --git a/.changeset/unlucky-pigs-tap.md b/.changeset/unlucky-pigs-tap.md new file mode 100644 index 00000000000..c4e508f983d --- /dev/null +++ b/.changeset/unlucky-pigs-tap.md @@ -0,0 +1,5 @@ +--- +"@remix-run/dev": patch +--- + +CSS imports with `?inline`, `?inline-css` and `?raw` are no longer incorrectly injected during SSR in development diff --git a/contributors.yml b/contributors.yml index 5a88c93ed6d..acbbfced8b8 100644 --- a/contributors.yml +++ b/contributors.yml @@ -157,6 +157,7 @@ - dhargitai - dhmacs - dima-takoy +- dj-rabel - dmarkow - dmillar - DNLHC diff --git a/packages/remix-dev/__tests__/styles-test.ts b/packages/remix-dev/__tests__/styles-test.ts new file mode 100644 index 00000000000..812db9dda04 --- /dev/null +++ b/packages/remix-dev/__tests__/styles-test.ts @@ -0,0 +1,60 @@ +import { isCssUrlWithoutSideEffects } from "../vite/styles"; + +describe("isCssUrlWithoutSideEffects", () => { + it("returns true for query parameters that result in an exported value with no side effects", () => { + let urls = [ + "my/file.css?inline", + "my/file.css?inline-css", + "my/file.css?inline&raw", + "my/file.css?raw", + "my/file.css?raw&url", + "my/file.css?url", + "my/file.css?url&something=else", + "my/file.css?something=else&url", + "my/file.css?url&raw", + + // other parameters mixed in + "my/file.css?inline&something=else", + "my/file.css?something=else&inline", + "my/file.css?inline&raw&something=else", + "my/file.css?something=else&inline&raw", + "my/file.css?raw&something=else&url", + "my/file.css?something=else&raw&url", + "my/file.css?url&something=else&raw", + "my/file.css?url&raw&something=else", + "my/file.css?something=else&url&raw", + ]; + + for (let url of urls) { + expect(isCssUrlWithoutSideEffects(url)).toBe(true); + } + }); + + it("returns false for other query parameters or no parameters", () => { + let urls = [ + "my/file.css", + "my/file.css?foo", + "my/file.css?foo=bar", + "my/file.css?foo&bar", + "my/file.css?inlinex", + "my/file.css?rawx", + "my/file.css?urlx", + + // values other than blank since Vite doesn't match these + "my/file.css?inline=foo", + "my/file.css?inline-css=foo", + "my/file.css?raw=foo", + "my/file.css?url=foo", + + // explicitly blank values since Vite doesn't match these + "my/file.css?inline=", + "my/file.css?inline-css=", + "my/file.css?raw=", + "my/file.css?url=", + ]; + + for (let url of urls) { + expect(isCssUrlWithoutSideEffects(url)).toBe(false); + } + }); +}); diff --git a/packages/remix-dev/vite/styles.ts b/packages/remix-dev/vite/styles.ts index f85b71e8b19..f2910da5f89 100644 --- a/packages/remix-dev/vite/styles.ts +++ b/packages/remix-dev/vite/styles.ts @@ -21,6 +21,33 @@ const cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`); const isCssFile = (file: string) => cssFileRegExp.test(file); export const isCssModulesFile = (file: string) => cssModulesRegExp.test(file); +// https://vitejs.dev/guide/features#disabling-css-injection-into-the-page +// https://github.com/vitejs/vite/blob/561b940f6f963fbb78058a6e23b4adad53a2edb9/packages/vite/src/node/plugins/css.ts#L194 +// https://vitejs.dev/guide/features#static-assets +// https://github.com/vitejs/vite/blob/561b940f6f963fbb78058a6e23b4adad53a2edb9/packages/vite/src/node/utils.ts#L309-L310 +const cssUrlParamsWithoutSideEffects = ["url", "inline", "raw", "inline-css"]; +export const isCssUrlWithoutSideEffects = (url: string) => { + let queryString = url.split("?")[1]; + + if (!queryString) { + return false; + } + + let params = new URLSearchParams(queryString); + for (let paramWithoutSideEffects of cssUrlParamsWithoutSideEffects) { + if ( + // Parameter is blank and not explicitly set, i.e. "?url", not "?url=" + params.get(paramWithoutSideEffects) === "" && + !url.includes(`?${paramWithoutSideEffects}=`) && + !url.includes(`&${paramWithoutSideEffects}=`) + ) { + return true; + } + } + + return false; +}; + const getStylesForFiles = async ({ viteDevServer, rootDirectory, @@ -71,7 +98,7 @@ const getStylesForFiles = async ({ if ( dep.file && isCssFile(dep.file) && - !dep.url.endsWith("?url") // Ignore styles that resolved as URLs, otherwise we'll end up injecting URLs into the style tag contents + !isCssUrlWithoutSideEffects(dep.url) // Ignore styles that resolved as URLs, inline or raw. These shouldn't get injected. ) { try { let css = isCssModulesFile(dep.file)