在我转前端以来,一直想要实现一个愿望:
“自己搭建一个可以自动解析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系统,如果你有任何不清楚的地方,可以留下你的评论,或者与我联系~
转载请注明出处。