Vue服务端渲染

Vue服务端渲染,第1张

概述一、服务端渲染 - 简介 所谓服务端渲染就是将代码的渲染交给服务器,服务器将渲染好的html字符串返回给客户端,再由客户端进行显示。 服务器端渲染的优点 有利于seo搜索引擎优化,因为服务端渲染是将渲 一、服务端渲染 - 简介

所谓服务端渲染就是将代码的渲染交给服务器,服务器将渲染好的HTML字符串返回给客户端,再由客户端进行显示。

服务器端渲染的优点有利于SEO搜索引擎优化,因为服务端渲染是将渲染好的HTML字符串返回给了客户端,所以其可以被爬虫爬取到;加快首屏渲染时间,不会出现白屏;服务器端渲染的缺点SSR会占用更多的cpu和内存资源vue中一些常用的浏览器API可能无法使用,比如vue的生命周期在服务器端渲染只能使用beforeCreate()和created(),因为服务端呈现的仅仅是HTML字符串是没有所谓的mount的。 二、服务端渲染 - 初体验

使用Vue的服务端渲染功能,需要引入Vue提供的服务端渲染模块vue-server-renderer,其作用是创建一个渲染器,该渲染器可以将Vue实例渲染成HTML字符串。

用Koa来搭建一个web服务器来实现:

① 目录结构

 

② 创建一个server.Js 文件

const Koa = require("koa");const Router = require("koa-router");const fs = require("fs");const app = new Koa(); // 创建服务器端app实例const router = new Router(); // 创建服务器端路由const Vue = require("vue");const VueServerRender = require("vue-server-renderer"); // 引入服务端渲染模块const vm = new Vue({ // 创建Vue实例    data() {        return {msg: "hello vm"}    },template: `<div>{{msg}}</div>` // 渲染器会将vue实例中的数据填入模板中并渲染成对应的HTML字符串});const template = fs.readfileSync("./server.template.HTML","utf8"); // 读取基本的HTML结构const render = VueServerRender.createRenderer({    template}); // 创建渲染器并以server.template.HTML作为HTML页面的基本结构router.get("/",async ctx => {    // ctx.body = await render.renderToString(vm);    ctx.body = await new Promise((resolve,reject) => {            render.renderToString(vm,(err,HTML) => { // 将vm实例渲染成HTML并插入到server.template.HTML模板中            console.log(`${HTML}`);        });    );});app.use(router.routes()); // 添加路由中间件app.Listen(3000,() => {    console.log("node server Listening on port 3000.");}); // 监听3000端口

注意:

server.template.HTML文件中必须有 <!--vue-ssr-outlet-->占位符,即将Vue实例vm渲染成的HTML字符串插入到占位符所在的位置;render.renderToString(vm)方法不传回调函数的时候返回的是Promise对象,但是如果传入了回调函数,那么就返回voID了,推荐自己创建一个Promise函数;Vue服务端渲染出来的字符串中会包含data-server-rendered="true"这样一个标识,标识这是由Vue服务端渲染的结果字符
<div data-server-rendered="true">hello vm</div>
 三、服务端渲染 - 引入Vue项目

上面初体验中,我们已经实现了一个简单的Vue服务端渲染,但是我们实际中Vue是一个很大的项目,里面是包含了很多组件的大型应用,而不是像初体验中的一个简单的Vue实例,所以我们必须引入一个Vue项目,包括Vue的入口文件main.Js、App.vue、components、public/index.HTML等,如:

通过webpack来打包我们的整个Vue项目,webpack将以Vue的根实例main.Js作为入口文件,打包出一个合并的最终的bundle.Js和一个页面入口index.HTML文件,该index.HTML文件引入bundle.Js后就能加载整个Vue项目中的页面以及页面中的事件等等,这里我们的Vue项目是一个很简单的模板项目,关键在于webpack的配置

// webpack.config.Js

const path = require("path");const resolve = (dir) => {    return path.resolve(__dirname,dir);}const VueLoader = require("vue-loader/lib/plugin");const HTMLWebpackPlugin = require("HTML-webpack-plugin");module.exports = {    entry: resolve("./src/main.Js"),// webpack 入口,即Vue的入口文件main.Js    output: {        filename: "bundle.Js",// 打包后输出的结果文件名        path: resolve("./dist") // 打包后输出结果存放目录    },resolve: {        extensions: [".Js",".vue"] // 没有写扩展名的时候,解析顺序    },module: {        rules: [            {                test: /\.Js$/,use: {                    loader: "babel-loader",// 将所有的Js文件通过babel-loader转换为ES5代码                    options: {                        presets: ["@babel/preset-env"]                    }                },exclude: /node_modules/            },{                test: /\.CSS$/,// 解析.vue文件中的CSS                use: [                    "vue-style-loader","css-loader"                ]            },{                test: /\.vue$/,// 解析.vue文件,需要配合其中的插件进行使用                use: "vue-loader"            }        ]    },plugins: [        new VueLoader(),// 解析.vue文件的插件        new HTMLWebpackPlugin({            filename: 'index.HTML',// 打包后输出的HTML文件名            template: resolve("./public/index.HTML") // 该模板文件在哪        })    ]}
打包输出后的dist目录中会出现两个文件: bundle.Js和index.HTML,直接在本地点击index.HTML文件即可执行并呈现整个Vue项目
 四、服务端渲染 - 将Vue项目分割为客户端和服务端

① 在非服务端渲染的时候,我们使用的打包入口文件是main.Js,其主要就是创建了一个Vue实例,并且渲染App.vue,然后将渲染好的App.vue挂载到index.HTML文件#app元素中,但是我们的服务端渲染是无法mount的,也就是说无法将渲染结果渲染到#app元素上,所以需要改造main.Js文件

// 改造后的main.Js文件

import Vue from "vue";import App from "./App";/**  1. main.Js在服务端渲染中的作用就是提供一个Vue项目的根实例,所以导出一个函数 2. 让客户端和服务端都能获取到Vue项目的根实例,然后根据需要, 3. 客户端通过手动调用$mount()进行挂载 4. */export default () => {    const app = new Vue({        render: h => h(App)    });    return {app}; // 返回整个Vue根实例}

 

② 新建两个入口文件: clIEnt-entry.Js 和 server-entry.Js
// clIEnt-entry.Js

import createApp from "./main";const {app} = createApp(); // 获取到Vue项目根实例app.$mount("#app"); // 将根实例挂载到#app上
此时将webpack.config.Js的入口文件改成clIEnt-entry.Js应该和之前是一样的

// server-entry.Js

import createApp from "./main";/**  * 服务端需要调用当前这个文件产生一个Vue项目的根实例 * 由于服务端与客户端是1对多的关系,所以不能每个客户端访问都返回同一个Vue项目根实例 * 所以需要返回一个函数,该函数返回一个新的Vue项目根实例 * */ export default () => {    const {app} = createApp(); // 获取到Vue项目根实例    return app;}
为什么客户端入口文件就不需要暴露一个一个函数?因为客户端可以被访问多次,即多次执行,每次执行返回的都是一个新的Vue项目实例了。而服务器只会启动一次,但是却需要每次客户端访问都返回一个新的Vue项目实例,所以必须放到函数中

 

③ 拆分webapck.config.Js,将其分成两个配置文件,同样一个用于客户端,一个用于服务端打包
由于客户端和服务端的webpack配置文件有很多是相同的,所以可以抽取出一个webpack.base.Js

// webpack.base.Js

const path = require("path");const resolve = (dir) => {    return path.resolve(__dirname,dir);}const VueLoader = require("vue-loader/lib/plugin");module.exports = {    output: {        filename: "[name].bundle.Js",// 打包后输出的结果文件名        path: resolve("./../dist/") // 打包后输出结果存放目录    },// 将所有的Js文件通过babel-loader转换为ES5代码                    options: {                        presets: ["@babel/preset-env"]                    }                },{                test: /\.CSS$/,// 解析.vue文件中的CSS                use: [                    "vue-style-loader",// 解析.vue文件的插件    ]}

// webpack-clIEnt.Js

const merge = require("webpack-merge");const base = require("./webpack.base");const path = require("path");const resolve = (dir) => {    return path.resolve(__dirname,dir);}const HTMLWebpackPlugin = require("HTML-webpack-plugin");module.exports = merge(base,{    entry: {        clIEnt: resolve("./../src/clIEnt-entry.Js"),// 给客户端入口文件取名clIEnt,output的时候可以获取到该名字动态输出    },plugins: [        new HTMLWebpackPlugin({            filename: 'index.HTML',// 打包后输出的HTML文件名            template: resolve("./../public/index.HTML") // 该模板文件在哪        })    ]});

// webpack-server.Js

const merge = require("webpack-merge");const base = require("./webpack.base");const path = require("path");const resolve = (dir) => {    return path.resolve(__dirname,{    entry: {        server: resolve("./../src/server-entry.Js"),target: "node",// 给node使用    output: {        libraryTarget: "commonjs2" // 把最终这个文件导出的结果放到module.exports上    },plugins: [        new HTMLWebpackPlugin({            filename: 'index.server.HTML',// 打包后输出的HTML文件名            template: resolve("./../public/index.server.HTML"),// 该模板文件在哪            excludeChunks: ["server"] // 排除某个模块,不让打包输出后的server.bundle.Js文件引入到index.server.HTML文件中        })    ]});
服务端webpack配置文件比较特殊,在output的时候需要配置一个libraryTarget,因为默认webpack输出的时候是将打包输出结果放到一个匿名自执行函数中的,通过将libraryTarget设置为commonjs2,就会将整个打包结果放到module.exports上;
服务端webpack打包后输出的server.bundle.Js文件不是直接引入到index.server.HTML文件中使用的,还需要经过处理渲染成HTML字符串才能插入到index.server.HTML文件中,所以打包输出后,要在HTML-webpack-plugin中排除对该模块的引用
由于webpack配置文件被分割,所以启动webapck-dev-server的时候需要指定配置文件,在package.Json文件中添加脚本
"scripts": {    "clIEnt:dev": "webpack-dev-server --config ./build/webpack.clIEnt.Js --mode development","clIEnt:build": "webpack --config ./build/webpack.clIEnt.Js --mode development","server:build": "webpack --config ./build/webpack.server.Js --mode development"  },
此时分别指向npm run clIEnt:build 和 npm run server:build即可在dist目录下生成index.HTML、clIEnt.bundle.Js,index.server.HTML、server.bundle.Js,其中clIEnt.bundel.Js被index.HTML引用,server.bundle.Js没有被index.server.HTML引入,index.server.HTML仅仅是拷贝到了dist目录下,同时server.bundle.Js的整个输出结果是挂在module.exports下的

佛山vi设计https://www.houdianzi.com/fsvi/ 豌豆资源搜索大全https://55wd.com

④ 将打包好的server.bundle.Js交给服务器进行渲染并生成HTML字符串返回给客户端,和之前初体验一样,创建一个web服务器,只不过,这次不是渲染一个简单的Vue实例,而是渲染整个打包好的server.bundle.Js

vue-server-renderer提供了两种渲染方式:

和初体验中的一样,把server.bundle.Js当作简单Vue实例进行渲染,我们打包后server.bundle.Js的内容都是挂到了module.exports上,所以我们可以直接require,require返回的结果是一个对象,该对象上只有一个属性即default,属性值为一个函数,执行该函数即可获取整个Vue项目对应的Vue实例。
// 获取server.bundle.Js中的Vue实例进行渲染const VueServerRender = require("vue-server-renderer"); // 引入服务端渲染模块const vm = require("./dist/server.bundle").default(); // 执行server.budle的default方法获取Vue实例const template = fs.readfileSync("./server.template.HTML","utf8"); // 读取基本的HTML结构const render = VueServerRender.createRenderer({    template}); // 创建渲染器并以server.template.HTML作为HTML页面的基本结构router.get("/",async ctx => {    ctx.body = await new Promise((resolve,reject) => {        render.renderToString(vm,HTML) => { // 将vm实例渲染成HTML并插入到server.template.HTML模板中            if (err) reject(err);            console.log(`${HTML}`);            resolve(HTML);        });    });});
通过vue-server-renderer提供的createBundleRenderer()方法进行渲染,该方法需要传入server.bundle.Js中的文件内容字符串,再传入模板HTML即可,所以需要读取server.bundle.Js中的内容:
// 直接渲染server.bundle.Jsconst VueServerRender = require("vue-server-renderer"); // 引入服务端渲染模块// 读取server.bundle.Js中的内容,即文件中的字符串const ServerBundle = fs.readfileSync("./dist/server.bundle.Js","utf8");const template = fs.readfileSync("./dist/index.server.HTML","utf8"); // 读取基本的HTML结构const render = VueServerRender.createBundleRenderer(ServerBundle,{ // 传入server.bundle.Js字符串创建渲染器    template});router.get("/",reject) => {        render.renderToString((err,HTML) => { // 将server.bundle.Js渲染成HTML字符串            if (err) reject(err);            resolve(HTML);        });    });});
重启服务器,再次访问,查看源码,可以看到页面已经不是一个空的基础页面了,而是真实包含HTML内容的页面,但是仍然存在一个问题,那就是之前的事件并不起作用了,因为服务器将sever.bundle.Js渲染成的是HTML字符串返回给客户端的,是不包含事件的,其中的事件执行函数在clIEnt.bundle.Js中,所以我们可以在index.server.HTML文件中通过script标签显式地引入clIEnt.bundle.Js,如:
<body>    <!--vue-ssr-outlet-->    <script src="clIEnt.bundle.Js"></script></body>

注意: 当访问页面的时候,就会向服务器请求clIEnt.bundle.Js文件,所以服务器需要将clIEnt.bundle.Js以静态资源的方式发布出去。

刚才我们是手动在index.server.HTML中通过script标签引入clIEnt.bundle.Js,非常的不方便,vue-server-renderer给我们提供了两个插件,vue-server-renderer/clIEnt-plugin和vue-server-renderer/server-plugin,可以在webpack配置文件中引入,那么打包的时候,会分别生成两个Json文件,vue-ssr-clIEnt-manifest.Json和vue-ssr-server-bundle.Json,这两个文件主要是生成客户端和服务端bundle的对应关系,这样就不需要我们收到引入clIEnt.bundle.Js了。
之前是通过读取server.bundle.Js的内容来渲染的,现在可以直接requirevue-ssr-server-bundle.Json文件即可,同时在渲染的时候再添加vue-ssr-clIEnt-manifest.Json即可,如:
// 直接渲染server.bundle.Jsconst VueServerRender = require("vue-server-renderer"); // 引入服务端渲染模块// 读取server.bundle.Js中的内容,即文件中的字符串// const ServerBundle = fs.readfileSync("./dist/server.bundle.Js","utf8");const ServerBundle = require("./dist/vue-ssr-server-bundle.Json");const clIEntManifest = require("./dist/vue-ssr-clIEnt-manifest.Json");const template = fs.readfileSync("./dist/index.server.HTML",{ // 传入server.bundle.Js字符串创建渲染器    template,clIEntManifest});
总结

以上是内存溢出为你收集整理的Vue服务端渲染全部内容,希望文章能够帮你解决Vue服务端渲染所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1028361.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存