The hidden power of Handlebars partials
At 4/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.
Partials primer
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.
The directory structure
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:
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.
Layouts, pages, and includes
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.
The layout file
Let’s first look at the layouts/base.hbs
layout file:
There are a few things going on, so let’s break this layouts/base.hbs
file down a bit to better understand it.
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
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.
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 page file
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:
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
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
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 files
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.
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.
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.
The final outcome
Now that we’ve walked through all the pieces in play, let’s do a recap to remind ourselves what we have.
layouts/base.hbs
layouts/base.hbs
- acts as the base layout file
- uses partial blocks for default and dynamic content
pages/page-one.hbs
pages/page-one.hbs
- 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
includes/*.hbs
includes/*.hbs
- 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:
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
:
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:
As you can see, we are able to create two different pages using the same base layout. Pretty neat!
There you have it!
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
-
This is comparable to the handlebars-layouts
{{#block}}
helper. Return to the text before footnote 1 - 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
-
This is comparable to the handlebars-layouts
{{#extend}}
helper. Return to the text before footnote 3 - We are taking advantage of the Handlebars partial parameters feature to set the page title. Return to the text before footnote 4
-
The way the inline partial is used here is comparable to the
{{#content}}
handlebars-layouts helper. Return to the text before footnote 5 -
This is comparable to the handlebars-layouts
{{#embed}}
helper. Return to the text before footnote 6 -
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 -
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 thecontent
helper as a subexpression to check whether the content has been provided using conditonal blocks. Return to the text before footnote 8