2

装依赖

vue-server-render vue服务端的渲染器 express服务端的服务器 开发模式开发
npm i vue-server-renderer express -D
server/index.js

const express = require('express')
const Vue = require('vue')
// express实例
const app = express()
// 创建Vue实例
const vm = new Vue({
  data: { count: 1 },
  template: `
    <div>{{count}}</div>
  `
})
// 创建渲染器
const renderer = require('vue-server-renderer').createRenderer()
// 服务器路由声明
app.get('/', async function (req, res) {
  // renderToString是一个异步处理  用then  或async await
  try {
    const html = await renderer.renderToString(vm)
    res.send(html)
  } catch (error) {
    res.status(500).send('internal Server Error')
  }
})
app.listen(3000, () => {
  console.log('渲染服务器成功了')
})

构建步骤

webpack根据执行环境生成server bundle和client bundle
image.png
路由 Vue-router
单页应用的页面路由,都是前端控制,后端只负责提供数据
一个简单的单页应用,使用vue-router,为了方便前后端公用路由数据,我们新建router.js 对外暴露createRouter

import Vue from 'vue'
import Router from 'vue-router'
// 页面
import Index from '@/components/Index'
import Detail from '@/component/Detail'

Vue.use(Router)
// 导出应当是Router实例工厂函数
export default createRouter() {
  return new Router({
    mode:'history',
    routes:[
      {path:'/',component:Index},
      {path:'/detail',component:Detail}
    ]
  })
}

main.js同级 app.js

// 通用文件:创建vue实例
import { createRouter } from './router'
import App from './App.vue'
import Vue from 'vue'
export function createApp (context) {
  const router = createRouter()
  const app = new Vue({
    router,
    render: h => h(App)
  })
  return { app, router }
}

main.js同级entry-server.js
服务端入口

import { createApp } from './app'
export default context => {
  // 返回一个Promise
  // 确保路由或组件准备就绪
  return new Promise((resolve, reject) => {
    // 创建Vue实例
    const { app, router } = createApp(context)
    // 跳转首屏地址
    router.push(context.url)
    // 路由就绪完成promise
    router.onReady(() => {
      resolve(app)
    }, reject)
  })
}

客户端的入口entry-client.js

import { createApp } from './app'
const { app, router } = createApp()
router.onReady(() => {
  // 挂载
  app.$mount('#app')
})

webpack配置 后端加入webpack

安装依赖配置
cnpm i cross-env vue-server-renderer webpack-node-externals lodash.merge -S
vue.config.js

// 导入两个webpack的插件 分别负责生成服务端和客户端的bundle
const VueSSRServerPlugin = require('vue-server-render/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
// 优化策略
const nodeExternals = require('webpack-node-externals')
const merge = require('lodash.merge')
// 根据WEBPACK_TARGET 环境变量做相应输出
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const target = TARGET_NODE ? 'server' : 'client'
module.exports = {
  css: {
    extract: false
  },
  configureWebpack: () => ({
    // 将entry指向应用程序server/client文件
    entry: TARGET_NODE ? `./src/${target}-entry.js` : './src/main.js',
    // 对bundle renderer 提供source map支持
    devtool: 'source-map',
    target: TARGET_NODE ? 'node' : 'web',
    // mock node中的一些全局变量
    node: TARGET_NODE ? undefined : false,
    output: {
      libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
    },
    // https://webpack.js.org/configuration/externals/#function //https://github.com/liady/webpack-node-externals
    // 外置化应用程序依赖模块,可以使服务器构建速度更快,
    // 并生成较小的bundle文件
    externals: TARGET_NODE ? nodeExternals({
      // 不要外置化webpack 需要处理的依赖模块
      // 你可以在这里添加更多的文件类型,例如,未处理*.vue原始文件
      // 你还应该将修改`global`(例如polyfill)的依赖模块列入白名单
      whitelist: [/\.css$/]
    }) : undefined,
    optimization: { // 优化策略分块
      splitChunks: undefined
    },
    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  }),
  chainWebpack: config => {
    config.module.rule('vue')
      .use('vue-loader')
      .tap(options => {
        merge(options, {
          optimizeSSR: false
        })
      })
  }
}

image.png

package.json

"scripts": {
    "build:client": "vue-cli-service build",
    "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
    "build": "npm run build:client && npm run build:server"
  },

宿主文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!--vue-ssr-outlet-->
</body>
</html>

server/index.js修改

const express = require('express')
const fs = require('fs')
const Vue = require('vue')
// express实例
const app = express()
const { createBundleRenderer } = require('vue-server-renderer')
const bundle = require('../dist/server/vue-ssr-server-bundle.json')
const clientManifest = require('../dist/client/vue-ssr-client-manifest.json')
const renderer = createBundleRenderer(bundle, {
  runInNewContext: false,
  template: false.readFileSync('./src/index.temp.html', 'utf-8'),
  clientManifest: clientManifest
})
function renderToString (context) {
  return new Promise((resolve, reject) => {
    renderer.renderToString(context, (err, html) => {
      if (err) {
        reject(err)
        return
      }
      resolve(html)
    })
  })
}
// 创建Vue实例
// const vm = new Vue({
//   data: { count: 1 },
//   template: `
//     <div>{{count}}</div>
//   `
// })
// 创建渲染器
// const renderer = require('vue-server-renderer').createRenderer()
// 服务器路由声明
app.use(express.static('../dist/client'))
app.get('*', async function (req, res) {
  // renderToString是一个异步处理  用then  或async await
  try {
    const context = {
      title: 'ssr - test',
      url: req.url
    }
    const html = await renderer.renderToString(context)
    res.send(html)
  } catch (error) {
    res.status(500).send('internal Server Error')
  }
})
app.listen(3000, () => {
  console.log('渲染服务器成功了')
})

整合vuex

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export function createStore () {
  return new Vuex.Store({
    state: {
      count: 199
    },
    mutations: {
      add (state) {
        state.count += 1
      }
    },
    actions: {

    }
  })
}

挂载store app.js

// 通用文件:创建vue实例
import { createRouter } from './router'
import App from './App.vue'
import Vue from 'vue'
import { createStore } from './store'
export function createApp (context) {
  const router = createRouter()
  const store = createStore()
  const app = new Vue({
    router,
    store,
    render: h => h(App)
  })
  return { app, router }
}

使用

<template>
    <div>
        <p>num:{{$store.state.count}}</p>
        <button @click="$store.commit('add')"></button>
    </div>
</template>

HappyCodingTop
526 声望847 粉丝

Talk is cheap, show the code!!


下一篇 »
虚拟dom