The hidden power of Handlebars partials

Programming - Apr 19, 2024

A project opportunity combined with my own curiosity allowed me to get a better understanding of Handlebars’ partials. Turns out you can do much more than I was aware of. Let’s dig in.

I was recently working on a small project with only a handful of static pages. Because it was small, we started without a templating system. Once we pushed further into the project, it became obvious the project would benefit from breaking apart the few static pages into partials & layout templates.

We had used the handlebars-layouts library in the past and enjoyed the awesome features it provides. I was tempted to install both Handlebars and handlebars-layouts, but that felt excessive.

I wondered if it was possible to only use Handlebars without the need of the additional handlebars-layouts library?

This is not a knock on handlebars-layouts. We’ll likely use it in future projects. But I wanted to see if I might be able to rely solely on Handlebars partials to reproduce some of the features offered by the handlebars-layouts helpers.

I took a moment to assess the project’s needs. I asked myself, what features offered by the handlebars-layouts helpers would I be seeking? I came up with this list of helpers it provides based on past experiences:

  • ability to {{#extend}} layouts
  • ability to {{#embed}} partials
  • ability to set up something similar to how the {{#block}} and {{#content}} helpers work together

Once I came up with this list, I started looking up the Handlebars partials documentation to get an idea of what might be possible. In doing so, I realized that I had never used Handlebars partials to their full potential. I started learning I could do quite a few things and started mapping them to my needs. Two of the features that caught my attention were partial blocks and inline partials.

I got excited to learn there was more I could do with Handlebars partials than I’d realized. I took what I had gathered and immediately started writing some code.

Before we get too deep into the case study, let’s get a better understanding of what Handlebars’ basic partials, partial blocks, and inline partials look like.

A basic partial looks like this:

{{> content-block }}

This will look for and render a partial that is registered under the name of content-block. If no partial is registered under that name, an error occurs.

From the Handlebars partial documentation:

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.

The syntax for a partial block looks like this:

{{#> content-block}} Default content {{/content-block}}

Using the partial block syntax has a benefit. If the content-block partial is not available, the content within the partial block will be rendered, in this case, “Default content”.

An inline partial looks like this:

{{#*inline "content-block"}} My new content {{/inline}}

From the docs:

Templates may define block scoped partials via the inline decorator.

Inline partials allow us to create partials on-the-fly. If we created this content-block inline partial and it existed on the same page as the content-block partial block, then the content within the inline partial (“My new content”) would be rendered in place of the partial block default content. The inline partial can also be used with the basic partial in the same fashion with the only difference being that basic partials don’t have default content.

Now that we have this understanding, let’s proceed to jump into the case study.

I wanted a directory structure allowing me to have layouts/, includes/ as well as pages/. Below is an example of what this tree structure might look like:

src/
├── pages
│   ├── page-one.hbs
│   └── page-two.hbs
└── partials
    ├── includes
    │   ├── hero.hbs
    │   └── footer.hbs
    └── layouts
        └── base.hbs

To aid in rendering to HTML, I used Gulp + gulp-compile-handlebars. I set up an html Gulp task to treat the src/pages/*.hbs partials as the source. Here is an example of what the Gulp task might look like:

// 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')); });

What is interesting to note is that whether we declare each file as a “page”, an “include” or a “layout”, they are all simply a Handlebars partial. This is key to allowing ourselves this flexibility. Once I understood this, my possibilities opened up.

Using this tree structure, for example, pages/page-one.hbs could extend the layouts/base.hbs layout which could include any of the includes/*.hbs chunks as needed. You can imagine how flexible it is to structure your files, the combinations are endless. If we wanted to, we could even break apart our files and create a design system as authored by Brad Frost. We’ll leave that exploration, though, for another day.

Continuing with the above tree structure example, let’s take a closer look at each of the files individually so we can better understand the relationships.

Let’s first look at the layouts/base.hbs layout file:

{{!-- layouts/base.hbs --}} {{#if title}} {{title}} {{else}} Base Page Title {{/if}} {{#> head-block}} {{!-- Custom content per page could be added. --}} {{/head-block}} {{#> hero-block}} {{!-- Hero content goes here. --}} {{/hero-block}}
{{#> footer-block}} {{!-- The `includes/footer` partial is the default content, but can be overridden. --}} {{> includes/footer }} {{/footer-block}}
{{#> scripts-block}} {{!-- Custom scripts per page can be added. --}} {{/scripts-block}}

There are a few things going on, so let’s break this layouts/base.hbs file down a bit to better understand it.

{{#> head-block}} {{!-- Custom content per page could be added. --}} {{/head-block}}

Here we’ve set an imaginary main.css file as the stylesheet for the base layout. We are then setting ourselves up, using a Handlebars partial block, to pass in any other <head> content as needed on a per-page basis when extending this layout.Footnote 1

{{#> hero-block}} {{!-- Hero content goes here. --}} {{/hero-block}} {{#> scripts-block}} {{!-- Custom scripts per page can be added. --}} {{/scripts-block}}

As with the head-block, we are using Handlebars partial blocks for the hero and scripts as well. This gives us the flexibility to reuse the same layout with different content & scripts if needed.

{{#> footer-block}} {{!-- The `includes/footer` partial is the default content, but can be overridden. --}} {{> includes/footer }} {{/footer-block}}

For the footer section, we are again using another Handlebars partial block. Different from the previous partial blocks, though, is the default content we’ve supplied via the {{> includes/footer }} basic partial.

If no content is passed to the footer-block partial block, then the default content will get rendered. In all of these partial blocks, we are using Handlebars’ comments as well.Footnote 2

The next piece of the puzzle is the page partial file. We will once again be using Handlebars partial blocks while also introducing Handlebars inline partials. Here is an example of what the pages/page-one.hbs file might look like:

{{!-- 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}}

Let’s take a moment to break this file down to better understand it as well.

{{#> layouts/base title="Page One" }} ... {{/layouts/base}}

Here we are using a Handlebars partial block again. This time, though, we are using partial blocks as a way to extend the layouts/base layout.Footnote 3 We are also setting the page title in the process.Footnote 4

{{#*inline "hero-block"}} ... {{/inline}}

This is the first time we make use of a Handlebars inline partial. This inline partial gets passed into the layouts/base layout and is then pulled in by the hero-block partial block we set up in the layout.Footnote 5

{{> includes/hero hero-src="http://fpoimg.com/500x200" hero-alt="Hero 1 alt title" }}

The last bit within the page file is the use of a Handlebars basic partial to include the indludes/hero partial.Footnote 6 This is the content that gets rendered within the hero-block inside the layout. We are also using partial parameters to set the hero-src and hero-alt.

The includes/*.hbs files are partials that can be included in either layouts or pages. Since we are doing both, let’s take a quick look at what these files might look like.

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

There is nothing ground-breaking going on within the includes/hero.hbs partial. It is simply expecting a hero-src and hero-alt value to be passed in.Footnote 7

The other file we should take a quick peek at is the includes/footer.hbs partial.

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

This is some default footer content.

Nothing special going on with this partial. It is being included in the layouts/base layout file as the default footer content.

Now that we’ve walked through all the pieces in play, let’s do a recap to remind ourselves what we have.

  • acts as the base layout file
  • uses partial blocks for default and dynamic content
  • acts as a page file
  • uses a partial block to extend the base layout
  • uses inline partials to feed content to the layout partial blocks
  • partials that can be included in either layouts or pages
  • can be used within a partial block or an inline partial

And now the part we’ve all been waiting for, the rendered page-one.html file. Using the example files we talked about above, the rendered outcome would look like this:

Page One
Hero 1 alt title

This is some default footer content.

Was that exciting or what? For fun, let’s create a second page using the same base layout, but let’s change a few details. Let’s call it 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"}} {{/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"}}

We are now overriding the default "footer-block" content with this content.

{{/inline}} {{!-- Let's add a script for this layout. --}} {{#*inline "scripts-block"}} {{/inline}} {{/layouts/base}}

The rendered HTML for the pages/page-two.hbs file would then look like this:

Page Two
Hero 2 alt title

We are now overriding the default "footer-block" content with this content.

As you can see, we are able to create two different pages using the same base layout. Pretty neat!

We were able to use Handlebars partial blocks, inline partials, basic partials and partial parameters to mimick what can be done using the handlebars-layouts {{#extend}, {{#embed}}, {{#block}} and {{#content}} helpers.

This was a fun exercise driven by curiosity which helped me understand Handlebars partials a whole lot better. As is always the case, we must evaluate each project individually to assess what library features and dependencies may or may not be needed. There is never a one-size-fits-all solution.Footnote 8

If you are curious to dig further, you can take a look at the GitHub repository I used to learn and write this article. Feel free to try out different combinations and setups to further experiment with the hidden power of Handlebars’ partials.

Footnotes

  1. This is comparable to the handlebars-layouts {{#block}} helper.  Return to the text before footnote 1
  2. Handlebars comments are used because they do not get rendered in the final HTML file. Had we used regular HTML comments, those comments would get rendered in the final output because anything within a partial block will get rendered.  Return to the text before footnote 2
  3. This is comparable to the handlebars-layouts {{#extend}} helper.  Return to the text before footnote 3
  4. We are taking advantage of the Handlebars partial parameters feature to set the page title.  Return to the text before footnote 4
  5. The way the inline partial is used here is comparable to the {{#content}} handlebars-layouts helper.  Return to the text before footnote 5
  6. This is comparable to the handlebars-layouts {{#embed}} helper.  Return to the text before footnote 6
  7. An improvement could be made by taking advantage of the Handlebars {{#if}}{{else}} conditional helper in case no partial parameters are passed in.  Return to the text before footnote 7
  8. There are a few features that don’t come for free if you are only using Handlebars. This includes the ability to append or prepend using the {{#content}} helper or the ability to use the content helper as a subexpression to check whether the content has been provided using conditonal blocks Return to the text before footnote 8
Previous Next
Copyrights
We respect the property rights of others, and are always careful not to infringe on their rights, so authors and publishing houses have the right to demand that an article or book download link be removed from the site. If you find an article or book of yours and do not agree to the posting of a download link, or you have a suggestion or complaint, write to us through the Contact Us .
Read More