Vue 2.0 服務端怎麽渲染
// 步驟 1:創建一(yī)個Vue實例 var Vue = require('vue') var app = new Vue({ render: function (h) { return h('p', 'hello world') } }) // 步驟 2: 創建一(yī)個渲染器 var renderer = require('vue-server-renderer').createRenderer() // 步驟 3: 将 Vue實例 渲染成 HTML renderer.renderToString(app, function (error, html) { if (error) throw error console.log(html) // => <p server-rendered="true">hello world</p> })
這樣子(zǐ),配合通常的(de) Node 服務端框架就可(kě)以簡單來實現服務端渲染了,可(kě)是,在真實場景中,我們一(yī)般采用 .vue
文件的(de)模塊組織方式,這樣的(de)話,服務端渲染就需要使用 webpack 來将 Vue 組件進行打包為(wèi)單個文件。
2. 配合 Webpack 渲染 .vue
文件
先建立一(yī)個服務端的(de)入口文件 server.js
import Vue from 'vue'; import App from './vue/App'; export default function (options) { const VueApp = Vue.extend(App); const app = new VueApp(Object.assign({}, options)); return new Promise(resolve => { resolve(app); }); }
這裏和(hé)浏覽器端的(de)入口文件大同小異,隻是默認導出了一(yī)個函數,這個函數接收一(yī)個服務端渲染時服務端傳入的(de)一(yī)些配置,返回一(yī)個包含了 app 實例的(de) Promise;
簡單寫一(yī)個 App.vue 的(de)文件
<template> <h1>{{ title }}</h1> </template> <script> module.exports = { props: ['title'] </script>
這裏将會讀取服務端入口文件傳入 options 的(de) data 屬性,取到 title 值,渲染到對應 DOM 中;
再看看 Webpack 的(de)配置,和(hé)客戶端渲染同樣是大同小異:
const webpack = require('webpack'); const path = require('path'); const projectRoot = __dirname; const env = process.env.NODE_ENV || 'development'; module.exports = { target: 'node', // 告訴 Webpack 是 node 代碼的(de)打包 devtool: null, // 既然是 node 就不用 devtool 了 entry: { app: path.join(projectRoot, 'src/server.js') }, output: Object.assign({}, base.output, { path: path.join(projectRoot, 'src'), filename: 'bundle.server.js', libraryTarget: 'commonjs2' // 和(hé)客戶端不同 }), plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(env), 'process.env.VUE_ENV': '"server"' // 配置 vue 的(de)環境變量,告訴 vue 是服務端渲染,就不會做(zuò)耗性能的(de) dom-diff 操作了 }) ], resolve: { extensions: ['', '.js', '.vue'], fallback: [path.join(projectRoot, 'node_modules')] }, resolveLoader: { root: path.join(projectRoot, 'node_modules') }, module: { loaders: [ { test: /\.vue$/, loader: 'vue' }, { test: /\.js$/, loader: 'babel', include: projectRoot, exclude: /node_modules/ } ] } };
其中主要就是三處不同:聲明 node 模塊打包;修改打包後模塊加載方式為(wèi) commonjs(commonjs2 具體可(kě)以看 Webpack 官方文檔);再就是 vue 的(de)服務端打包優化了,這部分如(rú)果不傳的(de)話後面 vue 服務端渲染會慢至幾十秒,一(yī)度以為(wèi)服務端代碼挂了。
最後就是服務端載入生成的(de) bundle.server.js 文件:
const fs = require('fs'); const path = require('path'); const vueServerRenderer = require('vue-server-renderer'); const filePath = path.join(__dirname, 'src/bundle.server.js'); // 讀取 bundle 文件,并創建渲染器 const code = fs.readFileSync(filePath, 'utf8'); const bundleRenderer = vueServerRenderer.createBundleRenderer(code); // 渲染 Vue 應用為(wèi)一(yī)個字符串 bundleRenderer.renderToString(options, (err, html) => { if (err) { console.error(err); } content.replace('<div id="app"></div>', html); });
這裏 options 可(kě)以傳入 vue 組件所需要的(de) data 等信息;下面還是以官方實例中的(de) express 來做(zuò)服務端示例下:
const fs = require('fs'); const path = require('path'); const vueServerRenderer = require('vue-server-renderer'); const filePath = path.join(think.ROOT_PATH, 'view/bundle.server.js'); global.Vue = require('vue') // 讀取 bundle 文件,并創建渲染器 const code = fs.readFileSync(filePath, 'utf8'); const bundleRenderer = vueServerRenderer.createBundleRenderer(code); // 創建一(yī)個Express服務器 var express = require('express'); var server = express(); // 部署靜态文件夾為(wèi) "assets" 文件夾 server.use('/assets', express.static( path.resolve(__dirname, 'assets'); )); // 處理(lǐ)所有的(de) Get 請求 server.get('*', function (request, response) { // 設置一(yī)些數據,可(kě)以是數據庫讀取等等 const options = { data: { title: 'hello world' } }; // 渲染 Vue 應用為(wèi)一(yī)個字符串 bundleRenderer.renderToString(options, (err, html) => { // 如(rú)果渲染時發生了錯誤 if (err) { // 打印錯誤到控制台 console.error(err); // 告訴客戶端錯誤 return response.status(500).send('Server Error'); } // 發送布局和(hé)HTML文件 response.send(layout.replace('<div id="app"></div>', html)); }); // 監聽5000端口 server.listen(5000, function (error) { if (error) throw error console.log('Server is running at localhost:5000') });
這樣子(zǐ)基本就是 Vue 服務端渲染的(de)整個流程了
編輯:--ns868