日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

vue服务端渲染实践

發(fā)布時間:2024/8/26 综合教程 25 生活家
生活随笔 收集整理的這篇文章主要介紹了 vue服务端渲染实践 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

首先什么是ssr?不是玩游戲抽的ssr卡牌,而是server side render 服務(wù)端渲染

什么是客戶端渲染?就是在瀏覽器渲染dom結(jié)構(gòu)和數(shù)據(jù)

什么是服務(wù)端渲染?就是在服務(wù)端把dom結(jié)構(gòu)渲染好,把想要展示的數(shù)據(jù)都插入想展示的地方,將資源一次性梭哈給瀏覽器

下面用兩個圖說明一下傳統(tǒng)的vue spa客戶端渲染和vue服務(wù)端渲染的區(qū)別

spa:

spa的優(yōu)點(diǎn):

開發(fā)效率高

服務(wù)端壓力小

spa的缺點(diǎn):

seo效果差(因?yàn)閐om結(jié)構(gòu)里的文字,圖片都是vue異步渲染出來的,首屏加載并沒有這些東西,搜索引擎的爬蟲往往根據(jù)文字內(nèi)容,圖片的title等信息抓取頁面信息)

首屏加載速度慢(因?yàn)榉?wù)器首次只給前端返回了一個index.html,里面只包含vue的根組件例如#app,頁面的內(nèi)容加載之前還需要去加載vue.js等其他的資源,然后通過異步ajax請求得到頁面數(shù)據(jù),再通過vue的數(shù)據(jù)更新機(jī)制重新渲染頁面)

ssr:

ssr的優(yōu)點(diǎn):

首屏渲染速度快

seo比較友好

ssr的缺點(diǎn):

開發(fā)體驗(yàn)不如spa,需要借助nodejs

服務(wù)端壓力大

目前vue常用的ssr模式有兩種開發(fā)方式,第一種是使用一些ssr框架,例如nuxt.js,第二種是在服務(wù)端單獨(dú)用vue實(shí)現(xiàn)ssr,nuxt的開發(fā)大家可以參照nuxt的官網(wǎng)https://www.nuxtjs.cn/

這里面給大家介紹一下不使用框架,直接使用nodejs+vue實(shí)現(xiàn)ssr

敲黑板!!正文開始

首先用一個小例子實(shí)現(xiàn)vue的ssr

1在本地使用vue-cli新建工程 ,這里使用的vue-cli3,對腳手架不了解的同學(xué)需要去官網(wǎng)了解一下

vue create ssr-app

2在根目錄下新建server文件夾,創(chuàng)建一個01-vue-ssr-demo.js文件,在里面編寫node代碼

在這之前需要安裝下面幾個文件

npm install vue-server-renderer -s    //服務(wù)端創(chuàng)建dom節(jié)點(diǎn)用
npm install vue-router -s                  //路由
npm install express -s                      //express框架
npm install nodemon -s                    //熱更新node服務(wù) 啟動項(xiàng)目時使用nodemon 代替 node

在01-vue-ssr-demo.js中插入下面代碼

// 創(chuàng)建一個express實(shí)例
const express = require('express')

const app = express()

// 導(dǎo)入vue
const Vue = require('vue')

// 創(chuàng)建渲染器
const { createRenderer } = require('vue-server-renderer')

const renderer = createRenderer()

// 導(dǎo)入路由
const Router = require('vue-router')
Vue.use(Router)


// 路由:問題2:由express在管理
app.get('*', async (req, res) => {
  // 創(chuàng)建一個路由器實(shí)例
  const router = new Router({
    routes: [
      { path: '/', component: { template: '<div>Index</div>' } },
      { path: '/detail', component: { template: '<div>detail</div>' } },
    ]
  })


  // 構(gòu)建渲染頁面內(nèi)容
  // 問題1:沒辦法交互
  // 問題3:同構(gòu)開發(fā)問題
  const vm = new Vue({
    router,
    data() {
      return {
        name: 'ssr-simple-demo'
      }
    },
    template: `
    <div>
      <router-link to="/">index</router-link>
      <router-link to="/detail">detail</router-link>
      <div>{{name}}</div>
      <router-view></router-view>
    </div>
    `
  })

  try {
    // 路由跳轉(zhuǎn)
    router.push(req.url)

    // 渲染: 得到html字符串
    const html = await renderer.renderToString(vm)
    // 發(fā)送回前端
    res.send(html)
  } catch (error) {
    res.status(500).send('服務(wù)器內(nèi)部錯誤')
  }

})

// 監(jiān)聽端口
app.listen(3000)

使用nodemon命令運(yùn)行服務(wù)

nodemon server/01-vue-ssr-demo.js

在瀏覽器輸入localhost:3000打開,可以看到下面的頁面

并且index detail點(diǎn)擊之后可以切換內(nèi)容

上面我們實(shí)現(xiàn)了一個簡單的vue ssr應(yīng)用,但是,如果你在dom上通過vue命令@click綁定了事件,在頁面上點(diǎn)擊是不會觸發(fā)的,原因就是后臺返回到前端的所有都是字符串,在前端接收到的也是一段普普通通的html代碼,在頁面上選擇查看網(wǎng)頁源代碼可以看到如下內(nèi)容

并沒有事件的綁定,點(diǎn)擊之后更不會觸發(fā)事件,那么我們怎么能讓返回到前端的頁面做一次激活?下面給大家講vue ssr的折中方案:原理是node服務(wù)根據(jù)用戶請求的地址,給用戶返回對應(yīng)路由的首屏的資源,之后用戶的一切操作都交由vue去管理,在前端執(zhí)行掛載,初始化,這個過程我們一般叫做zhushui。通過zhushui之后,我們的頁面就可以像正常的vue頁面一樣執(zhí)行點(diǎn)擊事件了。

代碼結(jié)構(gòu)如下:

標(biāo)注顏色的就是核心代碼部分,我們要對之前的代碼做一些修改

具體代碼如下:

1 router.js-管理路由的邏輯

和原來路由不同的地方是這里采用的工廠模式,每一次請求都返回一個router實(shí)例,后面要說的new Vue和Vuex的創(chuàng)建都要用這種返回實(shí)例的方法。

原因是現(xiàn)在我們編寫的代碼是在服務(wù)端,每一個人請求的地址都不一樣,如果同時有3個人請求了三個頁面,但是我只創(chuàng)建一個router對象返回的話肯定有2個人接收到的router是錯誤的,所以這里面用了工廠模式返回了一個路由實(shí)例,保證每次請求得到的router是不受污染的

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

// 工廠函數(shù),每次請求返回一個Router實(shí)例
export function createRouter() {
  return new Router({
    mode: 'history',
    routes: [
      {
        path: '/',
        name: 'home',
        component: Home
      },
      {
        path: '/about',
        name: 'about',
        // route level code-splitting
        // this generates a separate chunk (about.[hash].js) for this route
        // which is lazy-loaded when the route is visited.
        component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
      }
    ]
  })
}

2 store.js - 全局狀態(tài)管理,和router.js一樣,這里每次請求都會返回一個vuex的實(shí)例,原因同上

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 工廠函數(shù)
export function createStore() {
  return new Vuex.Store({
    state: {
      count: 108
    },
    mutations: {
      add(state) {
        state.count += 1;
      },
      // 加一個初始化
      init(state, count) {
        state.count = count;
      },
    },
    actions: {
      // 加一個異步請求count的action
      getCount({ commit }) {
        return new Promise(resolve => {
          setTimeout(() => {
            commit("init", Math.random() * 100);
            resolve();
          }, 1000);
        });
      },
    },
  })
}

3 main.js - 創(chuàng)建router實(shí)例/vuex實(shí)例/vue實(shí)例的方法 (并不是在這里直接就調(diào)用了,后面會在入口文件調(diào)用這里的方法創(chuàng)建實(shí)例)

import Vue from "vue";
import App from "./App.vue";
import { createRouter } from './router'
import { createStore } from "./store";

Vue.config.productionTip = false;

Vue.mixin({
  beforeMount() {
    const { asyncData } = this.$options;
    if (asyncData) {
      // 將獲取數(shù)據(jù)操作分配給 promise
      // 以便在組件中,我們可以在數(shù)據(jù)準(zhǔn)備就緒后
      // 通過運(yùn)行 `this.dataPromise.then(...)` 來執(zhí)行其他任務(wù)
      this.dataPromise = asyncData({
        store: this.$store,
        route: this.$route,
      });
    }
  },
});

// 需要每個請求返回一個Vue實(shí)例
export function createApp(context) {
  const router = createRouter()
  const store = createStore()
  const app = new Vue({
    router,
    store,
    context, // 用于和外面renderer交互
    render: h => h(App)
  })

  return {app,router,store}
}

4 entry-server.js -服務(wù)端入口文件,作用:創(chuàng)建vue實(shí)例,創(chuàng)建router實(shí)例,創(chuàng)建store實(shí)例,返回 vue實(shí)例 (app),將來和后端渲染器 vue renderer打交道

import { createApp } from "./main"
// 首屏渲染
// 將來和渲染器打交道
// 創(chuàng)建vue實(shí)例
export default context => {
  const {app, router, store} = createApp(context)
  
  return new Promise((resolve, reject) => {
    // 跳轉(zhuǎn)首屏地址去
    router.push(context.url)
    
    // 等待路由就緒
    router.onReady(() => {
      // 判斷是否存在asyncData選項(xiàng)
      // 獲取匹配路由相關(guān)組件
      const comps = router.getMatchedComponents()
      // 遍歷它們,并執(zhí)行可能存在的asyncData
      Promise.all(comps.map(comp => {
        if (comp.asyncData) {
          return comp.asyncData({
            store,
            route: router.currentRoute
          })
        }
      })).then(() => {
        // 數(shù)據(jù)已經(jīng)存入store,只需要序列化它,傳到前端在復(fù)原
        // 設(shè)置到上下文中的state,renderer將來會轉(zhuǎn)換它
        context.state = store.state
        // 返回實(shí)例
        resolve(app)
      })
      .catch(reject)
    }, reject)
    
  })
}

5 entry-client.js - 客戶端入口文件 作用:通過app.$mount 激活頁面vue

import { createApp } from "./main";

// 激活
const { app, router, store } = createApp()

// 還原store.state
// renderer會把它放到window.__INITIAL_STATE__
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
  app.$mount('#app')
})

6 index.html 宿主文件的修改 - public/index.html ,注意宿主元素注釋不要加空格,這個是固定的寫法。

<!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">
    <!-- 1.刪掉之前動態(tài)title -->
    <title>vue-study</title>
  </head>
  <body>
    <!-- 2.把宿主元素變成一個注釋 -->
    <!--vue-ssr-outlet-->
  </body>
</html>

7 app.vue和添加兩個測試的頁面vue文件

app.vue:

<template>
  <div id="app">
    <p>{{$store.state.count}}</p>
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </nav>

    <router-view></router-view>
  </div>
</template>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

view/About.vue

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>

view/Home.vue

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'home',
  components: {
    HelloWorld
  },
  asyncData({ store, route }) { // 約定預(yù)取邏輯編寫在預(yù)取鉤子asyncData中
    // 觸發(fā) action 后,返回 Promise 以便確定請求結(jié)果
    return store.dispatch("getCount");
  }
}
</script>

8 ssr.js服務(wù)端的啟動文件 server/ssr.js 服務(wù)端代碼

// 創(chuàng)建一個express實(shí)例
const express = require('express')

const app = express()

// 獲取絕對地址
const resolve = dir => require('path').resolve(__dirname, dir)

// 靜態(tài)文件服務(wù)
// 開發(fā)dist/client目錄,關(guān)閉默認(rèn)的index頁面打開功能
app.use(express.static(resolve('../dist/client'), {index: false}))

// 創(chuàng)建渲染器
const { createBundleRenderer } = require('vue-server-renderer')
// 參數(shù)1:服務(wù)端bundle
const bundle = resolve('../dist/server/vue-ssr-server-bundle.json')
const renderer = createBundleRenderer(bundle, {
  runInNewContext: false, // https://ssr.vuejs.org/zh/api/#runinnewcontext
  template: require('fs').readFileSync(resolve("../public/index.html"), "utf-8"), // 宿主文件
  clientManifest: require(resolve("../dist/client/vue-ssr-client-manifest.json")) // 客戶端清單
})


// 只做一個件事,渲染
app.get('*', async (req, res) => {

  try {
    const context = {
      url: req.url
    }
    // 渲染: 得到html字符串
    const html = await renderer.renderToString(context)
    // 發(fā)送回前端
    res.send(html)
  } catch (error) {
    res.status(500).send('服務(wù)器內(nèi)部錯誤')
  }

})

// 監(jiān)聽端口
app.listen(3000)

9 webpack的配置

首先需要安裝webpack插件

npm install webpack-node-externals lodash.merge -D

根目錄新增vue.confg.js 代碼如下

// 兩個插件分別負(fù)責(zé)打包客戶端和服務(wù)端
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");

// 根據(jù)傳入環(huán)境變量決定入口文件和相應(yīng)配置項(xiàng)
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";

module.exports = {
  css: {
    extract: false
  },
  outputDir: './dist/'+target,
  configureWebpack: () => ({
    // 將 entry 指向應(yīng)用程序的 server / client 文件
    entry: `./src/entry-${target}.js`,
    // 對 bundle renderer 提供 source map 支持
    devtool: 'source-map',
    // target設(shè)置為node使webpack以Node適用的方式處理動態(tài)導(dǎo)入,
    // 并且還會在編譯Vue組件時告知`vue-loader`輸出面向服務(wù)器代碼。
    target: TARGET_NODE ? "node" : "web",
    // 是否模擬node全局變量
    node: TARGET_NODE ? undefined : false,
    output: {
      // 此處使用Node風(fēng)格導(dǎo)出模塊
      libraryTarget: TARGET_NODE ? "commonjs2" : undefined
    },
    // https://webpack.js.org/configuration/externals/#function
    // https://github.com/liady/webpack-node-externals
    // 外置化應(yīng)用程序依賴模塊。可以使服務(wù)器構(gòu)建速度更快,并生成較小的打包文件。
    externals: TARGET_NODE
      ? nodeExternals({
          // 不要外置化webpack需要處理的依賴模塊。
          // 可以在這里添加更多的文件類型。例如,未處理 *.vue 原始文件,
          // 還應(yīng)該將修改`global`(例如polyfill)的依賴模塊列入白名單
          whitelist: [/.css$/]
        })
      : undefined,
    optimization: {
      splitChunks: undefined
    },
    // 這是將服務(wù)器的整個輸出構(gòu)建為單個 JSON 文件的插件。
    // 服務(wù)端默認(rèn)文件名為 `vue-ssr-server-bundle.json`
    // 客戶端默認(rèn)文件名為 `vue-ssr-client-manifest.json`。
    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  }),
  chainWebpack: config => {
    // cli4項(xiàng)目添加
    if (TARGET_NODE) {
        config.optimization.delete('splitChunks')
    }
      
    config.module
      .rule("vue")
      .use("vue-loader")
      .tap(options => {
        merge(options, {
          optimizeSSR: false
        });
      });
  }
};

10 在package 里增加打包命令,打包的時候使用npm run build,會自動執(zhí)行build:client和build:server

安裝依賴

npm i cross-env -D

代碼:

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "npm run build:server & npm run build:client",
    "build:client": "vue-cli-service build",
    "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build"
  },

打包完之后會在dist下面生成client文件夾和server文件夾

client下面的vue-ssr-client-manifest.json的作用是:描述了一些客戶端的信息,all數(shù)組里面的是將來要返回給前端要加載的一些資源

server下面的vue-ssr-server-bundle.json的作用是:將來'vue-server-renderer'會以這個json為工作目錄去創(chuàng)建dom

注意,因?yàn)槭欠?wù)端渲染的,每一次修改都需要執(zhí)行build命令以更新代碼

編寫完上面的代碼,執(zhí)行node命令起服務(wù) nodemon/server/ssr.js

然后在瀏覽器打開localhost:3000,會看到如下頁面,右面加載的資源就是vue-ssr-client-manifest.json 里面的all的文件

回顧整個編碼過程我們發(fā)現(xiàn)我們實(shí)際編寫vue代碼的部分并沒有改變什么,和spa開發(fā)有著一樣的 同構(gòu)體驗(yàn),主要的改變有下面幾點(diǎn):

1 vue-router/vuex/vue實(shí)例的創(chuàng)建都是使用的工廠函數(shù),每一次請求服務(wù)端都會創(chuàng)建一個實(shí)例,防止數(shù)據(jù)污染

2 增加了兩個入口文件 entry-client.js/entry-server.js

3 增加了webpack的配置,產(chǎn)出dist/server 和dist/client相關(guān)的文件,供renderer插件使用

4 增加了服務(wù)端代碼-核心內(nèi)容就是使用renderer函數(shù)渲染dom,并返回給客戶端,其中包含激活前端頁面的js代碼

最后總結(jié)一下對服務(wù)端渲染的理解以及使用場景

服務(wù)端渲染相比于spa應(yīng)用,主要為我們提供了2點(diǎn)優(yōu)勢,一個是快速的首屏加載,一個是seo引擎搜索

一般來說,對于首屏加載速度要求較高的場景是移動端的頁面,特別是hybrid混合開發(fā)的應(yīng)用,在app里嵌入webview的方式,如果首屏加載時間過長的話會出現(xiàn)一個白屏,會讓用戶從比較流暢的原生頁面切換到了一個白屏頁面,如果是弱網(wǎng)情況白屏?xí)r間過長的話極大的降低了用戶體驗(yàn);另一種是微信公眾號/企業(yè)微信的第三方應(yīng)用開發(fā),點(diǎn)擊微信里的鏈接跳轉(zhuǎn)到我們自己服務(wù)器的url的過程中,除了我們自己的請求還有很多微信重定向等需要耗時的操作,白屏的時間更加的長。我們無法通過前端代碼控制客戶端加載頁面的白屏期間的操作,也就是無法加載骨架屏或者加上loading提高用戶體驗(yàn)。因此移動端的hybrid開發(fā)和微信開發(fā)在技術(shù)選型的時候最好使用ssr的開發(fā)模式。

除了移動端之外,一些大型官網(wǎng)開發(fā)要求首屏渲染速度和搜索引擎seo抓取的時候也需要使用服務(wù)端渲染,搜索引擎爬蟲爬取網(wǎng)站并排名的最重要因素是網(wǎng)站首屏的加載速度,其次是里面的關(guān)鍵字,圖片的title,meta里的keysords等,而ssr模式剛好解決了這兩個問題。對于一些對首屏速度沒有要求的網(wǎng)站,且數(shù)據(jù)交互比較多的網(wǎng)站,例如后臺管理系統(tǒng),就完全不需要ssr的開發(fā)模式。

當(dāng)然,采用ssr模式的架構(gòu)開發(fā)也有缺點(diǎn),一是增加了代碼的復(fù)雜度,新手開發(fā)起來成本還是比較高的。二是在服務(wù)端渲染增加了服務(wù)端的壓力,如果服務(wù)器的配置比較低,而用戶數(shù)量很大的時候,服務(wù)器的cpu很容易滿載,如果公司有增加服務(wù)器的預(yù)算可以采用負(fù)載均衡,或者采用node server / nginx做一些緩存,如果登錄用戶有效的話將緩存的頁面直接返回給前端而不做服務(wù)端渲染。如果開發(fā)周期比較充裕,也可以做一些兼容性的判斷,用node監(jiān)聽當(dāng)前cpu的使用量,如果達(dá)到自己設(shè)定的閾值,那么就不用服務(wù)端渲染,返回給用戶spa的應(yīng)用,如果cpu占用的比較少,正常返回服務(wù)端渲染的頁面。

上面的例子只是給大家提供vue ssr開發(fā)的模式的一個思路,如果公司新開發(fā)的項(xiàng)目,如果需要采用ssr,還是建議大家采用nuxt.js。

總結(jié)

以上是生活随笔為你收集整理的vue服务端渲染实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。