1. 程式人生 > 其它 >ssr伺服器端渲染

ssr伺服器端渲染

技術標籤:vuevue

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">
        `
    })

可以將標籤插進去
在這裡插入圖片描述