http://fraserxu.me/posts/Using-Handlebarsjs-with-Expressjs/
在Express項目中使用Handlebars模板引擎
最近在用Expressjs做一個項目,前后端都用它來完成。自己之前有用過Express一段時間,但是大部分都是用它來編寫Restful的API,而沒有真正用它所提供的前端頁面渲染功能。
所以嚴格意義來講這是第一次完整的項目。開始做之后就遇到了一些需要做出決定的地方。眾所周知,Express的默認模板引擎是Jade.我在之前學習Express的時候,因為它是默認的引擎,所以有接觸和使用過一段時間,感覺也還行。Jade在編寫頁面時所提供的嵌套功能比較實用,可以節省很大的代碼量。
Jade is a high performance template engine heavily influenced by Haml and implemented with JavaScript for node. For discussion join the Google Group.
上面是Jade Github所在頁面的描述。可以得知它是一個注重性能,受Hamle影響,並特別針對Nodejs而編寫的前端模板引擎。
我們先來看一下Jade官方頁面所給的例子:
doctype 5
html(lang="en")
head
title= pageTitle
script(type='text/javascript').
if (foo) {
bar(1 + 5)
}
body
h1 Jade - node template engine
#container.col
if youAreUsingJade
p You are amazing
else
p Get on it!
p.
Jade is a terse and simple
templating language with a
strong focus on performance
and powerful features.
我們可以看到,對比原生的HTML, Jade明顯的一個優勢就是標簽數量上的減少。很多地方只要按照約定的縮進規則編寫,完全可以避免使用原生HTML時標簽忘記閉合的問題。同時Jade還提供了一些用於渲染判斷的條件,可以根據數據來決定顯示的內容等功能。
另外Jade的遍歷數據生成頁面功能,配合使用Json數據時特別好用,可以很大程度上減少代碼量。
而另外一個原因,也是覺大多數人使用Jade的原因,可能都跟我一樣,因為是Express框架自帶的模板引擎,而它的作者也是鼎鼎有名的TJ.
看了標題也許會奇怪,既然Jade出自大神之手,而且簡單易用,我為什么還要去選擇Handlebarsjs呢?
同樣我們看下官方描述:
Handlebars provides the power necessary to let you build semantic templates effectively with no frustration.
Mustache templates are compatible with Handlebars, so you can take a Mustache template, import it into Handlebars, and start taking advantage of the extra Handlebars features.
作為一個模板引擎,它繼承於著名的Mustache模板引擎,具備了渲染頁面的基礎功能,並在其基礎上進行拓展。
而另一個值得關注的是其作者Yehuda Katz,熟悉的朋友可能知道,他是著名JavaScript MVC框架Emberjs代碼的主要貢獻者之一,而且在他的影響下也成為了Emberjs的默認模板引擎。而另外,Yehuda本身也是W3C規范制定小組的成員之一,其影響也不亞於TJ.
拋開框架的背景,我們來看看實際的應用場景。工具無非好壞,順手才是王道。評斷一個東西好壞關鍵還是看它是否滿足自己的應用需求。
在開始做現在的項目之前,我已經用Jade完成了所有的功能,而且對於代碼也還比較滿意。但是在提交之后問題產生了。
因為這個項目不是我一個人在做,和我一起合作的同事之前沒有接觸過Jade,而且另外一位負責編寫樣式的同事對於JavaScript的模板引擎也不是很熟悉。這樣一來,由於我的原因,導致團隊成員之間無法協作。首先是JS開發人員需要時間來掌握和熟悉Jade語法,而另外一個更為嚴重,Jade語法的特性決定了其不利於配套CSS的書寫(這點通過編譯之后可以解決,但是一定程度上增加了工作量)。
於是我開始思考使用Jade是否正確。這里的兩個問題是我必須面對的,而項目的進度不能因為這個受到影響,於是我開始考慮選擇其他的模板引擎。
前面提到Emberjs用到了Handlebarsjs,所以在選擇時我很容易就想到了它。
Handlebars的官網給出了很多例子,而且上手也很容易,前后端通用,使用起來也很簡單,這里就不對其使用多做介紹。
回到文章重點,因為Express並不提供對Handlerbarjs的直接支持,這樣在使用時會面臨一定問題。
要在Express中使用Handlerbars作為模板引擎,首先需要做出一下設置:
-
安裝Express, Handlebars, Consolidate:
"dependencies": { "express": "3.x", "consolidate": "0.4.0", "handlebars": "1.0.7" }
-
配置選擇引擎:
// Use handlebars as template engine app.engine("html", consolidate.handlebars); app.set("view engine", "html"); app.set("views", __dirname + "/views");
-
注冊模板:
// Register partials var partials = "./views/partials/"; fs.readdirSync(partials).forEach(function (file) { var source = fs.readFileSync(partials + file, "utf8"), partial = /(.+)\.html/.exec(file).pop(); Handlebars.registerPartial(partial, source); })
這樣我們就可以在項目中使用Handlerbars來渲染頁面。但是這樣做后,我又遇到了另外一個問題。通過以上的方法我可以很容易的單獨去加載某個頁面。但是實際應用中,一般會有多個頁面,而且多個頁面之間會共享頁面的header和footer部分。這樣會導致重復編寫很多代碼。
在使用Jade是我們可以很容易的使用如下代碼來實現頁面模板功能:
include layout
但是由於Express並非直接支持Handlerbars,所以要實現這個功能還需要一定的設置。在Handlerbars中,可以通過{{> layout }} 來實現sub-template的功能。在查找了相關模塊之后,我發現了hbs這個Express中間件。
這個模塊使用起來很簡單,可以完美解決我所遇到的問題。使用方法如下:
-
安裝模塊:
npm install hbs --save
-
設置模板:
app.set('view engine', 'html'); app.engine('html', require('hbs').__express);
-
注冊模板:
var hbs = require('hbs'); hbs.registerHelper('helper_name', function(...) { ... }); hbs.registerPartial('partial_name', 'partial value');
如果需要注冊整個文件夾,也可使用如下命令:
var hbs = require('hbs');
hbs.registerPartials(__dirname + '/views/partials');
這樣,我們就可以做到頁面模板的重復利用,可以顯著減少代碼量。
而另外一個關鍵原因,在於Handlerbars對比Jade,語法更加簡單。最重要的還是其普通元素同樣使用原生HTML的寫法,這樣,對於編寫樣式的同事來講就會更加友好。使用傳統的方式編寫樣式,可以顯著降低學習成本,從而加快項目進度。
而Handerbars所帶來的一些其他功能,也會讓項目的開發變得更加輕松。
下面附上我項目的基本結構,希望能對同樣使用這種方案的同學有一定幫助。
.
├── app.js
├── node_modules
│ ├── express
│ ├── handlebars
│ ├── hbs
│ ├── less-middleware
│ ├── nodemon
│ └── request
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ │ └── app.js
│ ├── lib
│ │ ├── font
│ │ ├── js
│ │ └── stylesheets
│ └── stylesheets
│ ├── style.css
│ └── style.less
├── routes
│ ├── github.js
│ └── index.js
└── views
├── index.hbs
├── orgs.hbs
└── partials
├── footer.hbs
└── header.hbs