繩命在於折騰,我用 Nuxt.js 重構了部落格

田勇發表於2019-05-26

其實自己的部落格上線沒多久,之前閒時會寫些亂七八糟的玩意兒,一來當作總結和備忘,二來分享一些個人經驗,也是種很有趣的經歷。然後幾個月前,想著自己手裡有個註冊但閒置很久的域名,又正好有臺伺服器,就乾脆折騰個部落格。

不就是個部落格嘛?能有多難?也沒多想就用了之前使用過的 Hexo 擼了起來,只花了一晚上就弄上線了。不過上線一時爽,維護火葬場。之後花上它上面的時間要遠多於此,因為 hexo 如果想要充分的自定義模板或者功能,還是很麻煩的,特別是因為模板用的 pug 以及寫樣式用的 stylus 都是自己不擅長且不太喜歡的語言。幾番折騰下來總不得勁,終於心一橫,不如重構吧。

重構時要比當初選擇 hexo 時要謹慎多了,對比了下自己瞭解的一些工具,最終選擇了 Nuxt.js。

為什麼是 Nuxt.js?

最主要的原因就是自己用 Vue 很久了,學 Nuxt 的成本也就小得多。Nuxt 可以讓我用最熟悉的姿勢來寫程式碼,同時又能解決部落格在靜態化、SEO 等方面的一些要求,它的佈局、自動路由、外掛、中介軟體等特性讓我大有相見恨晚的感覺。

其實在作決定之前也試過 Vuepress,但 Vuepress 的出發點是文件類的站,並不太適合寫部落格。雖然 1.0 版中加入了部落格的支援,但目前仍在 alpha 階段,體驗不太好,更新進度又不理想,等到正式穩定可用的版本出來,估計黃花菜也涼了。

此外也考慮過用 hugo,甚至想過用 Laravel 來弄。但 hugo 基於 Go,自己完全不懂,而用 Laravel 寫部落格似乎大材小用了,畢竟我只需要一個靜態的小站,也不會給伺服器增加多少壓力。

具體實施

  1. 建立 nuxt 專案並進行基礎配置

    首先當然是建立專案,根據 Nuxt 文件使用 yarn create nuxt-app 命令建立一個新專案,根據需要配置好 eslint、typescript 等。

  2. 確定目錄結構(路由)、文章檔名命名規範

    因為之前用 hexo 部署的也是純靜態的站,只要之前所部署的舊檔案不刪除,那麼使用原來的連結仍然能訪問舊版的文章。所以也不用太糾結重構後路由的變化。當然這並不意味著不需要進行規劃。

    為此,我新建了一個 posts 目錄,用於儲存 markdown 檔案,資料夾內建與 markdown 檔案同名的資料夾用來存文章中用到的圖片等。

    -| posts/
    ----| hello-中國/
    ----| hello-中國.md

    這裡要注意一下的是,檔名將一些特殊字元和空格替換成了連詞符,而實際訪問用的路由是將檔名拼音化。為什麼不直接用拼音化檔名或者英文呢?主要是方便日後管理。

    然後在 pages 目錄下建立 psots/_slug.vue 頁面。這樣文章就可以用 https://tianyong90.com/psots/hello-zhong-g... 這樣形式來訪問了。

  3. 安裝並配置 @tianyong90/vue-markdown-loader

    @tianyong90/vue-markdown-loader 是自己之前從 vuepress 中提取的 markdown-loader 部分程式碼改寫出來的一個 webpack loader。它的主要功能是載入 markdown 檔案,進行一些處理,如解析 emoji、程式碼高亮等,最後返回可以供 vue-loader 的內容。最近又進一步優化,讓它可以返回 html 而被 html-loader 處理,或者直接返回一個包含 markdown 檔案資訊的物件。

    配置如下:

    build: {
    extend(config, ctx) {
      // frontmatter-markdown-loader
      config.module!.rules.push({
        test: /\.md$/,
        include: path.resolve(__dirname, 'posts'),
        use: [
          {
            loader: '@tianyong90/vue-markdown-loader',
            options: {
              mode: 'raw', // 這裡表示 import md 檔案後直接返回一個物件
              contentCssClass: 'markdown-body',
              markdown: {
                lineNumbers: true, // enable line numbers
              },
            },
          },
        ],
      })
    },
    }
  4. 文章頁的一些處理

    有了前面的這些,就可以開始動手處理文章頁了,這也是部落格的關鍵部分。而其中最為重要的工作就是根據 url 中拼音化的文章標題正確載入 posts 目錄中的 markdown 連結半渲染顯示,這些基本都在 asyncData 方法中完成。

    async asyncData({ params }) {
    // 這裡的 posts.json 是用指令碼生成的儲存 posts 目錄中文章列表資訊的
    // 相當於一個小的資料庫
    const { default: posts } = await import('~/posts/posts.json')
    
    // 連結中拼音化的檔名
    const slugifiedFilename = params.slug
    
    const thePost: any = posts.find((item: any) => {
      return item.slugifiedFilename === slugifiedFilename
    })
    
    // posts 目錄中 markdown 實際檔名
    const filename = thePost.filename
    
    // 解析渲染都交給前面提到的 @tianyong90/vue-markdown-loader 完成
    // 這裡的 html 就是渲染出來的 html,可以直接應用於 v-html 指令
    // attributes 則是 markdown 檔案頭部的 frontmatter 資料如標題、日期等
    const { html, attributes } = await import(`~/posts/${filename}.md`)
    
    return {
      ...attributes,
      html: html.replace(/src="\.\//g, `src="/_nuxt/posts/${filename}/`), // markdown 內容中圖片地址引用替換
    }
    },

    然後在模板中顯示這些資料,其中 html 使用 v-html 指令就可以了。

    <div class="container-fluid py-4">
    <div class="row post-info-sm">
      <div class="col-12">
        <h1 class="post-title" v-text="title" />
        <div class="post-date">{{ date }}</div>
      </div>
    </div>
    
    <div class="row">
      <div class="col-xs-12 col-md-10 col-xl-6 mx-auto">
        <div class="markdown-body" v-html="html" />
      </div>
    </div>
    </div>
  5. 佈局、樣式等細節

    部落格並不只是文字內容,因此還需要在佈局、樣式等方面下些功夫。因為自己設計水平實在有限,所以直接使用了 bootstrap 和 github-markdown-css,擼完文章列表頁以及文章內容頁就夠用了,其它的頁面看需要再加吧。

  6. 生成、部署以及自動化

    最後要生成靜態頁,而部落格所使用的又是動態路由,就需要在 nuxt.config.js 中的 genarate 薦中進行配置。

    如下,根據當前 posts 目錄下的 markdown 檔名來確定該生成哪些頁面。

    generate: {
    routes: ['404'].concat(posts.map(post => `/posts/${post.slugifiedFilename}`)),
    },

    執行 yarn run generate 後可以看到下面的結果,dist 目錄裡也出現了靜態檔案,剩下的就只是部署了。

繩命在於折騰,我用 Nuxt.js 重構了部落格

對於部署,配置上 Circle CI,當推關新內容上 master 分支時由 CI 進行構建並部署到自己伺服器簡直不能更爽。

此外,為了省事,還寫了幾個指令碼來建立新的 markdown 檔案和相應的資料夾,雖然這也不是必須的,但使用指令碼顯然要比手動建立要省事得多。

總結

Nuxt.js 確實是個好東西,寫了近三年 Vue 了才開始盤它,確實是有點兒遲了。Nuxt 利用 SSR 等機制能很好地彌補 SPA 應用在 SEO 等方面的不足,其自帶的生成靜態站的功能也非常適合平時寫一些部落格之類的應用。

感謝 Hexo 陪伴多年(雖然期間也沒用它寫出什麼東西來),但以後可能不會再用它了…… ?

相關文章