chriscalo / vite-vue-mpa

0 stars 0 forks source link

Architect the ideal SSR flow #10

Open chriscalo opened 8 months ago

chriscalo commented 8 months ago
import express from "express";
import { createSSRApp } from "vue";
import { renderToString } from "vue/server-renderer";

const server = express();

////////////////////////////////////////////////////////////////////////////////
// 1. Export server to composed with other handlers that run before or after
////////////////////////////////////////////////////////////////////////////////
export default server;

////////////////////////////////////////////////////////////////////////////////
// 2. Request comes in
////////////////////////////////////////////////////////////////////////////////
server.use((req, res, next) => {

  try {
    ////////////////////////////////////////////////////////////////////////////
    // 3. SSR handler matches path against eligible page components
    ////////////////////////////////////////////////////////////////////////////
    const pageComponentPath = await findMatchingPageComponent(req.originalUrl);

    ////////////////////////////////////////////////////////////////////////////
    // 4. If no page component found, pass to the next handler
    ////////////////////////////////////////////////////////////////////////////
    if (!pageComponentPath) {
      return next();
    }

    ////////////////////////////////////////////////////////////////////////////
    // 5. If matching page component found, import it
    ////////////////////////////////////////////////////////////////////////////
    const PageComponent = await import(pageComponentPath);

    ////////////////////////////////////////////////////////////////////////////
    // 6. Create SSR application from the page component
    ////////////////////////////////////////////////////////////////////////////
    const ssrApp = createSSRApp(PageComponent);

    ////////////////////////////////////////////////////////////////////////////
    // 7. Render the app, passing a context object to be accessible from within
    //    the Vue component hierarchy
    ////////////////////////////////////////////////////////////////////////////
    const context = { req };
    const ssrOutput = await renderToString(ssrApp, context);

    ////////////////////////////////////////////////////////////////////////////
    // 8. Grab HTML template and inject SSR output into it
    ////////////////////////////////////////////////////////////////////////////
    const htmlTemplate = await file("~/app.html");
    const html = htmlTemplate
      .replace(`<!--slot-head-->`, ssrOutput.head)
      // why is this not `ssrOutput.body`?!
      .replace(`<!--slot-body-->`, ssrOutput.html);

    ////////////////////////////////////////////////////////////////////////////
    // 9. Send the response to the browser
    ////////////////////////////////////////////////////////////////////////////
    res.status(200).set({ "Content-Type": "text/html" }).send(html);

  } catch (error) {
    console.error(error.stack);
    res.status(500).end(error.stack);
  }
});

What's great is it seems findMatchingPageComponent(req.originalUrl) is the only function we need to write.

chriscalo commented 7 months ago

I want to instead try the Vite approach described at https://vitejs.dev/guide/ssr.html.

Goal #1: We should only have to author .vue files that correspond to pages. No .js or .html entry points. We use unhead's <Head> component to inject content into the HTML <head>.

<template>
  <Head>
    <title>Hi</title>
    <meta name="description" content="hi hi" />
  </Head>

  <h1>Hello, World!</h1>
</template>

Goal #2: We first render the component on the server using the Vite method.

// 1. Read index.html
let template = fs.readFileSync(
  path.resolve(__dirname, 'index.html'),
  'utf-8',
)

// 2. Apply Vite HTML transforms. This injects the Vite HMR client,
//    and also applies HTML transforms from Vite plugins, e.g. global
//    preambles from @vitejs/plugin-react
template = await vite.transformIndexHtml(url, template)

// 3a. Load the server entry. ssrLoadModule automatically transforms
//    ESM source code to be usable in Node.js! There is no bundling
//    required, and provides efficient invalidation similar to HMR.
const { render } = await vite.ssrLoadModule('/src/entry-server.js')