两个示例的git地址:
1. 我的环境
2. 方式一:使用prerender-spa-plugin插件获得SSR的效果。
2.1 说明
2.2 初始化
1
|
vue init webpack vue-prerender-demo //此文章都是在webpack基础上配置的
|
1 2 3
|
cd vue-prerender-demo npm install npm run dev
|
2.3 配置
2.4 开始
1. 安装 prerender-spa-plugin, 因为依赖phantom js
,phantom 的安装比较蛋疼,太耗时了~
1
|
npm install prerender-spa-plugin -D
|
2. 开始 prerender 相关的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
//引用 var PrerenderSpaPlugin = require('prerender-spa-plugin')
//...
plugins: { //.... //配置 prerender-spa-plugin new PrerenderSpaPlugin( // 生成文件的路径,此处与webpack打包地址一致 path.join(config.build.assetsRoot), //config.build.assetsRoot为vue cli生成的配置,打包后的文件地址 // 配置要做预渲染的路由,只支持h5 history方式 [ '/', '/test'] )
//.... }
|
3. 编译
4. 验证
1 2
|
cd dist //进入到对应目录 python -m SimpleHTTPServer 8888 //将dist作为根目录,启动8888端口,
|
2.5 优缺点
3. 方式二:使用官方提供的轮子在node端做SSR
3.1 说明
3.2 约束
3.3 准备工作
1
|
vue init webpack vue-ssr-demo
|
1 2 3
|
cd vue-ssr-demo npm install npm run dev
|
3.4 开始折腾
1. 首先安装 ssr 支持
1
|
npm i -D vue-server-renderer
|
2. 增加路由test
与页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
<template> <div> Just a test page. <div> <router-link to="/">Home</router-link> </div> <div><h2>{{mode}}</h2></div> <div><span>{{count}}</span></div> <div><button @click="count++">+1</button></div> </div> </template> <script> export default { data () { return { mode: process.env.VUE_ENV === 'server' ? 'server' : 'client', count: 2 } } } </script>
|
3. 在src
目录下创建两个js:
1 2 3
|
src ├── entry-client.js # 仅运行于浏览器 └── entry-server.js # 仅运行于服务器
|
4. 修改router
配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export function createRouter () { return new Router({ mode: 'history', // 注意这里也是为history模式 routes: [ { path: '/', name: 'Hello', component: HelloWorld }, { path: '/test', name: 'Test', component: () => import('@/components/Test') // 异步组件 } ] }) }
|
5. 改造main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import Vue from 'vue' import App from './App' import { createRouter } from './router'
export function createApp () { // 创建 router 实例 const router = new createRouter() const app = new Vue({ // 注入 router 到根 Vue 实例 router, render: h => h(App) }) // 返回 app 和 router return { app, router } }
|
6. entry-client.js
加入以下内容:
1 2 3 4 5 6
|
import { createApp } from './main' const { app, router } = createApp() // 因为可能存在异步组件,所以等待router将所有异步组件加载完毕,服务器端配置也需要此操作 router.onReady(() => { app.$mount('#app') })
|
7. entry-server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
// entry-server.js import { createApp } from './main' export default context => { // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise, // 以便服务器能够等待所有的内容在渲染前, // 就已经准备就绪。 return new Promise((resolve, reject) => { const { app, router } = createApp() // 设置服务器端 router 的位置 router.push(context.url) // 等到 router 将可能的异步组件和钩子函数解析完 router.onReady(() => { const matchedComponents = router.getMatchedComponents() // 匹配不到的路由,执行 reject 函数,并返回 404 if (!matchedComponents.length) { // eslint-disable-next-line return reject({ code: 404 }) } // Promise 应该 resolve 应用程序实例,以便它可以渲染 resolve(app) }, reject) }) }
|
8. webpack
配置
1 2 3 4
|
build ├── webpack.base.conf.js # 基础通用配置 ├── webpack.client.conf.js # 客户端打包配置 └── webpack.server.conf.js # 服务器端打包配置
|
9. webpack 客户端的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') // ... // ... plugins: [ new webpack.DefinePlugin({ 'process.env': env, 'process.env.VUE_ENV': '"client"' // 增加process.env.VUE_ENV }), //... // 另外需要将 prod 的HtmlWebpackPlugin 去除,因为我们有了vue-ssr-client-manifest.json之后,服务器端会帮我们做好这个工作。 // new HtmlWebpackPlugin({ // filename: config.build.index, // template: 'index.html', // inject: true, // minify: { // removeComments: true, // collapseWhitespace: true, // removeAttributeQuotes: true // // more options: // // https://github.com/kangax/html-minifier#options-quick-reference // }, // // necessary to consistently work with multiple chunks via CommonsChunkPlugin // chunksSortMode: 'dependency' // }),
// 此插件在输出目录中 // 生成 `vue-ssr-client-manifest.json`。 new VueSSRClientPlugin() ] // ...
|
10. webpack 服务器端的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
const webpack = require('webpack') const merge = require('webpack-merge') const nodeExternals = require('webpack-node-externals') const baseConfig = require('./webpack.base.conf.js') const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') // 去除打包css的配置 baseConfig.module.rules[1].options = ''
module.exports = merge(baseConfig, { // 将 entry 指向应用程序的 server entry 文件 entry: './src/entry-server.js', // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import), // 并且还会在编译 Vue 组件时, // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。 target: 'node', // 对 bundle renderer 提供 source map 支持 devtool: 'source-map', // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports) output: { libraryTarget: 'commonjs2' }, // https://webpack.js.org/configuration/externals/#function // https://github.com/liady/webpack-node-externals // 外置化应用程序依赖模块。可以使服务器构建速度更快, // 并生成较小的 bundle 文件。 externals: nodeExternals({ // 不要外置化 webpack 需要处理的依赖模块。 // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件, // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单 whitelist: /\.css$/ }), plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 'process.env.VUE_ENV': '"server"' }), // 这是将服务器的整个输出 // 构建为单个 JSON 文件的插件。 // 默认文件名为 `vue-ssr-server-bundle.json` new VueSSRServerPlugin() ] })
|
1
|
baseConfig.module.rules[1].options = '' // 去除分离css打包的插件
|
11. 配置package.json
增加打包服务器端构建命令并修改原打包命令
1 2 3 4 5 6
|
"scripts": { //... "build:client": "node build/build.js", "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --progress --hide-modules", "build": "rimraf dist && npm run build:client && npm run build:server" }
|
12. 修改index.html
1 2 3 4 5 6 7 8 9 10
|
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue-ssr-demo</title> </head> <body> <!--vue-ssr-outlet--> </body> </html>
|
13. 运行构建命令
14. 构建服务器端(官方例子使用的express
,所以此 demo 将采用koa2
来作为服务器端,当然,无论是 koa 与 express 都不重要…)
1 2 3 4 5 6 7 8 9
|
const Koa = require('koa') const app = new Koa()
// response app.use(ctx => { ctx.body = 'Hello Koa' })
app.listen(3001)
|
15. 编写服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
const Koa = require('koa') const app = new Koa() const fs = require('fs') const path = require('path') const { createBundleRenderer } = require('vue-server-renderer')
const resolve = file => path.resolve(__dirname, file)
// 生成服务端渲染函数 const renderer = createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'), { // 推荐 runInNewContext: false, // 模板html文件 template: fs.readFileSync(resolve('./index.html'), 'utf-8'), // client manifest clientManifest: require('./dist/vue-ssr-client-manifest.json') })
function renderToString (context) { return new Promise((resolve, reject) => { renderer.renderToString(context, (err, html) => { err ? reject(err) : resolve(html) }) }) } app.use(require('koa-static')(resolve('./dist'))) // response app.use(async (ctx, next) => { try { const context = { title: '服务端渲染测试', // {{title}} url: ctx.url } // 将服务器端渲染好的html返回给客户端 ctx.body = await renderToString(context)
// 设置请求头 ctx.set('Content-Type', 'text/html') ctx.set('Server', 'Koa2 server side render') } catch (e) { // 如果没找到,放过请求,继续运行后面的中间件 next() } })
app.listen(3001)
|
16. 大功告成
3.4 优缺点
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理