SSR

裝依賴

vue-server-render vue服務端的渲染器 express服務端的服務器 開發模式開發
npm i vue-server-renderer express -D
server/index.jscss

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 對外暴露createRouterhtml

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
服務端入口vue

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.jsnode

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.jswebpack

// 導入兩個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修改git

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.jsgithub

// 通用文件:建立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 }
}

使用web

<template>
    <div>
        <p>num:{{$store.state.count}}</p>
        <button @click="$store.commit('add')"></button>
    </div>
</template>
相關文章
相關標籤/搜索