vue项目改造SSR(服务端渲染)

1、SSR(服务端渲染)是什么?

传统的vue项目浏览器渲染模式

浏览器渲染模式

缺点:1、SEO问题
2、首屏速度问题
3、消耗性能的问题

ssr服务器渲染模式

SSR

优点:
1、更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面
2、首屏渲染速度快

SSR 简单来说就是将页面在服务端渲染完成后在客户端直接展示。

2、SSR原理

简单示例

index.template.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{
   
   {title}}</title>
    {
   
   {
   
   { metas }}}
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>

server.js

// eslint-disable-next-line @typescript-eslint/no-var-requires
const Vue = require('vue');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const server = require('express')();

// eslint-disable-next-line @typescript-eslint/no-var-requires
const template = require('fs').readFileSync('./index.template.html', 'utf-8');

// eslint-disable-next-line @typescript-eslint/no-var-requires
const renderer = require('vue-server-renderer').createRenderer({
    template,
});
const context = {
    title: 'vue ssr',
    metas: `
        <meta name="keyword" content="vue,ssr">
        <meta name="description" content="vue srr demo">
    `,
};

server.get('*', (req, res) => {
    const app = new Vue({
        data: {
            url: req.url
        },
        template: `<div>访问的 URL 是: {
   
   { url }}</div>`,
    });

    renderer
        .renderToString(app, context, (err, html) => {
            console.log(html);
            console.log(err)
            if (err) {
                res.status(500).end('Internal Server Error')
                return;
            }
            res.end(html);
        });
})
server.listen(8081);

构建步骤

vue项目是通过虚拟 DOM来挂载到html的,所以对spa项目,爬虫才会只看到初始结构。虚拟 DOM,最终要通过一定的方法将其转换为真实 DOM。虚拟 DOM 也就是 JS 对象,整个服务端的渲染流程就是通过虚拟 DOM 的编译成完整的html来完成的。

我们通过服务端渲染解析虚拟 DOM成html之后,你会发现页面的事件,都没法触发。那是因为服务端渲染vue-server-renderer插件并没有做这方面的处理,所以我们需要客户端再渲染一遍,简称同构。所以Vue服务端渲染其实是渲染了两遍。下面给出一个官方的图:SSR构建图

需要通过Webpack打包生成两份bundle文件:
Client Bundle,给浏览器用。和纯Vue前端项目Bundle类似
Server Bundle,供服务端SSR使用,一个json文件

不管项目先前是什么样子,是否是使用vue-cli生成的。都会有这个构建改造过程。在构建改造这里会用到 vue-server-renderer 库,这里要注意的是 vue-server-renderer 版本要与Vue版本一样。

打包之后目录结构
SSR打包目录

3、vue项目改造SSR (vue-cli4/vue2 为示例)

1、安装依赖包

npm install vue-server-renderer lodash.merge  webpack-node-externals cross-env

2、修改router

// 原来的写法
// const router = new VueRouter({
//   mode: 'history',
//   base: process.env.BASE_URL,
//   routes
// })
//
// export default router

// 修改后的写法
export default function createRouter() {
  return new VueRouter({
    mode: "history",  // 一定要history 
    base: process.env.BASE_URL,
    routes,
  });
}

3、修改main.ts

// 原来的写法
// new Vue({
//   router,
//   store,
//   render: h => h(App)
// }).$mount('#app')

// 修改后的写法
export function createApp() {
  // 创建 router 
  const router = createRouter();
  const app = new Vue({
    router,
    render: (h) => h(App),
  });
  return { app, router };
}

4、创建entry-client.js

import { createApp }  from './main'

const { app } = createApp()

app.$mount('#app')

5、创建entry-server.js

import {createApp} from "./main.ts";
// context实际上就是server/index.js里面传参,后面会说到server/index.js
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export default context => {
    return new Promise((resolve, reject) => {
        const {app, router} = createApp();
        router.push(context.url) ;
        router.onReady(()=>{
            // 是否匹配到我们要用的组件
            const matchs = router.getMatchedComponents();
            if(!matchs) {
                return reject({code: 404})
            }
            resolve(app);
        }, reject);
    })
}

6、修改webpack打包文件

vue.config.js

// 服务器渲染插件
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");

// 环境变量:决定入口是客户端还是服务端,WEBPACK_TARGET在启动项中设置的,见package.json文件
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: {
            // 此处配置服务器端使用node的风格构建
            libraryTarget: TARGET_NODE ? "commonjs2" : undefined
        },
        // 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的 bundle 文件。
        externals: TARGET_NODE ? nodeExternals({
            // 不要外置化 webpack 需要处理的依赖模块。(以前whitelist,改成allowlist)
            allowlist: [/\.css$/]
        }) : undefined,
        optimization: { splitChunks: TARGET_NODE ? false : undefined}, // 这是将服务器的整个输出构建为单个 JSON 文件的插件。 // 服务端默认文件名为 `vue-ssr-server-bundle.json` // 客户端默认文件名为 `vue-ssr-client-manifest.json`
         plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
    }),
    chainWebpack: config => {
        config.module .rule("vue") .use("vue-loader") .tap(options => { merge(options, { optimizeSSR: false }); });
    }
};

7、创建SSR html 模板

index.template.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{
   
   {title}}测试ssr</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>

8、nodejs服务器


// nodejs服务器
const express = require("express");
const fs = require("fs");
// 创建express实例和vue实例
const app = express();

// 创建渲染器 获得一个createBundleRenderer
const { createBundleRenderer } = require("vue-server-renderer");
const serverBundle = require("../dist/server/vue-ssr-server-bundle.json");
const clientManifest = require("../dist/client/vue-ssr-client-manifest.json");
const template = fs.readFileSync("../src/index.template.html", "utf-8"); // ssr模板文件
const renderer = createBundleRenderer(serverBundle, {
    runInNewContext: false,
    template,
    clientManifest,
});

// 中间件处理静态文件请求
app.use(express.static("../dist/client", { index: false })); // 为false是不让它渲染成dist/client/index.html
// app.use(express.static('../dist/client'))  //如果换成这行代码,那么需要把dist/client/index.html 删除 ,不然会优先渲染该目录下的index.html文件

// 前端请求返回数据
app.get("*", async (req, res) => {
    try {
        const context = { url: req.url, title: "ssr",};
    // nodejs流数据,文件太大,用renderToString会卡
        const stream = renderer.renderToStream(context);
        let buffer = [];
        stream.on("data", (chunk) => {
            buffer.push(chunk);
        });
        stream.on("end", () => {
            res.end(Buffer.concat(buffer));
        });
    } catch (error) {
        console.log(error);
        res.status(500).send("服务器内部错误");
    }
});

/*服务启动*/
const port = 8091;
app.listen(port, () => {
    console.log(`server started at localhost:${port}`);
});

9、修改 package.json

"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
 "build": "npm run build:server && npm run build:client",
 "service": "cd server && node index.js"

10、启动服务

打包成客户端和服务器端

npm run build

启动node服务

npm run service

github地址:https://github.com/wang12321/SSR

猜你喜欢

转载自blog.csdn.net/wang15180138572/article/details/118549088