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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

Vue 服务端渲染原理 拆分成三步个步骤简单的实现一个案例

發布時間:2023/12/20 vue 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vue 服务端渲染原理 拆分成三步个步骤简单的实现一个案例 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言


可能我們平常接觸比較多的是使用 vue + vue全家桶來搭建起一個單頁(SPA)應用。用 服務端渲染 搭建項目比較少,本文是記錄我在學習 服務端渲染 過程中的一些見解,如有出錯或疏漏,麻煩幫忙指正!文章共分為三個步驟來實現搭建一個簡單 服務端渲染 項目:
  • 1.搭建 SPA 項目(實現客戶端渲染)
  • 2.簡單實現服務端渲染(不包含 vue-router 和 vuex)
  • 3.實現服務端渲染增加 vue-router 和 vuex

首先按慣例來,分析 客戶端渲染(SPA) 和 服務端渲染的區別

  • 使用服務端渲染,內容到達時間更快。無需等待所有的 JavaScript都完成下載并執行,所以用戶將會更快速地看到完整渲染的頁面,通??梢援a生更好的用戶體驗。
  • 使用服務端渲染有更好的 SEO,由于搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面。如果你的應用程序初始展示 loading 菊花圖,然后通過 Ajax 獲取內容,抓取工具并不會等待異步完成后再行抓取頁面內容。也就是說,如果 SEO 對你的站點至關重要,而你的頁面又是異步獲取內容,則你可能需要服務器端渲染解決此問題。

如果只是少些頁面需要 服務端渲染 來實現SEO,或許你可以了解下 prerender-spa-plugin,使用 預渲染 來實現。


另外 vue 官網還提供了 nuxt 框架,可以開箱即用,進行 srr 項目開發。


接下來,一步步來獨立配置一個 服務端渲染 項目。

正文


一、第一步,實現客戶端渲染

第一步我們先配置一個常用的 SPA 應用,也就是在客戶端實現渲染。使用的是 webpack + vue ,這個大家應該比較熟悉:


目錄結構:


package.json:

{"name": "demo01","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","start": "webpack-dev-server --config config/webpack.config.js --port 3000","build": "webpack --config config/webpack.config.js"},"keywords": [],"author": "","license": "ISC","dependencies": {"vue": "^2.6.10"},"devDependencies": {"@babel/core": "^7.4.5","autoprefixer": "^9.6.0","babel-loader": "^8.0.6","@babel/preset-env": "^7.4.5","clean-webpack-plugin": "^3.0.0","css-loader": "^3.0.0","file-loader": "^4.0.0","html-webpack-plugin": "^3.2.0","postcss-loader": "^3.0.0","url-loader": "^2.0.0","vue-loader": "^15.7.0","vue-style-loader": "^4.1.2","vue-template-compiler": "^2.6.10","webpack": "^4.34.0","webpack-cli": "^3.3.4","webpack-dev-server": "^3.7.1"} }

webpack配置:(/config/webpack.config.js)

var path = require('path') var VueLoaderPlugin = require('vue-loader/lib/plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') var CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPluginmodule.exports = {mode: 'development',entry: path.resolve(__dirname, '../src/app.js'),output: {path: path.resolve(__dirname, '../dist')},module: {rules: [{test: /\.js$/,use: 'babel-loader',exclude: /node_modules/},{test: /\.css$/,use: ['vue-style-loader', 'css-loader', 'postcss-loader']},{test: /\.(jpg|jpeg|png|gif|svg)$/,use: {loader: 'url-loader',options: {limit: 10000 // 10Kb}}},{test: /\.vue$/,use: 'vue-loader'}]},plugins: [new CleanWebpackPlugin(),new VueLoaderPlugin(),new HtmlWebpackPlugin({template: path.resolve(__dirname, '../src/index.html')})] }

.babelrc配置:

{"presets": ["@babel/preset-env"] }

postcss.config.js配置:

module.exports = {plugins: [require('autoprefixer'),] }

app.js:

import Vue from 'vue' import App from './App.vue'new Vue({el: '#app',render: h => h(App) })

App.vue:

<template><section><p>vue ssr案例第一步 - 客戶端渲染</p><home /><list /></section> </template><script> import home from './components/Home.vue' import list from './components/list.vue'export default {name: 'App',components: {home,list} } </script>

index.html:

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>客戶端渲染 - vue ssr案例第一步</title> </head> <body><div id="app"></div> </body> </html>

/src/components/List.vue:

<template><section class="list">list --- list --- list</section> </template><style> .list {background-color:darksalmon;margin: 20px;padding: 20px; } </style>

/src/components/Home.vue:

<template><section class="home">home --- home --- homr 123321</section> </template><style>.home {background-color: aquamarine;margin: 20px;padding: 20px;} </style>

以上就是一個簡單的SPA項目。但運行 npm run build 時可以對項目進行一個打包,生成如下文件(可投放于生產):


也可以借助 webpack-dev-server 來運行我們的項目,執行 npm run start ,然后在瀏覽器打開 http://localhost:3000:


查看網頁原代碼可以發現,home組件和list組件的內容并不存在,因為除了index.html的內容外,其他內容都是由js在客戶端渲染出來的,所以網頁原代碼里看不到這些由js渲染出來的內容,爬蟲也是找不到這些內容(爬蟲不會等到頁面中的js執行完在抓取數據)。

案例源碼

二、第二步,簡單實現服務端渲染(不包含 vue-router 和 vuex)

第二步,我們來實現一個簡單服務端渲染,首先分析下思路,那肯定要拿出官網提供原理圖了,如下:

從圖中可以看到,webpack會從兩個入口來進行打包處理,其中通過 Client entry 入口進行客戶端的打包,從 Server entry 入口進行服務端打包。


Server entry 打包的文件會在 Node Server (也就是服務端)運行,通過 Bundle Renderer 渲染成了 Html,然后把 HTML 丟給瀏覽器,瀏覽器根據得到的 HTML 渲染出頁面。


到瀏覽器端時,此時瀏覽器已經拿到服務端渲染出來的 HTML ,通過 Client entry 打包出來的 Client Bundle 是用來在瀏覽器執行(就是 客戶端激活 ),用以vue在瀏覽器端的激活,這樣,在瀏覽器端才能正常執行vue的生命周期以及指令等。

那接下來進行項目的改造。
目錄結構:




1.增加客戶端編譯入口文件(entry-client.js),用以創建實例,并且掛載。

import { createApp } from './app.js';const { app } = createApp();app.$mount('#app');
2.增加服務端編譯入口文件(entry-server.js)。
在客戶端,每個用戶訪問應用都會產生一個新的實例,每個實例都是獨立的,有自己的數據。但是在服務端,我們的應用是一直處于開啟的狀態,如果在全局聲明一個實例,實例會一直存在于內存中,這樣會照成 狀態污染 (cross-request state pollution),當有其他用戶來訪問時,聲明的實例并不是全新的,而是從內存中獲取,從而使得實例中數據不是初始化狀態。
所以這里我們返回一個函數,使得每次有用戶訪問的時候都在服務端重新生成一個實例,這樣每個用戶訪問應用才不會照成數據污染。 import { createApp } from './app.js';export default context => {return new Promise((resolve, reject) => {const { app } = createApp();resolve(app);}); }

3.修改app.js。同樣也需要返回一個函數,這樣每次調用才能產生一個全新的實例。

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

4.將webpack的配置分成三部分:公用配置(webpack.base.config.js)、服務端配置(webpack.server.config.js)、客戶端配置(webpack.client.config.js)

// webpack.base.config.jsvar path = require('path') var VueLoaderPlugin = require('vue-loader/lib/plugin')module.exports = {mode: 'development',output: {path: path.resolve(__dirname, '../dist'),filename: '[name].bundle.js'},resolve: {extensions: ['.js', '.vue']},module: {rules: [{test: /\.js$/,use: 'babel-loader',exclude: /node_modules/},{test: /\.css$/,use: ['vue-style-loader', 'css-loader', 'postcss-loader']},{test: /\.(jpg|jpeg|png|gif|svg)$/,use: {loader: 'url-loader',options: {limit: 10000 // 10Kb}}},{test: /\.vue$/,use: 'vue-loader'}]},plugins: [new VueLoaderPlugin()] } // webpack.client.config.jsconst path = require('path'); const merge = require('webpack-merge'); const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin var HtmlWebpackPlugin = require('html-webpack-plugin') const base = require('./webpack.base.config');module.exports = merge(base, {entry: {client: path.resolve(__dirname, '../src/entry-client.js')},plugins: [new CleanWebpackPlugin(),// 客戶端激活new HtmlWebpackPlugin({template: path.resolve(__dirname, '../src/index.template.html'),filename: 'index.template.html'})] }) // webpack.server.config.jsconst path = require('path'); const merge = require('webpack-merge'); const base = require('./webpack.base.config');module.exports = merge(base, {// 這允許 webpack 以 Node 適用方式處理動態導入(dynamic import),// 并且還會在編譯 Vue 組件時,告知 `vue-loader` 輸送面向服務器代碼。target: 'node',// 對 bundle renderer 提供 source map 支持devtool: 'source-map',entry: {server: path.resolve(__dirname, '../src/entry-server.js')},// 使用 Node 風格導出模塊(Node-style exports)output: {libraryTarget: 'commonjs2'} })

5.增加服務配置文件 /bin/www.js ,使用koa來搭建一個服務。

const Koa = require('koa'); const Router = require('koa-router'); const static = require('koa-static'); const path = require('path'); const fs = require('fs'); const app = new Koa() const router = new Router() const createBundleRenderer = require('vue-server-renderer').createBundleRenderer// 服務端執行vue操作 const bundle = fs.readFileSync(path.resolve(__dirname, '../dist/server.bundle.js'), 'utf-8'); // 客戶端激活 const template = fs.readFileSync(path.resolve(__dirname, '../dist/index.template.html'), 'utf-8') const renderer = createBundleRenderer(bundle, {template })// 資源文件 app.use(static(path.resolve(__dirname, '../dist')));router.get('/', (ctx, next) => {// 服務端渲染結果轉換成字符串renderer.renderToString((err, html) => {if (err) {console.error(err);ctx.status = 500;ctx.body = '服務器內部錯誤';} else {ctx.status = 200;ctx.body = html; // 將html字符串傳到瀏覽器渲染}}); });// 開啟路由 app.use(router.routes()).use(router.allowedMethods());// 應用監聽端口 app.listen(3002, () => {console.log('服務器端渲染地址: http://localhost:3002'); });

6.其他文件的代碼也貼出來

// App.js<template><section id="app"><p>服務端渲染(不含 vue-router 和 vuex) - vue ssr案例第二步</p><home /><list /></section> </template><script> import home from './components/Home.vue' import list from './components/list.vue'export default {name: 'App',components: {home,list} } </script> // index.template.html<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>服務端渲染(不含 vue-router 和 vuex) - vue ssr案例第二步</title> </head> <body><!--vue-ssr-outlet--> </body> </html> // Home.vue<template><section class="home">home --- home --- homr 123321</section> </template><style>.home {background-color: aquamarine;margin: 20px;padding: 20px;} </style> // List.vue<template><section class="list">list --- list --- list</section> </template><style> .list {background-color:darksalmon;margin: 20px;padding: 20px; } </style>

npm run build,打包后產生如下文件:


在瀏覽器中打開 http://localhost:3002


查看網頁原代碼,可以發現之前沒有出現的內容(home組件和list組件的內容)都出現了,因為我們的應用已經在服務端渲染了之后才丟到瀏覽器解析的。

案例源碼

三、第三步,實現服務端渲染增加 vue-router 和 vuex

目錄如下:


首先來優化下打包,將我們原本打包出來的 server.bundle.js文件換成json文件,這樣做有以下幾個有點:

  • 內置的 source map 支持(在 webpack 配置中使用 devtool: ‘source-map’)

  • 在開發環境甚至部署過程中熱重載(通過讀取更新后的 bundle,然后重新創建 renderer 實例)

  • 關鍵 CSS(critical CSS) 注入(在使用 *.vue 文件時):自動內聯在渲染過程中用到的組件所需的CSS。更多細節請查看 CSS 章節。

  • 使用 clientManifest 進行資源注入:自動推斷出最佳的預加載(preload)和預取(prefetch)指令,以及初始渲染所需的代碼分割 chunk。
    1.修改webpack.client.config.js

const path = require('path'); const merge = require('webpack-merge'); const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin var HtmlWebpackPlugin = require('html-webpack-plugin') const VueSSRClientPlugin = require('vue-server-renderer/client-plugin'); const base = require('./webpack.base.config');module.exports = merge(base, {entry: {client: path.resolve(__dirname, '../src/entry-client.js')},plugins: [new CleanWebpackPlugin(),new VueSSRClientPlugin(), // 打包成 vue-ssr-client-manifest.jsonnew HtmlWebpackPlugin({template: path.resolve(__dirname, '../src/index.template.html'),filename: 'index.template.html'})] })

2.修改webpack.server.config.js

const path = require('path'); const merge = require('webpack-merge'); const nodeExternals = require('webpack-node-externals') const VueSSRServerPlugin = require('vue-server-renderer/server-plugin'); const base = require('./webpack.base.config');module.exports = merge(base, {// 這允許 webpack 以 Node 適用方式(Node-appropriate fashion)處理動態導入(dynamic import),// 并且還會在編譯 Vue 組件時,// 告知 `vue-loader` 輸送面向服務器代碼(server-oriented code)。target: 'node',// 對 bundle renderer 提供 source map 支持devtool: 'source-map',// 因為是服務端引用模塊,所以不需要打包node_modules中的依賴,直接在代碼中require引用就好,生成較小的 bundle 文件。externals: [nodeExternals({// 不要外置化 webpack 需要處理的依賴模塊。// 你可以在這里添加更多的文件類型。例如,未處理 *.vue 原始文件,// 你還應該將修改 `global`(例如 polyfill)的依賴模塊列入白名單whitelist: /\.css$/})],entry: {server: path.resolve(__dirname, '../src/entry-server.js')},// 使用 Node 風格導出模塊(Node-style exports)output: {libraryTarget: 'commonjs2'},plugins: [new VueSSRServerPlugin(), // // 打包成 vue-ssr-server-bundle.json] })

3.新增 /router/index.js。同樣的作為一個函數引出,避免在服務器上運行時產生數據交叉污染。

import Vue from 'vue' import Router from 'vue-router' import Home from '../components/Home.vue' import List from '../components/List.vue'Vue.use(Router)function createRouter () {const routes = [{path: '/',component: Home},{path: '/list',component: List}]const router = new Router({mode: 'history',routes})return router }export default createRouter

4.修改app.js。在createApp時帶上router

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

5.修改 entry-server.js 。這時需要對路由進行匹配,我們會從服務端獲得當前用戶輸入的 url 作為 context 參數傳進來,然后通過 router.push(context.url) 進行路由跳轉,再通過匹配是否能找到該組件來返回對應的狀態。

import { createApp } from './app.js';export default context => {return new Promise((resolve, reject) => {const { app, router } = createApp();// 根據匹配到的路徑進行路由跳轉router.push(context.url);// 在router.onReady的成功回調中,找尋與url所匹配到的組件router.onReady(() => {// 查找所匹配到的組件const matchedComponents = router.getMatchedComponents()// 未找到組件if (matchedComponents.length <= 0) {return reject({state: 404,msg: '未找到頁面'})}// 成功并返回實例resolve(app)}, reject)}); }

6.修改www.js文件。router通過 ‘*’ 來獲取所有的請求攔截,并將 ctx.url 獲取到的用戶當前輸入的url作為 renderToString 的參數傳,上面第5小步的 'context’也就是這里 renderToString 的一個個參數。

const Koa = require('koa'); const Router = require('koa-router'); const static = require('koa-static'); const path = require('path'); const fs = require('fs'); const app = new Koa() const router = new Router() const favicon = require('koa-favicon') const createBundleRenderer = require('vue-server-renderer').createBundleRenderer// 記錄js文件的內容 const serverBundle = require(path.resolve(__dirname, '../dist/vue-ssr-server-bundle.json')) // 記錄靜態資源文件的配置信息 const clientManifest = require(path.resolve(__dirname, '../dist/vue-ssr-client-manifest.json')) // 客戶端激活 const template = fs.readFileSync(path.resolve(__dirname, '../dist/index.template.html'), 'utf-8') const renderer = createBundleRenderer(serverBundle, {runInNewContext: false,template: template,clientManifest: clientManifest })// 資源文件 app.use(static(path.resolve(__dirname, '../dist'))) app.use(favicon(path.resolve(__dirname, '../favicon.ico')))router.get('*', (ctx, next) => {let context = {url: ctx.url}// 服務端渲染結果轉換成字符串renderer.renderToString(context, (err, html) => {if (err) {console.error(err);ctx.status = 500;ctx.body = '服務器內部錯誤';} else {ctx.status = 200;ctx.body = html; // 將html字符串傳到瀏覽器渲染}}); });// 開啟路由 app.use(router.routes()).use(router.allowedMethods());// 應用監聽端口 app.listen(3003, () => {console.log('服務器端渲染地址: http://localhost:3003'); });

6.修改App.js

<template><section id="app"><p>實現ssr服務端渲染增加 vue-router 和 vuex - vue ssr案例第三步</p><br><div>當前的頁面路徑: <span style="font-size: 20px; color:#f52811;">{{$router.currentRoute.path}}</span></div><br><router-link to="/">Home</router-link><router-link to="/list">List</router-link><router-view></router-view></section> </template><script> export default {name: 'App' } </script>

執行npm run start,在瀏覽器打開 http://localhost:3003/


在瀏覽器打開 http://localhost:3003/list


同樣,打開查看源代碼都可以看到頁面內容都渲染出來了。

接下來把 vuex 結合進項目

現在 vue-router 也能正常使用了,接下來需要思考一件事,平常我們都需要從后端交互拿到數據,那在 服務端數據又怎么同步到我們的組件中呢?


平常我們多用 created 和 mounted 進行數據的獲取,然后將得到數據放在 data 里,最后再到視圖中進行數據渲染。但是,在服務端 vue 只進行了 beforeCreate 和 created,然后就會生成html字符串,最后再瀏覽器端,再瀏覽器端進行掛載(也就是說 瀏覽器端vue的生命周期是從 beforeMount 開始,不存在beforeCreate 和 created )。所以在 服務端 vue 的生命周期只有 beforeCreate 和 created 。


到后臺請求數據都是異步的,如果在服務端的 beforeCreate 或 created 中去獲取數據,可能接口數據還沒返回到給我們,服務端已經把html字符串傳到瀏覽器渲染了,所以數據內容還是無法顯示出來。


在客戶端是直接進行掛載,所以客戶端生命周期是總beforeMounted開始的,由于爬蟲不會等待客戶端js執行完,所以在客戶端獲取數據也是不可取的。


官網推薦使用 vuex,在頁面渲染前將獲取到的數據存于 store 中,這樣在掛載到客戶端之前就可以通過 store 得到數據。
大概的思路是:

  • 在組件內自定義一個函數(例如:asyncData),用于調用后端接口獲取數據。
  • 將獲取到的數據存于 store 中,在服務端,組件通過 store 調取數據。
  • 服務端將渲染完 html 字符串傳到瀏覽器,瀏覽器在掛載實例前同步 store 數據。

接下來調整項目:
7.增加 /store/index.js。同樣也是導出一個函數,防止數據交叉污染。
getDataApi 用于模擬調用后臺數據的接口 import Vue from 'vue' import Vuex from 'vuex'Vue.use(Vuex)function getDataApi () {return new Promise((resolve, reject) => {setTimeout(() => {resolve('模擬異步獲取數據');}, 1000);}); }function createStore () {const store = new Vuex.Store({state: {datas: '' // 數據},mutations: {setData (state, data) {state.datas = data // 賦值}},actions: {fetchData ({ commit }) {return getDataApi().then(res => {commit('setData', res)})}}})return store }export default createStore

8.app.js

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

9.entry-server。如果匹配到路由,在Promise.all里面會篩選出組件里擁有 asyncData 函數的組件,并執行 asyncData 函數。往下面的看 第11 小結源碼可知道,asyncData 就是執行 dispatch 去觸發 store獲取數據和保存數據。這里是關鍵,只有等Promise.all執行完了,獲取到數據,填充好 store 才返回 app實例,服務端才將 html 字符串傳到瀏覽器,數據才能同步。

context.state = store.state 作用是,當服務端 createBundleRenderer 時,如果有template參數,就會把 context.state 的值作為 window.INITIAL_STATE 自動插入到html模板中。

import { createApp } from './app.js';export default context => {return new Promise((resolve, reject) => {const { app, router, store } = createApp();// 根據匹配到的路徑進行路由跳轉router.push(context.url);// 在router.onReady的成功回調中,找尋與url所匹配到的組件router.onReady(() => {// 查找所匹配到的組件const matchedComponents = router.getMatchedComponents()// 未找到組件if (matchedComponents.length <= 0) {return reject({state: 404,msg: '未找到頁面'})}// 對所有匹配的路由組件調用 `asyncData()`Promise.all(matchedComponents.map(component => {if (component.asyncData) {console.log(component.asyncData)// 匹配的組件存在 asyncData 就將其執行return component.asyncData({ store, route: router.currentRoute })}})).then(res => {// 在所有預取鉤子(preFetch hook) resolve 后,我們的 store 現在已經填充入渲染應用程序所需的狀態。// 當我們將狀態附加到上下文,并且 `template` 選項用于 renderer 時,狀態將自動序列化為 `window.__INITIAL_STATE__`,并注入 HTML。context.state = store.state// 成功并返回實例resolve(app)}).catch(reject)}, reject)}); }

10.entry-client??蛻舳嗽趻燧d之前,先通過 store.replaceState(window.INITIAL_STATE) 將服務端得到的 store 數據進行同步,這樣客戶端 store 初始化的數據就和服務端 store 同步了。

import { createApp } from './app.js';const { app, router, store } = createApp();// 客戶端在掛載到應用程序之前,同步store狀態 if (window.__INITIAL_STATE__) {store.replaceState(window.__INITIAL_STATE__) }app.$mount('#app');

11.Home.vue組件。asyncData 用于在服務端獲取數據,這樣 {{$store.state.datas}} 在服務端中就可以實現數據數據讀取了。

<template><section class="home">home --- home --- homr 123321<h2>從服務端去獲取的數據 ===> {{$store.state.datas}}</h2></section> </template><script> export default {name: 'Home',asyncData ({ store, route }) {return store.dispatch('fetchData') // 服務端獲取異步數據},data () {return {}},mounted () {// 客戶端不存在 created 和 beforeCreated 生命周期console.log('store', this.$store)} } </script><style>.home {background-color: aquamarine;margin: 20px;padding: 20px;} </style>

12.www.js。koa 路由攔截里改為 async/await 寫法,否則,程序就不等組件渲染好,就直接跑下個 middleware 去了,頁面會渲染不出來。

const Koa = require('koa'); const Router = require('koa-router'); const static = require('koa-static'); const path = require('path'); const fs = require('fs'); const app = new Koa() const router = new Router() const favicon = require('koa-favicon') const createBundleRenderer = require('vue-server-renderer').createBundleRenderer// 記錄js文件的內容 const serverBundle = require(path.resolve(__dirname, '../dist/vue-ssr-server-bundle.json')) // 記錄靜態資源文件的配置信息 const clientManifest = require(path.resolve(__dirname, '../dist/vue-ssr-client-manifest.json')) // 客戶端激活 const template = fs.readFileSync(path.resolve(__dirname, '../dist/index.template.html'), 'utf-8') const renderer = createBundleRenderer(serverBundle, {runInNewContext: false,template: template,clientManifest: clientManifest })// 資源文件 app.use(static(path.resolve(__dirname, '../dist'))) app.use(favicon(path.resolve(__dirname, '../favicon.ico')))router.get('*', async (ctx, next) => {let context = {url: ctx.url}// 服務端渲染結果轉換成字符串await new Promise((resolve, reject) => {renderer.renderToString(context, (err, html) => {if (err) {console.error(err);ctx.status = 500;ctx.body = '服務器內部錯誤';reject} else {ctx.status = 200;ctx.type = 'html';ctx.body = html; // 將html字符串傳到瀏覽器渲染resolve(next())}});}) });// 開啟路由 app.use(router.routes()).use(router.allowedMethods());// 應用監聽端口 app.listen(3003, () => {console.log('服務器端渲染地址: http://localhost:3003'); });

執行 http://localhost:3003


圖中可以看到,服務端 store 已經嵌入在 html 中,可通過 window.INITIAL_STATE 獲取,所以 entry-client.js 中掛載前就是通過一下代碼進行客戶端 store 和服務端 store 同步。

// 客戶端在掛載到應用程序之前,同步store狀態 if (window.__INITIAL_STATE__) {store.replaceState(window.__INITIAL_STATE__) }

查看源代碼,如下圖:


我們模擬后臺請求接口獲得的數據 “模擬異步獲取數據” 也在源代碼中看到了。


案例源碼


到這里,一個簡單的 vue 服務端渲染項目就搭建起來了。

總結

以上是生活随笔為你收集整理的Vue 服务端渲染原理 拆分成三步个步骤简单的实现一个案例的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。