kit icon indicating copy to clipboard operation
kit copied to clipboard

Vite plugin has missing CSS

Open johnnysprinkles opened this issue 2 years ago • 7 comments

Describe the bug

Not sure what I'm doing wrong here, but I have a Vite plugin that uses the svelte.compile API to generate a component, which all works fine serverside and clientside, except that on the serverside the CSS is missing. If you don't hydrate or you turn off JS in the browser, the style never appears. Otherwise it does appear after a flash.

Reproduction

Repro:

git clone [email protected]:johnnysprinkles/repro.git
git checkout vite_plugin_missing_css
npm i
npm run build
npm run preview

(The code I added is all in one commit, https://github.com/johnnysprinkles/repro/commit/b559335780314f94eb337251f75614d1e26a034b)

I added a "star" component that has some styled SVG, normally this would be an import but for a minimal test case I have it hard coded. If you go to http://localhost:3000/about you should see small red stars but you see huge black ones. On the index page which does hydrate, the stars look correct after a flash.

This is a real head scratcher. I can see the output of the compile() call and it looks like it's doing the right thing for CSS. I see const css = {...} and $$result.css.add(css); in there, and I can even go into .svelte-kit/output/server/entries/pages/index.svelte.js and see the $$result.css.add(css$1) call there and console.log() the whole $$result.css which is a Set() with several items including the CSS for this star in there.

Index page with correct CSS after hydration: Screen Shot 2022-05-31 at 11 00 47 PM

About page with missing CSS: Screen Shot 2022-05-31 at 11 01 05 PM

Logs

No response

System Info

System:
    OS: macOS 12.1
    CPU: (8) arm64 Apple M1
    Memory: 163.56 MB / 8.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.13.1 - ~/.nvm/versions/node/v16.13.1/bin/node
    npm: 8.5.2 - ~/.nvm/versions/node/v16.13.1/bin/npm
  Browsers:
    Chrome: 102.0.5005.61
    Firefox: 100.0
    Safari: 15.2
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.50
    @sveltejs/kit: next => 1.0.0-next.347
    svelte: ^3.46.0 => 3.48.0

Severity

serious, but I can work around it

Additional Information

No response

johnnysprinkles avatar Jun 01 '22 06:06 johnnysprinkles

css: true adds the css via javascript. So if you disable js, that doesn't work.

May i ask why you opt to use a custom plugin calling svelte.compile yourself instead of leveraging vite-plugin-svelte with

Star.svg.svelte

<svelte:options namespace="svg"/>
<svg>...</svg>
<style>...</style>

or unplugin-icons with custom icons? https://github.com/antfu/unplugin-icons#custom-icons

dominikg avatar Jun 02 '22 13:06 dominikg

Sure I can provide more detail, that hard coded stuff was just for the minimal repro case.

We have thousands of icons defined by JSON structures, each struct has like "path d" attribute, translateX, translateY, viewBox, etc. I'm transforming those into styleable SVG.

The real code is this, verbatim:

        let code = `
          <svg viewBox="${viewBox}" {...$$props}>
            <path d="${svgPath}"></path>
          </svg>

          <style>
            svg {
              display: block;
              width: var(--icon-size, 24px);
              height: var(--icon-size, 24px);
              fill: var(--icon-color);
            }
            svg:hover {
              fill: var(--icon-color-hover);
            }
          </style>
        `;

I think using css: true to add via javascript is exactly what I'd want. For ssr, that code runs server-side, there is no "disabling javascript" in that context. Know what I mean? If I dig around in the outputted code, the stuff for generate: dom looks like it has append_styles() calls which is for the clientside, but the stuff for generate: ssr has the $$result.css.add() calls which should run server-side and emit CSS to be accumulated.

johnnysprinkles avatar Jun 02 '22 16:06 johnnysprinkles

Digging around the source code, my guess is that SvelteKit's manifest wasn't able to detect the component's CSS to inline it into the initial html response. I'm not sure where exactly is the bug, or maybe the Vite plugin should do something different, but it's also worth trying to have the Vite plugin leverage vite-plugin-svelte by loading the SVG code in the load hook, and let vite-plugin-svelte's transform hook do the rest.

bluwy avatar Jun 26 '22 03:06 bluwy

That would actually be ideal, I just wasn't sure how to make my plugin run before vite-plugin-svelte. Let me play around with enforce: pre.

johnnysprinkles avatar Jun 26 '22 17:06 johnnysprinkles

Also I wonder if #5138 is related.

johnnysprinkles avatar Jun 26 '22 17:06 johnnysprinkles

Did enforce: pre work? vite-plugin-svelte extracts the CSS into a separate .css module which Vite knows how to extract for SSR; any replacement plugin would need to jump through the same hoops, so getting the two plugins to work together would be ideal.

Rich-Harris avatar Jul 21 '22 23:07 Rich-Harris

@johnnysprinkles let us know if we're ok to close this issue — thanks

Rich-Harris avatar Sep 04 '22 01:09 Rich-Harris

Sorry for the long delay here. I guess what I was trying to do is make an alternative "vite-plugin-svelte" by doing the compile() call myself, but it's missing a couple essential steps. To get the CSS in there for SSR I'd need to do the equivalent of this line:

https://github.com/sveltejs/vite-plugin-svelte/blob/4de85681b9e68cde16ae27ebf0a9b7322f635e11/packages/vite-plugin-svelte/src/utils/compile.ts#L74

So a two step transform sounds a lot better. 1) SVG transforms to SvelteScript, and 2) SvelteScript to JS via vite-plugin-svelte. This should be easier now that the plugins are exposed in vite.config.js, just preprend one like this:

- plugins: [sveltekit()],
+ plugins: [sfSymbols(), sveltekit()],

However I can't seem to figure out the rollup plugin mechanism. I'd need the first plugin to rename the ID to something ending in ".svelte" so the sveltekit() plugin picks it up. I bet @Rich-Harris could tell me in two seconds how to do that.

johnnysprinkles avatar Oct 13 '22 00:10 johnnysprinkles

So, I don't know. It would be pretty slick to import one of the 5000 JSON files representing each of our SF Symbols, and have it automatically turn into a Svelte component. But might not be worth the trouble. I just made a general component and pass the data in as a prop, no plugin needed.

johnnysprinkles avatar Oct 13 '22 21:10 johnnysprinkles