使用Node.js开发一个静态博客生成器(三)——代码高亮

实现代码很简单,我们知道有highlight.js可以直接在浏览器中给pre标签中的代码加入高亮标签的样式,然后引入对于的高亮样式CSS文件即可,我们可以在使用任何程序的网站中使用这个库。
但是highlight.js经过压缩后还是有几十kb,同样,在浏览器中渲染HTML标签也会耗时耗资源,所以干脆在服务端(生成页面的时候)就给代码加上高亮样式,我这里举两个例子,分别是两个Markdown生成器的使用方法:

MarkdownIt

这是我现在在用的解析器,在生成md实例的时候可以直接启用highlight.js插件。

const MarkdownIt = require('markdown-it');
const hljs = require('highlight.js');
const md = new MarkdownIt({
  highlight: function(str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return '<pre><code class="hljs ' + lang + '">' +
          hljs.highlight(lang, str, true).value +
          '</code></pre>';
      } catch (__) {}
    }
    return '<pre><code>' + md.utils.escapeHtml(str) + '</code></pre>';
  }
});

然后使用md.render()方法解析MD时就会自动加上hljs的高亮样式了。

marked

这是另一款非常受欢迎的markdown解析器,我曾经也尝试使用过,相比markdownit,我认为它配置起来更加简单,同样,它也可以直接启用highlight.js拓展

const marked = require('marked');
const hljs = require('highlight.js');
marked.setOptions({
  highlight: (code) => hljs.highlightAuto(code).value,
})
marked(markdown)

使用Node.js开发一个静态博客生成器(二)——模板

art-template 是一个简约、超快的模板引擎。它采用作用域预声明的技术来优化模板渲染速度,从而获得接近 JavaScript 极限的运行性能,并且同时支持 NodeJS 和浏览器。

一个静态站点生成器自然要用到模板语言,如jade,ejs,handlebars,mustache等,这里我使用的是国人开发的art-template,据说速度是相当的快。

其实我在之前一直是不知道怎么使用模板语言,我原先对于模板文件的工作方式是这样理解的:

由JS获取要应用到模板文件的数据然后传入模板文件生成文件

而其实真正的方式是这样的:

由JS读取模板文件并将模板需要的数据作为参数传给模板函数然后得到生成好的数据,这一切的过程其实都在JS中实现,并不是我以为的要由模板文件来进行编译。

为什么要选择artTemplate呢

  • 可以使用传统模板语法,即双大括号!(这是我最喜欢的语法)

  • 查询文档很方便(有中文版,因为是由国人开发的)

  • 速度快,这点其实并没有太大的感觉

使用方法

使用方法非常简单,可以参考其中文文档

我这里简单说下如何将它与现有代码即这个博客生成器结合起来

Markdown解析器

这里我选择markdown-it,类似的还有marked、showdown等

var md = require('markdown-it')();
var result = md.render('# markdown-it rulezz!');
console.log(result) // <h1>markdown-it rulezz!</h1>

因为之前我们使用front-matter对markdown文件进行了解析,得到的文章内容应该是content.body,然后使用markdown-it将这个值解析为html即可。

模板编译

我们这里只需要用到一个核心方法:

template.render(source, data, options);

其中我们将模板文件作为source传进去,然后data应该为一个对象,额外的选项暂时不需要。

模板变量

接着来看一下一个博客生成器中应该用到哪些变量是必须得:

站点信息(site对象):
  标题(title):config.json文件定义
  链接(url):config.json文件定义
  所有文章(posts数组):posts.json中的posts值
  所有分类(categories对象):config.json文件定义,应为键对值对象
页面信息(page对象):
  标题(title):由front-matter解析得到的content.attributes.title
  日期(date):由front-matter解析得到的content.attributes.date(只有文章有)
  所属分类(category):由front-matter解析得到的content.attributes.content

然后将这些值全部放入一个对象中供template.render调用,为了方便复用,需要判断当前的页面是页面还是文章或者是分类页,所以我将它写成函数,判断当前是什么页面来进行解析并返回一个对象即可。

具体data.js的实现方式在这里是代码

编辑模板

然后就可以来编写模板了(主题)

可以这样来循环所有的文章

<ul>
  {{each site.posts}}
  <li>
    <a href="{{$value.path}}">{{$value.title}}</a>
    <span>{{$value.date}}</span>
  </li>
  {{/each}}
</ul>

得到所有的分类

{{each site.categories}}
<li class="pure-menu-item">
  <a class="pure-menu-link" href="{{site.url}}/{{$value.path}}">{{$value.title}}</a>
</li>
{{/each}}

对于单个分类页的变量储存方式,我像Jekyll那样将当前分类页的名称作为分类变量的第一项,然后将当前分类下的所有文章作为一个数组存到当前分类数组的第二项,所以可以这样来输出:

<h1>{{category[0]}}</h1> <!-- 分类页标题 -->
<!-- 文章 -->
{{each category[1]}}
<li>
  <a href="{{$value.path}}">{{$value.title}}</a>
  <span>{{$value.date}}</span>
</li>
{{/each}}

跟Jekyll很像是吧

使用Node.js开发一个静态博客生成器(一)

自己用Node.js写了一个非常简单的静态博客生成器,也就是现在这个网站,源代码在Github,这里记录一下开发过程

读取所有的Markdown文件

首先这个博客生成器是将Markdown文件生成为HTML,所以第一步要做的是读取所有的Markdown源文件,然后将其内容和信息都存入一个JSON文件中。

处理文件

rd模块可以遍历文件夹下所有的文件。

我们只需要使用readFileSync(dir)方法就可以返回文件夹下所有的文件的一个数组,然后使用forEach方法来遍历数组中每一个元素(即每个文件的路径),接着读取每一个文件的内容,使用fs模块的readFileSync方法来读取这个文件的内容。

let mdContent = fs.readFileSync(filePath, 'utf-8');

然后我们写一个函数来返回这个文件的文件名

const formatFileName = filePath => path.basename(filePath).slice(0, -3).replace(/ /g, '-').toLocaleLowerCase();

我们这里将文件中所有的空格替换成-符号,然后将所有的大写转换为小写。

然后这里我们使用到一个front-matter模块来读取这个md文件中的头信息,就像Jekyll和Hexo那样,我们需要在文章或页面的文件头部需要定义这篇文章的信息,如标题、时间、分类等。

var fs = require('fs');
var fm = require('front-matter');
fs.readFile('./example.md', 'utf8', function(err, data){
  if (err) throw err
  var content = fm(data)
  console.log(content)
})

fm会解析文件内容并返回一个对象:

{
    attributes: { // 头信息
        title: '',
        category: ''
    },
    body: '' // 内容主体
}

存入‘数据库’

接着我们将读取到的所有文件的内容和信息存入‘数据库’中,这里我们用一个JSON文件来代替数据库储存文章内容。

我们这里使用lowdb这个库,具体使用方法可以浏览这里,我这里对一个JSON文件初始化一个posts数组来储存所有文章。

_postsDb.defaults({posts: []})
  .write();

接着写一个方法,然后再之前遍历所有文章的时候将文章信息传入这个方法

this.appendPostsDb = currentDb => {
    _postsDb.get('posts')
      .push(
        {
          path: currentDb.path,
          title: currentDb.title,
          date: currentDb.date(),
          category: currentDb.category,
          content: currentDb.conetnt
        }
      )
      .write();
    // console.log("write db")
  };

然后,在每次遍历到文章的时候,使用fm或者其它方法得到这个文章的相关信息并存入一个对象,之后再将这个对象传给appendPostsDb()函数。

const post = {
  title: fContent.attributes.title,
  date: f => moment(fContent.attributes.date).format(f || 'YYYY-MM-DD'),
  category: fContent.attributes.category,
  conetnt: md.render(fContent.body),
  path: postLink(fileName)
};
writeDb.appendPostsDb(post);

对数据库进行排序

这样直接存入数据库后,是没有顺序的,这样很不方便以后我们直接读取所有文章,所有我们这里按照日期先后来给这个JSON排序一下。

function formatDate(item) {
  return parseInt(item.replace(/-/g, ''));
}
let posts = dbPosts.sort((a, b) => formatDate(b.date) - formatDate(a.date));

排序方法也是非常简单,直接将日期转换为一个数字字符串然后直接sort比较即可。