Handlebars partials 隐藏的力量

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

  • {{#extend}} layouts的能力
  • {{#embed}} partials的能力
  • 能像{{#block}} 和 {{#content}}同样配合

列出来后,我开始查Handlebars partials文档来看看行不行。而后我发现我历来没有利用到partials的所有潜力,只要作一点小小的努力就可让它们实现个人需求。有两个特性吸引了我:partial blocksinline partialsapp

我兴奋地意识到Handlebars partials能够作到比我想象中多得多的事情并立刻开始了工做。ide

Partials基础

在开始前,仍是先看一看基本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,在htmlgulp任务中把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, pages 和 includes

再来分别看看每一个文件:

Layouts 文件

首先是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 文件

拼图的下一部分就是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 文件

includes/*.hbs能够被layouts 和 pages引用。既然都用到了,那就看看大概是什么样子的:

{{!-- includes/hero.hbs --}}

<div class="hero">
  <img src="{{hero-src}}" alt="{{hero-alt}}"/>
</div>

没什么开创性的东西,只是简单地渲染传入的hero-srchero-alt(注:能够改进的地方:用{{#if}}{{else}}来判断参数是否为空)。

再看看includes/footer.hbs

{{!-- includes/footer.hbs --}}

<p>This is some default footer content.</p>

没啥特别的,这就是layouts/base中footer的默认内容。

最后成果

来归纳一下全部东西。

layouts/base.hbs

  • 充当基础layout文件
  • 使用partial blocks定义默认和动态内容

pages/page-one.hbs

  • 充当page文件
  • 使用partial block来继承基本layout
  • 使用inline partials填充内容到layout中的 partial blocks

includes/*.hbs

  • partials能够被 layouts 或 pages 引用
  • 能够在partial block或inline partial中使用

而后就是最后渲染出的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 partialspartial parameters模拟出了handlebars-layouts的{{#extend}, {{#embed}}, {{#block}} 和 {{#content}} helpers。

这是一次有趣的尝试,让我更好地理解了Handlebars partials。注意在不一样的项目中,咱们必须先评估库提供的特性,有哪些是须要的,哪些是不须要的,没有一个完美的解决方案(注:有些特性是没法模拟的,好比 {{#content}}append 和 prepend,又好比content做subexpression时,用conditonal blocks 检查content是否为空)。

若是你想要了解更多,能够看看这个项目,试试各类组合,看看能不能挖掘出更多的Handlebars partials的潜力。

相关文章
相关标签/搜索