• 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
    
    • 1

    2.安装依赖

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

    npm i vue-server-render express -D
    
    • 1

    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: "

    {{title}}

    hello, vue ssr!
    "
    , }); 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("渲染服务器启动成功"); });
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

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

    4.完整的ssr

    安装vue-router

    npm i vue-router -s
    
    • 1

    配置
    创建./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},
            ]
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    创建Index.vue

    <template>
     <div>
       Index Page
     </div>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建Detail.vue

    <template>
        <div>
            Detail Page
        </div>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    更新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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    入口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 };
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    服务端入口entry-client.js

    // 挂载、激活app
    import createApp from './app'
    
    const {app,router} = createApp();
    router.onReady(() => {
        app.$mount('#app')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    客户端入口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)
      });
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    添加webpack打包依赖

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

    具体配置,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
            });
          });
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

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

    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
    
    • 1

    2、添加依赖

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

    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()
      ],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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 */
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5、添加路由

    npm install vue-router@4
    
    • 1

    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 },
            ]
        }
     ]
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    配置页面
    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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    page.vue

    <script setup>
    import { ref } from 'vue'
    
    
    const count = ref(0)
    </script>
    
    <template>
    
    <h1>{{$store.state.count}}</h1>
      <p>
        <!--使用 router-link 组件进行导航 -->
        <!--通过传递 `to` 来指定链接 -->
        <!--`` 将呈现一个带有正确 `href` 属性的 `` 标签-->
        <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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    7、代码链接

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

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

  • 相关阅读:
    外包干了5天,技术退步明显.......
    为什么当下MES如此火热,各大制造业工厂都在推行?
    终于有清华大佬深入计算机底层总结出这份图解Java底层/网络手册
    Spring JDBC
    创客匠人工具助力教培机构快速适应线上教学
    PHP 初学 GO 学习笔记
    字节码增强技术-ASM
    大模型日报2024-04-23
    基于单片机16位智能抢答器设计
    vuex中的 actions 中,是不能使用 this.$message.error() 的
  • 原文地址:https://blog.csdn.net/qq_43548590/article/details/127788598