Vue.js 前端开发实战之 09-服务器端渲染
AI 摘要
初识服务器端渲染
客户端渲染概述
客户端渲染,即传统的单页面应用(SPA)模式,Vue.js 构建的应用程序默认情况下是一个 HTML 模板页面,只有一个 id 为 app 的 div 根容器,然后通过 webpack 打包生成 css、js 等资源文件,浏览器加载、解析来渲染 HTML。
在客户端渲染时,一般使用的是 webpack-dev-server 插件,它可以帮助用户自动开启一个服务器端,主要作用是监控代码并打包,也可以配合 webpack-hot-middleware 来进行热更替(HMR),这样能提高开发效率。
服务器端渲染概述
服务器端渲染,就是将页面或者组件通过服务器生成 HTML 字符串,将它们直接发送到浏览器,最后将静态标记“混合”为客户端上完全交互的应用程序。
Vue 进行服务器端渲染时,需要利用 Node.js 搭建一个服务器,并添加服务器端渲染的代码逻辑。
服务器端渲染的优点:
- 利于 SEO。
- 首屏渲染速度快。
- ......
服务器端渲染的缺点:
- 服务器端压力增加。
- 涉及构建设置和部署的要求比较严格。
- ......
服务器端渲染的注意事项:
- Vue 2.3.0+ 版本的服务器端渲染(SSR),要求 vue-server-renderer(服务端渲染插件)的版本要与 Vue 版本相匹配。
- 在使用 Vue-Router 路由插件时,路由模式只能是 history 模式。
服务器端渲染的简单实现
创建 vue-ssr 项目
在项目存储目录下,使用命令行工具创建一个 vue-ssr 项目。
示例:创建 vue-ssr 项目
终端执行:
# 创建文件夹
mkdir vue-ssr
# 切换目录
cd vue-ssr
# 初始化项目
npm init -y
# 安装Vue和服务器端渲染的插件vue-server-renderer
npm install vue@2.7.16 vue-server-renderer@2.7.16示例效果:
渲染 Vue 实例
vue-server-renderer 安装完成后,创建服务器脚本文件 test.js,实现将 Vue 实例的渲染结果输出到控制台中。
示例:渲染 Vue 实例
服务器脚本文件(test.js):
// 1、require导入Vue
const Vue = require('vue')
// 2、创建一个Vue实例
const vm = new Vue({
template: '<div>SSR的简单使用</div>'
})
// 3、导入vue-server-renderer,并创建一个renderer实例
const renderer = require('vue-server-renderer').createRenderer()
// 4、将Vue实例渲染为HTML
// 参数1:要渲染的Vue实例对象
// 参数2:回调箭头函数
// err:渲染过程中的错误信息
// html:渲染后的HTML代码
renderer.renderToString(vm, (err, html) => {
if (err) {
throw err
}
console.log(html)
})终端执行:
node test.js示例效果:
Express 搭建 SSR
Express 是一个基于 Node.js 平台的 Web 应用开发框架,用来快速开发 Web 应用。
示例:Express 搭建 SSR
终端执行:
npm install express页面模板(template.html):
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
下面的注释不能删除也不能修改
这个注释是HTML占位符
-->
<!--vue-ssr-outlet-->
</body>
</html>Express 配置文件(express.js):
// 1、require导入Vue和Express
const Vue = require('vue')
const express = require('express')
// 2、创建Express服务器
const server=express()
// 3、借助fs文件读取工具读取页面模板
const renderer = require('vue-server-renderer').createRenderer({
template: require('fs').readFileSync('./template.html', 'utf-8')
})
// 4、处理 GET 请求
// *:所有请求都会进入该方法
// req:请求对象
// res:响应对象
server.get('*', (req, res) => {
res.set({'Content-Type': 'text/html; charset=utf-8'})
// 5、创建一个Vue实例
const vm = new Vue({
data: {
title: '当前位置',
url: req.url
},
template: '<div>{{title}}:{{url}}</div>',
})
// 6、将Vue实例渲染为HTML后输出
renderer.renderToString(vm, (err, html) => {
// end():结束响应并返回结果
if (err) {
res.status(500).end('err: ' + err)
return
}
res.end(html)
})
})
// 7、为Express监听8080端口
server.listen(8080, function () {
console.log('server started at localhost:8080')
})终端执行:
node express.js示例效果:
Koa 搭建 SSR
Koa 是一个基于 Node.js 平台的 Web 开发框架,致力于成为 Web 应用和 API 开发领域更富有表现力的技术框架。
Koa 能帮助开发者快速地编写服务器端应用程序,通过 async 函数很好地处理异步的逻辑,有力地增强错误处理。
示例:Koa 搭建 SSR
终端执行:
npm install koaKoa 配置文件(koa.js):
// 1、require导入Vue和Koa
const Vue = require('vue')
const Koa = require('koa')
// 2、创建Koa实例
const server = new Koa()
// 3、借助fs文件读取工具读取页面模板
const renderer = require('vue-server-renderer').createRenderer({
template: require('fs').readFileSync('./template.html', 'utf-8')
})
// 4、添加一个中间件来处理所有请求
// async:异步函数
// ctx:上下文对象
// next:将要处理的下一个函数
server.use(async (ctx, next) => {
// 5、创建一个 Vue 实例
const vm = new Vue({
data: {
title: '当前位置',
url: ctx.url
},
template: '<div>{{title}}:{{url}}</div>'
})
// 6、将Vue实例渲染为HTML后输出
renderer.renderToString(vm, (err, html) => {
if (err) {
ctx.res.status(500).end('err: ' + err)
return
}
ctx.body = html
})
})
// 7、为Koa监听8081端口
server.listen(8081, function () {
console.log('server started at localhost:8081')
})终端执行:
node koa.js示例效果:
webpack 搭建服务器端渲染
基本流程
webpack 服务器端渲染需要使用 entry-server.js 和 entry-client.js 两个入口文件,两者通过打包生成两份 bundle 文件。其中,通过 entry-server.js 打包的代码是运行在服务器端,而通过 entry-client.js 打包的代码运行在客户端。
webpack 服务器端渲染的流程:
项目搭建
使用 webpack 可以实现服务器端渲染。
使用 webpack 可以实现服务器端渲染
进入 Vue-CLI GUI,创建 Vue-CLI 项目,并添加 vue-router、koa、koa-static、vue-server-renderer、cross-env、lodash.merge、webpack-node-externals 依赖:
路由配置文件(src/router/index.js):
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 将路由改造成工厂
// 确保每个请求时路由对象都是单例
export function createRouter() {
const routes = [
{
path: '/',
name: 'home',
component: () => import('../views/HomeView.vue')
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
const router = new VueRouter({
// 只能使用history模式
mode: 'history',
routes
})
return router
}项目入口配置文件(src/main.js):
import Vue from 'vue'
import App from './App.vue'
import {createRouter} from './router'
Vue.config.productionTip = false
export function createApp() {
const router = createRouter()
const app = new Vue({
router,
render: h => h(App)
})
return {app, router}
}服务端入口文件(src/entry-server.js):
import { createApp } from './main'
export default context => {
return new Promise((resolve, reject) => {
// 创建Vue实例和VueRouter路由对象
const { app, router } = createApp()
// 路由跳转
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
if (!matchedComponents.length) {
return reject(new Error('no components matched'))
}
resolve(app)
}, reject) })
}客户端入口文件(src/entry-client.js):
import {createApp} from "./main.js";
let {app,router} = createApp();
router.onReady(() => {
app.$mount("#app");
})Vue 项目配置文件(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");
// 根据传入环境变量决定入口文件和相应配置项
// 分别生成server/client的文件
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";
module.exports = {
configureWebpack: () => ({
entry: `./src/entry-${target}.js`, // 将entry指向应用程序的server/client文件
devtool: 'source-map',
target: TARGET_NODE ? "node" : "web",
node: TARGET_NODE ? undefined : false,
output: {
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
externals: TARGET_NODE
? nodeExternals({
allowlist: [/.css$/]
})
: undefined,
optimization: {
splitChunks: 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
});
});
if (TARGET_NODE) {
config.plugins.delete("hmr");
}
}
};npm 配置文件(package.json):
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
"build:ssr": "npm run build:server & npm run build:client"
},运行 build:ssr:
页面模板(template.html):
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>Koa 配置文件(koa.js):
const Koa = require("koa")
const path = require("path")
const koaStatic = require('koa-static')
const server = new Koa()
const resolve = file => path.resolve(__dirname, file)
// 开放dist文件夹
server.use(koaStatic(resolve('./dist')))
const { createBundleRenderer } = require("vue-server-renderer")
const bundle = require("./dist/vue-ssr-server-bundle.json")
const clientManifest = require("./dist/vue-ssr-client-manifest.json")
// 渲染页面
const renderer = createBundleRenderer(bundle, {
runInNewContext: false,
template: require('fs').readFileSync(resolve("./template.html"), "utf-8"),
clientManifest: clientManifest
});
function renderToString(context) {
return new Promise((resolve, reject) => {
renderer.renderToString(context, (err, html) => {
err ? reject(err) : resolve(html)
})
})
}
// 添加一个中间件来处理所有请求
server.use(async (ctx, next) => {
const context = {
title: "ssr test",
url: ctx.url
}
ctx.body = await renderToString(context)
})
server.listen(8081, function () {
console.log('server started at localhost:8081')
})终端执行:
node koa.js示例效果:
Nuxt.js 服务器端渲染框架
创建 Nuxt.js 项目
Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。
Nuxt.js 提供了利用 vue.js 开发服务端渲染的应用所需要的各种配置,为了快速入门,Nuxt.js 团队创建了脚手架工具 create-nuxt-app,可以使用脚手架工具快速创建 Nuxt.js 项目。
示例:创建 Nuxt.js 项目
终端执行:
# 全局安装create-nuxt-app脚手架
npm install create-nuxt-app -g
# 创建Nuxt.js项目
npx create-nuxt-app 项目名称示例效果:
终端执行:
# 切换目录
cd nuxtapp
# 终端执行
npm run dev示例效果:
页面和路由
在项目中,pages 目录用来存放应用的路由及视图。
- 当直接访问根路径“/”的时候,默认打开的是 index.vue 文件。
- Nuxt.js 会根据目录结构自动生成对应的路由配置,将请求路径和 pages 目录下的文件名映射。比如访问“/test”就表示访问 test.vue 文件,如果文件不存在,就会提示 This page could not be found 错误。
- pages 目录下的 vue 文件也可以放在子目录中,在访问的时候也要加上子目录的路径。
页面跳转
Nuxt.js 中使用 nuxt-link 组件来完成页面中路由的跳转,类似于 Vue 中的路由组件 router-link,它们具有相同的属性,并且使用方式也相同。
Nuxt.js 中,还可以使用编程式路由来完成页面中路由的跳转,与 VueRouter 的使用方式一致。