搭建自己的SSR
搭建自己的SSR一、基本使用渲染一个Vue实例在服务端把 Vue实例渲染为纯文本字符串mkdir vue-ssrcd vue-ssrnpm init -ynpm install vue vue-server-renderer// server.jsconst vue = require('vue')const renderer = require('vue-server-renderer').cr
·
搭建自己的SSR
一、基本使用
渲染一个Vue实例
在服务端把 Vue实例渲染为纯文本字符串
mkdir vue-ssr
cd vue-ssr
npm init -y
npm install vue vue-server-renderer
// server.js
const vue = require('vue')
const renderer = require('vue-server-renderer').createRenderer()
const app = new Vue({
template: `<div id="app">
<h1>{{msg}}</h1>
</div>`,
data: {
msg: 'xxx'
}
})
renderer.renderToString(app, (err, html)=> {
if(err) throw err
console.log(html)
})
// 运行
node server.js
结合到 Web 服务中
将渲染结果发送到客户端
// 安装 web服务
npm install express nodemon --save
// 加载并启动服务 server.js
const Vue = require('vue')
const renderer = require('vue-server-renderer').createRenderer()
const express = require('express')
const server = express()
server.get('/', (req, res) => {
const app = new Vue({
template: `<div>
<h1>{{ msg }}</h1>
</div>`,
data: {
msg: 'xxx'
}
})
renderer.renderToString(app, (err, html) => {
if(err) {
return res.status(500).end('Internal Server Error.')
}
// 为html 设置 UTF-8编码
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// res.end(html)
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
${ html }
</body>
</html>
`)
})
})
server.listen('3000', () => { // 一个端口对应一个程序,总共有 65535个端口
console.log('server running at port 3000.')
})
// 使用nodemon 运行
npx nodemon server.js
使用 HTML 模板
//index.tempalte.html
<body>
<!--vue-ssr-outlet--> // html会插入到这个位置
</body>
// server.js
const fs = require('fs')
const renderer = require('vue-server-renderer').createRenderer({
template: fs.readFileSync('./index.template.html', 'utf-8')
})
// ...
res.end(html) // 此时发送的html, 是与模板结合后的
在模板中使用外部数据
// server.js 多加一个参数
renderer.renderToString(app, {
title: '使用外部数据',
meta: `<meta name="description" content="vue ssr 插入外部数据">`
},(err, html) => { // ... })
// index.tempalte.html
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{{ meta }}} // html 使用 {{{ }}}
<title>{{ title }}</title> // 字符使用 {{ }}
二、构建配置
基本思路
服务端渲染只是将实例渲染成字符串,并没有实现客户端交互的功能。
服务端入口——> 打包——>渲染——>生成HTML
客户端入口——> 打包——>接管已生成的HTML ——> 激活交互功能
源码结构
// 1. src/App.vue
<template>
<div id="app">
<h1> {{ msg }}</h1>
<p>
<input type="text" v-model="msg"/>
</p>
<button @click="onClick">点击</button>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
msg: 'xxx'
}
},
methods: {
onClick(){
console.log('lagou ~')
}
}
}
</script>
// 2. src/app.js
/**
* 通用入口文件
* 纯客户端时,负责创建实例,并挂载到DOM。SSR,责任转移到纯客户端的entry 文件
* */
import Vue from 'vue'
import App from './App.vue'
// 导出一个工厂函数,用于创建新的应用程序、router、store 实例
export default createApp(){
const app = new Vue({
render: h => h(app)
})
return { app }
}
// 3. entry-client.js
/**
* 客户端入口文件
* 负责创建应用程序,并挂载到DOM
*
* */
import { createApp } from './app'
// 客户端特定引导逻辑...
const { app } = createApp()
// 挂载到 App.vue 中的 id="app"
app.$mount('#app')
// 4. entry-server.js
/**
* 服务端入口文件
*
* */
import { createApp } from './app'
export default context => {
const { app } = createApp()
// 服务端路由处理、数据预取...
return app
}
安装依赖
// 1. 生产依赖
vue Vue.js核心库
vue-server-renderer Vue服务端渲染工具
express 基于Node的Web服务框架
cross-en 通过npm sripts 设置的跨平台环境变量
// 2. 安装开发依赖
webpack
webpack-cli
webpack-merge
webpack-node-externals // 排除webpack中的node模块
@babel/core
@babel/plugin-transform-runtime
@babel/preset-env
babel-loader
css-loader
url-loader
file-loader
rimraf // 基于Node封装的跨平台 rm -rf 工具,清除之前的dist
vue-loader
vue-template-compiler
friendly-errors-webpack-plugin // webpack错误提示
webpack 配置文件
- build 文件夹
- webpack.base.config.js 公共配置
- webpack.client.config.js 客户端打包配置文件
- webpack.server.config.js 服务端打包配置文件
配置构建命令
// package.json
"scripts": {
"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js",
"build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js",
"build": "rimraf dist && npm run build:client && npm run build:server"
},
启动应用
// server.js
// 在服务端将vue实例渲染为字符串
const Vue = require('vue')
const fs = require('fs')
const express = require('express')
// 加载打包资源
const template = fs.readFileSync('./index.template.html', 'utf-8')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle, { // 打包后的启动 createRenderer ——> createBundleRenderer, 并加载打包后的文件
template,
clientManifest
})
// 启动服务
const server = express()
// 查找静态资源,处理返回
server.use('/dist/', express.static('./dist'))
server.get('/', (req, res) => {
renderer.renderToString({ // 打包中自动创建的实例
title: '打包运行',
meta: `<meta name="description" content="vue ssr">`
},(err, html) => {
if (err) {
console.dir(err, 'err')
return res.status(500).end('Internal Server Error.')
}
// 为html 设置 UTF-8编码
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(html)
})
})
server.listen('8080', () => {
console.log('server running at port 8080.')
})
注意: 使用 【SSR + 客户端混合】时,浏览器可能会更改一些特殊的HTML结构。如 <table>会自动注入<tbody>。 为能够正确匹配,请确保在模板中写入有效的HTML。
三、构建配置开发模式
基本思路
问题:路由、数据预取、每次构建都要重启服务
开发模式下,需要不断的重新生成打包文件,重新生成 renderer 成为核心操作。
// package.json
"scripts": {
"start": "cross-env NODE_ENV=production node server.js",
"dev": "node server.js"
},
- 开发模式 ——> 监视打包构建 ——> 重新生成 Renderer 渲染器
- 开发模式等待有 renderer渲染器后,调用 render 进行渲染
提取处理模块
- 创建 ./build/setup-dev-server.js 定义 setupDevServer()
- 监视构建serverBundle——> update() ——> 创建 renderer
- 监视构建template——> update() ——> 创建 renderer
- 使用 chokidar 插件进行监视,替代 fs.watch()、fs.watchFile()
- 监视构建 clientManifest ——> update() ——> 创建 renderer
/* 此时 代码 vue-ssr 调试 没有顺利进行 */
服务端监视打包
webpack ——> watch()
把数据写入内存中
使用 webpack-dev-middleware 插件
热更新
使用 webpack-hot-middleware 插件
四、路由处理
编写通用应用注意事项
- 每个请求应该都是全新的、独立的应用程序实例,以便不会有交叉请求造成的状态污染。
- 使用工厂函数 createApp() 返回 实例
- 服务器上“预取”数据,不支持响应式数据。
- 只有 beforeCreate 和 created 会在服务器端渲染(SSR)过程张松被调用。
- 避免使用 产生全局副作用的代码,如使用 setInterval 设置 timer.
- 如果使用了像 window 或 document 这样的特定平台 API,在Node.js中执行会报错。
- axios 可以向服务器和客户端都暴露相同的API。
- 自定义指令会在SSR过程中导致错误。
- 推荐使用组件
- 在创建服务器 renderer时,使用 directives 选项所提供的“服务器端版本”
配置 Vue-Router
使用Vue-Router处理路由
将路由注册到根实例
// app.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
// 导出一个工厂函数,用于创建新的应用程序、router、store 实例
export function createApp() {
const router = createRouter()
const app = new Vue({
router,
render: h => h(App)
})
return {
app,
router
}
}
适配服务端入口
服务端server适配
适配客户端入口
管理页面Head页面
使用 vue-meta 插件
五、数据预取
服务端不支持异步获取数据,需要 preload 或 prefetch, 放到 vuex 中。
更多推荐
已为社区贡献2条内容
所有评论(0)