合格前端系列第十一彈-初探 Nuxt.js 祕密花園

qiangdada發表於2018-04-03

前言

筆者最近業餘時間想著學點新東西,於是開始接觸伺服器端渲染(SSR),花了一週的時間學習和實戰,然後在週末高仿掘金擼了一個 SSR 的 Demo 專案。

技術棧選的是平滑開箱的 Nuxt,整個專案整合了 vue + nuxt + axios + vuex + vue-router (nuxt 自帶 vuex 和 vue-router)。

專案目前實現了以下幾大功能

  • 伺服器端渲染

  • 靜態頁面部署

  • 掘金首頁

  • 掘金推薦列表

  • 滾動分頁載入

  • 不同端的佈局適配

專案完整地址:nuxt-ssr-demo

前端交流群:731175396

一、效果圖

1、PC 端

合格前端系列第十一彈-初探 Nuxt.js 祕密花園

2、移動端

合格前端系列第十一彈-初探 Nuxt.js 祕密花園

看完最終完成的效果圖,接下來,開始我們的實戰之旅吧 ~

二、專案實戰

一個專案開始之前,我喜歡先搭好一個空架子。所以這裡還是老規矩,先帶著大家把專案空殼搭好吧。

1、使用 starter 模板

這裡關於專案初始化,我是直接使用的 Nuxt 官網提供的 starter 模板

# 安裝 vue-cli
npm install -g vue-cli
# 初始化專案
vue init nuxt-community/starter-template nuxt-ssr-demo
# 安裝依賴
cd nuxt-ssr-demo
npm install # Or yarn install
# 啟動本地服務
npm run dev
複製程式碼

訪問 http://localhost:3000 ,現在我們來看下初始化好的專案目錄

├── assets						css,圖片等資源都在這
├── components                  元件相關
├── layouts                     路由佈局
├── middleware                  中介軟體
├── pages                  		路由頁面
├── static                  	靜態資源
├── pages                  		路由頁面
├── store              	      	vuex 相關
├── nuxt.config.js              nuxt 相關配置
├── package.json              	依賴相關
├── README.md              	    專案介紹
複製程式碼

接觸過 vue 的小夥伴,看著這個目前可能會有點小疑問,為什麼沒有 router 路由相關的檔案?莫慌,Nuxt 會幫你將 pages 下面的檔案自動解析成路由。所以在接下來的開發中,記得別瞎在 pages 下面新增檔案,pages 下面的每一個 vue 檔案就是一個路由。

2、引入 axios

i. 安裝依賴

npm i axios -S
複製程式碼

ii. 封裝 axios

為了專案之後更加方便的開發,我們有必要對 axios 進行一層封裝,我們要時刻養成這樣一種好習慣。

首先在根目錄下面新建 service 目錄,在其下面建立 config.jsindex.js 兩個檔案,下面的程式碼僅供參考,如果你的專案還需要做額外的一些配置,可自行進行擴充

config.js 中寫入:

import http from 'http'
import https from 'https'

export default {
  // 自定義的請求頭
  headers: {
    post: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
    },
    'X-Requested-With': 'XMLHttpRequest'
  },
  // 超時設定
  timeout: 10000,
  // 跨域是否帶Token
  withCredentials: true,
  // 響應的資料格式 json / blob /document /arraybuffer / text / stream
  responseType: 'json',
  // 用於node.js
  httpAgent: new http.Agent({
    keepAlive: true
  }),
  httpsAgent: new https.Agent({
    keepAlive: true
  })
}
複製程式碼

index.js 中寫入:

import axios from 'axios'
import qs from 'qs'
import config from './config'

const service = axios.create(config)

// POST 傳參序列化
service.interceptors.request.use(
  config => {
    if (config.method === 'post') config.data = qs.stringify(config.data)
    return config
  },
  error => {
    return Promise.reject(error)
  }
)
// 返回結果處理
service.interceptors.response.use(
  res => {
    return res.data
  },
  error => {
    return Promise.reject(error)
  }
)

export default {
  // post 方法
  post (url, data) {
    console.log('post request url', url)
    return service({
      method: 'post',
      url,
      params: data
    })
  },
  // get 方法
  get (url, data) {
    console.log('get request url', url)
    return service({
      method: 'get',
      url,
      params: data
    })
  },
  // delete 方法
  delete (url, data) {
    console.log('delete request url', url)
    return service({
      methods: 'delete',
      url,
      params: data
    })
  }
}
複製程式碼

iii. 跨域處理

使用過 vue 的同學,肯定知道對於專案中的跨域,vue-cliwebpack 中的 proxy 選項進行了一層封裝。它暴露出來的是一個叫 proxyTable 的選項,是對 webpack 中的 proxy 和其三方外掛 http-proxy-middleware 的一個整合。

不幸的 Nuxt 中沒有 proxyTable 這麼一個配置項來進行跨域的配置。當然幸運的是,在 Nuxt 中你可以直接通過配置 http-proxy-middleware 來處理跨域。更幸運的是 Nuxt 官方提供了兩個包來處理 axios 跨域問題。

首先,進行安裝

npm i @nuxtjs/axios @nuxtjs/proxy -D
複製程式碼

然後在 nuxt.config.js 檔案裡進行配置

modules: [
  '@nuxtjs/axios'
],
axios: {
  proxy: true
},
proxy: {
  '/api': {
    target: 'xxx.target.com',
    pathRewrite: { '^/api': '' }
  }
}
複製程式碼

這裡需要注意,因為是伺服器端渲染,我們得時刻明確當前地址是屬於路由跳轉還是屬於 axios 請求。所以我們需要在 service/index.js 寫入以下判斷

// 判斷是路由跳轉還是 axios 請求
if (process.server) {
  config.baseURL = `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}`
}
複製程式碼

然後你就可以安心使用你的 axios 進行跨域請求了

合格前端系列第十一彈-初探 Nuxt.js 祕密花園

3、管理 vuex

先看下我們 store 目錄下需要的一些檔案

├── actions.js                  axios 請求相關
├── index.js					主入口檔案
├── mutations.js                同步狀態操作相關
├── state.js                  	初始狀態相關
複製程式碼

接下來我們以此看看每個檔案的內容

i. actions.js

import request from '~/service'

const api = '/api'

export const banner = async (store, params) => {
  return await request.get(`${api}/v1/get_banner`, params)
}
複製程式碼

ii. state.js

export default {
  counter: 1,
  isPhone: false
}
複製程式碼

iii. mutations.js

export function INCREMENT (state) {
  state.counter++
}

export function PCORPHONE (state, bool) {
  state.isPhone = bool
}
複製程式碼

iv. index.js

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import * as mutations from './mutations'
import * as actions from './actions'

Vue.use(Vuex)

const store = () => new Vuex.Store({
  state,
  mutations,
  actions
})

export default store
複製程式碼

然後你就可以在頁面中進行使用了

<template>
  <div class="page">
    <button @click="handleClick">{{ counter }}</button>
    <p>{{ banner.name }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  async asyncData ({ store, error }) {
    // 對 axios 進行批量處理
    let [ res ] = await Promise.all([
      store.dispatch('banner')
    ]).catch((e) => {
      error({ statusCode: 404, message: 'Post not found' })
    })
    return {
      banner: res.banner
    }
  },
  computed: {
    ...mapState({
      counter: state => state.counter
    })
  },
  methods: {
    handleClick () {
      this.$store.commit('INCREMENT')
    }
  }
}
</script>
複製程式碼

4、全域性元件管理

Nuxt 的專案不比 vue 的專案,提供了主入口檔案供我們對全域性元件進行配置。但要做到這個點也比較簡單,我們只需要按照 Nuxt 官網給出的規範來,將元件引入的相關配置寫入到 plugins 目錄下即可

比如,我需要引入三方元件庫 element-ui ,我們只需在 plugins 目錄下新建一個 element-ui.js 檔案,並寫入

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI)
複製程式碼

然後在 nuxt.config.js 檔案中引入

plugins: [
  '~/plugins/element-ui'
]
複製程式碼

最後你就可以在你的專案中使用 element-ui 元件庫裡面的元件了。

當然,你想配置自己本地的全域性元件,也是一樣的做法。先在 plugins 目錄下新建一個 js 檔案,然後引入你的檔案,最後再在 nuxt.config.js 檔案中引入即可。

5、全域性樣式管理

和元件管理同理,不同的就是,css 需要存放在 assets 目錄下。比如,現在我需要有一個 main.css 檔案對路由跳轉進行動態切換。

首選,你得在 assets/main.css 中寫入重置樣式吧

.page-enter-active, .page-leave-active {
  transition: opacity .2s;
}

.page-enter, .page-leave-active {
  opacity: 0;
}
複製程式碼

然後,你只要在 nuxt.config.js 進入引入即可

css: [
  '~/assets/stylus/main.styl'
]
複製程式碼

關於 Nuxt 更多的用法,我就不一一介紹了,詳細請參考:Nuxt.js 文件官網

然後關於專案的具體開發,也是家常便飯了,這裡我也不展開描述了。如果想了解的,可以去 github 自行查閱。有問題的話可以加老司機群 731175396 ,然後在群裡問我即可。

接下來的篇幅,我將講一下專案部署的一些點

三、專案部署

到這一步的同學,你得先確保你擁有一個自己的伺服器。如果沒有的話,感覺去買一個,現在阿里雲和騰訊雲都在搞活動,巨便宜哦 ~

OK,文章繼續。在進行部署講解前,我們先看一下 Nuxt 提供的幾個命令

命令 描述
nuxt 啟動一個熱載入的 Web 伺服器(開發模式) localhost:3000
nuxt build 利用 webpack 編譯應用,壓縮 JS 和 CSS 資源(釋出用)
nuxt start 以生成模式啟動一個 Web 伺服器 (nuxt build 會先被執行)
nuxt generate 編譯應用,並依據路由配置生成對應的 HTML 檔案 (用於靜態站點的部署)

1、靜態化頁面部署

我們從官網給出的文件可以看出,部署靜態化頁面需要用到的命令是 nuxt generate ,執行的時候會在根目錄下面生成 dist 目錄,裡面的檔案都是靜態化頁面需要的打包好的檔案。

這裡需要特別注意的一點是,如果你的專案中存在 axios 請求的話,記得在你本地開啟一個本地服務哦 ~ 不然打包的時候執行到 axios 請求的時候會報錯。因為前面我們通過使用 node 的 process 對執行環境進行了是跳轉還是請求的判定,而打包進行請求的時候是不依賴 node 環境的

i. 使用 gh-pages

這裡,我先假設小夥伴們都可以正常執行 nuxt generate 並生成對應的 dist 目錄。

為了專案的並行開發,我們一般會在 .gitignore 檔案裡面將打包檔案給忽略掉,但我們靜態化頁面的部署有需要用到 dist 目錄下的所有打包檔案。所以這裡我們將使用 gh-pages 將打包檔案釋出到我們的 git 倉庫

# 安裝 gh-pages
npm i gh-pages -D
複製程式碼

然後在 package.json 寫入配置(當然你也可以新建檔案執行釋出)

"scripts": {
  "deploy": "gh-pages -d dist"
}
複製程式碼

執行 npm run deploy ,然後你的 dist 目錄就會發到你們倉庫的 gh-pages 分支了

ii. 開始部署

打包檔案上傳好之後,你需要做的第一件事就是連線好你的伺服器。然後在 cd 到伺服器根目錄下面,在 data/www 目錄下面將你的專案 git clone 下來。然後切換到 gh-pages 分支

接下來,開始配置你的 nginx (還沒下載 nginx 的同學請自行解決)

 server {
  # 埠,預設是 80
  listen 81;
  # 服務名(寫域名或者 ip 地址都可以)
  server_name 123.12.123.12;
  # server 根目錄
  root  /data/www/nuxt-ssr-demo;
  # 主入口檔案
  index  index.html;
  # 反向代理
  location /api/ {
    proxy_pass https://xxx.target.com/;
  }
}
複製程式碼

然後重啟 nginx

sudo service nginx restart
複製程式碼

然後你就能在 http://123.12.123.12:81 訪問到你部署好的靜態化頁面了

2、伺服器端渲染部署

看到上面靜態化頁面部署,詳細有同學會問。進行靜態化頁面部署,豈不是沒有了伺服器端渲染的優勢了。

對的,如果你的專案只是靜態頁面的話,做靜態化部署是完全 OJBK 的。但如果牽扯到請求,還是乖乖的進行伺服器端的部署吧 ~

開始前,請確保你的伺服器上已經搭建好了 node 環境。沒有的同學,我建議使用 nvm 安裝 node 。接下來,開始部署

i. 進行服務代理

第一步,將之前 clone 下面的 git 專案切換到主開發分支,然後為了之後的方便修改一下你的 package.json

"scripts": {
  "build": "npm run lint && nuxt build && npm start",
  "start": "nuxt start"
}
複製程式碼

第二步,啟動服務

npm run build
複製程式碼

第三步,配置你的 nginx 檔案

# 通過 upstream nodejs 可以配置多臺 nodejs 節點,做負載均衡
# keepalive 設定存活時間。如果不設定可能會產生大量的 timewait
# proxy_pass 反向代理轉發 http://nodejs

upstream nodenuxt {
    server 127.0.0.1:3000; # nuxt 專案監聽埠
    keepalive 64;
}
server {
  listen 82;
  server_name 118.25.109.133;
  
  location / {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Nginx-Proxy true;
    proxy_cache_bypass $http_upgrade;
    proxy_pass http://nodenuxt; # 反向代理
  }
}
複製程式碼

最後,重新啟動 nginx 服務

sudo service nginx restart
複製程式碼

ii. 使用 pm2 做程式守護

如果我們按照上面的步驟進行部署的話,伺服器會經常斷掉連線,那我們的服務也就斷了。所以為了守護好我們的 nodejs 程式,這裡我將使用 pm2 對程式進行守護

首先全域性安裝 pm2

npm i pm2 -g
複製程式碼

然後進入到專案目錄,執行

pm2 start npm --name "nuxt-ssr-demo" -- run build
複製程式碼

然後,媽媽再也不用擔心我的 nodejs 程式說斷就斷啦 ~

合格前端系列第十一彈-初探 Nuxt.js 祕密花園

對於 pm2 用法,請小夥伴們輸入 pm2 --help 然後自行查閱

總結

文章到這就要結束了,這裡我做一個小總結。在一週的學習和實戰中,產出一個高仿掘金的 SSR Demo 的同時,也踩了一些坑。

對於 Nuxt,在使用層面,是比較簡單、好上手的。相對 vue-ssr 來說,它大大的簡化了開發的配置,讓開發人員可以只需思考業務的開發,而不用太去擔心檔案的配置。其中 Nuxt 通過監聽 pages 目錄檔案變更並自動生成路由更是直接省去了我們平常對於路由的配置。

但是,目前 Nuxt 整體還是有待提高的,目前社群相關的三方外掛比較有限,市面上相關的參考資料也相對比較少。

不管如何,希望 Nuxt 社群可以越來越好吧 ~

最後,想看專案原始碼的小夥伴,這裡我再最後放一次連結

專案完整地址:nuxt-ssr-demo

前端交流群:731175396

相關文章