首發於微信公衆號《前端成長記》,寫於 2019.10.12javascript
有句老話說的好,好記性不如爛筆頭。人生中,總有那麼些東西你願去執筆寫下。css
本文旨在把整個搭建的過程和遇到的問題及解決方案記錄下來,但願可以給你帶來些許幫助。html
本文涉及的主要技術:前端
個人博客vue
個人博客的折騰史分紅下面三個階段:java
基於 hexo 搭建靜態博客,結合 Github Pages 提供域名和服務器資源node
自行採購服務器和域名,進行頁面和接口的開發及部署,搭建動態博客react
基於 Github Pages 和 Github Api 搭建動態博客git
第1種方式,文章內容採用 Markdown
編寫,靜態頁面經過 hexo 生成,部署到 Github Pages 上。缺點很明顯,每次有新內容,都須要從新編譯部署。github
第2種方式,靈活度極高,能夠按需開發。缺點也很明顯,開發和維護工做量大,同時還須要服務器和域名成本。
第3種方式,採用 ISSUE
來記錄文章,自然支持 Markdown
,接口調用 Github Api,部署到 Github Pages 上。除了一次性開發外沒有任何額外成本。
顯而易見,本博客此次改版就是基於第3種方式來實現的,接下來咱們從0開始一步步作。
因爲是我的博客,技術選型能夠大膽嘗試。
筆者選擇了 vue-cli 進行項目結構的初始化,同時採用 vue3.x
的語法 Composition-Api 進行頁面開發。採用 Github API v4
,也就是 GraphQL
語法進行 API
調用。
前往 Node.js官網 下載,這裏推薦下載 LTS
穩定版。下載後按照步驟進行安裝操做便可。
Window 下記得選上
Add To Path
,保證全局命令可用
執行如下代碼全局安裝便可。
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依據進行開發。
項目總體分紅幾個頁面:
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 '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 (轉載請註明出處:chenjiahao.xyz)