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
读完这篇文章,你可以尝试从这些方面进行拓展:
- 本项目仓库中的代码有很多地方写的不够好,你能找出来吗?来提PR吧!
- 尝试批量编译不同的数据,形成静态网页组。
- 尝试编写不同的模板,用
a
标签尝试将网页组进行组合,形成静态网站。 - 尝试引入
markdown
等富文本展现工具,来在你的数据中书写富文本并在网页中展现。 - 尝试开源你的项目,引导更多人为你的项目产出模板(对应的模式就是Hexo的各种主题)。
- 研究一下模板引擎是如何工作的,试着写一个自己的模板引擎。
- 使用
uglifyjs
之类的工具对编译产物进行压缩混淆。
相关仓库
- 50行实现一个模板引擎的核心原理 在这个仓库中,我实现了一个不改变html语法的模板引擎,如果想要实现一个像
Pug
那样拥有自己的一套语法,并且编译成html的模板引擎,你需要实现属于自己语法的语法解析器
,然后使用类似node-html-parser
的工具将其转化为html代码。 - 自动部署某个文件夹内的静态文件到 Github Pages 类似于一个通用的
hexo-deployer-git
插件,我在之前的文章基于githooks和node的自动部署环境搭建中提到过它。事实上后来想一想觉得直接用Shell脚本可能还来的更方便快捷一点。