vite-ssr-vue2
Version:
Vite utility for vue2 server side rendering
207 lines (197 loc) • 6.96 kB
JavaScript
;
var fs = require('fs');
var path = require('path');
var vite = require('vite');
var replace = require('@rollup/plugin-replace');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
var replace__default = /*#__PURE__*/_interopDefaultLegacy(replace);
const defaultHtmlParts = [
"headTags",
"body",
"bodyAttrs",
"htmlAttrs",
"initialState"
].reduce((acc, item) => ({ ...acc, [item]: `\${${item}}` }), {});
const buildHtml = (template, parts = defaultHtmlParts) => {
return template.replace("<html", `<html${parts.htmlAttrs}`).replace("<body", `<body${parts.bodyAttrs}`).replace("</head>", `${parts.headTags ? `${parts.headTags}
` : ""}</head>`).replace('<div id="app"></div>', `${parts.body}<script>window.__INITIAL_STATE__=${parts.initialState}<\/script>`);
};
const cookieParse = (str) => {
if (!str) {
return {};
}
return str.split(/; */).reduce((obj, str2) => {
if (str2 === "") {
return obj;
}
const eq = str2.indexOf("=");
const key = eq > 0 ? str2.slice(0, eq) : str2;
let val = eq > 0 ? str2.slice(eq + 1) : null;
if (val != null) {
try {
val = decodeURIComponent(val);
} catch (ex) {
}
}
obj[key] = val;
return obj;
}, {});
};
const readIndexTemplate = async (server, url) => await server.transformIndexHtml(url, await fs.promises.readFile(path__default["default"].resolve(server.config.root, "index.html"), "utf-8"));
const createHandler = (server, options) => {
return async (req, res, next) => {
const response = res;
if (req.method !== "GET" || !req.originalUrl) {
return next();
}
response.redirect = (url, statusCode = 307) => {
response.statusCode = statusCode;
response.setHeader("location", url);
response.end();
};
const error = (e, statusCode = 404) => {
response.statusCode = statusCode;
response.setHeader("content-type", "text/html; charset=utf-8");
response.end(e.message);
};
try {
const template = await readIndexTemplate(server, req.originalUrl);
const entry = options.ssr;
if (!entry) {
response.statusCode = 200;
response.end("");
return;
}
const entryResolve = path__default["default"].join(server.config.root, entry);
const ssrMoudile = await server.ssrLoadModule(entryResolve);
const render = ssrMoudile.default || ssrMoudile;
const headers = req.headers;
const protocol = server.config?.server?.https ? "https" : "";
const context = {
hostname: headers.host,
protocol: headers["x-forwarded-proto"] || protocol || "http",
url: req.originalUrl || "/",
cookies: cookieParse(headers["cookie"]),
ip: headers["x-forwarded-for"]?.split(/, /)?.[0] || req.socket.remoteAddress,
memcache: null,
statusCode: 200,
headers: req.headers,
responseHeaders: { "content-type": "text/html; charset=utf-8" }
};
const htmlParts = await render(req.originalUrl, {
req,
res: response,
context,
manifest: options.manifest,
logModules: options.logModules
});
const html = buildHtml(template, htmlParts);
response.statusCode = context.statusCode;
Object.keys(context.responseHeaders).map((key) => response.setHeader(key, context.responseHeaders[key]));
response.end(html);
} catch (e) {
console.error(e);
error(e);
}
};
};
const rollupBuild = async (config, options, {
clientOptions = {},
serverOptions = {}
} = {}) => {
const clientBuildOptions = vite.mergeConfig({
build: {
isBuild: true,
outDir: path__default["default"].resolve(config.root, "dist/client"),
manifest: "assets-manifest.json",
ssrManifest: true
}
}, clientOptions);
const clientResult = await vite.build(clientBuildOptions);
const indexHtml = clientResult.output.find((file) => file.type === "asset" && file.fileName === "index.html");
const entry = options.ssr;
if (!entry) {
throw new Error("Entry point not found");
}
const entryResolved = path__default["default"].join(config.root, entry);
const html = buildHtml(indexHtml.source);
const serverBuildOptions = vite.mergeConfig({
build: {
isBuild: true,
outDir: path__default["default"].resolve(config.root, "dist/server"),
ssr: entryResolved,
rollupOptions: {
plugins: [
replace__default["default"]({
preventAssignment: true,
values: {
__VITE_SSR_VUE_HTML__: html
}
})
]
}
}
}, serverOptions);
await vite.build(serverBuildOptions);
if (options.custom) {
const chunks = Object.keys(options.custom);
for (const chunk of chunks) {
const entryResolved2 = path__default["default"].join(config.root, options.custom[chunk]);
const buildOptions = vite.mergeConfig({
build: {
isBuild: true,
outDir: path__default["default"].resolve(config.root, `dist/${chunk}`),
ssr: entryResolved2
}
}, serverOptions);
await vite.build(buildOptions);
}
}
await fs.promises.unlink(path__default["default"].join(clientBuildOptions.build?.outDir, "index.html")).catch(() => null);
const type = serverBuildOptions.build?.rollupOptions?.output?.format === "es" ? "module" : "commonjs";
let pkg = {};
try {
pkg = JSON.parse((await fs.promises.readFile(path__default["default"].resolve(config.root, "./package.json"))).toString());
} catch (e) {
}
const packageJson = {
type,
version: pkg.version || "",
main: path__default["default"].parse(serverBuildOptions.build?.ssr).name + ".js",
ssr: {
assets: (await fs.promises.readdir(clientBuildOptions.build?.outDir)).filter((file) => !/(index\.html|manifest\.json)$/i.test(file))
},
...serverBuildOptions.packageJson || {}
};
await fs.promises.writeFile(path__default["default"].join(serverBuildOptions.build?.outDir, "package.json"), JSON.stringify(packageJson, null, 2));
};
var plugin = (opt = {}) => {
const options = opt;
options.name = options.name || "vite-ssr-vue2";
return {
name: options.name,
config() {
return {
ssr: {
noExternal: [options.name]
}
};
},
async configResolved(config) {
if (config.command === "build") {
if (!config.build.isBuild) {
await rollupBuild(config, options);
process.exit(0);
}
} else {
config.logger.info("\n --- SSR ---\n");
}
},
async configureServer(server) {
const handler = opt.serve ? opt.serve(server, options) : createHandler(server, options);
return () => server.middlewares.use(handler);
}
};
};
module.exports = plugin;