基於vue-ssr的探索

xanggang發表於2018-06-24

最近公司專案, 需要需要使用vue伺服器端渲染,中間踩了許多坑,專案快要結束了總結一下。

一、 核心概念

vuessr的核心之一就是, 在使用者初次訪問專案時,先在伺服器端獲取資料,渲染好首頁以html的形式返回給使用者, 之後的頁面都在客戶端進行。所以需要理解哪些些生命週期和動作是在伺服器端進行,哪些是在客戶端進行,以及如何進行伺服器端和客戶端資料的同步。 在本文章中,用koa指代專案中的後端邏輯, 用nuxt指代前端邏輯。

二、 專案架構

nuxt文件中vue init nuxt-community/starter-template <project-name>生成的專案是沒有node端程式碼的,只適合比較簡單的專案。通過vue init nuxt/koa <project-name>可以生成基於koa的nuxt專案,server/index就是koa的入口檔案。實現後端邏輯。此時, nuxt會作為一個koa的中介軟體存在。可以獲取koa的上下文。

我將模板下的server/index.js改寫成class形式

// server/index.js
class Server {
  constructor() {
  /** 服務啟動時載入其他中介軟體 **/
    this.app = new Koa()
    // koa-router 
    this.app.use(wx.routes()).use(wx.allowedMethods())
    // 登陸、許可權
    this.app.use(authorize)
    // log4 日誌
    this.app.on('error', (err, ctx) => {
      log.logError(ctx, err, new Date())
    })
  }

  async start() {
    const nuxt = new Nuxt(config)
    if (config.dev) {
      const builder = new Builder(nuxt)
      await builder.build()
    }
    this.app.use(async (ctx, next) => {
      await next()
      ctx.status = 200 // koa defaults to 404 when it sees that status is unset
      return new Promise((resolve, reject) => {
        ctx.res.on('close', resolve)
        ctx.res.on('finish', resolve)
        // 在這一步中, 將ctx.req注入到nuxt中,可以在nuxt中的asyncData、fetch、的nuxtServerInit方法獲取
        nuxt.render(ctx.req, ctx.res, promise => {
          // nuxt.render passes a rejected promise into callback on error.
          promise.then(resolve).catch(reject)
        })
      })
    })

    this.app.listen(port, host)
    console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console
  }
}
複製程式碼

三、環境變數

nuxt中獲取的rocess.env.NODE_ENV 和koa中的rocess.env.NODE_ENV是不一致的

dev 屬性的值會被 nuxt 命令 覆蓋:

當使用 nuxt 命令時,dev 會被強制設定成 true
當使用 nuxt build, nuxt start 或 nuxt generate 命令時,dev 會被強制設定成 false
複製程式碼

實際在啟動的時候, koa的環境變數跟隨node 而nuxt的是取決於nuxt的指令。 需要在nuxt中區分環境變數的話推薦在nuxt.config.js中通過env屬性配置。在啟動專案的時候通過cross-env來設定環境

  env: {
    PATH_TYPE: process.env.NODE_ENV,
    serverUrl: process.env.NODE_ENV === 'production' ? 'https://api.xxx.com/api/v1' : 'https://apiceshi.xxx.com/api/v1',
    h5Url: process.env.NODE_ENV === 'production' ? 'https://m.xxx.com' : 'https://mtest.xxx.com'
  }
複製程式碼

四, koa中介軟體

nuxt是作為koa的一箇中介軟體形式存在,並且是最後執行的。利用這一點,可以實現koa路由和nuxt路由的區分。 如果註釋掉await nex() 會進入koa的authorize頁面,求是不會經過nuxt的。 否則nuxt會覆蓋掉這個路由。所以在開發的時候要注意中介軟體的順序 舉個例子

// koa-router
wx.get('/authorize', async (ctx, next) => {
    ctx.body = 'authorize'
    // await next()
})
複製程式碼

五, 跨域

nuxt必定會遇到跨域問題,而且無法通過cookie和session的形式進行會話。 這部分需要api伺服器提供支援

六,koa和nuxt的資料傳輸

既然知道koa的ctx.req會傳入nuxt,那麼就很好解決了, 在koa的中介軟體中, 將資料傳給ctx.req。 然後就可以在nuxtServerInit中進行初始化。 但是要注意,nuxtServerInit只會觸發一次。 這種方式只適合基礎資料的初始化

七, nuxt資料獲取

頁面初次載入, 第一個頁面會在伺服器端渲染,要注意window物件的問題,例如無法獲取cookie、部分UI庫無法正常渲染等等問題。在我開發的時候,將介面分為兩部分,不需要依賴客戶端資源的放在fetch、asyncData中進行,其他的放在mounted進行。fetch中呼叫可以將資料同步到store中, 而asyncData會將資料合併到當前頁面的data中。

八,全域性元件,初始化方法

nuxt中沒有vue專案中的main和App檔案,缺少一個全域性的入口作為專案初始化的觸發點和全域性元件的掛載點。 例如,有一個全域性的元件,一個初始化方法需要每個頁面都重複一遍也太low了。 這個時候可以在專案最外層包裹一個入口。

基於vue-ssr的探索
在最外層的index中放置全域性元件、利用store進行狀態控制。寫在index/mounted的方法必定會觸發一次。

九,nuxt中介軟體。

nuxt router-middleware 只會在路由變動的時候觸發。在伺服器端渲染的第一個頁面是不會觸發的。

十, 404頁面

nuxt沒有router配置檔案。 所以404頁面只能通過layouts的error.vue來實現。

十一, UI庫定製樣式

引入的UI庫在需要修改原始樣式的時候, 一般都是通過在mian引入一個全域性的css檔案來覆蓋原有樣式實現。 在nuxt中,可以在nuxt.config/css中配置

相關文章