vite-plugin-vue2
vite-plugin-vue2 copied to clipboard
Component assets missing from SSR render
We're trying to setup SSR for Vue 2.7 as we move towards a future Vue 3 migration of a large Vue 2 codebase. We've moved from Webpack to Vite in the process, but we're seeing an issue with a FOUC on all pages in both the dev server and production builds.
During the template render, the SSR server does not appear to be picking up all the components that were walked during the render. I would expect these components to be collected into context._registeredComponents
which appears to be what the template renderer is using to inject the CSS and scripts.
This functionality does not appear to be functioning properly. And because there isn't a createSSRApp
equivalent for Vue 2.7, we're not able to collect the modules for doing our own template rendering either (as shown here.)
What's the recommended process for eliminating this FOUC for Vue 2.7 applications?
Tangentially related to https://github.com/vitejs/vite-plugin-vue2/issues/56
Actually I think I can see that this is todo.. https://github.com/vitejs/vite-plugin-vue2/blob/main/src/main.ts#L135 -- would really appreciate work towards this, or guidance on how we can contribute or develop our own solution.
So I patched the plugin by adding some code that works for our use case to the above TODO'd section:
Obviously this may cause problems if you have components that use the beforeCreate
lifecycle hook -- we don't -- but the idea is pretty simple. After this, the example Vue SSR code works fine.
if (ssr) {
const normalizedPath = slash(path.normalize(path.relative(options.root, filename)));
let moduleId = JSON.stringify(normalizedPath);
output.push(`
__component__.options.beforeCreate = function() {
const ctx = this.$ssrContext;
ctx.modules = ctx.modules || [];
if (!ctx.modules.includes(${moduleId})) {
ctx.modules.push(${moduleId});
}
};
`)
}
For those having trouble, my solution is similar to Iwansbrough's. I created a burner plugin.
vite.vue-moduleid-extractor.js
/**
* MIT License
*
* Added this plugin so that you can use ctx.modules to know the rendered vue modules in vue SSR.
* @vitejs/plugin-vue2 should handle this, but it has been TODO for more than a year.
* https://github.com/vitejs/vite-plugin-vue2/blob/v2.2.0/src/main.ts#L135
*
* phonezawphyo
* 2023-10-12
*/
import path from "node:path"
import { normalizePath } from 'vite'
const parseVueRequest = (id) => {
const [filename, rawQuery] = id.split(`?`, 2)
const query = Object.fromEntries(new URLSearchParams(rawQuery))
if (query.vue != null) {
query.vue = true
}
if (query.index != null) {
query.index = Number(query.index)
}
if (query.raw != null) {
query.raw = true
}
if (query.url != null) {
query.url = true
}
if (query.scoped != null) {
query.scoped = true
}
return {
filename,
query,
}
}
export default ({root}) => {
return {
name: 'vue-moduleid-extractor',
transform(code, id, opt) {
const ssr = opt?.ssr === true
const { filename, query } = parseVueRequest(id)
if (ssr && filename.endsWith('.vue') && !query.vue) {
const normalizedFilename = normalizePath(
path.relative(root, filename),
)
let moduleId = JSON.stringify(normalizedFilename)
code = code.replace(/export default __component__\.exports$/, "")
code += `
__component__.options.beforeCreate = (__component__.options.beforeCreate||[]).concat([
function() {
const ctx = this.$ssrContext;
ctx.modules = ctx.modules || [];
if (!ctx.modules.includes(${moduleId})) {
ctx.modules.push(${moduleId});
}
}
]);
export default __component__.exports;
`
return { code, map: null }
}
}
}
}
Usage in vite.config.js
import vueModuleIdExtractor from "./vite.vue-moduleid-extractor"
...
plugins: [
vue(),
vueModuleIdExtractor({root:process.cwd()})
],
...