IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue-SSR学习笔记 -> 正文阅读

[JavaScript知识库]Vue-SSR学习笔记

一、创建一个Vue实例

先创建一个服务器入口文件server.js,安装Vue提供的服务端渲染的工具npm install vue vue-server-renderer --save
如果是客户端渲染会有<div id=”app”></div>的标签,最终内容是通过JS动态渲染进去的,但对SEO不太友好,所以要用服务端渲染
先渲染一个Vue实例

const Vue = require('vue')
const app = new Vue({
    template: '<div>Hello Vue SSR!</div>'
})

然后使用vue-server-renderer的createRenderer方法创建一个渲染器,再调用renderToString把Vue实例渲染成字符串返回

const renderer = require('vue-server-renderer').createRenderer()
renderer.renderToString(app, (err, html) => {
    if(err) throw err;
    console.log(html)
})

执行server.js node server.js 可以看到渲染后的html模板结果
在这里插入图片描述

接下来就是创建一个服务器,然后把html模板结果返回
下载express框架 npm install express --save 引入并执行express

const server = require('express')()

然后处理get请求

server.get('*', (req, res) => {}

所有的get请求都经过该中间件,里面创建一个Vue实例,它含有一个url属性返回请求路径,再把它渲染到页面

const app = new Vue({
        data: {
            url: req.url
        },
        template: '<div>当前访问的url是:{{url}}</div>'
    })

然后把Vue实例用渲染器渲染到页面上,如果出错则报错,否则就返回HTML模板

renderer.renderToString(app, (err, html) => {
        if(err) {
            res.status(500).end('服务器内部错误')
        } else{
            res.end(`
            <!DOCTYPE html>
            <html lang="zh">
                <meta charset="utf-8"></meta>
                <head><title>Hello SSR</title></head>
                <body>${html}</body>
            </html>
            `)
        }
    })

服务器运行在8080端口

server.listen(8080, () => {
    console.log('服务器运行在8080端口')
})

访问8080端口,可以看到页面渲染成功
在这里插入图片描述

然后查看源代码,可以看到他是显示一个完整的HTML模板

Nuxt也是这样做的,只不过做了很多方法的封装
在这里插入图片描述

二、使用模板

单独新建一个index.template.html文件要想服务器返回的HTML模板插入到template文件,先在template.html文件添加一个固定的注释节点

<body>
    <!--vue-ssr-outlet-->
</body>

引入template文件,那么渲染器就会把模板渲染到注释节点上去

const VueServerRenderer = require('vue-server-renderer')

    const template = fs.readFileSync('./index.template.html', 'utf-8')
    const renderer = VueServerRenderer.createRenderer({
        template
    })

所以就不用手写模板字符串了,不够优雅, 直接返回渲染后的模板

renderer.renderToString(app, (err, html) => {
        if(err) {
            res.status(500).end('服务器内部错误')
        } else{
            res.end(html)
        }
    })

此外还可以传入额外内容,例如定义一个上下文对象,保存title标题和meta元信息

const context = {
        title: 'Vue SSR',
        metas: `
        <meta name="keyword" content="vue,ssr">
        <meta name="description" content="vue ssr demo">
        `
    }

在渲染器第二个参数传入context

renderer.renderToString(app, context, (err, html) => {})

然后使用插值的方式插入模板文件
在这里插入图片描述

可以看到标题成功渲染,而元信息标签被转义后渲染,所以要使用{{{}}}插值则原封不动地渲染

在这里插入图片描述
在这里插入图片描述

三、服务端渲染Vue项目的源码结构

vue-ssr官方图
在这里插入图片描述

app.js就是整个Vue程序的入口文件,客户端渲染的Vue2项目流程

import Vue from 'vue'

new Vue().$mount()

但是我们服务端Node.js是没有DOM这个概念的,只有ECMAscript,所以$mount挂载的代码应该移动到客户端里面去,所以存在两个入口:一个服务端入口(Server entry)和一个客户端入口(Client entry)
所以先定义一个App.vue组件

<template>
  <div id="app">
      <div class="demo" @click="onClick">
          一段内容
      </div>
  </div>
</template>

<script>
export default {
    methods: {
        onClick() {
            console.log('handleClick')
        }
    }
}
</script>
<style>
.demo {
    width: 300px;
    height: 300px;
    background-color: orange;
}
</style>

app.js定义一个工厂函数返回一个渲染渲染App组件的Vue实例,但先不挂载到页面

import Vue from 'vue'
import App from './App.vue'

export function createApp() {
    const app = new Vue({
        render: h => h(App)
    })
    return { app }
}

这时回到客户端入口,引入并执行工厂函数,并把App组件挂载到#app节点即可

import { createApp } from './app.js'

const { app } = createApp()
app.$mount('#app')

服务端入口一样创建Vue实例,但是导出一个函数在每次请求时执行创建并返回一个新实例防止污染。

import { createApp } from "./app";

export default () => {
    const { app } = createApp()
    return app
}

中间通过webpack打包客户端入口打包到browser,服务端的打包到Node server,server bundle作用是渲染器根据它渲染出Vue的服务端渲染结果,他是一个静态内容字符串,插入到HTML里面,返回到浏览器端,所以他没有动态交互逻辑。此时就需要客户端打包后的结果经过hydrate(激活)动态逻辑。

先配置webpack配置
webpack.base是公共配置

const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

const resolve = (dir) => {
    return path.resolve(__dirname, dir)
}
module.exports = {
    output: {
        filename: '[name].bundle.js',
        path: resolve('../dist')
    },
    resolve: {
        extensions: ['.js', '.vue']
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                },
                exclude: /node_modules/
            }, 
            {
                test: /\.css$/,
                use: ['vue-style-loader', 'css-loader']
            },
            {
                test: /\.vue$/,
                use: 'vue-loader'
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin()
    ]
}

webpack.client是客户端特有配置,比如入口不一样

const path = require('path')

const { merge } = require('webpack-merge')
const  base = require('./webpack.base')
const resolve = (dir) => {
    return path.resolve(__dirname, dir)
}
module.exports = merge(base, {
    entry: {
        client: resolve('../src/entry-client.js')
    }
})

webpack.server是服务端特有配置,它要配置和node风格匹配的相关输入和目标等

const path = require('path')

const { merge } = require('webpack-merge')
const  base = require('./webpack.base')
const resolve = (dir) => {
    return path.resolve(__dirname, dir)
}
module.exports = merge(base, {
    entry: {
        server: resolve('../src/entry-server.js')
    },
    target: 'node',
    output: {
        libraryTarget: 'commonjs2'
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.ssr.html',
            template: resolve('../public/index.ssr.html'),
            excludeChunks: ['server'],    // 与server相关的js不需要,只需要模板字符串
            minify: {
                removeComments: false     // 不希望删掉注释节点
            }
        })
    ]
})

运行npm run client:build打包得到dist目录下client.bundle.js客户端的代码
在这里插入图片描述
然后再运行npm run server:build得到服务端打包后的html文件和服务端的js代码,但server.bundle.js不会直接引入到index.ssr.html,而是在服务器中使用
在这里插入图片描述

四、客户端激活

用渲染器的createBundleRenderer方法可以把如css之类的样式插入到模板,第一个参数是打包后的server文件,然后配置打包后的html模板

const serverBundle = fs.readFileSync('./dist/server.bundle.js', 'utf-8')
const template = fs.readFileSync('./dist/index.ssr.html', 'utf-8')

const renderer = VueServerRenderer.createBundleRenderer(serverBundle, {
    template
})

再把服务端渲染的结果返回到浏览器

server.get('*', (req, res) => {
    renderer.renderToString().then(html => {
        res.send(html)
    })
})

此时可以看到html渲染完成
在这里插入图片描述
查看源代码可以看到是服务端渲染后的结果
在这里插入图片描述
但是css样式和点击事件不生效,那是因为html中没有引入相应的client打包后的js文件进行样式添加和交互逻辑的添加,所以需要进行客户端的激活。本质上是把客户端打包后的结果放在服务端生成的html文件上

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue SSR</title>
</head>
<body>
    <!--vue-ssr-outlet-->
    <script src="./client.bundle.js"></script>
</body>
</html>

运行服务器可以看到client文件已被激活,但是还是不生效
在这里插入图片描述
原因是client.bundle.js不被视作为静态资源文件,所以需要配置静态资源

server.use(express.static(path.resolve(__dirname, 'dist')))

此时js文件能够正确加载,样式生效
在这里插入图片描述
点击事件也能正常触发
在这里插入图片描述

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-24 15:11:47  更:2022-02-24 15:13:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:58:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码