MonkeyBinBin

被程式設計的猴子

Nuxt.js

Nuxt 系列 - #5 Pages 開發大小事


詳細說明 Nuxt Pages 開發的細節,包含 api 資料取得、資源檔案位置如何引用、樣式配置與設定。

Async Data

Nuxt.js 增加了一個 asyncData 的方法,在 Vue 組件初始化前執行,用來取得 api 資料,因此在這個方法內無法使用 this 來引用組件的實體對象,僅能使用傳入的 context 來獲得相關的資訊與方法。官方建議呼叫 api 使用 axios 套件,因此接下來的程式碼範例將會使用 axios。

asyncData 使用方式有兩種,可以挑選自已熟悉的方式來使用,方法如下:

  1. 返回 Promise
     export default {
         asyncData ({ params }) {
             return axios.get(`https://my-api/posts/${params.id}`)
             .then((res) => {
                 return { title: res.data.title }
             })
         }
     }
  2. 使用 async、await
     export default {
         async asyncData ({ params }) {
             const { data } = await axios.get(`https://my-api/posts/${params.id}`)
             return { title: data.title }
         }
     }

asyncData 回覆的結果將會與 Vue 組件中 data 方法回傳的 json 物件合併,提供給組件使用。與開發一般 Vue 應用程式時,通常都在 mounted 生命周期中去呼叫 api 有些許不同。

asyncData 傳入的 context 包含三大類可用屬性與方法,分別為 Universal、Server-side、Client-side。

除了 asyncData 有 context 可用,還有其他 Nuxt.js 的方法 fetch、plugins、middleware、nuxtServerInit 也都會傳入 context。在上述這些方法中還可以使用 process.server 判斷是否為 Server-side 或 process.client 判斷是否為 Client-side。

以下程式碼可以看出目前 context 內包含了那些屬性與方法

function (context) {
    // Universal keys
    const {
        app,
        store,
        route,
        params,
        query,
        env,
        isDev,
        isHMR,
        redirect,
        error
    } = context
    // Server-side
    if (process.server) {
        const { req, res, beforeNuxtRender } = context
    }
    // Client-side
    if (process.client) {
        const { from, nuxtState } = context
    }
}

如何在 asyncData 取得動態路由傳入的參數?
解構 context 取得 params,"params.參數名稱"即可取得傳入參數值

export default {
    asyncData ({ params }) {
        console.log('pages/article/_id.vue 傳入參數 id => ', params.id)
    }
}

錯誤處理
解構 context 取得 error 方法,當需要拋出錯誤時呼叫 error 方法,Nuxt.js 就會顯示錯誤頁面

export default {
    asyncData ({ params, error }) {
        return axios.get(`https://my-api/posts/${params.id}`)
            .then((res) => {
                return { title: res.data.title }
            })
            .catch((e) => {
                error({ statusCode: 404, message: 'Post not found' })
            })
    }
}

Plugins

當需要使用 Vue.use、Vue.filter、Vue.component 時,應在 plugins 資料夾建立程式檔案並且在 nuxt.config.js 增加載入 plugins 的設定。

目前專案有使用 bootstrap-vuevue-disqus@fortawesome/vue-fontawesome 套件,另外也增加了一些 filter 的設定,都是透過 plugins 方式載入。套件的使用方式基本上都參照套件內的說明,filter 的設定方式也跟原本 Vue 的寫法一樣,只是在 Nuxt.js 需要在 nuxt.config.js 中設定 plugins。

目前 blog 文章內容格式是使用 Markdown,因此必需將 Markdown 轉換為 HTML 的內容,利用 marked 套件來做轉換,搭配 Vue filter 的方式使用。另外還有增加一個顯示日期的 filter。

新增 plugins/filters.js 檔案

import Vue from 'vue'
import highlightjs from 'highlight.js'
import marked, { Renderer } from 'marked'
import pathHelper from '~/helpers/path'

// Create your custom renderer.
const renderer = new Renderer()
renderer.link = (href, title, text) => `<a target="_blank" href="${href}" title="${title}">${text}</a>`
renderer.table = (header, body) => `<table class="table table-striped">${header}${body}</table>`

// Set the renderer to marked.
marked.setOptions({
        renderer,
        baseUrl: pathHelper.getBaseUrl(),
        highlight: function (code, language) {
        // Check whether the given language is valid for highlight.js.
        const validLang = !!(language && highlightjs.getLanguage(language))
        // Highlight only if the language is valid.
        const highlighted = validLang ? highlightjs.highlight(language, code).value : code
        // Render the highlighted code with `hljs` class.
        return `<pre><code class="hljs ${language}">${highlighted}</code></pre>`
    }
})

Vue.filter('parseMd', marked)

Vue.filter('parseDatetime', function (datetime) {
    const parseDate = moment(datetime, 'YYYY-MM-DD')
    return parseDate.format('LL')
})

在 nuxt.config.js 增加 plugins 設定

export default {
    plugins: [
        '~/plugins/filters.js',
        // or
        { src: '~/plugins/filters.js' }
    ]
}

載入 plugins 也是可以選擇要在 client、server 或者都要使用,只需要在 nuxt.config.js 設定即可。設定方式有2種:

  1. 傳入物件包含 src 與 mode 設定
     export default {
         plugins: [
             { src: '~/plugins/both-sides.js' }, // both client & server
             { src: '~/plugins/client-only.js', mode: 'client' }, // only in client side
             { src: '~/plugins/server-only.js', mode: 'server' } // only in server side
         ]
     }
  2. 檔案命名
     export default {
         plugins: [
             '~/plugins/baz.js', // both client & server
             '~/plugins/foo.client.js', // only in client side
             '~/plugins/bar.server.js' // only in server side
         ]
     }

Assets

Nuxt 預設使用 file-loader 與 url-loader 來處理圖片與字型檔案的載入與使用。如果有不需要透過 webpack 處理的靜態資源檔案,可以放置在 static 資料夾,執行打包後會將檔案複製到產出結果的資料夾。需要透過 webpack 處理的圖片與字型檔案則統一放置於 assets 資料夾。

Webpack Assets 檔案位置:
assets/
 ∟ image.png

Webpack Assets 使用方式:

<template>
  <img src="~/assets/image.png">
</template>

Static Assets 檔案位置:
static/
 ∟ image.png

Static Assets 使用方式:

<template>
  <img src="/image.png">
</template>

注意事項:
從 Nuxt 2.0 後,在 css 使用路徑別名 ~/ 時必需移除斜線,才能正確的載入檔案。例如:background: url("~assets/img/avatar.jpg");

樣式配置

使用 SCSS 撰寫樣式

SCSS 樣式檔案結構
assets/
 ∟ sass/
  ∟ helpers/
   ∟ _functions.scss
   ∟ _mixins.scss
   ∟ _variables.scss
  ∟ main.scss

將 SCSS functions、mixins 與 variables 分檔案放置方便管理與維護
Global 樣式放置於 assets/sass/main.scss
Pages 與 Components 自身樣式設定於各自的 .vue 檔案內,並且啟用 scoped 設定

目前規畫的 _variables.scss 內容,主要是顏色的設定

$primary-color: #1dc8cd;
$secondary-color: lighten(saturate($primary-color, 25), 50);
$tertiary-color: #1de099;
$marked-primary-color: #c91414;
$marked-secondary-color: lighten(saturate($marked-primary-color, 25), 50);

若要讓 _functions.scss、_mixins.scss 與 _variables.scss 能夠在整個應用程式的樣式中使用,必需使用 @nuxtjs/style-resources 套件引入上述3個檔案

@nuxtjs/style-resources 使用方式:

  1. 安裝 @nuxtjs/style-resources 套件
     npm add -D @nuxtjs/style-resources
  2. 在 nuxt.config.js 增加 buildModules 與 styleResources 設定
     export default {
         buildModules: [
             '@nuxtjs/style-resources',
         ],
         styleResources: {
             // your settings here
             scss: [
                 path.resolve(__dirname, 'assets/sass/helpers/_variables.scss'),
                 path.resolve(__dirname, 'assets/sass/helpers/_functions.scss'),
                 path.resolve(__dirname, 'assets/sass/helpers/_mixins.scss')
             ]
         }
     }

設置完成後即可在任何 .scss 檔案、Pages 與 Components 使用 functions、mixins 與 variables。以下為目前 blog 專案內 pages/article/_id.vue style 設定範例

<style lang="scss" scoped>
.md-content {
    & /deep/ img {
        max-width: 100%;
    }
}
.article__date::after {
    content: "";
}
.other_article_link {
    color: $primary-color;
}
</style>

最後在 nuxt.config.js 設定全站要使用的所有樣式檔案

export default {
    css: [
        '@fortawesome/fontawesome-svg-core/styles.css',
        // 載入bootstrap
        'bootstrap/scss/bootstrap.scss',
        'bootstrap-vue/dist/bootstrap-vue.css',
        // 載入highlight.js樣式(可選擇不同theme)
        'highlight.js/styles/zenburn.css',
        // 載入aos樣式
        'aos/src/sass/aos.scss',
        // 主要css樣式(customer)
        '~/assets/sass/main.scss'
    ]
}

針對 Pages 的說明大概到這邊,下一篇要介紹內容管理的服務 contentful,如何在上面建立、管理 blog 的內容與如何跟 Nuxt.js 整合。

參考資料

Nuxt.js - Async Data
Nuxt.js - Assets
Nuxt.js - Plugins
@nuxtjs/style-resources