实现 Vue 服务端渲染(Vue SSR)
什么是服務端渲染(SSR)?
Vue.js 是構建客戶端應用程序的框架,但是也可以將同一個組件渲染為服務端的 HTML 字符串,將它們直接發送到瀏覽器,最后將這些靜態標記“激活”為客戶端上完全可交互的應用程序。
服務器渲染的 Vue.js 應用程序也可以叫做“同構”或“通用”,程序上的大部分代碼都可以在服務器和客戶端上運行。
是否需要服務器渲染?
與傳統 SPA 相比,SSR 的主要優勢在于:
- 更好的 SEO
- 更快的內容到達時間(time-to-content)
需要注意的是:
- 開發條件有限,由于沒有動態更新,服務端渲染過程中,只有 beforeCreate和created鉤子函數被調用,在這兩個生命周期函數中應該避免產生全局副作用的代碼,例如在其中使用setInterval設置 timer。
- 涉及構建設置和部署的更多要求。服務器渲染應用程序,需要處于 Node.js server 運行環境。
- 更多的服務器端負載。如果預料到在高流量環境下使用,需要準備相應的服務器負載,并明智地采用緩存策略。
如果確實需要服務端渲染,那么可以繼續看下面的用法。
基本用法
安裝
npm install vue vue-server-renderer --save 復制代碼注意:
- Node.js 版本 6+
- vue-servier-renderer和vue必須匹配版本
構建步驟
1. 建立入口文件
對于客戶端應用程序和服務器應用程序,都需要使用 webpack 打包兩個 Bundle,服務器需要 Server Bundle 用于服務器渲染,Client Bundle 會發送給瀏覽器,用于混合靜態標記。
一個基本項目像這樣:
src ├── components │ ├── Foo.vue │ └── Baz.vue ├── App.vue ├── app.js # 通用 entry(universal entry) ├── index.template.html ├── entry-client.js # 僅運行于瀏覽器 └── entry-server.js # 僅運行于服務器 復制代碼因單線程機制,在服務端渲染中有類似于單例的操作,所有的請求都會共享這個單例的操作,所以應該使用工廠函數來確保每個請求之間的獨立性。app.js主要是 export 一個createApp函數。類似地 store和router都需要導出這樣的工廠函數:
# app.js import Vue from 'vue'; import App from './App.vue'; import { createStore } from './store'; import { createRouter } from './router';export const createApp = () => {const store = createStore();const router = createRouter();const app = new Vue({router,store,render: h => h(App)});return { app, router, store }; };復制代碼在客戶端 entry 中創建應用程序,并且將其掛載到 DOM 中。
# entry-client.js import { createApp } from './app'; const { app, router, store } = createApp();if (window.__INITIAL_STATE__) {store.replaceState(window.__INITIAL_STATE__); }router.onReady(() => {# 添加路由鉤子函數,用于處理 asyncData.# 在初始路由 resolve 后執行,以便我們不會二次預取(double-fetch)已有的數據。# 使用 `router.beforeResolve()`,以便確保所有異步組件都 resolve。router.beforeResolve((to, from, next) => {...});app.$mount('#app'); });復制代碼服務器 entry 使用 default export 導出函數,并在每次渲染中重復調用此函數。在這里可以執行服務端路由匹配 (server-side route matching) 和數據預取邏輯(data-pre-fetching logic)。
# entry-server.js import { createApp } from './app';export default context => {# 因為有可能會是異步路由鉤子函數或組件,所以我們將返回一個 Promise,# 以便服務器能夠等待所有的內容在渲染前就已經準備就緒。return new Promise((resolve, reject) => {const { app, router, store } = createApp();router.push(context.url);router.onReady(() => {# 服務器端數據預取...}, reject);}); };復制代碼2. webpack 構建配置
配置文件結構像這樣:
build ├── dev-server.js ├── setup-dev-server.js ├── webpack.base.conf.js ├── webpack.client.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.server.conf.js 復制代碼package.json打包命令:
"scripts": {"dev": "NODE_ENV=dev node server/index.js","build:client": "webpack --config build/webpack.client.conf.js --progress --hide-modules --progress","build:server": "webpack --config build/webpack.server.conf.js --progress --hide-modules --progress","build:prod": "NODE_ENV=prod npm run build:client && NODE_ENV=prod npm run build:server",}, 復制代碼3.開發服務器集成
開發服務使用的是 Koa,配置參考:
import Koa from 'koa'; import koaRouter from 'koa-router'; import { createBundleRenderer } from 'vue-server-renderer';const app = new Koa(); const router = koaRouter(); const createRenderer = (bundle, options) => {return createBundleRenderer(bundle,{...options, { runInNewContext: false }); };const renderData = (ctx, renderer) => {const context = {url: ctx.url};return new Promise((resolve, reject) => {renderer.renderToString(context, (err, html) => {if (err) {reject(err);}resolve(html);});}); };let renderer; require('../build/setup-dev-server.js')(app, (bundle, options) => {renderer = createRenderer(bundle, options); });# proxy api request const proxy = require('koa-server-http-proxy'); # 代理配置...router.get('*', async (ctx, next) => {if (!renderer) {ctx.type = 'html';return (ctx.body = 'waiting for compilation...');}let html;try {html = await renderData(ctx, renderer);} catch (e) {# 處理特殊情況...}ctx.body = html; });app.use(router.routes()).use(router.allowedMethods());app.listen(80, '0.0.0.0', () => {console.log(`server is running...`); });復制代碼4.線上服務器集成
線上服務使用的是 Egg.js,參考配置如下:
# app/controller/home.js const Controller = require('egg').Controller; const path = require('path'); const { createBundleRenderer } = require('vue-server-renderer');const serverBundle = require('../public/vue-ssr-server-bundle.json'); const clientManifest = require('../public/vue-ssr-client-manifest.json'); const template = require('fs').readFileSync(path.resolve(__dirname, '../public/index.html'),'utf-8' ); const renderer = createBundleRenderer(serverBundle, {runInNewContext: false,template,clientManifest });class HomeController extends Controller {async index() {const ctx = this.ctx;const context = { url: ctx.url };try {# 傳入context渲染上下文對象renderer.renderToString(context, (err, html) => {if (err) {throw err;}ctx.status = 200;# 傳入了template, html結構會插入到<!--vue-ssr-outlet-->ctx.body = html;});} catch (error) {ctx.status = 500;ctx.body = 'Internal Server Error';}} }module.exports = HomeController;復制代碼路由匹配:
router.get(/^(?!\/api\/)/, controller.home.index); 復制代碼如此依照開發和生產環境配置,能夠實現基本的服務端渲染。篇幅有限,大段代碼暫時沒有貼出,后續會開放源代碼示例。
總結
以上是生活随笔為你收集整理的实现 Vue 服务端渲染(Vue SSR)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 光纤收发器怎么使用?
- 下一篇: EasyUI(DataGrid修改删除)