ssr伺服器端渲染
1 SSR
全稱:server side render(伺服器端渲染),讓我們可以在伺服器端渲染應用程式
前端渲染問題:
1 白屏時間長,影響使用者體驗
2 不利於搜尋引擎優化(SEO)
所以我們要在伺服器端渲染應用程式
伺服器端渲染:vue為伺服器端渲染提供了模板–vue-server-render
該模組提供了createRenderer方法,來建立渲染器
渲染器提供了renderToString方法,渲染應用程式元件
渲染器實現了promise規範,因此可以通過then方法監聽成功,可以通過cache方法監聽失敗
渲染模板
渲染模板:想在模板中渲染應用程式分成兩步:
第一步 在createRenderer方法中,通過template引入模板檔案
第二步 在模板檔案中,通過 定義應用程式渲染位置
前後端同步渲染
瀏覽器端渲染(前端):
1 白屏時間長,影響使用者體驗 2 不利於SEO優化
伺服器端渲染(後端):
無法繫結互動
為了解決瀏覽器端與伺服器端渲染的問題,我們要使用前後端同步渲染技術
- 1 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name= "viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 定義渲染位置 -->
<!-- 注意,不要新增空格 -->
<!--vue-ssr-outlet-->
</body>
</html>
- 2 元件檔案test.js
// 引入vue
let Vue = require( 'vue');
// 例項化
module.exports = new Vue({
template: `
<div>
<h1 @click="clickH1">hello {{msg}}</h1>
</div>
`,
data: {
msg: 'test'
},
methods: {
// 伺服器端渲染無法繫結互動,所以此點選事件不會生效
clickH1() {
console.log('click h1');
}
},
})
- 3 伺服器端
// 引入express
let express = require('express');
// 引入ejs
let ejs = require('ejs');
// 引入vue在伺服器端的渲染器
let { createRenderer } = require('vue-server-renderer');
// 引入fs
let fs = require('fs');
// 引入path
let path = require('path');
// 引入元件
let testApp = require('./test');
// 獲取根目錄
let root = process.cwd();
// 建立渲染器
let render = createRenderer({
// 同步讀取,引入模板檔案
template: fs.readFileSync(path.join(root, './views/index.html'), 'utf-8')
})
// 定義應用程式
let app = express()
// 拓展名
app.engine('.html', ejs.__express)
// 定義路由
app.get('/', (req, res) => {
// 渲染字串
render.renderToString(testApp)
.then(
// 渲染成功
data => res.end(data),
// 渲染失敗
err => console.log('fail', err)
)
})
// 監聽埠號
app.listen(3000)
此時,就將元件渲染出來了,但是沒有互動
2 前後端同步渲染
前端渲染
是為了給使用者使用,因此希望資源載入的塊(壓縮,打包,新增指紋等等)
為了更好的效果,我們要載入樣式;為了看到頁面,我們要處理模板等,。。。
但是伺服器端渲染不需要考慮這些問題,只需要一個應用程式元件,並最終將其渲染成字串
因此前後端渲染有不同的入口檔案,有不同的webpack配置
有不同的入口檔案
前端入口檔案,要讓應用程式元件上樹;後端入口檔案,只需要一個應用程式元件
不同的webpack配置
前端要考慮樣式,模板,效能優化(壓縮,打包,新增指紋等)等
後端只要將ES Module規範,編譯成CommonJS規範即可
- 目錄部署
config 儲存配置檔案
home 前端開發的專案
views 模板目錄
static 靜態資源
app.js 伺服器入口檔案
- 伺服器端配置
1 不需要模板,不用寫html外掛
2 不需要樣式,不用寫style-loader,不需要拆分樣式
3 不需要效能優化、拆分模組,新增指紋,伺服器端本地引入檔案,因此打包在一起就可以了
4 通過target:node配置,說明給node使用
5 在output.libraryTarget:commonjs2,將ES Module規範轉成commonjs規範
6 使用vue-server-renderer外掛,釋出json檔案
在引數物件中,通過filename屬性,可以自定義json檔案的釋出位置
- 伺服器端渲染
為了使用釋出的json檔案,vue-server-render提供了createBundleRenderer方法
第一個引數表示json檔案
第二個引數表示配置物件
template定義模板檔案的位置
插值語法:vue伺服器端渲染允許我們在渲染頁面的時候,向頁面中插入一些資料
在renderToString方法中傳遞資料
在模板中,通過插值語法插入資料
{{}} 渲染文字插值
{{{}}} 渲染標籤插值
- 1 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
// 渲染位置
<!--vue-ssr-outlet-->
</body>
</html>
- 2 app.vue
<template>
<div id="app">
<h1 @click="clickH1">app part -- {{msg}}-- {{$store.state.num}}</h1></div>
</template>
<script>
export default {
data() {
return {
msg: 'hello'
}
},
methods: {
clickH1() {
console.log('click h1');
}
}
}
</script>
- 入口檔案main.js
import Vue from 'vue';
// 引入應用程式
import App from './App';
// 引入store
import store from './store'
// 引入樣式
import './style.scss';
export default new Vue({
// 訂閱
store,
// 渲染應用程式
render: h => h(App)
})
- 瀏覽器端入口檔案entry-client.js
// 引入vue例項化物件,引入main.js
import app from './main';
// 上樹
app.$mount('#app');
- 伺服器端入口檔案entry-server.js
import app from './main';
伺服器端不需要上樹
// 暴露介面
export default app;
- 定義webpack命令
{
"scripts": {
"client": "webpack --config ./config/webpack.client.js",
"start:client": "webpack -w --config ./config/webpack.client.js",
"serve": "webpack --config ./config/webpack.server.js",
"start:serve": "webpack -w --config ./config/webpack.server.js",
"start": "npm run client && npm run server"
}
}
- 瀏覽器端webpack配置
// 引入path
let path = require('path')
// vue外掛
let { VueLoaderPlugin } = require('vue-loader');
// 引入html外掛
let HtmlWebpackPlugin = require('html-webpack-plugin');
// 拆分css
let MiniCssExtractPluin = require('mini-css-extract-plugin')
// 壓縮css
let OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// 獲取根目錄
const root = process.cwd();
module.exports = {
// 模式
mode: 'production',
// 解決問題
resolve: {
// 別名
alias: {
vue$: 'vue/dist/vue.js',
'@': path.join(root, './home/src'),
'@v': path.join(root, './home/src/views'),
'@c': path.join(root, './home/src/components')
},
// 副檔名
extensions: ['.js', '.vue']
},
// 入口
entry: './home/src/entry-client.js',
// 釋出
output: {
filename: 'static/[name].js',
// 釋出的相對位置位置
path: root,
// 修改進入靜態檔案的相對位置
publicPath: '/'
},
// 模組
module: {
// 載入機
rules: [
// vue
{
test: /\.vue$/,
loader: 'vue-loader'
},
// css
{
test: /\.css$/,
use: [
// 'style-loader',
// 1 在樣式檔案的style-loader後面新增loader
MiniCssExtractPluin.loader,
'css-loader'
]
},
// less
{
test: /\.less$/,
use: [
// 'style-loader',
MiniCssExtractPluin.loader,
'css-loader',
'less-loader'
]
},
// scss
{
test: /\.scss$/,
use: [
// 'style-loader',
MiniCssExtractPluin.loader,
'css-loader',
'sass-loader'
]
}
]
},
// 外掛
plugins: [
// 使用vue載入機需要引入外掛
new VueLoaderPlugin(),
// 處理模板
new HtmlWebpackPlugin({
// 模板位置
template: './home/public/index.html',
// 模板檔案釋出位置
filename: './views/index.html',
// 新增指紋
hash: true,
// 優化
防止<!--vue-ssr-outlet-->被壓縮
minify: {
collapseWhitespace: true,
// 取消移除註釋
removeComments: false,
removeRedundantAttributes:true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
}
}),
// 2 定義檔案釋出的位置
new MiniCssExtractPluin({
filename: 'static/style.css'
}),
// 壓縮css
new OptimizeCssAssetsPlugin()
],
// 拆分庫檔案
optimization: {
// 拆分庫檔案
splitChunks: {
cacheGroups: {
lib: {
name: 'lib',
chunks: 'initial',
test: /node_modules/
}
}
}
}
}
- 伺服器端webpack配置
// 引入path
let path = require('path')
// vue外掛
let { VueLoaderPlugin } = require('vue-loader');
// 引入伺服器端渲染外掛
let VueServerRendererPlugin = require('vue-server-renderer/server-plugin');
// 獲取根目錄
const root = process.cwd();
module.exports = {
// 模式
mode: 'development',
通過target:node配置,說明給node使用
// .....告訴webpack給node使用
target: 'node',
// 解決問題
resolve: {
// 別名
alias: {
vue$: 'vue/dist/vue.js',
'@': path.join(root, './home/src'),
'@v': path.join(root, './home/src/views'),
'@c': path.join(root, './home/src/components')
},
// 副檔名
extensions: ['.js', '.vue']
},
// 入口
entry: './home/src/entry-server.js',
// 釋出
output: {
filename: 'static/[name].js',
// 釋出的相對位置位置
path: root,
// 修改進入靜態檔案的相對位置
publicPath: '/',
在output.libraryTarget:commonjs2,將ES Module規範轉成commonjs規範
// ......編譯成commonjs規範
libraryTarget: 'commonjs2'
},
// 模組
module: {
// 載入機
rules: [
// vue
{
test: /\.vue$/,
loader: 'vue-loader'
},
// css
{
test: /\.css$/,
use: [
'css-loader'
]
},
// less
{
test: /\.less$/,
use: [
'css-loader',
'less-loader'
]
},
// scss
{
test: /\.scss$/,
use: [
'css-loader',
'sass-loader'
]
}
]
},
// 外掛
plugins: [
// 使用vue載入機需要引入外掛
new VueLoaderPlugin(),
// 釋出位置
將ES Module規範,編譯成CommonJS規範,輸出到bundle.json
使用vue-server-renderer外掛,釋出json檔案
在引數物件中,通過filename屬性,可以自定義json檔案的釋出位置
new VueServerRendererPlugin({
filename: '/server/bundle.json'
})
]
}
- 伺服器端app.js
// 引入express
let express = require('express');
// 引入ejs
let ejs = require('ejs');
let path = require('path');
let fs = require('fs');
const root = process.cwd();
// 引入vue
let Vue = require('vue');
// 引入vue在伺服器端的渲染器
let { createBundleRenderer } = require('vue-server-renderer');
// 建立渲染器
為了使用釋出的json檔案,vue-server-render提供了createBundleRenderer方法
let renderer = createBundleRenderer(path.join(root, './server/bundle.json'), {
// 傳遞模板
template: fs.readFileSync(path.join(root, './views/index.html'), 'utf-8')
})
// 定義應用程式
let app = express()
// 拓展名
app.engine('.html', ejs.__express)
// 靜態化
app.use('/static/', express.static(path.join(root, './static')))
// 定義路由
app.get('/', (req, res) => {
插值語法:vue伺服器端渲染允許我們在渲染頁面的時候,向頁面中插入一些資料
在renderToString方法中傳遞資料
// 渲染
renderer.renderToString()
.then(
html => res.end(html),
err => console.log(err)
)
})
// 監聽埠號
app.listen(3000)
伺服器端渲染的目的是為了SEO優化,並且讓使用者更快速的看到頁面,瀏覽器端渲染的目的是,當頁面載入之後,再次繫結互動,這就是前後端同步渲染
插值
在renderToString方法中傳遞資料
在模板中,通過插值語法插入資料
{{}} 渲染文字插值
{{{}}} 渲染標籤插值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{{seo}}}
<title>{{title}}</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
伺服器端app.js
插值語法:vue伺服器端渲染允許我們在渲染頁面的時候,向頁面中插入一些資料
在renderToString方法中傳遞資料
// renderToString的引數表示提供的資料
renderer.renderToString({
title: 'hello',
seo: `
<meta name="keywords" content="test">
<meta name="description" content="It's just a test">
`
})
可以將標籤插進去