UNPKG

vite-ssr-vue2

Version:

Vite utility for vue2 server side rendering

207 lines (197 loc) 6.96 kB
'use strict'; 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;