50行代碼實現Hexo等靜態博客引擎的核心原理

點擊閱讀原文,以得到更好的閱讀體驗javascript

本文采用的語言是NodeJS,須要讀者有必定的CSS、HTML、JS基礎閱讀。css

像hexo、Jekyll等靜態博客引擎的主要功能爲將模板與數據經過程序進行拼接編譯,生成能夠瀏覽的網頁。大量不一樣的數據與相同的模板拼接便可造成展現的靜態網頁組。多個網頁組相結合便可生成靜態網站。html

本篇文章將要實現一個以Pug模板引擎和Stylus樣式引擎來編譯出一個靜態網頁的工具,其中的模板與數據相互分離,模板文件、數據、輸出路徑也能夠進行設置。java

工程代碼:github.com/Zainking/te…node

構思

咱們想要實現的目標是一個將html模板和數據分離的靜態網頁產出工具。實際上這種東西的核心就是模板引擎。咱們選擇使用Pug模板引擎來實現功能;而且爲了統一語法,使用類Python的縮進語法書寫樣式,咱們同時使用Stylus樣式引擎書寫樣式。 咱們將每份數據保存在json文件中,將模板和樣式也分別寫好放置在文件中,在程序運行時統一讀取編譯,再將編譯後的字符串保存在html文件中便可完成需求。git

編寫模板和樣式文件

下面是一個Pug語法實例github

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:web

<!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字符串進行替換。咱們能夠在 pugjs.org/api/getting… 學習其詳細語法。json

咱們用Pug語法書寫一個簡歷的模板文件放在template/index.pugapi

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;
}
複製代碼

咱們能夠在 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腳本可能還來的更方便快捷一點。
相關文章
相關標籤/搜索