At the beginning of the New Year, I think everyone here is in the waiting demand (PS: paddling fish), sorry, I am proud to say that I am too. A few days fish touch down, the in the mind is not the taste, looking at the students around every day are learning this learning that, make me not very bashful. So while we are still waiting for all the solutions and frameworks to come out, let’s put together the server side of Vue3.0 and write it for ourselves.

Well, that’s all for now. We started the research based on Webpack and VUe-CLI, but gave up after encountering some problems on the way. Due to uVU’s recent obsession with Vite, vue-CLI related ecology is also lagging behind. So backhand on a hand vite play, see what magic let us especially big thirty night still in the masturbating code.

The beginning of learning is undoubtedly to seeThe document”, coinciding with vite2.0’s launch. I found the one that was easySSR demo.Once you see this sentence, it shows that the next journey is very exciting, can start to play the imagination of the place is very much. No more words, download first, and then according to their own want to modify it.

Open the demo project, the general logic has been written for us almost, the rest of the server prefetch data, store state takeover, etc., we can say that a little transformation can be used for production, very nice.

So let’s talk about the code. Js file, this file is actually to help us start an SSR server.

// @ts-check
const fs = require('fs')
const path = require('path')
const express = require('express')
const serialize = require('serialize-javascript');

const isTest = process.env.NODE_ENV === 'test'| |!!!!! process.env.VITE_TEST_BUILDasync function createServer(
  root = process.cwd(),
  isProd = process.env.NODE_ENV === 'production'
) {
  const resolve = (p) = > path.resolve(__dirname, p)

  const indexProd = isProd
    ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
    : ' '

  const manifest = isProd
    ? // @ts-ignore
      require('./dist/client/ssr-manifest.json')
    : {}

  const app = express()

  / * * *@type {import('vite').ViteDevServer}* /
  let vite
  if(! isProd) { vite =await require('vite').createServer({
      root,
      logLevel: isTest ? 'error' : 'info'.server: {
        middlewareMode: true
      }
    })
    app.use(vite.middlewares)
  } else {
    app.use(require('compression') ())// Put the packed CSS, JS and other files into the static file server
    app.use(
      require('serve-static')(resolve('dist/client'), {
        index: false
      })
    )
  }

  app.use(The '*'.async (req, res) => {
    try {
      const url = req.originalUrl

      let template, render
      // Read the index.html template file
      if(! isProd) {console.log('Current request path', url);
        template = fs.readFileSync(resolve('index.html'), 'utf-8')
        template = await vite.transformIndexHtml(url, template)
        render = (await vite.ssrLoadModule('/src/entry-server.js')).render
      } else {
        template = indexProd
        // @ts-ignore
        render = require('./dist/server/entry-server.js').render
      }
      // Call the server-side rendering method to render the Vue component into a DOM structure, and analyze the js, CSS and other files that need to be preloaded.
      const [appHtml, preloadLinks, store] = await render(url, manifest)
      // Add + to the server prefetch data store, insert HTML template file
      const state =  ("<script>window.__INIT_STATE__" + "=" + serialize(store, {isJSON: true}) + "</script>");
      // Replace the stand character in the HTML with the corresponding resource file
      const html = template
        .replace(` <! --preload-links-->`, preloadLinks)
        .replace(` <! --app-html-->`, appHtml)
        .replace(` <! --app-store-->`, state)

      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      vite && vite.ssrFixStacktrace(e)
      console.log(e.stack)
      res.status(500).end(e.stack)
    }
  })

  return { app, vite }
}
// Create a node server for SSR
if(! isTest) { createServer().then(({ app }) = >
    app.listen(3000.() = > {
      console.log('http://localhost:3000')}}))// for test use
exports.createServer = createServer
Copy the code

index.html

Is the template file in server.js, you can see that there is the corresponding booth symbol to be replaced

src/main.ts

Because every request that reaches the server requires a new code that is not tainted by the previous request, this file is essentially the factory function of the current running environment, returning a new vue instance, router instance, Store instance, and so on each time.

src/entry-server.js

Server-side render entry functions

import { createApp } from "./main";
import { renderToString } from "@vue/server-renderer";

import { getAsyncData } from '@src/utils/publics';

export async function render(url, manifest) {
  const { app, router, store } = createApp();

  Url / / synchronization
  router.push(url);
  store.$setSsrPath(url);
  await router.isReady();
  When the route is ready, call the custom hook to fetch data from the server
  await getAsyncData(router, store, true);

  // Generate an HTML string
  const ctx = {};
  const html = await renderToString(app, ctx);

  // Generate the resource prefetch array according to the server prefetch manifest generated during packaging
  const preloadLinks = ctx.modules
    ? renderPreloadLinks(ctx.modules, manifest)
    : [];
  return[html, preloadLinks, store]; } to omit...Copy the code

SRC /entry-client.js Client rendering entry function

import { createApp } from './main'
const { app, router, store } = createApp()

router.isReady().then(() = > {
  // Hang on the dom of the current vue instance whose id is app
  app.use(VueRescroll)
     .use(VueImageLazyLoad)
     .mount('#app');
})
// Enable post-route hooks for page data requests
router.afterEach(() = > {
  getAsyncData(router, store, false);
})

Copy the code

GetAsyncData adds the asyncData hook as a data prefetch hook, as vue2.0 does

Execute the register Store hook
export const registerModules = (components: Component[], router: Router, store: BaseStore) = > {
  return components
    .filter((i: any) = > typeof i.registerModule === "function")
    .forEach((component: any) = > {
      component.registerModule({ router: router.currentRoute, store });
    });
};

// Call the asyncData hook of the currently matched component to prefetch data
export const prefetchData = (components: Component[], router: Router, store: BaseStore) = > {
  const asyncDatas: any[] = components.filter(
    (i: any) = > typeof i.asyncData === "function"
  );
  return Promise.all(
    asyncDatas.map((i) = > {
      return i.asyncData({ router: router.currentRoute.value, store }); })); };// SSR custom hooks
export const getAsyncData = (
  router: Router,
  store: BaseStore,
  isServer: boolean
): Promise<void> = > {return new Promise(async (resolve) => {
    const { matched, fullPath } = router.currentRoute.value;

    // The component to which the current route matches
    const components: Component[] = matched.map((i) = > {
      return i.components.default;
    });
    // Dynamically register store
    registerModules(components, router, store);

    if(isServer || store.ssrPath ! == fullPath) {// Prefetch data
      awaitprefetchData(components, router, store); ! isServer && store.$setSsrPath("");
    }

    resolve();
  });
};
Copy the code

. Vue has the same prefetch data as data and computed data

	async asyncData({ store, router }: any) {
		if(! store.blog)return;
		const { blogDetail } = store.blog;
        blogDetail.$assignParams({
            id: router.query.id
        })
        await blogDetail.loadData();
	},
Copy the code

For SSR transformation to do the above, there are some project optimization, such as modularization, TS, store registration on demand, and some custom plug-ins, not one way, like students can download the source code or fork in the past to play, the experiment SSR temporarily does not support TSX/JSX writing method. Students who need to communicate are also welcome to communicate in the comments section.

Project warehouse: github.com/Vitaminaq/c… Repository of plug-ins used in the project: github.com/Vitaminaq/p… (If you like, please help yourself. Welcome to join the development.)