Vue2实践-SSR

339 阅读1分钟

基础

  • onReady:onReady 是 Vue Router 的一个钩子函数,用于在路由导航完成后执行特定的操作,它可以在路由导航完成后执行异步任务、处理动画效果或其他需要在视图渲染之后执行的逻辑
  • createRenderer:用于在服务器端进行 Vue.js 应用程序渲染的 Vue 2.x 库模块。它允许您在服务器上将 Vue 组件渲染为 HTML 字符串,从而实现服务器端渲染

实践

index.js

import Koa2 from 'koa'
import staticFiles from 'koa-static'
import { createRenderer } from 'vue-server-renderer'
import Vue from 'vue'
import App from './App.vue'
import VueMeta from 'vue-meta'
import { createRouter, routerReady } from './route'
import { createStore } from './pages/vuex/store'
import { getServerAxios } from './util/getAxios'

Vue.use(VueMeta)

const renderer = createRenderer()

const app = new Koa2()

/**
 * 静态资源直接返回
 */
app.use(staticFiles('public'))

/**
 * 应用接管路由
 */
app.use(async function (ctx) {
  const req = ctx.request

  //图标直接返回
  if (req.path === '/favicon.ico') {
    ctx.body = ''
    return false
  }

  const router = createRouter() //创建路由
  const store = createStore(getServerAxios(ctx)) //创建数据仓库

  const vm = new Vue({
    router,
    store,
    render: (h) => h(App),
  })

  const meta_obj = vm.$meta() // 生成的头信息

  // 路由跳转
  router.push(req.url)

  // 等到 router 将可能的异步组件和钩子函数解析完
  await routerReady(router)

  const matchedComponents = router.getMatchedComponents()

  if (!matchedComponents.length) {
    ctx.body = '没有找到该网页,404'
    return
  }

  ctx.set('Content-Type', 'text/html;charset=utf-8')

  let htmlString

  const context = { title: 'hello' } //创建一个上下文对象

  try {
    await Promise.all(
      matchedComponents.map((Component) => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute,
          })
        }
      })
    )
    htmlString = await renderer.renderToString(vm, context)
  } catch (error) {
    ctx.status = 500
    ctx.body = 'Internal Server Error'
  }
  const result = meta_obj.inject()
  const { title, meta } = result

  ctx.body = `<html>
  <head>
   ${title ? title.text() : ''}
   ${meta ? meta.text() : ''}
   ${context.styles ? context.styles : ''}
  </head>
    <body>
      ${htmlString}
      <script>
         var context = {
           state: ${JSON.stringify(store.state)}
         }
      </script>
      <script src="./index.js"></script>
    </body>
  </html>`
})

app.listen(3000)

route.js

export const routerReady = async (router) => {
  return new Promise((resolve) => {
    router.onReady(() => {
      resolve(null);
    });
  });
};

client/index.js

import Vue from 'vue'
import App from '../App.vue'
import { createRouter } from '../route'
import VueMeta from 'vue-meta'
import { createStore } from '../pages/vuex/store'
import { getClientAxios } from '../util/getAxios'

Vue.config.productionTip = false

Vue.use(VueMeta)

const router = createRouter() //创建路由

const store = createStore(getClientAxios())

if (window.context && window.context.state) {
  store.replaceState(window.context.state)
}

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#root', true)