隨筆- 19  評論- 6  文章- 0 

干貨!從0開始,0成本搭建個人動態博客

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

導讀

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

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

本文涉及的主要技術:

  • Semantic Versioning,Commitzion,etc...

在線查看

背景

我的博客的折騰史分成下面三個階段:

  • 基于 搭建靜態博客,結合 提供域名和服務器資源

  • 自行采購服務器和域名,進行頁面和接口的開發及部署,搭建動態博客

  • 基于 和 搭建動態博客

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

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

第3種方式,采用 ISSUE 來記錄文章,天然支持 Markdown ,接口調用 ,部署到 上。除了一次性開發外沒有任何額外成本。

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

技術選型

由于是個人博客,技術選型可以大膽嘗試。

筆者選擇了 進行項目結構的初始化,同時采用 vue3.x 的語法 進行頁面開發。采用 ,也就是 GraphQL 語法進行 API 調用。

上手開發

環境準備

node

前往 下載,這里推薦下載 LTS 穩定版。下載后按照步驟進行安裝操作即可。

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

vue-cli

執行以下代碼全局安裝即可。

npm install -g @vue/cli

項目初始化

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

vue create my-blog

init

完成初始化并安裝依賴后,查看到的項目目錄如下:

dir

其他依賴安裝

  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

項目開發

我的博客之前是使用的 風格主題,所以本次也是以此為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 = 'http://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 ,這里是 要求的鑒權。

這里有兩個坑,只有在打包提交代碼后才發現:

  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 相關,其余部分請查閱

我們首先需要引入 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)
  }
}
...

數據查詢的語法參考 。

我這里是以 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. 文章評論部分

這里我采用的是 ,請按照步驟初始化項目,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 = 'http://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 = 'http://utteranc.es/client.js';

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

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

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

Ⅵ.搜索頁開發

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

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

...
      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 的全部語法,只是根據項目需要進行了一個基本的嘗試。

項目發布和部署

項目的提交

項目的提交采用 ,采用的理由是:提交格式規范化,可以快速生成變更日志等,后期可做成自動化。參考對應使用使用步驟使用即可。

項目的版本管理

項目的版本管理采用

項目的部署

編寫了一個 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 'chenjiahao.xyz@gmail.com'
git add -A
git commit -m 'deploy'

git push -f git@github.com:ChenJiaH/blog.git master:gh-pages

cd -

gh-pages 的使用需要先創建 用戶名.github.io 的倉庫

結尾

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

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

(完)


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

posted on 2019-10-14 18:16 McChen 閱讀(...) 評論(...) 編輯 收藏