Zainking / Blog

使用 Github issues 创建的中文博客

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

50行代码实现Hexo等静态博客引擎的核心原理

Zainking opened this issue · comments

本文采用的语言是NodeJS,需要读者有一定的CSS、HTML、JS基础阅读。

像hexo、Jekyll等静态博客引擎的主要功能为将模板与数据通过程序进行拼接编译,生成可以浏览的网页。大量不同的数据与相同的模板拼接即可形成展示的静态网页组。多个网页组相结合即可生成静态网站。

本篇文章将要实现一个以Pug模板引擎和Stylus样式引擎来编译出一个静态网页的工具,其中的模板与数据相互分离,模板文件、数据、输出路径也可以进行设置。

工程代码:https://github.com/Zainking/template2html

构思

我们想要实现的目标是一个将html模板和数据分离的静态网页产出工具。实际上这种东西的核心就是模板引擎。我们选择使用Pug模板引擎来实现功能;并且为了统一语法,使用类Python的缩进语法书写样式,我们同时使用Stylus样式引擎书写样式。
我们将每份数据保存在json文件中,将模板和样式也分别写好放置在文件中,在程序运行时统一读取编译,再将编译后的字符串保存在html文件中即可完成需求。

编写模板和样式文件

下面是一个Pug语法实例

doctype html
html(lang="en")
  head
    title= pageTitle
    script(type='text/javascript').
      if (foo) bar(1 + 5)
  body
    h1 Pug - node template engine
    #container.col
      if youAreUsingPug
        p You are amazing
      else
        p Get on it!
      p.
        Pug is a terse and simple templating language with a
        strong focus on performance and powerful features.

编译成为下面的Html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Pug</title>
    <script type="text/javascript">
      if (foo) bar(1 + 5)
    </script>
  </head>
  <body>
    <h1>Pug - node template engine</h1>
    <div id="container" class="col">
      <p>You are amazing</p>
      <p>Pug is a terse and simple templating language with a strong focus on performance and powerful features.</p>
    </div>
  </body>
</html>

这是它的语法功能。除此之外,模板引擎的最大的用处就是在模板之中填充数据。对于Pug而言,它会在编译时将所有模板字符串内容中的#{data}用编译时传入的同名数据data字符串进行替换。我们可以在 https://pugjs.org/api/getting-started.html 学习其详细语法。

我们用Pug语法书写一个简历的模板文件放在template/index.pug

Stylus是和Sass、Less相似的样式引擎,提供了更简便的CSS写法。
下面是一个语法示例:

body
  font: 12px Helvetica, Arial, sans-serif;

a.button
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;

它会被Stylus引擎编译成如下CSS

body {
  font: 12px Helvetica, Arial, sans-serif;
}
a.button {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}

我们可以在 http://stylus-lang.com/ 学习其详细语法,我们用Stylus语法书写一个简历的样式文件放在template/style.styl

需要注意,对于本需求,其实CSS引擎并非是必要的。

至此,我们完成了模板文件和样式文件的编写。我们将需要填充的简历数据以json格式保存在data/0.json。注意:json中数据的明明应该与模板文件中数据名称一一对应。

读取模板、样式和数据

编写readString方法,使NodeJS可以读取文件中的字符串,以便读取模板文件:

function readString(path) {
  return fs.readFileSync(path).toString()
}

像这样使用它就可以读取到刚刚编写的模板文件里面的字符串了:

const pugStr = readString(pugPath)
const stylStr = readString(stylPath)

同时因为数据是json格式,所以可以直接用require引入:

const data = require(dataPath)

注意,为了后期修改方面,我们将pugPath, stylPath, dataPath, outputPath(输出文件夹路径)都保存到了配置文件中。

至此,我们完成了模板、样式和数据的读取。

编译Html字符串

暂且先把目标定为将所有样式和模板、数据都打包到一个html中去。
先使用Stylus引擎编译Stylus字符串,此时我们实现stylus2CssAsync方法将Stylus的编译过程Promise化,这样之后写代码可以用async / await语法,会比较好看。

function stylus2CssAsync(stylStr) {
  return new Promise(resolve => stylus.render(stylStr, (err, css) => resolve(css)))
}

之后先调用这个方法编译Stylus字符串,再将编译结果和数据一同放入模板字符串。

const __style = await stylus2CssAsync(stylStr)
const html = pug.render(pugStr, { ...data, __style })

这样我们就可以获得所需求的html字符串了。

将字符串输出为Html文件

获得了网页对应的html字符串,我们就可以编写对应的output函数来输出字符串到文件了:

function output(path, data) {
  fs.mkdirSync(path)
  fs.writeFileSync(`${path}/index.html`, data)
}

这个时候在根目录下执行node index.js,就可以看见生成了dist目录,并在目录下生成了对应的index.html文件。

但是如果你想再修改一下模板,重新生成文件,这个时候就会报错:因为之前的目录已经存在,没法再重新生成一个同名目录了。这个时候我们可以采用编译时比较通用的手段:先删除之前存在的产物目录。这个时候我们就需要一个递归删除函数:

function delDir(path) {
  let files = [];
  if (fs.existsSync(path)) {
    files = fs.readdirSync(path);
    files.forEach((file, index) => {
      let curPath = path + "/" + file;
      if (fs.statSync(curPath).isDirectory()) {
        delDir(curPath); //递归删除文件夹
      } else {
        fs.unlinkSync(curPath); //删除文件
      }
    });
    fs.rmdirSync(path);
  }
}

完成delDir函数之后,将它放到output的开始:

function output(path, data) {
  delDir(path)
  fs.mkdirSync(path)
  fs.writeFileSync(`${path}/index.html`, data)
}

这样我们就完成了最终的输出函数。

至此,我们成功生成了产物dist/index.html,用浏览器即可运行。此时可以过修改数据随意产出不同的网页。也可以通过修改模板来生成新的网页。我们最初的需求大致完成。

Tips

读完这篇文章,你可以尝试从这些方面进行拓展:

  1. 本项目仓库中的代码有很多地方写的不够好,你能找出来吗?来提PR吧!
  2. 尝试批量编译不同的数据,形成静态网页组。
  3. 尝试编写不同的模板,用a标签尝试将网页组进行组合,形成静态网站。
  4. 尝试引入markdown等富文本展现工具,来在你的数据中书写富文本并在网页中展现。
  5. 尝试开源你的项目,引导更多人为你的项目产出模板(对应的模式就是Hexo的各种主题)。
  6. 研究一下模板引擎是如何工作的,试着写一个自己的模板引擎。
  7. 使用uglifyjs之类的工具对编译产物进行压缩混淆。

相关仓库

  1. 50行实现一个模板引擎的核心原理 在这个仓库中,我实现了一个不改变html语法的模板引擎,如果想要实现一个像Pug那样拥有自己的一套语法,并且编译成html的模板引擎,你需要实现属于自己语法的语法解析器,然后使用类似node-html-parser的工具将其转化为html代码。
  2. 自动部署某个文件夹内的静态文件到 Github Pages 类似于一个通用的 hexo-deployer-git 插件,我在之前的文章基于githooks和node的自动部署环境搭建中提到过它。事实上后来想一想觉得直接用Shell脚本可能还来的更方便快捷一点。