乾貨!從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
完成初始化並安裝依賴後,檢視到的專案目錄如下:
其他依賴安裝
@vue/composition-api
使用 Vue 3.0
語法必要依賴
npm install @vue/composition-api --save
graphql-request
簡單輕巧的的 graphQL
客戶端。同樣還有 Apollo
, Relay
等可以進行選擇。選擇它的理由是:簡單輕巧,以及基於 Promise
。
npm install graphql-request --save
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 要求的鑑權。
這裡有兩個坑,只有在打包提交程式碼後才發現:
token
不能直接提交到Github
,否則再使用時會發現失效。這裡我猜測是安全掃描機制,所以我上面將token
分成兩部分拼接繞過這個。Content-Type
需要設定成x-www-form-urlencoded
,否則會跨域請求失敗。
接下來我們將修改 main.js
檔案,將請求方法掛載到 Vue例項
上。
...
import Vue from 'vue';
import Http from './api/api';
Vue.prototype.$http = Http;
...
Ⅱ.文章列表開發
檢視原始碼
主要將介紹 composition-api
和 graphqh
相關,其餘部分請查閱 Vue文件
我們首先需要引入 composition-api
,修改 main.js
檔案
...
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);
...
然後新建一個 Archives.vue
去承接頁面內容。
首先的變動是生命週期的變動,使用 setup
函式代替了之前的 beforeCreate
和 created
鉤子。值得注意的有兩點:
- 該函式和
Templates
一起使用時返回一個給template
使用的資料物件。 - 該函式內沒有
this
物件,需使用context.root
獲取到根例項物件。
...
export default {
setup (props, context) {
// 通過context.root獲取到根例項,找到之前掛載在Vue例項上的請求方法
context.root.$http(xxx)
}
}
...
資料查詢的語法參考 Github Api。
我這裡是以 blog
倉庫 的 issue
來作為文章的,所以我這裡的查詢語法大致意思:
按照 owner
和 name
去查倉庫, owner
是 Github
賬號,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();
}
});
};
...
Ⅳ.文章詳情開發
檢視原始碼
文章詳情分成兩部分:文章詳情查詢和文章評論。
- 文章詳情查詢
這裡首先引入 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;
};
- 文章評論部分
這裡我採用的是 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)