如何快速实现 markdown 转 HTML 文档?

我想要在 Github 上开一个主题博客,我希望通过 Markdown 语法写作,然后生成 HTML 并附带自定义样式显示在网页上。

我找到了 gulp-markdown 这个库,看起来符合我的需求场景。然而这个库有一个问题,他只能将 Markdown 语法书写的文字转换为 HTML 标签,但并不能自动添加 doctype 文档声明,这就意味着生成的 HTML 文档并不合法。

# 标题

我是正文。

会被编译为

<h1>标题</h1>
<p>我是正文</p>

而不是

<!doctype html>
<html>
    <head>
        <title>XXX<title>
    </head>
    <body>
        <h1>标题</h1>
        <p>我是正文</p>
    </body>
</html>

这就很尴尬了,我不知道这个库的作者为什么要这么设计这个库,我觉得它应该有一个配置参数可以自动添加 HTML 文档声明等信息,于是我去查找其文档(注意,它的文档只能FQ才能看),然而呵呵并没有。

这就很尴尬了,怎么办呢?首先我想到的是,使用其他类似 HTML 模版的库来组合实现我想要的效果,然而搜了一圈发现,并没有合适的。因为这类库都需要首先在 HTML 文档中使用其模版语法标记 HTML 要插入的位置,然后在声明要插入的内容。可是我们并没有 HTML 模版,套路不同。

所以这个思路行不通,接下来我想到的方案是:

  1. 写一个 Gulp 插件解决这个问题;
  2. 这个问题肯定不是我第一次遇见,我先查查别人的解决方案;

懒惰是第一生产力,我当然先选择方案二,果不其然,全网看下来,貌似只有一个台湾小哥想到了一个解决方案,大体的思路是使用大陆前端娱乐圈知名人士方方老师写的一个 gulp-html-extend 库,实现一种类模版语言的 HTML 混入。为了实现这一点,你需要在每个 Markdown 文档里写下模版语言语法,类似这样:

<!-- @ @master  = ../../layout/master.html-->
<!-- @ @block  =  content-->

# 我是标题
我是正文

<!-- @ @close-->

额,很显然这个解决方案并不优雅。为什么我要在每次开心书写 Markdown 的时候先写这堆奇怪的注释啊。我拒绝。

那么我们又回到了最初的起点,那个残酷的方案一又萦绕在我心头,要写一个 Gulp 插件吗?不!我的懒惰不允许我就在这里放弃!

当你不知所措的时候,不妨回到最初的起点,思考问题的本质是什么,这通常会激发我的灵感。这次我也这么做了,果不其然有所收获。

让我们一起想想,我实际要做的无非就是把由 gulp-markdown 生成的 HTML 标签插入到一个这样的「壳子」里:

<!doctype html>
<html>
    <head>
        <title>XXX<title>
    </head>
    <body>
        <!-- 在这里插入 -->
    </body>
</html>

所以我只需要在每个生成的 HTML 文件的头尾插入这些字符:

<!doctype html>
<html>
    <head>
        <title>XXX<title>
    </head>
    <body>

和这些字符:

    </body>
</html>

就行了,所以问题转变为一个文件字符串拼接的问题,我很快就找到了 gulp-header 这个库,它能实现我们在每个文件头部插入字符的需求,而根据「对称就是美」定理,当然会有一个 gulp-footer 库来解决我们后半部分的需求,这样我们的问题就被完美解决了!

但是,老实讲这样的方案还是不够优雅,因为我们给文件前后插入太多字符了,我写代码习惯遵照 make it simple and stupid 的原则,因此对这个方案并不满意,我希望只插入最少的字符解决这个问题,因此接下来的问题就是:最少能插入多少字符保障 HTML 文档的规范性?

进过一番资料的查找,我发现,原来 <html><head><body> 标签都是可以省略的!只要一个 HTML 文档具备 <!doctype html> 文档声明和 <title> 标签,对于现代浏览器而言,就是一个合法合规的 HTML 文档!浏览器会自动生成 <html><head><body> 标签,并且把第一个不能放入 <head> 标签的标签和随后的标签都插入 <body> 标签中。没想到浏览器这么智能吧,我也没想到。

现在真相大白,解决这个问题的方案就很简单了,我们就只用在生成的 HTML 文档中添加最少的字符,这是我的 gulpfile.js 的配置:

const gulp = require('gulp')
const markdown = require('gulp-markdown')
const header = require('gulp-header')

gulp.task('default', () =>
  gulp
    .src('blog/**/*.md')
    .pipe(header('<!doctype html><title>Blog</title>\n\r'))
    .pipe(
      markdown({
        headerIds: false,
      }),
    )
    .pipe(gulp.dest('html')),
)

搞定!

总结

通过这一番调研,我找到了解决 Markdown 生成规范的 HTML 文档的一种比较优雅的解决方案,并在调研过程中学到了关于 HTML 文档必备标签的相关知识,可谓是获益匪浅。希望阅读这篇文章的你也能够有所收获。

最后我再啰嗦两句,可能有人会问,为什么不搞个浏览器同步渲染,所见即所得,同步编辑岂不是更加炫酷,这个其实我有想过,最终决定不搞这个的理由是我觉得既然使用了 Markdown 语法,我的目的就是专心写作,我并不想因为要看到样式而分心,因此我在编辑器中只管我要写什么,当我写好 CSS 后,我就对内容如何呈现已经心中有数了,因此我认为「所见即所得」的功能没什么用。当然,如果你还是想要添加,相信对你而言也不是什么难事,毕竟方法之前的那位台湾小哥已经给出了。