Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An option to inject CSS styles into the generated .html files <head> tag instead of separate .css files #18062

Open
4 tasks done
mk26710 opened this issue Sep 9, 2024 · 0 comments

Comments

@mk26710
Copy link

mk26710 commented Sep 9, 2024

Description

I would really love to see an option in Vite's config which would inject all the generated CSS/PostCSS/Tailwind or whatever styles right into the build/index.html file's <head> tag. This is typically the behavior you will get by default using a framework like Nuxt, for example, also this the behavior we have when we run Vite projects in dev mode (npm run dev).

The problem is that sometimes you might end up with layout shifts if your styles are in a separate CSS file. I have been having this issue with basically any framework including Vue, React, Svelte and so on whenever I don't choose to use SSR metaframework (I think that's what they call Nuxt/Next nowadays?). In my experience basically nothing solves the issue until you put the styles right into the <head>'s <style> tag.

The layout shifts usually occur when you have some kind of CSS transitions/animation on the website you are building or sometimes it just happens for whatever reason, unfortunately my experience is not that deep and all that I know is that moving styles from CSS files to the <style> tag in <head> solves the issue. As an inexperienced developer I once spent a few days trying to understand why some of my projects had those weird flashes of colors, elements sizes shifts on initial load of the website for the first 1-2 seconds and it was extremely frustrating to debug as well, since almost nobody could tell me why this is happening (and if I'm being completely honest - I still have no idea why browsers behave like this) until I noticed that whenever I used an SSR framework such as Nuxt there was no such issue and the biggest difference that I've noticed is that when I use Vite for an SPA (no magical SSR or SSG) the styles are located in separate .css files meanwhile the SSR framework injects them right into the HTML response content.

I understand that this might be a really minor issue but it is such a frustrating problem that I've been dealing with while working on my personal projects or some small SPAs for other people.

Suggested solution

My idea is that there should be a way to tell Vite to generate .html files with the styles being injected right into these files, and the .css would simply be not generated at all (but it's probably better to make that optional, just in case?). This option could be called cssInject and would have to be of boolean type with default value set to false, so the injection would be an opt-in feature (I would prefer it being an opt-out actually, personally, but my relatively small experience tells me it better be an opt-in).

So, for example a Vite config would look something similar to this

export default defineConfig({
  plugins: [vue()],
  build: {
    cssInject: true,
    outDir: "build",
  },
});

Alternative

I've been browsing GitHub for various solutions and I remember seeing someone implement a Vite plugin which essentially solves the problem and I took it and modified a little bit, but I honestly do not remember this person's username anymore as it was several months ago.

Let me explain what the following code does.

  1. Set cssCodeSplit to false to prevent generation of multiple .css files;
  2. Use injectCssAsStyleTag plugin which is triggered after Vite builds everything, the plugin's code essentially finds the generated .css file (since we disabled css code splitting there should be just one) and puts it's contents into the <style> tag in <head>;
  3. Use removeStylesheetLinks plugin to cleanup a little bit by removing the <link> tags of ALL .css files, you might need to tweak this a little bit if you want/need to keep some <link> tags that are used for stylesheets.
import { fileURLToPath, URL } from "node:url";
import { defineConfig, HtmlTagDescriptor } from "vite";
import type { Plugin } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), injectCssAsStyleTag(), removeStylesheetLinks()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
  build: {
    cssCodeSplit: false,
    outDir: "build",
  },
});

function injectCssAsStyleTag(): Plugin {
  return {
    name: "inject-css-as-style-tags",
    enforce: "post",
    apply: "build",
    transformIndexHtml(html, ctx) {
      const htmlTagDescriptors: HtmlTagDescriptor[] = [];
      const bundle = ctx.bundle;
      if (bundle == null) {
        return [];
      }

      Object.values(bundle)
        .filter((output) => output.fileName.endsWith(".css"))
        .forEach((output) => {
          if (output.type === "asset" && typeof output.source === "string") {
            htmlTagDescriptors.push({
              tag: "style",
              children: output.source,
              injectTo: "head",
            });
          }
        });

      return htmlTagDescriptors;
    },
  };
}

function removeStylesheetLinks(): Plugin {
  return {
    name: "remove-stylesheet-links",
    enforce: "post",
    apply: "build",
    transformIndexHtml(html, ctx) {
      return html.replaceAll(/<link\s+rel="stylesheet"(\s.*\s)href="(.*)\.css">/gi, "");
    },
  };
}

Additional context

No response

Validations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant