初學(xué)ssr入坑 初學(xué)vue服務(wù)端渲染疑惑非常多,我們大部分前端都是半路出家,上手都是前后端分離,對(duì)服務(wù)端并不了解,不說java、php語言了,連node服務(wù)都還沒搞明..."/>
溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue ssr服務(wù)端渲染(小白解惑)

發(fā)布時(shí)間:2020-09-29 17:56:10 來源:腳本之家 閱讀:169 作者:liubingyang 欄目:web開發(fā)

>初學(xué)ssr入坑

初學(xué)vue服務(wù)端渲染疑惑非常多,我們大部分前端都是半路出家,上手都是前后端分離,對(duì)服務(wù)端并不了解,不說java、php語言了,連node服務(wù)都還沒搞明白,理解服務(wù)端渲染還是有些困難的;

網(wǎng)上有非常多的vue服務(wù)渲染的入門案例,但看了很久,很多,還是一頭霧水,搞不明白這些文件和關(guān)鍵字的聯(lián)系和意思:

  • server.js
  • entrt-client.js
  • server-js
  • built-server-bundle.js
  • vue-ssr-server-bundle.json
  • vue-ssrclientmanifest.json
  • createBundleRenderer
  • clientManifest

這篇內(nèi)容會(huì)按照 基礎(chǔ)服務(wù)端渲染--vue實(shí)例渲染--加入vueRouter--加入vueX的順序入坑,后續(xù)應(yīng)該還有--開發(fā)模式--seo優(yōu)化--部分渲染,這里先不挖那么多坑了;

>基礎(chǔ)服務(wù)端渲染

顧名思義,得啟個(gè)服務(wù):(建個(gè)新項(xiàng)目,不要用vue-cli)

vue ssr服務(wù)端渲染(小白解惑)

//server.js
const express = require('express');
const chalk = require('chalk');//加個(gè)chalk就是console好看點(diǎn)。。

const server = express();

server.get('*', (req, res) => {
res.set('content-type', "text/html");
res.end(`
<!DOCTYPE html>
<html lang="en">
 <head><title>Hello</title></head>
 <body>你好</body>
</html>
`)
})

server.listen(8080,function(){
let ip = getIPAdress();
console.log(`服務(wù)器開在:http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//node下的os模塊可以拿到啟動(dòng)該文件的服務(wù)端的部分信息
var interfaces = require('os').networkInterfaces();
for (var devName in interfaces) {
 var iface = interfaces[devName];
 for (var i = 0; i < iface.length; i++) {
  var alias = iface[i];
  if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
   return alias.address;
  }
 }
}
}

啟動(dòng) node server.js

vue ssr服務(wù)端渲染(小白解惑)

再看頁面 正常,這就是最基礎(chǔ)的服務(wù)端渲染

vue ssr服務(wù)端渲染(小白解惑)

其實(shí)就是一個(gè)get請(qǐng)求,返回一個(gè)字符串,瀏覽器默認(rèn)展示返回結(jié)果;

然而對(duì)于這個(gè)字符串的解析還不明確,什么意思,比如:

vue ssr服務(wù)端渲染(小白解惑)

去掉這句話,頁面就成了這樣,原因不深究,自己百度

vue ssr服務(wù)端渲染(小白解惑)

>加入vue實(shí)例

跳過官網(wǎng)說的built-server-bundle.js應(yīng)用,意思就是不用管這個(gè)文件了,只是一個(gè)過渡文件,項(xiàng)目中也不會(huì)用到。直接使用createBundleRenderer方法,直接用vue-ssr-server-bundle.json;

看下現(xiàn)在的目錄結(jié)構(gòu):

vue ssr服務(wù)端渲染(小白解惑)

新增了5個(gè)文件;有關(guān)客戶端的配置entry-client.js不是必須的,這里先不管;

app.js是用來創(chuàng)建vue實(shí)例的;

entry-server.js是用來創(chuàng)建生成vue-ssr-server-bundle.json(需要用到app.js)所需的配置配件;是給webpack.server.config.js用的;

webpack.server.config.js是用來生成vue-ssr-server-bundle.json的;

vue-ssr-server-bundle.json是給server.js中的createBundleRenderer用的。

//app.js 
import Vue from 'vue'
import Vue from './App.vue'//這里一定要寫上.vue,不然會(huì)匹配到app.js,require不區(qū)分大小寫0.0
export default createApp=function(){
return new Vue({
 render:h => h(App)
})
}

一個(gè)createApp生成一個(gè)vue實(shí)例;

//App.vue
<template>
<div id='app'>
 這是個(gè)app
</div>
</template>
<script>
export default {}
</script>

還沒用到<router-view>

//weback-base.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
output:{
 path:path.resolve(__dirname,'./dist'),
 filename:'build.js',
},
module: {
 rules: [
  {
   test:/\.js$/,
   use: {
    loader: 'babel-loader',
    options: {
     presets: ['@babel/preset-env']
    }
   },
   exclude:[/node_modules/,/assets/]
  },
  {
   test:/\.vue$/,
   use:['vue-loader']
  }
 ]
},
resolve: {
 alias:{
  '@':path.resolve(__dirname,'../')
 },
 extensions:['.js','.vue','.json']
},
plugins:[
 new VueLoaderPlugin()
]
}

有關(guān)webpack配置不啰嗦

//webpack.server.config.js用來生成vue-ssr-server-bundle.json
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

module.exports = merge(baseConfig, {
 entry: './entry-server.js',

 // 這允許 webpack 以 Node 適用方式(Node-appropriate fashion)處理動(dòng)態(tài)導(dǎo)入(dynamic import),
 // 并且還會(huì)在編譯 Vue 組件時(shí),
 // 告知 `vue-loader` 輸送面向服務(wù)器代碼(server-oriented code)。
 target: 'node',

 // 對(duì) bundle renderer 提供 source map 支持
 devtool: 'source-map',

 // 此處告知 server bundle 使用 Node 風(fēng)格導(dǎo)出模塊(Node-style exports)
 output: {
 libraryTarget: 'commonjs2'
 },


 // 這是將服務(wù)器的整個(gè)輸出
 // 構(gòu)建為單個(gè) JSON 文件的插件。
 // 默認(rèn)文件名為 `vue-ssr-server-bundle.json`
 plugins: [
 new VueSSRServerPlugin()
 ]
})

這個(gè)配置哪都能找到,重點(diǎn)是VueSSRServerPlugin這個(gè)插件,生成vue-ssr-server-bundle.json全靠它,去掉的話生成的是built-server-bundle.js;關(guān)于merge插件,libraryTarget,target配置問題自己百度webpack去0.0;

//entry-server.js
import { createApp } from './src/app'

export default context => {
 return createApp()
}

固定寫法,返回一個(gè)函數(shù)供createBundleRenderer使用;

生成vue-ssr-server-bundle.json

到目前為止安裝的插件有:

vue ssr服務(wù)端渲染(小白解惑)

自己手動(dòng)一個(gè)一個(gè)裝就行了。

生成vue-ssr-server-bundle.json,使用webpack命令

vue ssr服務(wù)端渲染(小白解惑)

一切都手動(dòng),熟悉webpack;

vue ssr服務(wù)端渲染(小白解惑)

修改server.js

const express = require('express');
const chalk = require('chalk');

const server = express();
const serverBundle = require('./dist/vue-ssr-server-bundle.json')//**新增**//
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
 runInNewContext: false, // 看名字也知道是生成某個(gè)新的Context對(duì)象,默認(rèn)是true,改成false理解為某種緩存機(jī)制,提高服務(wù)器效率
 template: require('fs').readFileSync('./index.html', 'utf-8'),
 })//**新增**//
server.get('*', (req, res) => {
 //res.set('content-type', "text/html");
 //res.end(`
 //<!DOCTYPE html>
 //<html lang="en">
 // <head><title>Hello</title></head>
 // <body >
 // <div style='color:red'>你好</div>
 // </body>
 // </html>
 //改成下面這樣
 const context = {//這里的參數(shù)現(xiàn)在還沒用,但這個(gè)對(duì)象還是得用,要做renderToString的參數(shù)
 url:req.url
 }
 renderer.renderToString(context, (err, html) => {
  if (err) {
  res.status(500).end('Internal Server Error')
  return
  } else {
  res.end(html)
  }
 })
 `)
 })

server.listen(8080,function(){
 let ip = getIPAdress();
 console.log(`服務(wù)器開在:http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//node下的os模塊可以拿到啟動(dòng)該文件的服務(wù)端的部分信息,細(xì)節(jié)自己去node上面查
 var interfaces = require('os').networkInterfaces();
 for (var devName in interfaces) {
  var iface = interfaces[devName];
  for (var i = 0; i < iface.length; i++) {
   var alias = iface[i];
   if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
    return alias.address;
   }
  }
 }
}

試一蛤:node server.js

vue ssr服務(wù)端渲染(小白解惑)

正常,箭頭指的地方官網(wǎng)有解釋。別忘了inde.html中加入一行注釋:

vue ssr服務(wù)端渲染(小白解惑)

后續(xù)修改title,meta頭部都是通過類似的注釋方式,原理就是正則匹配替換字符串-。-;

>加入路由vue-router

新增幾個(gè)文件

vue ssr服務(wù)端渲染(小白解惑)

需要修改的文件有:

App.vue//加個(gè)router-view就行

vue ssr服務(wù)端渲染(小白解惑)

//app.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
export function createApp(){
 const app = new Vue({
  router,
  render:h => h(App)
 })
 return {app,router}
}

把a(bǔ)pp實(shí)例和router都拋出去,給entry-server.js用

// entry-server.js
import { createApp } from './src/app'

export default context => {
 //這里用promise的原因有很多,其中有一個(gè)就是下面這個(gè)onReady方法是異步的。createBundleRenderer支持promise
 return new Promise((resolve, reject) => {
 const { app, router } = createApp()

 router.push(context.url)

 router.onReady(() => {//onReady方法還有g(shù)etMatchedComponents方法還是需要了解一下
  const matchedComponents = router.getMatchedComponents()
  if (!matchedComponents.length) {
  return reject({ code: 404 })
  }
  resolve(app)
 }, reject)
 })
}

最后看一下router.js

//router.js
 import Vue from 'vue'
 import VueRouter from 'vue-router'
//頁面要先聲明后使用,不要問為什么
import home from './pages/home'
import store from './pages/store'

Vue.use(VueRouter)
export default new VueRouter({
 mode: 'history',
 routes:[
  {path:'/',name:'home',component:home},
  {path:'/store',name:'store',component:store},
 ]
})

再看一下兩個(gè)頁面的代碼;

 //store.vue 
 <template>
 <div>this is store</div>
 </template>
 <script>
   export default {}
 </script>

改的差不多了,試一哈:

重新打個(gè)包webpack --config webpack.server.js

啟動(dòng)node server

vue ssr服務(wù)端渲染(小白解惑)

>entry-client.js是干啥的

到目前為止還沒用到entry-client.js叫客戶端配置,不著急使用,先做個(gè)測(cè)試,寫點(diǎn)邏輯試試:
修改下store.vue

//store.vue
<template>
<div @click='run'>{{msg}}</div>
</template>
<script>
 export default {
  data(){
   msg:'this is store'
  },
  created(){
   this.msg = 'this is created'
  },
  mounted(){
   this.msg = 'this is mounted'
  },
  methods: {
   run(){
    alert('this is methods')
   }
  }
 }
</script>

看這個(gè)樣子頁面最終展示的結(jié)果應(yīng)該是this is mounted,然而結(jié)果是這樣的:

vue ssr服務(wù)端渲染(小白解惑)

很好解釋,服務(wù)端對(duì)于鉤子函數(shù)的理解也是很正確的,created會(huì)在頁面返回之前執(zhí)行,而mounted是在vue實(shí)例成型之后執(zhí)行,就是頁面渲染后,這個(gè)是要在客戶端才會(huì)執(zhí)行,可是為什么頁面出來了沒有執(zhí)行mounted,而且run的點(diǎn)擊事件沒有生效;

看看頁面:

vue ssr服務(wù)端渲染(小白解惑)

一個(gè)js文件都沒加載,怎么執(zhí)行邏輯,就是個(gè)靜態(tài)頁面0.0;

這時(shí)候entry-client.js就出場(chǎng)了

vue ssr服務(wù)端渲染(小白解惑)

新增兩個(gè)文件

//entry-client.js 
import { createApp } from './src/app.js';

const { app } = createApp();

app.$mount('#app');

基本配置;

//webpack.client.config.js

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: './entry-client.js',
 optimization:{
 runtimeChunk:true
 },
 plugins: [
 // 此插件在輸出目錄中
 // 生成 `vue-ssr-client-manifest.json`。
 new VueSSRClientPlugin(),
 ]
})

這個(gè)地方重點(diǎn)除了VueSSRClientPlugin生成vue-ssr-client-manifest.json外,optimization是webpack4產(chǎn)物,用來分離生成共公chunk,配置還算復(fù)雜,可以看下這里webpack4 optimization總結(jié)

修改下server.js

//server.js

 const express = require('express');
 const chalk = require('chalk');

 const server = express();

 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,{
  runInNewContext: false, // 推薦
  template: require('fs').readFileSync('./index.html', 'utf-8'),
  clientManifest // //新增
  })
 server.get('*', (req, res) => {
  res.set('content-type', "text/html");
  const context = {
   url:req.url
   }

   renderer.renderToString(context, (err, html) => {
    if (err) {
    res.status(500).end('Internal Server Error')
    return
    } else {
    res.end(html)
    }
   })

  })

 server.listen(8080,function(){
  let ip = getIPAdress();
  console.log(`服務(wù)器開在:http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
 })

 function getIPAdress(){//node下的os模塊可以拿到啟動(dòng)該文件的服務(wù)端的部分信息,細(xì)節(jié)自己去node上面查
  var interfaces = require('os').networkInterfaces();
  for (var devName in interfaces) {
   var iface = interfaces[devName];
   for (var i = 0; i < iface.length; i++) {
    var alias = iface[i];
    if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
     return alias.address;
    }
   }
  }
 }

打包下:webpack --config webpack.client.config.js

vue ssr服務(wù)端渲染(小白解惑)

node server 一下,看看頁面

vue ssr服務(wù)端渲染(小白解惑)

js有了,可是為什么還不行,不能點(diǎn)0.0;

看看。奧報(bào)錯(cuò)了

vue ssr服務(wù)端渲染(小白解惑)

讀取不到靜態(tài)文件;

修改server.js加個(gè)靜態(tài)文件托管:

vue ssr服務(wù)端渲染(小白解惑)

再看看

vue ssr服務(wù)端渲染(小白解惑)

事件也有了,頁面沒變化,console一下,發(fā)現(xiàn)值其實(shí)已經(jīng)變了,只是失去了響應(yīng)式;這就是為什么要用vuex的緣故;

>加入vuex

開始想在頁面中用this.$set方法,然而行不通,而且不可能給每個(gè)值都重新寫一個(gè)這個(gè)方法;

vue ssr服務(wù)端渲染(小白解惑)

加個(gè)sotre.js

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

 export default new Vuex.Store({
 state: {
  msg: ''
 },
 actions: {
  setMsg ({ commit }, val) {
   commit('setMsg', val)
  }
 },
 mutations: {
  setMsg (state, val) {
  Vue.set(state, 'msg', val)//關(guān)鍵
  }
 }
 })

很基礎(chǔ)的邏輯,關(guān)鍵在Vue.set這個(gè)方法,重新增加了響應(yīng)式;
修改下app.js

//app.js
 import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'//加個(gè)store就行了
export function createApp(){
 const app = new Vue({
  router,
  store,
  render:h => h(App)
 })
 return {app,router}
}

store.vue改成這樣

<template>
 <div @click='run'>{{msg}}</div>
</template>
<script>
 export default {
  data(){},
  created(){
   this.$store.dispatch('setMsg','this is created')
  },
  computed:{
   msg(){
    return this.$store.state.msg;
   }
  },
  mounted(){
   this.$store.dispatch('setMsg','this is mounted')
  },
  methods: {
   run(){
    alert('this is methods')
   }
  }
 }
</script>

重新打個(gè)包,想一下,修改頁面的話只需要重新打包c(diǎn)lient,如果修改了app.js兩個(gè)就要都重新打包了;

node server 一下

vue ssr服務(wù)端渲染(小白解惑)

這回總算完成了;

>總結(jié)

服務(wù)端渲染東西還是挺多的,涉及領(lǐng)域也非常廣,比如vue,webpack,node,它們的生態(tài)圈都大的可怕,需要學(xué)習(xí)東西非常多,
坑又多,又大,又深,后面還有很多問題要解決:

異步數(shù)據(jù)加載;//html返回前先渲染一部分接口拿到的數(shù)據(jù)
怎么做seo優(yōu)化;//做服務(wù)端渲染的重要原因,處理異步數(shù)據(jù)加載問題也是為了這個(gè)
緩存怎么加;
開發(fā)環(huán)境搭建;//你并不希望每改一行代碼就重新手動(dòng)打個(gè)包,重啟下服務(wù)吧0.0
還有怎么實(shí)現(xiàn)部分頁面ssr;//一個(gè)項(xiàng)目不可能所有頁面都服務(wù)端渲染,太耗性能,服務(wù)器壓力大呀;

還有很多疑惑:

比如為什么會(huì)失去響應(yīng)式,webpack到底該怎么配置。。

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI