UNPKG

@akryum/vue-cli-plugin-ssr

Version:

Turn your app into an isomorphic SSR app!

186 lines (158 loc) 5.49 kB
const express = require('express') const { createBundleRenderer } = require('vue-server-renderer') const fs = require('fs') const favicon = require('serve-favicon') const LRU = require('lru-cache') const compression = require('compression') const config = require('./config') const DEFAULT_OPTIONS = { prodOnly: false, } module.exports = (app, options) => { options = Object.assign({}, DEFAULT_OPTIONS, options) const isProd = process.env.NODE_ENV === 'production' if (options.prodOnly && !isProd) return const templatePath = config.templatePath const { publicPath } = config.service.projectOptions try { // Vue bundle renderer let renderer // In development: wait for webpack compilation // when receiving a SSR request let readyPromise const directives = config.directives const lruCacheOptions = config.lruCacheOptions || {} const defaultRendererOptions = { cache: new LRU({ max: 1000, maxAge: 1000 * 60 * 15, ...lruCacheOptions, }), runInNewContext: false, inject: false, directives, shouldPrefetch: (file, type) => { if (config.shouldNotPrefetch.indexOf(file) > -1) return false if (type === 'script' || type === 'style') return true }, shouldPreload: (file, type) => { if (config.shouldNotPreload.indexOf(file) > -1) return false if (type === 'script' || type === 'style') return true }, } if (isProd) { // In production: create server renderer using template and built server bundle. // The server bundle is generated by vue-ssr-webpack-plugin. const template = fs.readFileSync(templatePath, 'utf-8') const serverBundle = require(`${config.distPath}/vue-ssr-server-bundle.json`) // The client manifests are optional, but it allows the renderer // to automatically infer preload/prefetch links and directly add <script> // tags for any async chunks used during render, avoiding waterfall requests. const clientManifest = require(`${config.distPath}/vue-ssr-client-manifest.json`) renderer = createBundleRenderer(serverBundle, { ...defaultRendererOptions, template, clientManifest, }) } else { // In development: setup the dev server with watch and hot-reload, // and create a new renderer on bundle / index template update. const { setupDevServer } = require('./dev-server') readyPromise = setupDevServer({ server: app, templatePath, onUpdate: ({ serverBundle }, options) => { // Re-create the bundle renderer renderer = createBundleRenderer(serverBundle, { ...defaultRendererOptions, ...options, }) }, }) } if (config.applyDefaultServer) { // Serve static files const serve = (filePath, cache) => express.static(filePath, { maxAge: cache && isProd ? config.staticCacheTtl : 0, index: false, }) // Serve static files app.use(compression({ threshold: 0 })) app.use(favicon(config.favicon)) if (config.api.hasPlugin('pwa')) { app.use('/service-worker.js', serve(config.serviceWorkerPath)) } const serveStaticFiles = serve(config.distPath, true) app.use(publicPath, (req, res, next) => { if (/index\.html/g.test(req.path)) { next() } else { serveStaticFiles(req, res, next) } }) } if (config.extendServer) { config.extendServer(app) } // Render the Vue app using the bundle renderer const renderApp = (req, res) => { res.setHeader('Content-Type', 'text/html') const context = Object.assign({ req, url: req.url, title: config.defaultTitle, httpCode: 200, }, config.extendContext && config.extendContext(req, res, process)) renderer.renderToString(context, (err, renderedHtml) => { let html = renderedHtml if (err || context.httpCode === 500) { console.error(`error during render url : ${req.url}`) // Render Error Page let errorHtml = config.error500Html ? fs.readFileSync(config.error500Html, 'utf-8') : '500 | Internal Server Error' if (err) { console.error(err) if (!isProd) { const errorMessage = `<pre>${err.stack}</pre>` config.error500Html ? errorHtml = errorHtml.replace('<!--server-error-msg-->', errorMessage) : errorHtml += errorMessage } if (config.onError) { config.onError(err) } } html = errorHtml res.status(500) } else { res.status(context.httpCode) } if (config.onRender) { config.onRender(res, context) } res.send(html) }) } // Process SSR requests let ssr if (isProd) { ssr = renderApp } else { // In development: wait for webpack compilation // when receiving a SSR request ssr = (req, res) => { readyPromise.then(() => renderApp(req, res)).catch(console.error) } } app.get(`${publicPath}*`, (req, res, next) => { if (config.skipRequests(req)) { return next() } ssr(req, res) }) return readyPromise } catch (e) { console.error(e) } }