最近几天折腾了下express,想找个合适的模版引擎,下面是一些折腾过程的备忘
选择一门模版语言时,可能会考虑的几点
注意到hbs,似乎满足大部分的需求: https://github.com/donpark/hbs
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);
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 直接看例子。目录机构如下,可以看到多了个 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官方其实就提供了demo https://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); });