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

Style injection conflicts with SSR hydration on document #15765

Open
5 of 7 tasks
swwind opened this issue Jan 31, 2024 · 4 comments
Open
5 of 7 tasks

Style injection conflicts with SSR hydration on document #15765

swwind opened this issue Jan 31, 2024 · 4 comments

Comments

@swwind
Copy link

swwind commented Jan 31, 2024

Describe the bug

tl;dr: vite dev injected <style> tags maybe removed by preact's hydration on <html> element.

Actually my SSR framework renders the whole <html> tag and I was trying to figure out how to deal with the styles in dev server.

Suppose I have a project like this:

// App.tsx
import "./app.css"; // any styles
export function App() {
  return <html>
    <head> ... </head>
    <body> ... </body>
  </html>
}
// entry.tsx
import { render } from 'preact';
import { App } from './App.tsx';
render(<App />, document, document.documentElement);

And in server side, I just have something like this:

import { render } from 'preact-render-to-string';
import { App } from './App.tsx';
// when request coming
const html = render(<App />); // <html><head>...</head><body>...</body></html>
return new Response('<!DOCTYPE html>' + html);

The code above could work for production build, as I can read the manifest file and know which entry requires which style and inject them into the final html code. However, when I was using dev server, I don't know how many styles there in the project, and I can only rely on vite's dynamic style injection. Here comes the problem.

To describe the problem more accurately, let's think it step by step after open browser:

  1. client start import "./entry.tsx"
  2. continue import "./App.tsx"
  3. continue import "./app.css", the injection code __vite__updateStyle runs, a style tag injected to head
  4. the hydrate function in "./entry.tsx" runs and everything just added got removed

After these steps, no styles can get rendered.

Reproduction

n/a

Steps to reproduce

No response

System Info

all latest version

Used Package Manager

pnpm

Logs

No response

Validations

@swwind
Copy link
Author

swwind commented Jan 31, 2024

Actually I just found I can manually save those injected styles before hydration and inject them after hydration.

function hydrate(vnode: VNode) {
  let injections: NodeListOf<HTMLStyleElement> | null = null;
  if (import.meta.env.DEV) // save before hydration
    injections = document.head.querySelectorAll("style[data-vite-dev-id]");
  render(vnode, document, document.documentElement);
  if (import.meta.env.DEV && injections) // inject after hydration
    injections.forEach((element) => document.head.appendChild(element));
}

Don't know if there are any side effects, but it seems to work well.

@sapphi-red
Copy link
Member

I'm not sure if this is something Vite can fix.
It seems a fix is included in a canary react.
https://remix.run/docs/en/main/future/vite#styles-disappearing-in-development-when-document-remounts

@swwind
Copy link
Author

swwind commented Feb 10, 2024

I'm not sure if this is something Vite can fix. It seems a fix is included in a canary react. https://remix.run/docs/en/main/future/vite#styles-disappearing-in-development-when-document-remounts

Thanks for commenting. Although I think this should be an expected behavior for react/preact as re-mount of html element should remove every other elements that should not exist. In another word, we should not let react to fix it, this should be remix's work when hydrating.

I was expecting vite to expose something inside @vite/client such as the sheetsMap variable or some remountStyles() function for us to manage injected styles manually. (Although the solution I provided before is not unusable)

@sapphi-red
Copy link
Member

In another word, we should not let react to fix it

I think it cannot be handled by a meta-framework side completely because it seems to affect many examples (facebook/react#24430).

I was expecting vite to expose something inside @vite/client such as the sheetsMap variable or some remountStyles() function for us to manage injected styles manually. (Although the solution I provided before is not unusable)

Perhaps Vite can provide a helper for that if it's a general problem among many rendering frameworks (e.g. React, Vue, Svelte).

import.meta.hot.runWithoutStyles(() => {
  hydrate(<App />, document)
})

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

2 participants