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

来源:https://www.cnblogs.com/McChen/archive/2019/10/14/11673008.html

首發於微信公眾號《前端成長記》,寫於 2019.10.12 導讀 有句老話說的好,好記性不如爛筆頭。人生中,總有那麼些東西你願去執筆寫下。 本文旨在把整個搭建的過程和遇到的問題及解決方案記錄下來,希望能夠給你帶來些許幫助。 本文涉及的主要技術: "Vue3.0 Composition API" "G ...


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

導讀

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

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

本文涉及的主要技術:

線上查看

我的博客

背景

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

  • 基於 hexo 搭建靜態博客,結合 Github Pages 提供功能變數名稱和伺服器資源

  • 自行採購伺服器和功能變數名稱,進行頁面和介面的開發及部署,搭建動態博客

  • 基於 Github PagesGithub 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

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

項目開發

我的博客之前是使用的 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)


您的分享是我們最大的動力!

更多相關文章
  • 看了知乎上的話題 如何才能通俗易懂的解釋javascript裡面的‘閉包’?,受到一些啟發,因此結合實例將回答中幾個精要的答案做一個簡單的分析以便加深理解。 1. "閉包就是跨作用域訪問變數。" 【示例一】 在 getName 函數中獲取 name,首先在 getName 函數的作用域中查找 nam ...
  • 前面,跟大家簡單地介紹了負載均衡和Nginx的一些基礎配置( "Nginx負載均衡配置實例" ),接下來,跟大家介紹一下Nginx的常用命令,便於日常的運維。 "查看原文" 停止Nginx的方法 通過之前的學習,大家知道瞭如何配置並啟動Nginx,但如果想停止Nginx服務,該如何操作呢?下麵介紹停 ...
  • npm multer 文件上傳 Express app 範本就不寫了,僅記錄一下上傳部分的代碼。 const fs = require('fs'); const express = require('express'); const multer = require('multer'); const ...
  • webpack 插件 ProvidePlugin:自動載入模塊,而不必到處 import 或 require 。 ...
  • 一.a標簽完成 二.js實現下載 三.js中ajax實現音頻或者視頻不跳轉進行文件下載 寫代碼的思路 四.fetch實現 ...
一周排行
  • 場景 在Winfom中可以在頁面上多個按鈕或者右鍵的點擊事件中觸發同一個自定義的委托事件。 實現 在位置一按鈕點擊事件中觸發 string parentPath = System.IO.Directory.GetParent("指定路徑").ToString(); //獲取指定路徑的父級目錄並作為參 ...
  • asp.net 根據html模板導出excel public class ExcelHelper { /// <summary> /// 根據html模板文件生成excel文件 /// </summary> /// <param name="ds">數據源</param> /// <param na ...
  • asp.net 使用NPOI讀取excel文件內容 NPOI下載地址:NPOI public class ExcelHelper { /// <summary> /// 讀取Excel文件數據到DataSet,一個Sheet對應一個DataTable /// </summary> /// <para ...
  • 場景 使用Visual Studio 開發Winform程式,使用SVN進行項目版本管理。 在添加引用時,會出現在A電腦中添加了絕對路徑的引用,在B電腦中就會出現找不到 並且將此引用標識為?的狀態。 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi ...
  • asp.net 使用 Application 限制單一登錄 原理:用戶登錄後系統會分配一個與用戶唯一對應的SessionID,將當前用戶ID與其SessionID對應保存在Application中,一旦該用戶在其他地方重覆登錄則Application中保存的SessionID就會被更新,導致當前se ...
  • 當我們的系統時間不正常,比如設置一個日期-1999年9月9日,會引發證書問題。 系統時間不正常-IE有概率能訪問 觸發NavigateError事件,異常代碼INET_E_INVALID_CERTIFICATE -- 這是一個必要不充分條件,系統時間不正常時IE有相關證書異常,更新時間能解決此類異常 ...
  • //加密 public static string GDEncode(string data, string Key) { Key = "12345678"; byte[] byKey = System.Text.ASCIIEncoding.ASCII.GetBytes(Key); byte[] b ...
  • static void CopyFiles() { string sourceDir = @"D:\C\ll"; string destDir = @"D:\LL"; if (!Directory.Exists(destDir)) { Directo... ...
  • //接收的為空時,則表示客戶端下線,跳出迴圈 if (r == 0) { break; }; string str = Encoding.UTF8.GetString(buffer, 0, r); //RemoteEndPoint:可以得到遠程客戶端的IP和埠號。 ShowMsg(socketSe... ...
  • 本文梯子 前言 1、.net core 框架性能測試 2、.net core 執行過程 3、中間件執行過程 4、AOP切麵 5、整體框架結構與資料庫表UML 一、創建第一個Core 1、SDK 安裝 2、新建項目 2、新建項目(3.0SDK) 3、項目整體結構分析 二、重要文件說明 1、Progra ...
x