點擊閱讀原文,以得到更好的閱讀體驗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.pug。api
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中去。 先使用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字符串,咱們就能夠編寫對應的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
,用瀏覽器便可運行。此時能夠過修改數據隨意產出不一樣的網頁。也能夠經過修改模板來生成新的網頁。咱們最初的需求大體完成。
讀完這篇文章,你能夠嘗試從這些方面進行拓展:
a
標籤嘗試將網頁組進行組合,造成靜態網站。markdown
等富文本展示工具,來在你的數據中書寫富文本並在網頁中展示。uglifyjs
之類的工具對編譯產物進行壓縮混淆。Pug
那樣擁有本身的一套語法,而且編譯成html的模板引擎,你須要實現屬於本身語法的語法解析器
,而後使用相似node-html-parser
的工具將其轉化爲html代碼。hexo-deployer-git
插件,我在以前的文章基於githooks和node的自動部署環境搭建中提到過它。事實上後來想想以爲直接用Shell腳本可能還來的更方便快捷一點。