一、模板引擎:就是來生成界面的啊,只不過實現了view和數據分離以及一些其它的功能(預加載等)。 php
二、Handlebars :但他是一個單純的模板引擎,在前端框架滿天飛的年代感受是有點弱了(好比thinkphp裏面就有模板板塊進行渲染)。css
三、thinkphp的模板板塊的介紹:ThinkPHP內置了一個基於XML的性能卓越的模板引擎,這是一個專門爲ThinkPHP服務的內置模板引擎,使用了XML標籤庫技術的編譯型模板引擎,支持兩種類型的模板標籤,使用了動態編譯和緩存技術,並且支持自定義標籤庫。 html
四、hbs模板模板引擎語法:{{> header }},兩個大括號,先後端均可以使用前端
先引用下百科的說法:webpack
Handlebars 是 JavaScript 一個語義模板庫,經過對view和data的分離來快速構建Web模板。它採用"Logic-less template"(無邏輯模版)的思路,在加載時被預編譯,而不是到了客戶端執行到代碼時再去編譯, 這樣能夠保證模板加載和運行的速度。git
好吧,看了有點懵閉。這裏關鍵詞就是兩個:無邏輯、預加載。全部的模板引擎都是view和data分離,這點不用說。無邏輯準確點來講應該是弱邏輯,畢竟裏面仍是有一些if、each邏輯在的。你可能看過不少這樣寫的模板語言:github
1 <% if (names.length) { %> 2 <ul> 3 <% names.forEach(function(name){ %> 4 <li><%= name %></li> 5 <% }) %> 6 </ul> 7 <% } %>
注:閉合的大括號必定不要忘了寫哦。web
看這種 js 與 HTML 的雜交寫法我以爲很眼疼,個人眼裏代碼的可讀性是很是重要的,這種寫法真不是個人那杯茶!不過這種模板技術的實現方式卻是值得一探,推薦看看這個20行代碼的模板引擎實現:http://blog.jobbole.com/56689/,挺有意思的作法,固然用eval也能夠作。thinkphp
而 Handlebar 的語法就簡單精練了許多,好比上面的能夠寫成:express
1 {{#if names.length}} 2 <ul> 3 {{#each names}} 4 <li>{{this}}</li> 5 {{/each}} 6 </ul> 7 {{/if}}
就喜歡這種一目瞭然的感受,固然還有其餘的swig、tx出的art-template之類的模板引擎,蘿蔔青菜各有所愛,就很少說了。
語法很簡單,就是用大括號將 data 包裹起來。其中兩個 {{}} 會將內容作HTML編碼轉換,這裏你輸入的HTML標籤代碼什麼的都會按你輸入的字符輸出;而三個 {{{}}} 的時候則不作轉換,你在裏面輸入<h1>最後是真的能獲得一個h1標籤的。其餘一些規則要素分別有:
1)塊級
在 Handlebars裏面,每一個#就表明了一個局部塊,每一個塊都有自身的做用域範圍。舉例來講:
1 // 數據 2 hehe: { words: 'hehehehe' } 3 yoyo: { words: 'yoyoyoyo'}
對應的模板:
1 {{#hehe}} 2 <p>{{words}}</p> 3 {{/hehe}} 4 {{#yoyo}} 5 <p>{{words}}</p> 6 {{/yoyo}}
這個例子很好理解,words屬性都是根據自身的對象來輸出的。這裏仍是按照塊級做用域去理解會比較簡單(雖然js並無塊級做用域。。。),也能夠用this來指代當前對象。注意,即便是#if、#each也是有做用域的,不要跟js中的做用範圍混爲一談。
2)路徑
對於對象來講,你能夠按照上文的例子同樣直接使用 name 的 length 屬性,還可使用使用路徑的表達方式去訪問對象的其餘層級。舉個栗子:
1 var post = { 2 title: "Blog Post!", 3 author: [{ 4 id: 47, 5 name: "Jack" 6 },{ 7 id: 20, 8 name: "Mark" 9 }] 10 };
模板要這麼寫:
1 {{#post}} 2 {{#if author.length}} 3 <h3>{{title}}</h3> 4 <ul> 5 {{#each author}} 6 <li>{{../title}}'s author is {{name}}</li> 7 {{/each}} 8 </ul> 9 {{/if}} 10 {{/post}}
li標籤裏面已是在 author 字段以內了,因此要使用 '../' 來轉到上層的 title。
3)helper
上面其實已經用過helper了,內置的helper有if、each、unless、with等,固然你也能夠本身去寫helper。因爲Handlebar的弱邏輯屬性,若是要實現複雜一點的邏輯就須要去自定義helper。舉個栗子:
1 //判斷是不是偶數 2 Handlebars.registerHelper('if_even', function(value, options) { 3 console.log('value:', value); // value: 2 4 console.log('this:', this); // this: Object {num: 2} 5 console.log('fn(this):', options.fn(this)); // fn(this): 2是偶數 6 if((value % 2) == 0) { 7 return options.fn(this); 8 } else { 9 return options.inverse(this); 10 } 11 });
helper是這樣用的:
1 {{#if_even num}} 2 {{this.num}}是偶數 3 {{else}} 4 {{this.num}}是奇數 5 {{/if_even}}
固然輸出你也能想到,就是根據奇數偶數輸出相應信息。咱們看看定義的一個function(value, options){},這個items就是咱們使用模板時候的num,options是一些配置項,這裏咱們用到的是fn函數,這個函數執行的結果就是編譯的結果(這裏結果是「2是偶數」這一句話)。另一個options.inverse就是取反,對應的就是模板裏面的else語句了。
but:在模板中過分使用邏輯,實際上就是模糊了模板的專一點,這有違本來數據和表現分離的出發點。我仍是認爲模板應該專一數據綁定,邏輯應該在數據層作預處理,而後將結果返回給模板,而不是讓模板去作各類數據的運算。
4)partial
使用模板引擎最重要的一點就是使用其partial功能,Handlebars裏面是按照註冊再使用的方式來管理partial的。舉個栗子:
1 Handlebars.registerPartial('userMessage', 2 '<{{tagName}}>By {{author.firstName}} {{author.lastName}}</{{tagName}}>' 3 + '<div class="body">{{body}}</div>' 4 );
使用的時候就能夠直接使用{{> userMessage}}將這個小塊引入到頁面中了。這裏就是簡單的局部替換,因此partial裏面的data跟當前頁面的data是在同一級的做用域內,也就是說你只要定義好author、body傳進去就好了。tagName這個屬於表現層的變量,應該在hbs文件裏面進行聲明,也便是{{> userMessage tagName="h1" }}這樣使用。
直接引入js的方式就很少說了,這裏我是使用webpack來統一管理各類資源的。Handlebars對應的webpack插件爲handlebars-loader,loader的配置很是簡單:
1 { 2 test: /\.hbs$/, 3 loader: "handlebars" 4 }
Handlebars的後綴有兩種,全稱的handlebars以及簡稱的hbs,也能夠直接用html,但仍是跟普通html文件區分開來好一點。
使用模板的好處固然就是能夠組件化開發了。我這裏採用的目錄是這樣的:
其中頁面組件指的是應用中的頁面單元,頁面是由各類控件組件組成的,這些都已是共識了,就再也不贅述了。引用的方法有幾種:
(1)由於Handlebar編譯出來的只是一個字符串,因此咱們能夠用js做爲入口去管理組件,每一個組件的js文件引入相應的css和模板,輸出爲dom字符串。頁面引用組件的時候就直接引用js模塊獲得dom字符串,而後將dom字符串渲染到相應的{{{}}}中去。這種js大一統的方式跟如今主流框架的作法是同樣的,能夠將邏輯、樣式、內容和資源統一塊兒來管理,組件也得內聚性比較強。
1 // header.js
2 require('./header.scss');
3 var headerTpl = require('./header.hbs');
4 var data = {words: "This is header!"}; //data能夠用參數傳入
5 var header = headerTpl(data);
6 module.exports = header;
7
8 // home.hbs
9 <div class="home">
10 {{{ header }}}
11 <h2>This is {{name}} page.</h2>
12 {{{ footer }}}
13 </div>
14
15 // home.js
16 require('./home.scss');
17 var header = require('../../component/header/header.js');
18 var footer = require('../../component/footer/footer.js');
19 var homeTpl = require('./home.hbs');
20 var data = {
21 header: header,
22 footer: footer,
23 name: 'home'
24 };
25 var home = homeTpl(data);
26 module.exports = home;
(2) 另外的方案就是使用局部模板的方式了,這種方式對一些不帶js邏輯的組件很是合適,好比頁頭頁尾這些純內容的組件。在hbs裏面能夠直接按照路徑去引用particle,而後把引入組件的時候提供partial所需的數據,例如home頁面就是這樣的:
<div class="home"> {{> ../../component/header/header}} <h2>this is {{}} page</h2> {{> ../../component/footer/footer}} </div>
既然咱們已經用了webpack來管理,固然也可讓webpack來處理引用路徑了,這裏只須要在配置裏面聲明partial的路徑便可直接引用,loader配置:
1 { 2 test: /\.hbs$/, 3 loader: "handlebars", 4 query: { 5 partialDirs: [ 6 path.join(SRC_PATH, 'component', 'header'), 7 path.join(SRC_PATH, 'component', 'footer'), 8 path.join(SRC_PATH, 'page', 'home') 9 ] 10 } 11 }
模板文件:
<div class="home"> {{> header }} <h2>This is {{name}} page.</h2> {{> footer }} </div>
上面列出的幾種方式各有優劣,使用partial的方式能夠將相應的模板文件集中放到一個view文件夾裏面,partialDirs就不用寫一大堆路徑了。我的仍是更偏向於使用第一種方式,每一個組件的css、html、js文件作成一個總體的方式,遵循就近管理原則。
Node後端使用hbs也很是方便,這裏我用的是express框架,直接後端渲染。固然更精細的作法就是首屏渲染、僅移動端後端渲染了,在這種混搭的場合模板是能夠通用的,這樣就減小了必定的開發工做量。目前express中自帶4種模板引擎,jade、esj、hogan與hbs,我是使用express-generator來生成項目腳手架的,輸入命令爲: express --hbs 項目名。
express-generator中的hbs用的是hbs庫(https://github.com/donpark/hbs),而並不是不少資料介紹的express-handlebars。hbs默認使用layout模板,實際上就是將你的模板文件替換掉{{{body}}}。layout是可配置的,能夠在渲染選項中經過layout項來配置。
1 res.render('index', { 2 title: 'Express', 3 head: '<h1>head part</h1>', 4 layout: true //默認爲true,設爲false則不啓用layout模板 5 });
目前我所接觸到的hbs項目都是express+hbs+zepto/jq這一套,若是有用其餘前端框架的話,通常也不會用到hbs了,因此只說說這種狀況。後端渲染跟前端渲染的開發模式略有差別,但思路仍是同樣的要作組件化開發。上文說過前端使用hbs的時候是以js爲入口,而在後端使用hbs的話我的認爲更適合使用局部模板的方式。
個人目錄是這樣的:
頁面統一放入views中,局部模板放入views/partial裏面。js和css仍是按照官方默認的方式集中管理。使用局部模板要先註冊,須要在app.js這個服務器腳本里面加入如下代碼:
1 var hbs = require('hbs'); 2 hbs.registerPartials(__dirname + '/views/partials');
模板文件中引入小模板:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>{{title}}</title>
5 <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
6 <link rel="stylesheet" href="/css/main.css"/>
7 {{> resource}}
8 </head>
9 <body>
10 {{> header}}
11 <div class="container">
12 {{{body}}}
13 </div>
14 {{> footer}}
15 </body>
16 </html>
resource模板主要是控制不一樣頁面引入的不一樣資源:
1 {{#each css}} 2 <link rel='stylesheet' href={{this}} /> 3 {{/each}} 4 {{#each js}} 5 <script src={{this}}></script> 6 {{/each}}
頁面渲染的時候就是這樣的:
1 res.render('index', { 2 title: 'Express', 3 css: ['/css/home.css', '/css/home_add.css'], 4 js: ['/js/home.js'], 5 name: "茄果" //這個是頁面中用到的數據,與title同一性質 6 });
這種方式的一個問題就是css、js這些資源的寫法跟咱們日常直接在html引用的方式不同。好比我想資源引用寫在頁面中,好比home.hbs裏面,若是直接寫入home.hbs裏面的話,內容是直接插入到{{{body}}}的位置,但咱們想要的是在head的位置啊。這個如何實現呢?以前咱們定義partial只是爲了簡單的替換,這一次除了替換還要作一個插入dom的操做,這個就要用到helper來幫咱們完成了。不少時候咱們要把css放入頭部,而把js放在頁面尾部,因此layout文件要改形成:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>{{title}}</title> 5 <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0"> 6 <link rel="stylesheet" href="/css/main.css"/> 7 {{#each cssList}} 8 <link rel='stylesheet' href={{this}} /> 9 {{/each}} 10 </head> 11 <body> 12 {{> header}} 13 <div class="container"> 14 {{{body}}} 15 </div> 16 {{> footer}} 17 {{#each jsList}} 18 <script src={{this}}></script> 19 {{/each}} 20 </body> 21 </html>
如今只要註冊helper把hbs文件中定義的值傳入cssList、jsList中便可。另外還要考慮到頁面組件引用的時候可能會出現重複依賴的狀況,因此要作一個去重的工做。註冊helper與註冊partial同樣,都要寫在app.js文件中,下面給出css的寫法,js也相似:
1 hbs.registerHelper('css', function(str, option) { 2 var cssList = this.cssList || []; 3 str = str.split(/[,,;;]/); 4 console.log('css: ',str); 5 str.forEach(function (item) { 6 if(cssList.indexOf(item)<0) { 7 cssList.push(item); 8 } 9 }); 10 this.cssList = cssList.concat(); 11 });
頁面中引入css、js就應該是這樣:
{{css "/css/home_add.css"}} {{js "/js/home.js"}} {{> p}} <p>This is home page!</p>
上面例子中的局部模板p做爲一個組件,也引用了相應的css和js,寫法跟頁面的寫法是同樣的。
handlebars做爲一個很是輕量級的模板引擎,單純從模板這個功能上看,他的先後端通用性強,命令簡單明瞭,代碼可讀性強。但他是一個單純的模板引擎,在前端框架滿天飛的年代感受是有點弱了。不管是前端仍是後端,各類大框架都有渲染模塊。固然不喜歡大框架的全家桶卻是能夠考慮使用handlebars,因此主要仍是要看項目吧。青菜蘿蔔,各有所好。