在我轉前端以來,一直想要實現一個願望:
“自己搭建一個可以自動解析Markdown文件的個人站”
今天終於實現啦,先貼上我的Blog地址
確認需求
其實一個最簡單的個人站,就是許多的HTML頁面,你只要可以用HTML寫出來就可以,然後掛到Github pages
上。但這並不是我想要的。
也有許多的人會選擇用Vuepress,Hexo,Wordpress,Jekyll等等這樣的部落格框架來搭建自己的部落格,我也都嘗試過,有很多的主題可以給你選擇,你甚至可以自己寫一個主題或者修改其他人的主題讓你的部落格變得獨一無二,但這也不是我想要的。
那,我想要的是什麼呢?
用Markdown語法書寫部落格,支援程式碼高亮。
- 部落格所有頁面都是自定義樣式。
- Markdown的YAML開頭支援自定義欄位,便於在頁面上展示。
- 在寫部落格的同時支援頁面更新,實時看到效果。
- 其他部落格基本的功能。
其實上面很多的部落格系統,或者靜態部落格生成器,都可以滿足上面大部分的條件,我沒有使用的原因主要是以下幾點:
- 我很難把控整個流程,如果我在其他人的主題頁面想要增加一些功能,很吃力。
- 對部落格的配置,都會有預料之外的效果。
- 一些主題也不完善,總是少了自己需要的功能,並且對於Markdown一些基本的功能的支援,也參差不齊。
- 頁面的構造和樣式的調整,自由度不夠。
分析需求
看到這些需求,其實重點不在於你要用什麼框架來寫。vue也好react也好甚至Jquery或者原生的JS,都可以。
重點在於你如何處理Markdown檔案,把它轉換成你需要的物件,並且在你的頁面中,可以通過路由來控制頁面的內容的切換。
簡而言之,就是兩點:
- 部落格資料
- 頁面路由
當你可以解決這兩個問題,那就解決了所有的問題,因為剩下的就是擼頁面了,天高任你飛,和太陽肩並肩。
資料的獲取
或許也可以換一個小標題,怎樣拿到Markdown裡面的資料,並且在頁面上讀取資料呢?
需要這個資料是因為考慮到,在首頁你可能需要展示所有的頁面分類,和所有的Tags,甚至所有的文章的標題和內容,因為你需要做一個部落格的檢索?
我把以上提到的所有的部落格框架的原始碼看了一遍,想看看對他們是怎麼處理這個問題的。
然後在我首先在React-static的原始碼裡面,找到了這個:Jdown
這是一個解析Markdown的包,甚至一開始我都是用這個來解析我的Markdown檔案中的YAML標籤的內容,並且我還和包的作者DanWebb聊了很多關於搭建個人站的問題。
直到我專案的最後才發現,這個包使用起來會有一些問題,對於一些過長的中文,可能他會解析失敗,我也找不到規律,對於我來說,要去閱讀他的原始碼來定位問題,需要太多的時間,然後我想找一個替代的包,來實現同樣的功能。
然後我就找到了gray-matter
我用這個包成功的把Markdown檔案的YAML頭解析為一個JSON物件。我是怎麼做的呢?
在專案(打包/編譯)的JS中:
- 遍歷一個固定目錄(也就是我所有md檔案存放的目錄),獲取到所有的以md結尾的檔案物件。
- 對每個檔案的YAML頭資訊進行轉換,拿到JSON物件。
- 對JSON物件的內容進行解析,例如取出所有的tag存放到同一個陣列中(你也可以放在頁面上來做這件事)
- 把所有的頁面的JSON物件放在一個陣列裡,用nodeJS的fs模組寫入到一個
data.js
的檔案中(這個你可以自己定義目錄)
至此,所有頁面獲取資料的過程就結束了。
在頁面上使用的時候,就只需要引入這個data.js
的檔案然後就可以拿到頁面的資料啦~
頁面路由
頁面路由是我們實現這個部落格系統的關鍵,因為在上一步,我們只是拿到了YAML
的資訊,但是我們並沒有拿到這個文件內容,就算我們拿到了內容,也需要我們把他解析為HTML之後,才可以展示出來,那現在怎麼做呢?
其實用過webpack的人都知道,webpack有一個loader,我們就是用到markdown的loader來做這樣一件事情,loader就像是一個翻譯工具,把原始檔的內容處理之後,返回新的結果,甚至可以多重翻譯之後再返回。那我們就需要用Markdown的loader.
那我們可以在路由中設定,把component設定成對應的md檔案,這時候Webpack就會使用loader來解析這個md檔案,變成我們需要的HTML頁面,同時我們也可以在解析的過程中,加入自定義的語法,增強和自定義我們的markdown。
在router檔案中的設定類似於下面這樣
{
path: "/post/2018-05-20-first",
component: () => import('../posts/2018-05-20-first.md')
}
複製程式碼
你以為就這樣簡單的結束了嗎?
太天真了少年,因為webpack是不支援import的動態引數的,也就是說,頁面跑起來之後,想要通過YAML的資訊,來拼接出router的值,是不可行的,就算你可以拿到檔名。
我們總不能寫一篇文章,就往這個router裡面加入一條記錄吧?
這一步也困擾了我很久,通過資料的蒐集和檢視其它人的原始碼,我在Vuepress的原始碼中找到了答案。尤大大是怎麼做的呢?
有興趣的朋友可以閱讀一下Vuepress的原始碼,關鍵檔案的路徑是~/lib/prepare/codegen.js
程式碼貼出來(關鍵的資訊我已經打上了註釋):
exports.genRoutesFile = async function ({
siteData: { pages },
sourceDir,
pageFiles
}) {
function genRoute ({ path: pagePath, key: componentName }, index) {
const file = pageFiles[index]
const filePath = path.resolve(sourceDir, file)
// 這一段實際上就是你的路由資訊
let code = `
{
name: ${JSON.stringify(componentName)},
path: ${JSON.stringify(pagePath)},
component: ThemeLayout,
beforeEnter: (to, from, next) => {
import(${JSON.stringify(filePath)}).then(comp => {
Vue.component(${JSON.stringify(componentName)}, comp.default)
next()
})
}
}`
const dncodedPath = decodeURIComponent(pagePath)
if (dncodedPath !== pagePath) {
code += `,
{
path: ${JSON.stringify(dncodedPath)},
redirect: ${JSON.stringify(pagePath)}
}`
}
if (/\/$/.test(pagePath)) {
code += `,
{
path: ${JSON.stringify(pagePath + 'index.html')},
redirect: ${JSON.stringify(pagePath)}
}`
}
return code
}
const notFoundRoute = `,
{
path: '*',
component: ThemeNotFound
}`
return (
// 這裡你可以放入很多其他的需要在路由檔案裡面引入的資訊
`import ThemeLayout from '@themeLayout'\n` +
`import ThemeNotFound from '@themeNotFound'\n` +
`import { injectMixins } from '@app/util'\n` +
`import rootMixins from '@app/root-mixins'\n\n` +
`injectMixins(ThemeLayout, rootMixins)\n` +
`injectMixins(ThemeNotFound, rootMixins)\n\n` +
`export const routes = [${pages.map(genRoute).join(',')}${notFoundRoute}\n]`
)
}
複製程式碼
這個檔案在做什麼呢?既然import不支援動態的引數,那我們就直接生成一個router檔案,然後使用這個router來配置我們的路由不就可以了嗎?
在自己的程式碼裡面,把這一步加入到解析markdown的YAML資訊這個步驟裡,這樣我在拿到了頁面基本資訊的同時,也進行了路由的配置。
完成圖
經過一些頁面的設計,終於完成啦,這裡也貼一下blog的原始碼,歡迎大家star
貼一波圖:
All Post頁面:
Contact 頁面:
Tags 頁面:
Post頁面:
Resume頁面:
寫在最後
這個blog系統,也零零碎碎花了接近一個月的時間,終於是告一段落了,當然這篇文章裡面會有許多我沒有提到的部分,比如怎麼部署到域名下啊,怎麼打包編譯釋出到github pages,怎麼實現一些頁面的效果。
為什麼我沒有寫這些呢?因為這些都有許多現成的答案啦。
最後新人求一波關注啦~關於這個blog系統,如果你有任何不清楚的地方,可以留下你的評論,或者與我聯絡~
轉載請註明出處。