-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
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
SSR CSS injection in development #2282
Comments
I just inlined the css as const collectCssUrls = (mods: Set<ModuleNode>, styles: Map<string, string>) => {
for (const mod of mods) {
if (mod.ssrModule && mod.file && mod.id) {
if (mod.file.endsWith('.css') || /\?vue&type=style/.test(mod.id)) {
styles.set(mod.url, mod.ssrModule.default)
}
}
if (mod.importedModules.size > 0) {
collectCssUrls(mod.importedModules, styles)
}
}
} Some changes need to be made in updateStyle in order to make hydration work, or at least expose vite/packages/vite/src/client/client.ts Line 195 in 89e0e33
|
Your method is great! It seems like some sfc style modules might be determined lazily and so aren't in the module graph on first load (but then exist on subsequent ones)? |
Yeah, you should call |
That makes sense. I moved it down shortly after writing that last comment and don't think I've seen the intermittent partial loads since. Looking forward to this behavior being supported internally! (okay to keep this issue open to track that effort?) |
This is how vite-plugin-ssr solves this: see |
This could be fixed with this vite plugin: module.exports = {
enforce: 'post',
apply: 'serve',
transform (code, id, ssr) {
if (ssr && id.endsWith('.css')) {
return `global.css = (global.css || []).concat("${code.trim().slice(16, -1)}")`;
}
},
} and then in server.js const html = template.replace('<!--HEAD-->', `<style>${global.css.join('\n')}</style>`) |
It looks like both of our approaches stopped working somewhere between 2.3.0 and 2.3.4 and it's not immediately obvious from the CHANGELOG. Investigating... ( |
I'm guessing you would be looking for this change 65d333d |
Yes was just about to edit my comment (after scanning the diff). :) Do you have any ideas / suggestions on how to proceed? |
I posted a working implementation on the other issue. Both the attempts from this issue and the alternative I proposed use artifacts of the internal build process, which leads to instability. Here is a way to test
After that open localhost:3000, the html will have the styles embedded. It is a small change from ssr-vue from the playground of this very repository, the minimal test case to show how to do it, here are the changes from the vanilla ssr-vue: ferdinando-ferreira@e70bd50 |
)" fix for vitejs#3610 and partial fix for vitejs#2282 This reverts commit 65d333d
Working solution with Hydration. // server
import { componentsModules, collectCss } from "./collect-css-ssr";
// You have to identify the vue components to render. In our case we use `router.matchedComponents` from `vue-router`.
// But if you have a single entry for you app, just point to your app.vue file.
const componentsToRender = router.getMatchedComponents();
const componentsPath = componentsToRender.map((component) => component.options.__file);
const html = renderer.renderToString(app, context);
const matchedModules = componentsModules(componentsPath, vite);
const css = collectCss(matchedModules);
return html.replace("<!--dev-ssr-css-->", css); Then in the client, you have to listen for component updates, and remove the CSS from the header. // client-entry.ts
import { removeCssHotReloaded } from "./collect-css-ssr";
removeCssHotReloaded(); Util collect-css-ssr.ts // collect-css-ssr.ts
import { ViteDevServer, ModuleNode, UpdatePayload } from "vite";
/**
* Collect SSR CSS for Vite
*/
export const componentsModules = (components: string[], vite: ViteDevServer) => {
const matchedModules = new Set<ModuleNode>();
components.forEach((component) => {
const modules = vite.moduleGraph.getModulesByFile(component);
modules?.forEach((mod) => matchedModules.add(mod));
});
return matchedModules;
};
export const collectCss = (
mods: Set<ModuleNode>,
styles = new Map<string, string>(),
checkedComponents = new Set(),
) => {
for (const mod of mods) {
if (
(mod.file?.endsWith(".scss") ||
mod.file?.endsWith(".css") ||
mod.id?.includes("vue&type=style")) &&
mod.ssrModule
) {
styles.set(mod.url, mod.ssrModule.default);
}
if (mod.importedModules.size > 0 && !checkedComponents.has(mod.id)) {
checkedComponents.add(mod.id);
collectCss(mod.importedModules, styles, checkedComponents);
}
}
let result = "";
styles.forEach((content, id) => {
const styleTag = `<style type="text/css" vite-module-id="${hashCode(id)}">${content}</style>`;
result = result.concat(styleTag);
});
return result;
};
/**
* Client listener to detect updated modules through HMR, and remove the initial styled attached to the head
*/
export const removeCssHotReloaded = () => {
if (import.meta.hot) {
import.meta.hot.on("vite:beforeUpdate", (module: UpdatePayload) => {
module.updates.forEach((update) => {
const moduleStyle = document.querySelector(
`[vite-module-id="${hashCode(update.acceptedPath)}"]`,
);
if (moduleStyle) {
moduleStyle.remove();
}
});
});
}
};
const hashCode = (moduleId: string) => {
let hash = 0,
i,
chr;
if (moduleId.length === 0) return hash;
for (i = 0; i < moduleId.length; i++) {
chr = moduleId.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}; |
Closing this issue as there isn't a concrete change to be applied to Vite in it. @tjk or others that are interested, please create a Discussion with a summary and a link back to this issue if you would like to keep this thread open (not moving the issue to a discussion automatically as it would destroy the layout). |
Relevant previous issues / discussion:
User-land vue-specific patch that (partially, see NOTE below) works for me (haven't seen how it hydrates yet) --
Curious about latest thoughts on this!
NOTE: It looks like there is some non-determinism to the above... sometimes css modules are missing...
The text was updated successfully, but these errors were encountered: