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

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

首發於微信公眾號《前端成長記》,寫於 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)


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

-Advertisement-
Play Games
更多相關文章
  • 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元素 子元素選擇器:只選擇某個元素的子元素 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...