Current section

Files

Jump to
live_vue priv static server.js
Raw

priv/static/server.js

import fs from "fs";
import { basename, resolve } from "path";
import { createSSRApp, h } from "vue";
import { renderToString } from "vue/server-renderer";
import { migrateToLiveVueApp } from "./app";
import { mapValues } from "./utils";
const mockLive = {
el: {},
liveSocket: {},
pushEvent: () => 0,
pushEventTo: () => 0,
handleEvent: () => () => { },
removeHandleEvent: () => { },
upload: () => { },
uploadTo: () => { },
vue: {
props: {},
slots: {},
app: {},
},
};
export const getRender = (componentsOrApp, manifest = {}) => {
const { resolve, setup } = migrateToLiveVueApp(componentsOrApp);
return async (name, props, slots) => {
const component = await resolve(name);
const slotComponents = mapValues(slots, base64 => () => h("div", { innerHTML: atob(base64).trim() }));
const app = setup({
createApp: createSSRApp,
component,
props,
slots: slotComponents,
plugin: {
install: (app) => {
// we don't want to mount the app in SSR
app.mount = (...args) => undefined;
// we don't have hook instance in SSR, so we need to mock it
app.provide("_live_vue", Object.assign({}, mockLive));
},
},
el: {},
ssr: true,
});
if (!app)
throw new Error("Setup function did not return a Vue app!");
const ctx = {};
const html = await renderToString(app, ctx);
// the SSR manifest generated by Vite contains module -> chunk/asset mapping
// which we can then use to determine what files need to be preloaded for this
// request.
const preloadLinks = renderPreloadLinks(ctx.modules, manifest);
// easy to split structure
return preloadLinks + "<!-- preload -->" + html;
};
};
/**
* Loads the manifest file from the given path and returns a record of the assets.
* Manifest file is a JSON file generated by Vite for the client build.
* We need to load it to know which files to preload for the given page.
* @param path - The path to the manifest file.
* @returns A record of the assets.
*/
export const loadManifest = (path) => {
try {
// it's generated only in prod build
const content = fs.readFileSync(resolve(path), "utf-8");
return JSON.parse(content);
}
catch (e) {
// manifest is not available in dev, so let's just ignore it
return {};
}
};
function renderPreloadLinks(modules, manifest) {
let links = "";
const seen = new Set();
modules.forEach((id) => {
const files = manifest[id];
if (files) {
files.forEach(file => {
if (!seen.has(file)) {
seen.add(file);
const filename = basename(file);
if (manifest[filename]) {
for (const depFile of manifest[filename]) {
links += renderPreloadLink(depFile);
seen.add(depFile);
}
}
links += renderPreloadLink(file);
}
});
}
});
return links;
}
function renderPreloadLink(file) {
if (file.endsWith(".js")) {
return `<link rel="modulepreload" crossorigin href="${file}">`;
}
else if (file.endsWith(".css")) {
return `<link rel="stylesheet" href="${file}">`;
}
else if (file.endsWith(".woff")) {
return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`;
}
else if (file.endsWith(".woff2")) {
return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`;
}
else if (file.endsWith(".gif")) {
return ` <link rel="preload" href="${file}" as="image" type="image/gif">`;
}
else if (file.endsWith(".jpg") || file.endsWith(".jpeg")) {
return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`;
}
else if (file.endsWith(".png")) {
return ` <link rel="preload" href="${file}" as="image" type="image/png">`;
}
else {
// TODO
return "";
}
}