一個項目的機會再加上我本身的探索,讓我對Handlebars partials有了更深的理解。事實證實,你能夠作得比我瞭解的更多。css
我最近在負責一個小項目,只有不多的靜態頁面。由於過小了,咱們最開始沒有使用模板系統。當項目開始往深一層推動時,咱們發現把靜態頁面拆分紅partials & layout的模板明顯更好。html
咱們之前用過handlebars-layouts而且很喜歡它提供的那些特性。我原本想同時安裝Handlebars和handlebars-layouts,可是又感受過於累贅了。git
我在想有沒有可能只使用Handlebars而不用額外的handlebars-layouts庫?github
我並非想吐槽handlebars-layouts,咱們之後可能還會用到這個項目。可是我想試試只用Handlebars partials來實現一些handlebars-layouts經過helper來實現的一些特性。express
花了一些時間來評估項目需求後我問本身:handlebars-layouts helpers提供的哪些特性是我須要的?根據過去的經驗,我列出了以下helpers:gulp
列出來後,我開始查Handlebars partials文檔來看看行不行。而後我發現我歷來沒有利用到partials的所有潛力,只要作一點小小的努力就可讓它們實現個人需求。有兩個特性吸引了我:partial blocks和inline partials。app
我興奮地意識到Handlebars partials能夠作到比我想象中多得多的事情並立刻開始了工做。ide
在開始前,仍是先看一看基本partials, partial blocks 和 inline partials。ui
基本partials就像這樣:this
{{> content-block }}
它會試圖找到一個命名爲content-block
的partial進行渲染,不然就報錯。
Handlebars partial的文檔上寫:
The normal behavior when attempting to render a partial that is not found is for the implementation to throw an error. If failover is desired instead, partials may be called using the block syntax.渲染一個partial的默認行爲是,若是沒找到就報錯。若是要進行錯誤處理,就要用block的方式來使用。
partial block就像這樣:
{{#> content-block}} Default content {{/content-block}}
這樣寫有一個好處,就是當content-block
沒有找到時,partial block裏面的部分就會被渲染,像上面就會渲染「Default content」。
而inline partial就像這樣:
{{#*inline "content-block"}} My new content {{/inline}}
文檔裏是這樣說的:
Templates may define block scoped partials via the inline decorator.經過使用inline修飾符,能夠在模板中定義塊級partials。
inline partial容許你臨時建立partials。若是頁面中有一個content-block
partial block,而後咱們建立了content-block
inline partial的話,inline partial中的內容(「My new content」) 就會替換partial block中默認內容。inline partial一樣能夠替換基本partials,只是基本partials沒有默認內容而已。
瞭解完這些後咱們就能夠開始了。
我想同時擁有layouts/
、includes/
和pages/
,目錄結構看起來以下:
src/ ├── pages │ ├── page-one.hbs │ └── page-two.hbs └── partials ├── includes │ ├── hero.hbs │ └── footer.hbs └── layouts └── base.hbs
爲了進行渲染使用了Gulp + gulp-compile-handlebars,在html
gulp任務中把src/pages/*.hbs
當成source目錄,就像下面這樣:
// gulpfile.js const handlebars = require('gulp-compile-handlebars'); const rename = require('gulp-rename'); gulp.task('html', () => { return gulp.src('./src/pages/*.hbs') .pipe(handlebars({}, { ignorePartials: true, batch: ['./src/partials'] })) .pipe(rename({ extname: '.html' })) .pipe(gulp.dest('./dist')); });
須要注意,咱們口中的「page」, 「include」和「layout」,本質上都是Handlebars partial,這就是保證可擴展性的關鍵。一旦瞭解了這個,新世界的大門就打開了。
再來分別看看每一個文件:
首先是layouts/base.hbs
:
{{!-- layouts/base.hbs --}} <!doctype html> <html> <head> <title> {{#if title}} {{title}} {{else}} Base Page Title {{/if}} </title> <link rel="stylesheet" href="main.css"> {{#> head-block}} {{!-- Custom <head> content per page could be added. --}} {{/head-block}} </head> <body> {{#> hero-block}} {{!-- Hero content goes here. --}} {{/hero-block}} <footer> {{#> footer-block}} {{!-- The `includes/footer` partial is the default content, but can be overridden. --}} {{> includes/footer }} {{/footer-block}} </footer> {{#> scripts-block}} {{!-- Custom scripts per page can be added. --}} {{/scripts-block}} </body> </html>
有些值得注意的地方,拆開來單獨看:
<link rel="stylesheet" href="main.css"> {{#> head-block}} {{!-- Custom <head> content per page could be added. --}} {{/head-block}}
引入了一個虛構的main.css
。而後設置了head-block
,當一個具體的page繼承此layout時在這裏傳入<head>
中的內容(注:就跟handlebars-layouts的{{#block}} helper同樣)。
{{#> hero-block}} {{!-- Hero content goes here. --}} {{/hero-block}}
{{#> scripts-block}} {{!-- Custom scripts per page can be added. --}} {{/scripts-block}}
跟head-block
同樣,咱們在hero和scripts部分都用了Handlebars partial blocks。在同一套模板有不一樣的內容和scripts的時候,顯得更加靈活。
<footer> {{#> footer-block}} {{!-- The `includes/footer` partial is the default content, but can be overridden. --}} {{> includes/footer }} {{/footer-block}} </footer>
footer部分咱們又用了Handlebars partial block,不一樣的是咱們用了{{> includes/footer }}
基本partial來指定默認內容。
當footer-block
沒有被傳入內容時就會渲染默認內容。全部代碼中,咱們都用的Handlebars註釋(注:這些註釋不會被渲染到HTML中,若是你用HTML註釋就會被渲染到HTML中,由於partial block中全部內容都會被渲染)。
拼圖的下一部分就是page partial。咱們也用到了Handlebars partial blocks,同時還有Handlebars inline partials,下面是pages/page-one.hbs
的示例:
{{!-- pages/page-one.hbs --}} {{#> layouts/base title="Page One" }} {{#*inline "hero-block"}} {{> includes/hero hero-src="img/hero-1.png" hero-alt="Hero 1 alt title" }} {{/inline}} {{/layouts/base}}
一樣拆開來看看:
{{#> layouts/base title="Page One" }} ... {{/layouts/base}}
這裏又用到了Handlebars partial block。可是此次,咱們用它來繼承layouts/base
(注:就跟handlebars-layouts {{#extend}} helper同樣),同時設置了page的title
(注:用到了partial 參數特性)。
{{#*inline "hero-block"}} ... {{/inline}}
這是咱們第一次用到Handlebars inline partial。這個inline partial被傳入layouts/base
而後被其中的hero-block
注入(注:用法就跟 handlebars-layouts 的 {{#content}} helper 同樣)。
{{> includes/hero hero-src="http://fpoimg.com/500x200" hero-alt="Hero 1 alt title" }}
最後咱們引入indludes/hero
基本partial(注:就跟handlebars-layouts {{#embed}} helper同樣)。
includes/*.hbs
能夠被layouts 和 pages引用。既然都用到了,那就看看大概是什麼樣子的:
{{!-- includes/hero.hbs --}} <div class="hero"> <img src="{{hero-src}}" alt="{{hero-alt}}"/> </div>
沒什麼開創性的東西,只是簡單地渲染傳入的hero-src
和 hero-alt
(注:能夠改進的地方:用{{#if}}{{else}}
來判斷參數是否爲空)。
再看看includes/footer.hbs
:
{{!-- includes/footer.hbs --}} <p>This is some default footer content.</p>
沒啥特別的,這就是layouts/base
中footer的默認內容。
來歸納一下全部東西。
layouts/base.hbs
pages/page-one.hbs
includes/*.hbs
而後就是最後渲染出的page-one.html
文件,看起來就是這樣:
<!-- page-one.html --> <!doctype html> <html> <head> <title>Page One</title> <!-- No extra `head-block` content was passed in, only `main.css` from the base layout rendered. --> <link rel="stylesheet" href="main.css"> </head> <body> <!-- The `hero-block` content --> <div class="hero"> <img src="http://fpoimg.com/500x200" alt="Hero 1 alt title"/> </div> <footer> <!-- The `footer-block` content --> <p>This is some default footer content.</p> </footer> </body> </html>
讓咱們再試試使用同一個layout,但改一些東西,就叫它pages/page-two.hbs
好了:
{{!-- pages/page-two.hbs --}} {{#> layouts/base title="Page Two" }} {{!-- Let's add a second stylesheet for this layout. --}} {{#*inline "head-block"}} <link rel="stylesheet" href="some-other-styles.css"> {{/inline}} {{!-- Let's change the hero image for this layout. --}} {{#*inline "hero-block"}} {{> includes/hero hero-src="http://fpoimg.com/400x400" hero-alt="Hero 2 alt title" }} {{/inline}} {{!-- Let's override the "footer-block" content. --}} {{#*inline "footer-block"}} <p>We are now overriding the default "footer-block" content with this content.</p> {{/inline}} {{!-- Let's add a script for this layout. --}} {{#*inline "scripts-block"}} <script src="new-script.js"></script> {{/inline}} {{/layouts/base}}
渲染出來就是這樣:
<!-- page-two.html --> <!doctype html> <html> <head> <title>Page Two</title> <link rel="stylesheet" href="main.css"> <!-- The `head-block` added a stylesheet. --> <link rel="stylesheet" href="some-other-styles.css"> </head> <body> <!-- Our new `hero-block` content. --> <div class="hero"> <img src="http://fpoimg.com/400x400" alt="Hero 2 alt title"/> </div> <footer> <!-- We overrode the default `footer-block` content. --> <p>We are now overriding the default "footer-block" content with this content.</p> </footer> <!-- The `script-block` added a script. --> <script src="new-script.js"></script> </body> </html>
這樣咱們就用同一個layout渲染出了兩個不一樣的頁面。
咱們用Handlebars 的partial blocks, inline partials, basic partials 和partial parameters模擬出了handlebars-layouts的{{#extend}, {{#embed}}, {{#block}} 和 {{#content}} helpers。
這是一次有趣的嘗試,讓我更好地理解了Handlebars partials。注意在不一樣的項目中,咱們必須先評估庫提供的特性,有哪些是須要的,哪些是不須要的,沒有一個完美的解決方案(注:有些特性是沒法模擬的,好比 {{#content}}
的 append 和 prepend,又好比content
做subexpression時,用conditonal blocks 檢查content是否爲空)。
若是你想要了解更多,能夠看看這個項目,試試各類組合,看看能不能挖掘出更多的Handlebars partials的潛力。