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