1. 程式人生 > 其它 >VUE2--從零實現服務端渲染(模擬vue-cli初始化專案的目錄結構)

VUE2--從零實現服務端渲染(模擬vue-cli初始化專案的目錄結構)

Vue.js 是構建客戶端應用程式的框架。預設情況下,可以在瀏覽器中輸出 Vue 元件,進行生成 DOM 和操作 DOM。然而,也可以將同一個元件渲染為伺服器端的 HTML 字串,將它們直接傳送到瀏覽器,最後將這些靜態標記"啟用"為客戶端上完全可互動的應用程式。在服務端渲染模板字串的這一過程即為SSR。

概要

Vue.js 是構建客戶端應用程式的框架。預設情況下,可以在瀏覽器中輸出 Vue 元件,進行生成 DOM 和操作 DOM。然而,也可以將同一個元件渲染為伺服器端的 HTML 字串,將它們直接傳送到瀏覽器,最後將這些靜態標記"啟用"為客戶端上完全可互動的應用程式。在服務端渲染模板字串的這一過程即為SSR。

初始化專案並安裝依賴

建立一個專案目錄為vue-ssr-demo, 進入目錄,輸入如下命令:

// 初始化專案
npm init
// 安裝依賴
// 用於建立node服務
npm install express -D
npm install vue -S
npm install vue-router -S
// 用於將 Vue 例項渲染為 HTML
npm install vue-server-renderer -S

安裝完成後,可以看到我們的package.json檔案如下:

{
  "name": "vue-ssr-demo",
  "version": "1.0.0",
  "description": "vue ssr demo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "ssr"
  ],
  "author": "elwin",
  "license": "ISC",
  "dependencies": {
    "vue": "^2.6.14",
    "vue-router": "^3.5.2",
    "vue-server-renderer": "^2.6.14"
  },
  "devDependencies": {
    "express": "^4.17.1"
  }
}

按照如下建立目錄結構

router.js程式碼

編寫路由模組,返回一個路由例項

const VueRouter = require("vue-router")
const Vue = require("vue")

// 安裝路由外掛,如果外掛是一個物件,必須提供 install 方法。如果外掛是一個函式,它會被作為 install 方法
Vue.use(VueRouter)
// 因為是node環境,所以要使用COMMON JS規範
module.exports = () => {
  // 返回一個vue路由例項
  return new VueRouter({
    mode:"history",
    routes:[
      {
        path:"/",
        component: {
          template:`<h1><b style="color:red;">Hello SSR</b> this is home page</h1>`
        },
        name:"home"
      },
      {
        path:"/about",
        component: {
          template:`<h1><b style="color:red;">Hello SSR</b> this is about page</h1>`
        },
        name:"about"
      }
    ]
  })
}

app.js程式碼

編寫vue專案入口檔案相關程式碼,返回app例項和路由例項

const Vue = require("vue")
const createRouter = require("./router")

module.exports = (context) => {
  // 建立一個路由例項
  const router = createRouter()
  const app =  new Vue({
    router, // 掛載到vue例項上
    data:{
      message:"Hello SSR",
    },
    template:`
      <div>
        <h1>{{message}}</h1>
        <ul>
          <li>
            <router-link to="/">home</router-link>
          </li>
          <li>
            <router-link to="/about">about</router-link>
          </li>
        </ul>
        <router-view></router-view>
      </div>
    `
  })
  // 返回app例項和路由例項
  return {
    app,
    router
  }
}

entry-server.js程式碼

現在我們需要在 entry-server.js 中實現伺服器端路由邏輯 (server-side routing logic):

const createApp = require("./app.js")

// context執行上下文環境
module.exports = (context) => {
  // 因為有可能會是非同步路由鉤子函式或元件,所以我們將返回一個 Promise,
  // 以便伺服器能夠等待所有的內容在渲染前,
  // 就已經準備就緒。
  return new Promise(async (reslove, reject) => {
    // 解構出瀏覽器訪問的url
    const { url } = context
    // 傳入執行上下文,例項化app例項,解構出app和router
    const {app, router} = createApp(context)
    // 導航到瀏覽器訪問URL,使app例項匹配到對應的元件
    router.push(url)
    /**
     * router.onReady(callback, [errorCallback])
     * 在路由完成初始導航時呼叫,這意味著它可以解析所有的非同步進入鉤子和路由初始化相關聯的非同步元件。
     * 這可以有效確保服務端渲染時服務端和客戶端輸出的一致。
     * 第二個引數 errorCallback 只在 2.4+ 支援。它會在初始化路由解析執行出錯 (比如解析一個非同步元件失敗) 時被呼叫。
     */
    // 等到 router 將可能的非同步元件和鉤子函式解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // 匹配不到的路由,執行 reject 函式,並返回 404
      if(!matchedComponents.length) {
        return reject({ code: 404 })
      }
      // Promise 應該 resolve 應用程式例項,以便它可以渲染
      reslove(app)
    },reject)
  })
}

server.js程式碼

構建伺服器端執行邏輯,返回瀏覽器訪問的url所對應的app例項渲染出的html字串

const express = require("express")
const app = express()
const App = require('./src/entry-server.js')
const path = require("path")
const vueServerRender = require("vue-server-renderer").createRenderer({
  template: require("fs").readFileSync(path.join(__dirname,"./index.template.html"), "utf-8")
})

app.get('*', async(request, response) => {
  response.status(200)
  response.setHeader("Content-type", "text/html;charset=utf-8")
  // 解構出瀏覽器訪問的url
  const { url } = request
  // favicon.ico 圖示用於收藏夾圖示和瀏覽器標籤上的顯示,如果不設定,瀏覽器會請求網站根目錄的這個圖示,如果網站根目錄也沒有這圖示會產生 404。
  if (url ==='/favicon.ico') return
  let vm
  vm = await App({ url }).catch(err => { console.log(err) })
  vueServerRender.renderToString(vm).then((html) => {
    response.end(html)
  }).catch(err => console.log(err))
})

// 監聽5000埠
app.listen(5000, () => {
  console.log('服務已開啟^_^')
})

啟動node服務,效果如下:
home頁面

about頁面