一个项目的机会再加上我本身的探索,让我对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的潜力。