乾貨!從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)


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

更多相關文章
  • https://blog.csdn.net/qq_18683985/article/details/97374288 ...
  • 寫了一個自定義的UIView,其中包含代理 <!--5f39ae17-8c62-4a45-bc43-b32064c9388a:W3siYmxvY2tJZCI6IjIwMTAtMTU3MTAzNjc3OTIyNiIsImJsb2NrVHlwZSI6ImltYWdlIiwic3R5bGVzIjp7ImJ ...
  • 「柒留言」更新的換國旗頭像小功能,獲取頭像顯示模糊... 1、頭像模糊 國慶之前,更新了「柒留言」小程式加國旗頭像的小功能,但是頭像模糊這個坑我在發佈新版之前還沒解決。 一直以為是代碼出了問題,各種搜索,巧的是正好也有類似的答案,然後我就被帶進鍋里了,弄了半天還是模糊,無奈之下弄了個用戶自行上傳圖片 ...
  • 直接貼代碼吧: html代碼 css代碼: 實現效果: 知識點總結:1.uli li橫向排列可使用float,之前經常使用diplay:flex;這個可能會導致一些問題,後面遇到再添上 2.父元素:hover >子元素{ dispay:block/none}:通過父元素hover屬性控制子元素顯示與 ...
  • css 效果 ...
  • <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv ...
  • 初識Nodejs Node.js的誕生 作者Ryan Dahl 瑞恩·達爾 2004 紐約 讀數學博士 2006 退學到智利 轉向開發 2009.5對外宣佈node項目,年底js大會發表演講 2010 加入Joyent雲計算公司 2012 退居幕後 作者Ryan Dahl 瑞恩·達爾 2004 紐約 ...
  • 通配符選擇器 * 與任何元素匹配 派生選擇器: 後代選擇器(包含選擇器):後代選擇器可以選擇作為元素後代的元素 A B 對A元素中的B元素應用樣式 後代選擇器中兩個元素間的層次間隔可以是無限的(也可A B C) ,以上 A B 應用樣式會選擇從A繼承的所有B元素 子元素選擇器:只選擇某個元素的子元素 ...
一周排行
  • 一、直接使用線程的問題每次都要創建Thread對象,並向操作系統申請創建一個線程,這是需要耗費CPU時間和記憶體資源的。無法直接獲取線程函數返回值無法直接捕捉線程函數內發生的異常 使用線程池可以解決第一個問題二、.NET中的線程池 在這裡只簡單的介紹一下ThreadPool,由於TPL的存在,我工作中... ...
  • 上次課程我們新建了管理員的模板頁。 本次我們就完善這個模板頁,順便加入樣式和一些基本的組件,配置好整個項目的UI風格。 一、引入 共用的css和js文件 後端庫用nuget, 前端庫用libman. 右鍵wwwroot文件夾,選擇菜單 Add / Client-Side Library 我們使用ad ...
  • 場景 在使用IIS部署ASP.NET的Web項目時提示: InvalidOperationException:未能映射路徑“/” 註: 博客: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載。 實現 ...
  • 場景 ASP.NET中新建MVC項目並連接SqlServer資料庫實現增刪改查: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/107024544 在上面實現了新建簡單的MVC項目以及連接資料庫實現簡單的增刪改查後怎樣將網站部署到 ...
  • --先給GridView控制項註冊滑鼠按下事件gv.MouseDown += new System.Windows.Forms.MouseEventHandler(this.gv_MouseDown); --在滑鼠按下事件裡面增加滑鼠右鍵判斷,並增加滑鼠右鍵菜單複製單元格功能。 private voi ...
  • 用C#代替Javascript來做Web應用,是有多爽? 今天聊聊 Blazor。 Blazor 是一個 Web UI 框架。這個框架允許開發者使用 C# 來創建可運行於瀏覽器的具有完全交互 UI 的 Web 應用。 可以理解為,這是一個 C# 語言的 Vue / Angular / React,可 ...
  • 場景 ASP.NET中新建Web網站並部署到IIS上(詳細圖文教程): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/107199747 在上面博客中已經將網站部署到了IIS上。 但是如果網站很大,頁面比較多,甚至每個頁面都有不少 ...
  • 從事這麼多年的.NET,這段時間來,學習另外一門技術Python。 購買相關的書籍,不停地看書。 然後在VS安裝Python,然後可以上機練習,編寫代碼...... ...
  • 一個微小的投入就會帶來巨大的突變 集群安全模式 為什麼出現集群安全模式呢? ​ Namenode啟動時,首先將鏡像文件載人記憶體,並執行編輯日誌中的各項操作。一旦在內存中成功建立文件系統元數據的映像,則創建一個新的Fsimage文件和一個空的編輯日誌。此時,** Namenode開始監聽Datanod ...
  • 1. 通過new對象實現反射機制( 對象.getClass() ) 2. 通過路徑實現反射機制( Class.forName("包名.類名") ) 3. 通過類名實現反射機制 ( 類名.Class ) class Student { private int id; String name; prot ...