vue.js - 【Vue】详解 Vue SSR 服务器端渲染 - 前端工程化之路 - SegmentFault 思否


本站和网页 https://segmentfault.com/a/1190000015964813 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

vue.js - 【Vue】详解 Vue SSR 服务器端渲染 - 前端工程化之路 - SegmentFault 思否注册登录问答专栏标签招聘活动发现✓使用“Bing”搜本站使用“Google”搜本站使用“百度”搜本站站内搜索注册登录【Vue】详解 Vue SSR 服务器端渲染TiJay9746关注作者首页专栏前端工程化之路文章详情52【Vue】详解 Vue SSR 服务器端渲染TiJay9746发布于2018-08-10  
简介
使用 Vue.js 构建客户端应用程序时,默认情况下是在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。而使用 SSR 可以将同一个组件渲染为服务器端的 HTML 字符串,然后将它们直接发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。
如何看一个网页是否是服务器端渲染?
简单的方式是在 Chrome 浏览器打开控制台/开发者工具,查看 Network 中加载的资源,如下图所示 segmentfault 网站,可以看到第一个文件总是 document 类型,这是服务器发送过来的完整的 HTML 文档,浏览器只需要加载 css/js 进行视图渲染即可。看 Vue SSR 官网也是服务器端渲染:
这里说的渲染,就是指生成 HTML 文档的过程,和之前浏览器的 CSS+HTML 渲染没有关系。简单来说,浏览器端渲染,指的是用 JS 去生成 HTML,例如 React, Vue 等前端框架做的路由。服务器端渲染,指的是用后台语言通过一些模版引擎生成 HTML,例如 Java 配合 VM 模版引擎、NodeJS配合 Jade 等,将数据与视图整理输出为完整的 HTML 文档发送给浏览器。
一、入门配置
1. 起步
新建项目,安装 Vue 与 SSR 依赖包 vue-server-renderer
$ mkdir testSSR // 新建空文件夹 vueSSR
$ cd testSSR // 进入 vueSSR 目录
$ npm init // 初始化,生成 package.json
$ npm install vue vue-server-renderer --save-dev // 安装
先使用 vue-server-renderer 渲染一个简单的 Vue 组件
$ touch test.js // 新建 test.js
// test.js
const Vue = require('vue')
const app = new Vue({ // 创建一个 Vue 实例
template: `<div>Hello World</div>`
})
const vueRenderer = require('vue-server-renderer')
const renderer = vueRenderer.createRenderer() // 创建一个 renderer
// 通过 renderToString 将 Vue 实例渲染为 HTML
// 函数签名: renderer.renderToString(vm, context?, callback?): ?Promise<string>
renderer.renderToString(app, (err, doc) => {
if (err) throw err
console.log(doc)
})
运行 test.js,输出渲染后的 HTML
注意到应用程序的根元素上添加了一个特殊的属性 data-server-rendered,这是让客户端 Vue 知道这部分 HTML 是由 Vue 在服务端渲染的。
2. 引入模板
上例只是渲染一个 vue 组件,通常应用程序都会抽象出一个或多个模板来嵌入不同的组件。
Render 的 template 选项为整个页面的 HTML 提供一个模板。此模板应包含注释 <!--vue-ssr-outlet-->,作为渲染应用程序内容的占位符。
首先创建一个 HTML 模板 index.template.html
<!--index.template.html -->
<!doctype html>
<html lang="en">
<head><title></title></head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
这里的 <!--vue-ssr-outlet--> 注释就是应用程序 HTML 标记注入的地方。
将此模板通过 fs 读取, 然后在 createRenderer( ) 时注入,修改 test.js 如下:
// test.js
//const renderer = vueRenderer.createRenderer()
const fs = require('fs')
const renderer = vueRenderer.createRenderer({
template: fs.readFileSync('./index.template.html', 'utf-8') // 同步读取文件
})
运行 test.js 可以看到之前定义的 hello world 组件已嵌入模板中。
二、服务器端整合
选取基于 node.js 的 express 作为服务器,示例 vue ssr 在服务器端的工作。
1. 启动 express server
$ cd testSSR // 进入项目
$ npm install express --save-dev // 安装 express
$ touch server.js // 新建 server.js
引入 express 并设置一个测试路由
// server.js
const express = require('express')
const server = express()
server.get('/mytest', (request, response) => {
response.send("hello world "+request.url)
})
server.listen(8000)
运行$ node server.js 后打开浏览器访问 http://localhost:8000/mytest
服务器启动成功。
2. Renderer 渲染
首先创建一个可以重复执行的工厂函数,为每个请求创建新的 Vue 实例,如果创建一个单例对象,它将在每个传入的请求之间共享,很容易导致交叉请求状态污染。
$ cd testSSR // 进入项目
$ touch app.js // 新建 app.js
// app.js
const Vue = require('vue')
module.exports = function createApp (context) {
return new Vue({
data: {
url: context.url
},
template: `<div>Vue SSR URL: {{ url }}</div>`
})
然后在 server.js 中引入 app.js 创建实例,并配置路由与请求渲染。
// server.js
const createApp = require('./app')
const vueRenderer = require('vue-server-renderer')
const renderer = vueRenderer.createRenderer()
server.get('/ssr', (request, response) => {
const context = { url: request.url }
const app = createApp(context)
renderer.renderToString(app, (err, doc) => {
if (err) throw err
response.send(doc)
})
})
运行$ node server.js 后打开浏览器访问 http://localhost:8000/ssr?sadas=2222
3. 插入模板
增加页面模板,使用之前定义的 index.template.html 作为模板,注入到一个新的 renderer
// server.js
const fs = require('fs')
const rendererTmp = vueRenderer.createRenderer({
template: fs.readFileSync('./index.template.html', 'utf-8') // 同步读取文件
})
server.get('/template', (request, response) => {
const context = { url: request.url }
const app = createApp(context)
rendererTmp.renderToString(app, (err, doc) => {
if (err) throw err
response.send(doc)
})
})
运行$ node server.js 后打开浏览器访问 http://localhost:8000/template
可以看到一个简单的服务器端渲染已经完成。
三、项目工程化
1. SSR 项目结构
通常 Vue 应用程序是由 webpack 和 vue-loader 构建,并且许多 webpack 特定功能不能直接在 Node.js 中运行(例如通过 file-loader 导入文件,通过 css-loader 导入 CSS)。
对于客户端应用程序和服务器应用程序,我们都要使用 webpack 打包 - 服务器需要「服务器 bundle」然后用于服务器端渲染(SSR),而「客户端 bundle」会发送给浏览器,用于混合静态标记。基本流程如下图。
所以一个基本的项目目录可能如下:
src
├── config
│ ├── webpack.base.config.js
│ ├── webpack.client.config.js
│ └── webpack.server.config.js
├── components
│ ├── Foo.vue
│ └── xxx.vue
├── build
│ ├── index.js
│ └── xxx.js
├── template
│ ├── index.template.html
│ └── xxx.html
├── route.js # vue-router 路由
├── App.vue # 根实例
├── app.js # 通用 entry
├── entry-client.js # 配置 仅运行于浏览器
├── entry-server.js # 配置 仅运行于服务器
├── server.js # 服务器
├── webpack.config.js
└── package.json
2. 配置路由
使用 vue-router
$ npm intall vue-router --save-dev
$ touch route.js
在新建的 router.js 中创建 router,类似于 createApp,我们也需要给每个请求一个新的 router 实例,所以文件导出一个 createRouter 函数
// router.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export function createRouter () {
return new Router({
mode: 'history',
routes: [
// ...
})
修改 app.js,添加路由
// app.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
export function createApp () {
// 创建 router 实例
const router = createRouter()
const app = new Vue({
// 注入 router 到根 Vue 实例
router,
render: h => h(App)
})
// 返回 app 和 router
return { app, router }
3. 配置 webpack
新建 entry-server.js,实现服务器端路由逻辑:
// entry-server.js
import { createApp } from './app'
export default context => {
// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
// 以便服务器能够等待所有的内容在渲染前,
// 就已经准备就绪。
return new Promise((resolve, reject) => {
const { app, router } = createApp()
// 设置服务器端 router 的位置
router.push(context.url)
// 等到 router 将可能的异步组件和钩子函数解析完
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// 匹配不到的路由,执行 reject 函数,并返回 404
if (!matchedComponents.length) {
return reject({ code: 404 })
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app)
}, reject)
})
服务器端配置 webpack.server.config.js,是用于生成传递给 createBundleRenderer 的 server bundle
// webpack.server.config.js
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(baseConfig, {
// 将 entry 指向应用程序的 server entry 文件
entry: '/path/to/entry-server.js',
// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
// 并且还会在编译 Vue 组件时,
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
target: 'node',
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
output: {
libraryTarget: 'commonjs2'
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,
// 并生成较小的 bundle 文件。
externals: nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
whitelist: /\.css$/
}),
// 这是将服务器的整个输出
// 构建为单个 JSON 文件的插件。
// 默认文件名为 `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
})
在生成 vue-ssr-server-bundle.json 之后,只需将文件路径传递给 createBundleRenderer:
// server.js
const { createBundleRenderer } = require('vue-server-renderer')
const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', {
// ……renderer 的其他选项
})
除了 server bundle 之外,我们还可以生成客户端构建清单(client build manifest)。使用客户端清单(client manifest)和服务器 bundle(server bundle),renderer 现在具有了服务器和客户端的构建信息,因此它可以自动推断和注入资源预加载 / 数据预取指令(preload / prefetch directive),以及 css 链接 / script 标签到所渲染的 HTML。
// webpack.client.config.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(baseConfig, {
entry: '/path/to/entry-client.js',
plugins: [
// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
// 以便可以在之后正确注入异步 chunk。
// 这也为你的 应用程序/vendor 代码提供了更好的缓存。
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
// 此插件在输出目录中
// 生成 `vue-ssr-client-manifest.json`。
new VueSSRClientPlugin()
})
这样就可以生成客户端构建清单(client build manifest)。
4. Bundle Renderer
到目前为止,我们假设打包的服务器端代码,将由服务器通过 require 直接使用:
const createApp = require('/path/to/built-server-bundle.js')
然而在每次编辑过应用程序源代码之后,都必须停止并重启服务。这在开发过程中会影响开发效率。此外,Node.js 本身不支持 source map。
vue-server-renderer 提供一个名为 createBundleRenderer 的 API,用于处理此问题,通过使用 webpack 的自定义插件,server bundle 将生成为可传递到 bundle renderer 的特殊 JSON 文件。
// server.js
const { createBundleRenderer } = require('vue-server-renderer')
const template = require('fs').readFileSync('/path/to/template.html', 'utf-8')
const serverBundle = require('/path/to/vue-ssr-server-bundle.json')
const clientManifest = require('/path/to/vue-ssr-client-manifest.json')
const renderer = createBundleRenderer(serverBundle, {
runInNewContext: false, // 推荐
template, // (可选)页面模板
clientManifest // (可选)客户端构建 manifest
})
// 在服务器处理函数中……
server.get('/', (req, res) => {
const context = { url: req.url }
// 这里无需传入一个应用程序,因为在执行 bundle 时已经自动创建过。
// 现在我们的服务器与应用程序已经解耦!
renderer.renderToString(context, (err, html) => {
// 处理异常……
res.end(html)
})
})
四、其他
此外,vue SSR 提供 css 管理、缓存管理、流式渲染等,期待以后继续整理。Vue SSR 指南:https://ssr.vuejs.org/zh/API 参考:https://ssr.vuejs.org/zh/api/
vue.jsssr阅读 27.7k更新于 2018-08-27 赞52收藏38分享本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议前端工程化之路只要投入时间,一切都很简单。关注专栏TiJayIn me, past, present, future meet...974 声望115 粉丝关注作者0 条评论得票最新提交评论评论支持部分 Markdown 语法:**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用 @ 来通知其他用户。推荐阅读【Webpack】一些配置优化与解决方案开始 官网是最好的学习资料,本篇文章略过入门配置这些内容,整理了一些常用的配置点。 在 webpack 打包过程查询的依赖关系: ES2015 import 语句 CommonJS require() 语句 AMD define 和 require 语句 css/sass/l...TiJay赞 8阅读 3.1k一个被忽略的前端细分领域大家好,我卡颂。回想下你学新技术的主要途径是什么?看书?看技术文档?看博文?看视频?归纳起来,无外乎文字、视频两种形式。从纸媒时代到互联网时代,再到移动互联网时代,虽然信息的载体发生变化,但信息的...卡颂赞 18阅读 1.5kVue2.0 + ElementUI 手写权限管理系统后台模板(一)——简述挤一下: 一开始以为没有多少人用就没建群,但是加我的人太多了,好多问题都是重复的,所以建个群大家互相沟通交流方便点,但是建的有点晚,错过了好多人所以群里人有点少,QQ群: 157216616酒一壶_茶一盏赞 15阅读 17.8kvue中style scope深度访问新方式(:deep())1、>>>如果vue的style使用的是css,那么则 {代码...} 但是像scss等预处理器却无法解析>>>,所以我们使用下面的方式.2、/deep/ {代码...} 但是有些开发者反应,在vue-cli3编译时,deep的方式会...寒水寺一禅赞 11阅读 34.2k评论 9给我实现一个前端的 Excel 导入和导出功能前言【负责人 A】:现在报表部分基于接口的 Excel 的导入和导出功能有点慢,前端这边能不能实现一下这个功能,然后我们在比对看看效果!【切图仔 B】: 接口这边不能优化一下吗?比如排查下慢的原因什么的。【负...熊的猫赞 16阅读 2.1k一个开源vue网站博客,nuxt开源网站,前后端分离项目开媛笔记,基于nuxt ssr首屏服务器端渲染 。用于分享、记录、交流和学习,希望可以帮助到小伙伴们。同时网站在不断更新,创造属于猿(媛)的世界 -$Bao Yalong ..Let's Go! [链接]jigsaw赞 16阅读 8.2k评论 32022 你还不会微前端吗 (上) — 从巨石应用到微应用微前端系列分为 上/下 两篇,本文为 上篇 主要还是了解微前端的由来、概念、作用等,以及基于已有的微前端框架进行实践,并了解微前端的核心功能所在,而在下篇 2022 你还不会微前端吗 (下) — 揭秘微前端核心原理...熊的猫赞 14阅读 1.3kTiJayIn me, past, present, future meet...974 声望115 粉丝关注作者宣传栏文章目录跟随▲5238产品热门问答热门专栏热门课程最新活动翻译酷工作课程Java 开发课程PHP 开发课程Python 开发课程前端开发课程移动开发课程资源每周精选用户排行榜帮助中心建议反馈合作关于我们广告投放职位发布讲师招募联系我们合作伙伴关注产品技术日志社区运营日志市场运营日志团队日志社区访谈条款服务协议隐私政策下载 AppCopyright © 2011-2022 SegmentFault. 当前呈现版本 22.12.19浙ICP备15005796号-2浙公网安备33010602002000号ICP 经营许可 浙B2-20201554杭州堆栈科技有限公司版权所有