最近幾天折騰了下express,想找個合適的模版引擎,下面是一些折騰過程的備忘
選擇標准
選擇一門模版語言時,可能會考慮的幾點
- 語法友好(micro tmpl那種語法真是夠了)
- 支持模版嵌套(子模版的概念)
- 支持模版繼承(extend)
- 前后端共用
- 有容錯處理(最好定位到具體出錯位置)
- 支持預編譯(性能好)
注意到hbs,似乎滿足大部分的需求:https://github.com/donpark/hbs
getting started
demo地址:https://github.com/chyingp/blog/tree/master/demo/2015.04.01-hbs/getting-started
目錄結構如下:
.
├── app.js ├── node_modules │ ├── express │ └── hbs ├── package.json └── views └── index.hbs
看下app.js
內容,還是比較容易理解的。模版views/index.hbs
沒什么好說的,語法跟handlbars
一樣
var express = require('express'), hbs = require('hbs'), app = express(); app.set('view engine', 'hbs'); // 用hbs作為模版引擎 app.set('views', __dirname + '/views'); // 模版所在路徑 app.get('/', function(req, res){ res.render('index', {title: 'hbs demo', author: 'chyingp'}); }); app.listen(3000);
模版繼承:layout.hbs
demo地址:https://github.com/chyingp/blog/tree/master/demo/2015.04.01-hbs/inherit-from-layout
如果稍微看過hbs
源碼可以知道,hbs默認會到views
下找layout.hbs
這個模版,將這個模板作為基本骨架,來渲染返回的頁面。
以getting-started
里的例子來說,比如用戶請求 http://127.0.0.1:3000,那么,處理步驟如下
- 查找
views/index.hbs
,進行編譯,並將編譯的結果保存為 A - 查找
views/layout.hbs
,如果
- 存在:對
layout.hbs
進行編譯,其中{{{body}}}
標簽替換成 A,並返回最終編譯結果B - 不存在:返回A
- 存在:對
直接看例子。目錄機構如下,可以看到多了個layout.hbs
。
.
├── app.js ├── node_modules │ ├── express │ └── hbs ├── package.json ├── public │ └── style.css └── views ├── index.hbs ├── layout.hbs └── profile.hbs
layout.hbs
的內容如下:
<!DOCTYPE html> <html> <head> <title>{{title}}</title> <link rel="stylesheet" type="text/css" href="/style.css"> </head> <body> {{{body}}} </body> </html>
相應的,index.hbs
調整為
<h1>Demo by {{author}}</h1> <p>{{author}}: welcome to homepage, I'm handsome!</p>
再次訪問 http://127.0.0.1:3000,可以看到返回的頁面
模版繼承+自定義擴展
demo地址:https://github.com/chyingp/blog/tree/master/demo/2015.04.01-hbs/inherit-and-override
在項目中,我們會有這樣的需求。頁面的基礎骨架是共享的,但某些信息,每個頁面可能是不同的,比如引用的css文件、meta標簽等。那么,除了上面提到的“繼承”之外,還需要引入類似“覆蓋”的特性。
hbs官方其實就提供了demohttps://github.com/donpark/hbs/blob/master/examples/extend/ ,感興趣的同學可以去圍觀下。可以看到,在app.js
里面加入了下面的 helper function`,這就是實現”覆蓋“ 的關鍵代碼了。
var blocks = {}; hbs.registerHelper('extend', function(name, context) { var block = blocks[name]; if (!block) { block = blocks[name] = []; } block.push(context.fn(this)); // for older versions of handlebars, use block.push(context(this)); }); hbs.registerHelper('block', function(name) { var val = (blocks[name] || []).join('\n'); // clear the block blocks[name] = []; return val; });
此外,layout.hbs
需要做點小改動。里面比較明顯的變化是加入了下面的block標記
{{{block "stylesheets"}}}
{{{block "scripts"}}}
那么,可以在index.hbs
里對這些標記的內容進行覆蓋(或者說自定義),包括其他的模版,如果有需要,都可以對這兩個`block進行覆蓋。
{{#extend "stylesheets"}} <link rel="stylesheet" href="/css/index.css"/> {{/extend}} let the magic begin {{#extend "scripts"}} <script> document.write('foo bar!'); </script> {{/extend}}
那么問題來了。如果有這樣的需求:所有的頁面,都引用 style.css
,只有 index.hbs
引用 index.css
,那么上面的改動還不足以滿足這個需求。
其實,只需要改幾行代碼就可以實現了,擴展性點個贊。改動后的app.js
如下
var blocks = {}; hbs.registerHelper('extend', function(name, context) { var block = blocks[name]; if (!block) { block = blocks[name] = []; } block.push(context.fn(this)); // for older versions of handlebars, use block.push(context(this)); }); // 改動主要在這個方法 hbs.registerHelper('block', function(name, context) { var len = (blocks[name] || []).length; var val = (blocks[name] || []).join('\n'); // clear the block blocks[name] = []; return len ? val : context.fn(this); });