使用 Vue 和 Markdown 创建简单博客
我并不是一个“天生”的前端开发者,学习 vue 并利用我的 Java 知识重新学习 JavaScript 对我来说是一个挑战。更不用说那些打包器、构建器,比如 webpack、gulp、grunt 和其他一些奇怪的工具,它们增加了学习现代 Web 框架的整体复杂性。但如果说有一件事帮助我学习新技术,那就是直接开始构建小项目。教学也是一种被广泛接受的技术,它将极大地帮助你学习。这并不意味着你必须聚集一大群听众来听你教一些东西。有时,你需要的听众只是你自己。写下你所学的东西,目的是把它教给过去(或未来)愚蠢的自己。我知道这有点奇怪和矛盾,但请相信我。
正是出于这种想法,一年前,我决定在我的个人网站上添加一个博客版块,纯粹为了好玩。我刚刚把之前的作品集移植到了 Vue,想知道有什么最简单的方法可以添加博客功能。我知道,有些人可能会对使用前端框架开发一个简陋的作品集网站感到不满。但如果你的目标只是让自己上手并熟悉一个框架,我认为用它来构建你的作品集网站是一个不错的开始。这是一个简单的项目,可以让你真正熟悉基础知识,并给你足够的动力去完成它。
这就是我做的。这篇文章讲述了我如何在我的 Vue 作品集网站上添加博客功能。我的思考过程、决策、方法以及一些需要注意的地方。
我从一开始就知道,我希望它非常简单。我知道 ButterCMS 很好,但我不想让它变得太复杂,毕竟这只是一个简单的个人网站。我真正想要的是像每次写新文章时硬编码并提交一个新网页一样简单,但又足够简单,让我不必费心编写 HTML 标记。这时 Markdown 就派上用场了。Markdown 是一种非常流行的轻量级标记语言,正好符合我的要求。
那就这么定了。归根结底,我只想完成两件事:
- 能够用 Markdown 写我的博客
- Vue 必须能够将这些标记显示为常规 html 页面
本质上,为了实现这两个目标,我只需要弄清楚 Vue 如何解析 Markdown。现在,我并不是 Webpack 的专家,几年前我搭建网站的时候,甚至都不知道如何自己配置 Webpack,我当时为了学习当时正在学习的框架而推迟了配置。不过,接触了一段时间后,我能够理解一些概念和它的作用。在这种情况下,我知道我需要一个能够解析 Markdown 的 Webpack 加载器。就像.vue
文件通过 Webpack 传输后,由于……而能够正常输出一样vue-loader
。
Vue-Markdown-加载器
意识到这一点后,我做的第一件事就是去谷歌搜索“vue markdown loader”。结果第一个搜索结果就是QingWei-Li的vue-markdown-loader 仓库vue.config.js
。文档很简单,我在我的 上添加了这段代码:
module.exports = {
chainWebpack(config){
config.module.rule('md')
.test(/\.md/)
.use('vue-loader')
.loader('vue-loader')
.end()
.use('vue-markdown-loader')
.loader('vue-markdown-loader/lib/markdown-compiler')
.options({
raw: true
})
}
}
就像如何在 Vue 中vue-loader
实现.vue
文件vue-markdown-loader
一样.md
。本质上,Markdown 现在可以解释为 Vue 组件。为了测试这一点,components/
我在我的目录中创建了以下内容foo.md
:
# This is a test
## Heading 2
_lorem ipsum_ dolor __amet__
将其作为组件导入App.vue
并在模板中使用它。
<template>
<div id="app">
<foo />
...
</div>
</template>
<script>
import Foo from '@/components/foo.md'
export default {
components: { Foo },
...
}
</script>
快速yarn serve
访问一下localhost
,瞧!成功了!
至此,我们已经验证了 Vue 项目可以理解并渲染 Markdown。现在我们可以用 Markdown 编写博客.md
,并在任何需要的地方引用它们。由于它是一个组件,我们可以将它用作路由组件,例如router.js
:
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Foo from './components/foo.md'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{ path: '/', name: 'home', component: Home },
{ path: '/foo', name: 'foo', component: Foo }
]
})
现在,每次访问/foo
它都会渲染我们的 markdown 内容foo.md
。很简洁吧?这样就完成了,但如果能简化一下添加新文章的流程岂不是更好?我们可以创建一个单独的文件,把所有博客文章都放进去,每当有新文章发布时,我们都会更新这个文件——真是巧妙的间接方法。
[
"foo",
"another-post",
]
我们必须稍微改变一下注册路由组件的方式。我们必须以编程方式构建这些路由,并使用动态导入来实现动态组件注册:
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Blogs from './statics/blogs.json'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{ path: '/', name: 'home', component: Home },
...Blogs.map(entry => ({
path: `/${entry}`,
name: entry,
component: () => import(`./markdowns/${entry}.md`)
}))
]
})
注意,在上面的代码中,我们将 markdown 文件名分配给了path
和name
。为了结构更合理,我们将所有 markdown 文件放在了各自的目录中。这样,router.js
每次添加新博客文章时,我们就不用再费力地处理它们了。现在,唯一要做的就是创建指向它们的实际链接。
整合起来
利用我们所学的知识,我把所有东西整合在一起,并在此链接上创建了一个可运行的演示。您也可以在此存储库中查看代码。与我们之前的实验相比,有一些细微的变化。
首先我们看一下目录结构:
请注意,我创建了子目录2019
和stories
,分别指向博客的不同版块。这会稍微改变我们路由的结构,但会极大地改善整体的 UI。
正在查看statics/data/blogs.json
:
{
"2019": [
{
"id": "vue-markdown-blog",
"date": "March 10, 2019",
"title": "Creating a Simple Blog using Vue + Markdown",
"description": "Simple and neat way to add a blogging feature to add on your website."
}
],
"stories": [
{
"id": "maud-sparrow",
"date": "April 21, 2018",
"title": "Maud Sparrow and the Four Wicked Horses",
"description": "Once upon a time there was a generous girl called Maud Sparrow. She was on the way to see her Albert Connor, when she decided to take a short cut through Spittleton Woods..."
},
{
"id": "nico-borbaki",
"date": "May 5, 2018",
"title": "Nefarious Nico Borbaki",
"description": "Nico Borbaki looked at the enchanted newspaper in his hands and felt conflicted..."
},
{
"id": "jack-butterscotch",
"date": "June 10, 2018",
"title": "Jack Butterscotch | The Dragon",
"description": "In a hole there lived a tender, silver dragon named Jack Butterscotch. Not an enchanted red, stripy hole, filled with flamingos and a cold smell, nor yet a short, hairy, skinny hole with nothing in it to sit down on or to eat: it was a dragon-hole, and that means happiness..."
},
{
"id": "tiny-arrow-wars",
"date": "July 27, 2018",
"title": "Galactic Tiny Arrow Wars",
"description": "A long, long time ago in a tiny, tiny galaxy..."
},
{
"id": "gargoyle-club",
"date": "August 7, 2018",
"title": "Club of Gargoyle",
"description": "Molly Thornhill suspected something was a little off when her creepy daddy tried to club her when she was just six years old. Nevertheless, she lived a relatively normal life among other humans."
},
{
"id": "simon-plumb",
"date": "September 20, 2018",
"title": "Simon Plumb and the Two Kind Gerbils",
"description": "Once upon a time there was a virtuous boy called Simon Plumb. He was on the way to see his Annie Superhalk, when he decided to take a short cut through Thetford Forest..."
}
]
}
我将其转换为一个对象,而不是字符串数组。每个键都指向博客版块,并与其子目录匹配。每个键下的对象数组指向实际的博客条目。UIdate
中还会用到许多其他类似的属性,但最重要的是id
指向实际 Markdown 组件的那个。
(此外,您可以将所有内容放在一个js
文件中,而不是json
。这是个人喜好问题。将其放在一个js
文件中可能会使您的生产构建在 webpack 完成后小得多。但是将其保存在json
下的文件中将statics
充当一个简单的 REST 端点,我可以GET
从中发出请求,如果我最终将它集成到其他 UI 中,这将很有用。)
我已经实现了所有这些额外的更改,以便能够显示这样的 UI:
我们最后需要做的是调整router.js
。它的作用基本上就是将博客版块映射到路由对象中。每个版块下的条目将成为其各自路由对象的子对象。基本上,我们将能够使用以下格式的路径访问我们的博客条目:${section}/${blog entry}
,例如:2019/vue-markdown-blog
。
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
Vue.use(Router)
import BlogEntries from './statics/data/blogs.json';
const blogRoutes = Object.keys(BlogEntries).map(section => {
const children = BlogEntries[section].map(child => ({
path: child.id,
name: child.id,
component: () => import(`./markdowns/${section}/${child.id}.md`)
}))
return {
path: `/${section}`,
name: section,
component: () => import('./views/Blog.vue'),
children
}
})
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
...blogRoutes
]
})
这项设置给我们的博客带来了另一项关键改进:样式调整。请注意,它Blog.vue
充当了我们博客条目的包装器布局组件。它包含以下代码:
<template>
<div class="blog">
<router-view />
<router-link to="/" tag="a" class="back">« Back</router-link>
</div>
</template>
造型
现在,我们可以随意设置渲染后的 Markdown 样式了。关键在于<router-view />
使用/deep/
选择器来指定最终要显示的内容。如下所示:
<template>
<div class="blog">
<router-view />
<router-link to="/" tag="a" class="back">« Back</router-link>
</div>
</template>
<style lang="scss" scoped>
.blog {
max-width: 50vw;
margin: 10rem auto;
/deep/ {
h1 {
font-size: 3rem;
margin-bottom: .2rem;
color: #42b883;
}
h4 {
margin-bottom: 3rem;
color: #35495e;
}
}
}
</style>
访问markedstyle.com获取更多 Markdown 样式灵感。你甚至可以导入外部 Markdown 样式,但别忘了用.blog /deep/
选择器将其正确包装起来。例如:
/** external style **/
.blog /deep/ {
/** paste external markdown style here **/
}
不过需要注意的是,应该尽可能避免使用 ,/deep/
因为它在某种程度上违背了 的初衷scoped
,而且 Chrome 已经弃用了它。Vue-loader 仍然支持它,所以它仍然有效。但我认为在这种情况下它还是有用的。我们不想污染全局 CSS 作用域,所以希望将样式包含在Blog.vue
的子元素中,所以我们会同时使用scoped
和/deep/
。(不过,如果有人对此有更好的解决方案,我很乐意采纳。)
就这样!一个简单的博客功能,无需使用任何其他第三方服务,即可为您的网站带来。只需简单易用的 Vue。
进一步改进
如果您想更进一步,可以使用博客文章中的附加字段来添加页面元数据,date
例如title
,,,description
等等。如果您还实现了某种社交分享功能,那么这种方法会非常有效。您可以访问我的网站来查看具体操作:www.josephharveyangeles.com