vue服务端渲染ssr

一、SSR概念

Server-side rendering(SSR)是应用程序通过在服务器上显示网页而不是在浏览器中渲染的能力。服务器端向客户端发送一个完全渲染的页面(准确来说是仅仅是 HTML 页面)。同时,结合客户端的JavaScript bundle 使得页面可以运行起来。

传统web渲染技术

传统的web渲染技术asp .net php jsp是浏览器向服务器发送请求。服务器进行数据库查询等操作后拼接成html返回到浏览器:
在这里插入图片描述

SPA

而SPA(single page web application)单页面应用程序首屏加载较慢不利于不利于SEO,单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。

在这里插入图片描述

SSR

SSR尽量不占用前端的资源,前端这块耗时少,速度快。有利于SEO优化。不利于前后端分离,主要工作在后端哪里,开发效率慢。

在这里插入图片描述

二、webpack+vue2的实现方式

1.创建工程

vue create ssr

2.安装依赖

渲染器vue-server-renderer
nodejs服务器express

npm i vue-server-render express -D

3.编写一个简单的SSR

创建一个express服务器,将vue ssr集成进来,./server/index.js
运行 node index.js

// nodejs服务器
const express = require("express");
const Vue = require("vue");

// 创建express实例和vue实例
const app = express();
// 创建渲染器
const renderer = require("vue-server-renderer").createRenderer();

// 将来用渲染器渲染page可以得到html内容
const page = new Vue({
    
    
  data: {
    
     title: "SSR" },
  template: "<div><h1>{
    
    {title}}</h1><div>hello, vue ssr!</div></div>",
});

app.get("/", async (req, res) => {
    
    
  try {
    
    
    const html = await renderer.renderToString(page);
    // eslint-disable-next-line no-console
    console.log(html);
    res.send(html);
  } catch (error) {
    
    
    res.status(500).send("服务器内部错误");
  }
});

app.listen(3000, () => {
    
    
  // eslint-disable-next-line no-console
  console.log("渲染服务器启动成功");
});

运行如下
在这里插入图片描述
在浏览器访问localhost:3000
在这里插入图片描述

4.完整的ssr

安装vue-router

npm i vue-router -s

配置
创建./src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

import Index from '@/components/Index'
import Detail from '@/components/Detail'

Vue.use(Router)

// 这里为什么不导出一个router实例哪?
// 每次用户请求都需要创建router实例
export default function createRouter() {
    
    
    return new Router({
    
    
        mode: 'history',
        routes: [
            {
    
    path: '/', component: Index},
            {
    
    path: '/detail', component: Detail},
        ]
    })
}

创建Index.vue

<template>
 <div>
   Index Page
 </div>
</template>

创建Detail.vue

<template>
    <div>
        Detail Page
    </div>
</template>

更新App.vue

<template>
  <div id="app">
    <!-- <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/> -->
    <nav>
      <router-link to="/">首页</router-link>
      <router-link to="/detail">详情页</router-link>
    </nav>
    <router-view></router-view>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
    
    
  name: 'app',
  components: {
    
    
    HelloWorld
  }
}
</script>

<style>
#app {
    
    
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

入口app.js

// 创建vue实例
import Vue from "vue";
import App from "./App.vue";
import createRouter from "./router";

export default function createApp() {
    
    
  const router = createRouter();
  const app = new Vue({
    
    
    router,
    render: h => h(App),
  });
  return {
    
     app, router };
}

服务端入口entry-client.js

// 挂载、激活app
import createApp from './app'

const {
    
    app,router} = createApp();
router.onReady(() => {
    
    
    app.$mount('#app')
})

客户端入口entry-server.js

// 渲染首屏
import createApp from "./app";

// context哪来的?
export default context => {
    
    
  return new Promise((resolve, reject) => {
    
    
    const {
    
     app, router } = createApp();
    // 进入首屏
    router.push(context.url)
    router.onReady(() => {
    
    
        resolve(app);
    }, reject)
  });
};

添加webpack打包依赖

npm install webpack-node-externals lodash.merge -D

具体配置,vue.config.js

// webpack插件
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");

// 环境变量:决定入口是客户端还是服务端
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";

module.exports = {
    
    
  css: {
    
    
    extract: false
  },
  outputDir: './dist/'+target,
  configureWebpack: () => ({
    
    
    // 将 entry 指向应用程序的 server / client 文件
    entry: `./src/entry-${
     
     target}.js`,
    // 对 bundle renderer 提供 source map 支持
    devtool: 'source-map',
    // 这允许 webpack 以 Node 适用方式处理动态导入(dynamic import),
    // 并且还会在编译 Vue 组件时告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
    target: TARGET_NODE ? "node" : "web",
    node: TARGET_NODE ? undefined : false,
    output: {
    
    
      // 此处告知 server bundle 使用 Node 风格导出模块
      libraryTarget: TARGET_NODE ? "commonjs2" : undefined
    },
    // 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的 bundle 文件。
    externals: TARGET_NODE
      ? nodeExternals({
    
    
          // 不要外置化 webpack 需要处理的依赖模块。
          // 可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
          // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
          whitelist: [/\.css$/]
        })
      : undefined,
    optimization: {
    
    
      splitChunks: undefined
    },
    // 这是将服务器的整个输出构建为单个 JSON 文件的插件。
    // 服务端默认文件名为 `vue-ssr-server-bundle.json`
    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  }),
  chainWebpack: config => {
    
    
    config.module
      .rule("vue")
      .use("vue-loader")
      .tap(options => {
    
    
        merge(options, {
    
    
          optimizeSSR: false
        });
      });
  }
};

最终代码结构如下
在这里插入图片描述

5.代码链接

完整代码链接如下:
https://download.csdn.net/download/qq_43548590/86947216?spm=1001.2014.3001.5503

此次参考为:
https://www.bilibili.com/video/BV1dE411C7f5?p=1

三、Vite+Vue3的实现方式

官方地址:https://vitejs.cn/guide/ssr.html
官方示例:https://github.com/vitejs/vite/tree/main/packages/playground/ssr-vue
此处我使用的是vite-ssr https://github.com/frandiox/vite-ssr进行搭建

1、创建一个Vue项目

yarn create vite --template vue

2、添加依赖

yarn add vite-ssr vue@3 vue-router@4 @vueuse/head

3、将 Vite SSR 插件添加到您的 Vite 配置文件中

vite.config.js

// vite.config.js
import vue from '@vitejs/plugin-vue'
import viteSSR from 'vite-ssr/plugin.js'
// import react from '@vitejs/plugin-react'

export default {
    
    
  plugins: [
    viteSSR(),
    vue(), // react()
  ],
}

4、修改main.js

可以在注释出添加自己的业务逻辑

import {
    
     createApp } from 'vue'
import './style.css'
import App from './App.vue'
import routes from './routes'
import viteSSR from 'vite-ssr'

export default viteSSR(App, {
    
     routes }, (context) => {
    
    
    /* Vite SSR main hook for custom logic */
    /* const { app, router, initialState, ... } = context */
  })

5、添加路由

npm install vue-router@4

routes.js

import about from './components/about.vue'
import Home from './components/home.vue'
export default [
    {
    
    
        path:"/",
        component: ()=>import('./page.vue'),
        children:[
         {
    
     path: 'about', component: about },
         {
    
     path: 'hello', component: Home },
        ]
    }
 ]
  

配置页面
component/home.vue

<script setup>
import {
    
     ref } from 'vue'

defineProps({
    
    
  msg: String
})

const count = ref(0)
</script>

<template>
 <h1>home</h1>
</template>

<style scoped>
a {
    
    
  color: #42b983;
}
</style>

component/about.vue

<script setup>
import {
    
     ref } from 'vue'

defineProps({
    
    
  msg: String
})

const count = ref(0)
</script>

<template>
  <h1>about</h1>
</template>

<style scoped>
a {
    
    
  color: #42b983;
}
</style>

page.vue

<script setup>
import {
    
     ref } from 'vue'


const count = ref(0)
</script>

<template>

<h1>{
    
    {
    
    $store.state.count}}</h1>
  <p>
    <!--使用 router-link 组件进行导航 -->
    <!--通过传递 `to` 来指定链接 -->
    <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
    <router-link to="/hello">Go to hello</router-link>
    ================
    <router-link to="/about">Go to about</router-link>
     <router-view></router-view>
  </p>
</template>

<style scoped>
a {
    
    
  color: #42b983;
}
</style>

6、创建node服务器进行服务端渲染

server.js

// This is a simple Node server that uses the built project.
//这是一个使用构建项目的简单节点服务器。

const path = require('path')
const express = require('express')





// This contains a list of static routes (assets)
//它包含静态路由(资产)的列表
const {
    
     ssr } = require(`./dist/server/package.json`)

// The manifest is required for preloading assets
//预装资产需要清单
const manifest = require(`./dist/client/ssr-manifest.json`)

// This is the server renderer we just built
//这是我们刚刚构建的服务器渲染器
const {
    
     default: renderPage } = require(`./dist/server`)

// const api = require('./api')

const server = express()

// Serve every static asset route 为每个静态路由提供服务
for (const asset of ssr.assets || []) {
    
    
  server.use(
    '/' + asset,
    express.static(path.join(__dirname, `./dist/client/` + asset))
  )
}

// Custom API to get data for each page
// See src/main.js to see how this is called
// api.forEach(({ route, handler, method = 'get' }) =>
//   server[method](route, handler)
// )

// Everything else is treated as a "rendering request"
server.get('*', async (request, response) => {
    
    
  const url =
    request.protocol + '://' + request.get('host') + request.originalUrl

  const {
    
     html, status, statusText, headers } = await renderPage(url, {
    
    
    manifest,
    preload: true,
    // Anything passed here will be available in the main hook
    request,
    response,
    // initialState: { ... } // <- This would also be available
  })

  response.writeHead(status || 200, statusText || headers, headers)
  response.end(html)
})

const port = 8080
console.log(`Server started: http://localhost:${
      
      port}`)
server.listen(port)

7、代码链接

vite-ssr项目地址
https://download.csdn.net/download/qq_43548590/86947311

参考地址:https://github.com/frandiox/vite-ssrhttps://gitee.com/blueskyliu/vite-ssr

猜你喜欢

转载自blog.csdn.net/qq_43548590/article/details/127788598