1. 程式人生 > >乾貨!從0開始,0成本搭建個人動態部落格

乾貨!從0開始,0成本搭建個人動態部落格

首發於微信公眾號《前端成長記》,寫於 2019.10.12

導讀

有句老話說的好,好記性不如爛筆頭。人生中,總有那麼些東西你願去執筆寫下。

本文旨在把整個搭建的過程和遇到的問題及解決方案記錄下來,希望能夠給你帶來些許幫助。

本文涉及的主要技術:

  • Vue3.0 - Composition API
  • GraphQL
  • ESLint
  • Semantic Versioning,Commitzion,etc...

線上檢視

我的部落格

背景

我的部落格的折騰史分成下面三個階段:

  • 基於 hexo 搭建靜態部落格,結合 Github Pages 提供域名和伺服器資源

  • 自行採購伺服器和域名,進行頁面和介面的開發及部署,搭建動態部落格

  • 基於 Github Pages 和 Github Api 搭建動態部落格

第1種方式,文章內容採用 Markdown 編寫,靜態頁面通過 hexo 生成,部署到 Github Pages 上。缺點很明顯,每次有新內容,都需要重新編譯部署。

第2種方式,靈活度極高,可以按需開發。缺點也很明顯,開發和維護工作量大,同時還需要伺服器和域名成本。

第3種方式,採用 ISSUE 來記錄文章,天然支援 Markdown ,介面呼叫 Github Api,部署到 Github Pages 上。除了一次性開發外沒有任何額外成本。

顯而易見,本部落格這次改版就是基於第3種方式來實現的,接下來我們從0開始一步步做。

技術選型

由於是個人部落格,技術選型可以大膽嘗試。

筆者選擇了 vue-cli 進行專案結構的初始化,同時採用 vue3.x 的語法 Composition-Api 進行頁面開發。採用 Github API v4 ,也就是 GraphQL 語法進行 API 呼叫。

上手開發

環境準備

node

前往 Node.js官網 下載,這裡推薦下載 LTS 穩定版。下載後按照步驟進行安裝操作即可。

Window 下記得選上 Add To Path ,保證全域性命令可用

vue-cli

執行以下程式碼全域性安裝即可。

npm install -g @vue/cli

專案初始化

通過 vue-cli 來初始化專案,按照下面內容選擇或自行按需選擇。

vue create my-blog

完成初始化並安裝依賴後,檢視到的專案目錄如下:

其他依賴安裝

  1. @vue/composition-api

使用 Vue 3.0 語法必要依賴

npm install @vue/composition-api --save
  1. graphql-request

簡單輕巧的的 graphQL 客戶端。同樣還有 Apollo, Relay 等可以進行選擇。選擇它的理由是:簡單輕巧,以及基於 Promise

npm install graphql-request --save
  1. github-markdown-css

使用 Github 的風格渲染 Markdown,選擇它的理由是原汁原味。

npm install github-markdown-css --save

專案開發

我的部落格之前是使用的 fexo 風格主題,所以本次也是以此為UI依據進行開發。

專案整體分成幾個頁面:

  • /archives 文章列表
  • /archives/:id 文章詳情
  • /labels 標籤列表
  • /links 友鏈
  • /about 關於
  • /board 留言板
  • /search 搜尋

Ⅰ.請求封裝

檢視原始碼

import { GraphQLClient } from 'graphql-request';

import config from '../../config/config';
import Loading from '../components/loading/loading';

const endpoint = 'https://api.github.com/graphql';

const graphQLClient = new GraphQLClient(endpoint, {
  headers: {
    authorization: `bearer ${config.tokenA}${config.tokenB}`,
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  },
});

const Http = (query = {}, variables = {}, alive = false) => new Promise((resolve, reject) => {
  graphQLClient.request(query, variables).then((res) => {
    if (!alive) {
      Loading.hide();
    }
    resolve(res);
  }).catch((error) => {
    Loading.hide();
    reject(error);
  });
});

export default Http;

我們可以看到配置了 headers ,這裡是 Github Api 要求的鑑權。

這裡有兩個坑,只有在打包提交程式碼後才發現:

  1. token 不能直接提交到 Github,否則再使用時會發現失效。這裡我猜測是安全掃描機制,所以我上面將 token 分成兩部分拼接繞過這個。

  2. Content-Type 需要設定成 x-www-form-urlencoded ,否則會跨域請求失敗。

接下來我們將修改 main.js 檔案,將請求方法掛載到 Vue例項 上。

...
import Vue from 'vue';
import Http from './api/api';

Vue.prototype.$http = Http;
...

Ⅱ.文章列表開發

檢視原始碼

主要將介紹 composition-apigraphqh 相關,其餘部分請查閱 Vue文件

我們首先需要引入 composition-api ,修改 main.js 檔案

...
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);
...

然後新建一個 Archives.vue 去承接頁面內容。

首先的變動是生命週期的變動,使用 setup 函式代替了之前的 beforeCreatecreated 鉤子。值得注意的有兩點:

  1. 該函式和 Templates 一起使用時返回一個給 template 使用的資料物件。
  2. 該函式內沒有 this 物件,需使用 context.root 獲取到根例項物件。
...
export default {
  setup (props, context) {
    // 通過context.root獲取到根例項,找到之前掛載在Vue例項上的請求方法
    context.root.$http(xxx)
  }
}
...

資料查詢的語法參考 Github Api。

我這裡是以 blog 倉庫 的 issue 來作為文章的,所以我這裡的查詢語法大致意思:

按照 ownername 去查倉庫, ownerGithub 賬號,name 是倉庫名稱。
查詢 issues 文章列表,按照建立時間倒序返回,first 表示每次返回多少條。after 表示從哪開始查。所以結合這個就很容易實現分頁,程式碼如下:

...
// 引入,使用 reactive 建立響應式物件
import {
  reactive,
} from '@vue/composition-api';
export default {
  setup (props, context) {
    const archives = reactive({
      cursor: null
    });
    const query = `query {
      repository(owner: "ChenJiaH", name: "blog") {
        issues(orderBy: {field: CREATED_AT, direction: DESC}, labels: null, first: 10, after:${archives.cursor}) {
          nodes {
            title
            createdAt
            number
            comments(first: null) {
              totalCount
            }
          }
          pageInfo {
            endCursor
            hasNextPage
          }
        }
      }
    }`;
    // 通過context.root獲取到根例項,找到之前掛載在Vue例項上的請求方法
    context.root.$http(query).then(res => {
      const { nodes, pageInfo } = res.repository.issues
      archives.cursor = pageInfo.endCursor  // 最後一條的標識
    })
  }
}
...

Ⅲ.標籤列表開發

檢視原始碼

這裡我沒有找到 issues 中返回全部的 labels 資料,所以只能先查全部 label ,再預設查詢第一項 label ,語法如下:

...
const getData = () => {
    const query = `query {
        repository(owner: "ChenJiaH", name: "blog") {
          issues(filterBy: {labels: ${archives.label}}, orderBy: {field: CREATED_AT, direction: DESC}, labels: null, first: 10, after: ${archives.cursor}) {
            nodes {
              title
              createdAt
              number
              comments(first: null) {
                totalCount
              }
            }
            pageInfo {
              endCursor
              hasNextPage
            }
            totalCount
          }
        }
      }`;
    context.root.$http(query).then((res) => {
      ...
    });
  };
  const getLabels = () => {
    context.root.$loading.show('努力為您查詢');
    const query = `query {
      repository(owner: "ChenJiaH", name: "blog") {
        labels(first: 100) {
          nodes {
            name
          }
        }
      }
    }`;
    context.root.$http(query).then((res) => {
      archives.loading = false;
      archives.labels = res.repository.labels.nodes;

      if (archives.labels.length) {
        archives.label = archives.labels[0].name;

        getData();
      }
    });
  };
...

Ⅳ.文章詳情開發

檢視原始碼

文章詳情分成兩部分:文章詳情查詢和文章評論。

  1. 文章詳情查詢

這裡首先引入 github-markdown-css 的樣式檔案,然後給 markdown 的容器加上 markdown-body 的樣式名,內部將會自動渲染成 github 風格的樣式。

...
<template>
...
    <div class="markdown-body">
      <p class="cont" v-html="issue.bodyHTML"></p>
    </div>
...
</template>
<script>
import {
  reactive,
  onMounted,
} from '@vue/composition-api';
import { isLightColor, formatTime } from '../utils/utils';

export default {
    const { id } = context.root.$route.params;  // 獲取到issue id
    const getData = () => {
      context.root.$loading.show('努力為您查詢');
      const query = `query {
          repository(owner: "ChenJiaH", name: "blog") {
            issue(number: ${id}) {
              title
              bodyHTML
              labels (first: 10) {
                nodes {
                  name
                  color
                }
              }
            }
          }
        }`;
      context.root.$http(query).then((res) => {
        const { title, bodyHTML, labels } = res.repository.issue;
        issue.title = title;
        issue.bodyHTML = bodyHTML;
        issue.labels = labels.nodes;
      });
    };
};
</script>
<style lang="scss" scoped>
  @import "~github-markdown-css";
</style>
...

注意這裡有個label顏色的獲取

眾所周知,Github Label 的字型顏色是根據背景色自動調節的,所以我這裡封裝了一個方法判斷是否為亮色,來設定文字顏色。

// isLightColor
const isLightColor = (hex) => {
  const rgb = [parseInt(`0x${hex.substr(0, 2)}`, 16), parseInt(`0x${hex.substr(2, 2)}`, 16), parseInt(`0x${hex.substr(4, 2)}`, 16)];
  const darkness = 1 - (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;
  return darkness < 0.5;
};
  1. 文章評論部分

這裡我採用的是 utterances ,請按照步驟初始化專案,Blog Post 請選擇 Specific issue number ,這樣評論才會是基於該 issue 的,也就是當前文章的。然後在頁面中按下面方式配置你的相關資訊引入:

...
import {
  reactive,
  onMounted,
} from '@vue/composition-api';
export default {
  setup(props, context) {
    const { id } = context.root.$route.params;  // issue id
    const initComment = () => {
      const utterances = document.createElement('script');
      utterances.type = 'text/javascript';
      utterances.async = true;
      utterances.setAttribute('issue-number', id);
      utterances.setAttribute('theme', 'github-light');
      utterances.setAttribute('repo', 'ChenJiaH/blog');
      utterances.crossorigin = 'anonymous';
      utterances.src = 'https://utteranc.es/client.js';

      // 找到對應容器插入,我這裡用的是 comment
      document.getElementById('comment').appendChild(utterances);
    };

    onMounted(() => {
      initComment();
    });    
  }
}
...

這個方案的好處是:資料完全來自 Github Issue ,並且自帶登入體系,非常方便。

Ⅴ.留言板開發

檢視原始碼

剛好上面部分提到了 utterances ,順勢基於這個開發留言板,只需要把 Blog Post 更換成其他方式即可,我這裡選擇的是 issue-term ,自定義標題的單條 Issue 下留言。為了避免跟文章那裡區分,所以我使用另外一個倉庫來管理留言。實現程式碼如下:

...
import {
  onMounted,
} from '@vue/composition-api';

export default {
  setup(props, context) {
    context.root.$loading.show('努力為您查詢');

    const initBoard = () => {
      const utterances = document.createElement('script');
      utterances.type = 'text/javascript';
      utterances.async = true;
      utterances.setAttribute('issue-term', '【留言板】');
      utterances.setAttribute('label', ':speech_balloon:');
      utterances.setAttribute('theme', 'github-light');
      utterances.setAttribute('repo', 'ChenJiaH/chenjiah.github.io');
      utterances.crossorigin = 'anonymous';
      utterances.src = 'https://utteranc.es/client.js';

      document.getElementById('board').appendChild(utterances);

      utterances.onload = () => {
        context.root.$loading.hide();
      };
    };

    onMounted(() => {
      initBoard();
    });
  },
};
...

Ⅵ.搜尋頁開發

檢視原始碼

這裡碰到一個坑,找了很久沒有找到模糊搜尋對應的查詢語法。

這裡感謝一下 simbawus ,解決了查詢語法的問題。具體查詢如下:

...
      const query = `query {
        search(query: "${search.value} repo:ChenJiaH/blog", type: ISSUE, first: 10, after: ${archives.cursor}) {
          issueCount
          pageInfo {
            endCursor
            hasNextPage
          }
          nodes {
            ... on Issue {
              title
              bodyText
              number
            }
          }
        }
      }`;
...

還好有 ... 拓展運算子,要不然 nodes 這裡面的解析格式又不知道該怎麼寫了。

Ⅶ.其他頁面開發

其他頁面多數為靜態頁面,所以按照相關的語法文件開發即可,沒有什麼特別的難點。

另外我這也未使用 composition-api 的全部語法,只是根據專案需要進行了一個基本的嘗試。

專案釋出和部署

專案的提交

專案的提交採用 commitizen ,採用的理由是:提交格式規範化,可以快速生成變更日誌等,後期可做成自動化。參考對應使用使用步驟使用即可。

專案的版本管理

專案的版本管理採用 Semantic Versioning 2.0.0

專案的部署

編寫了一個 deploy.sh 指令碼,並配置到 package.json 中。執行 npm run deploy 將自動打包並推送到 gh-pages 分支進行頁面的更新。

// package.json
{
  ...
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "inspect": "vue-cli-service inspect",
    "deploy": "sh build/deploy.sh",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md"
  },
 ...
}
#!/usr/bin/env sh

set -e

npm run build

cd dist

git init
git config user.name 'McChen'
git config user.email '[email protected]'
git add -A
git commit -m 'deploy'

git push -f [email protected]:ChenJiaH/blog.git master:gh-pages

cd -

gh-pages 的使用需要先建立 使用者名稱.github.io 的倉庫

結尾

至此,一個0成本的動態部落格已經完全搭建好了。開發過程中還遇到了一些 eslint 相關的提示和報錯,直接搜尋基本可解決。

如有疑問或不對之處,歡迎留言。

(完)


本文為原創文章,可能會更新知識點及修正錯誤,因此轉載請保留原出處,方便溯源,避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗
如果能給您帶去些許幫助,歡迎 ⭐️star 或 ✏️ fork
(轉載請註明出處:https://chenjiahao.xyz)