-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
2466 lines (2398 loc) · 99.4 KB
/
Copy pathindex.html
File metadata and controls
2466 lines (2398 loc) · 99.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Erwin Francisco Macias Ghiglione" />
<meta
name="description"
content="Templating languages introduction and orientation."
/>
<meta name="color-scheme" content="dark" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta
property="og:url"
content="https://efrmg.github.io/templating-guide/"
/>
<meta
property="og:title"
content="Templating languages introduction and orientation."
/>
<meta
property="og:description"
content="A beginner's guide to templating. We use Handlebars and Nunjucks. This is a complete introduction using many tools: Ghost, Metalsmith, Eleventy and Next.js."
/>
<meta
property="og:image"
content="https://efrmg.github.io/templating-guide/presentation-card.webp"
/>
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:creator" content="@FranciscoM5896" />
<meta
property="twitter:url"
content="https://efrmg.github.io/templating-guide/"
/>
<meta
property="twitter:title"
content="Templating languages introduction and orientation."
/>
<meta
property="twitter:description"
content="A beginner's guide to templating. We use Handlebars and Nunjucks. This is a complete introduction using many tools: Ghost, Metalsmith, Eleventy and Next.js."
/>
<meta
property="twitter:image"
content="https://efrmg.github.io/templating-guide/presentation-card.webp"
/>
<link rel="icon" type="image/svg+xml" href="/logo.webp" />
<title>Guide to Handlebars and Nunjucks Templating</title>
</head>
<body>
<!-- Tippy.js sets an aria-describedby attr, an explicit title attr is not needed, nor strict requirement in HTML, hence the suppression of such warning at ".markuplintrc.json" -->
<div class="main-wrapper">
<div class="main-grid">
<div class="sidebar-button" aria-expanded="false">
<label for="hamburger-check" class="hamburger-check">
<input id="hamburger-check" type="checkbox" />
<svg viewBox="0 0 32 32">
<path
class="hamburger-line hamburger-line-top-bottom"
d="M27 10 13 10C10.8 10 9 8.2 9 6 9 3.5 10.8 2 13 2 15.2 2 17 3.8 17 6L17 26C17 28.2 18.8 30 21 30 23.2 30 25 28.2 25 26 25 23.8 23.2 22 21 22L7 22"
></path>
<path class="hamburger-line" d="M7 16 27 16"></path>
</svg>
</label>
</div>
<aside class="sidebar" id="sidebar">
<h1>Handlebars & Nunjucks</h1>
<hr class="first" />
<nav>
<ul class="platforms">
<li class="first-last">
<a href="#introduction">Introduction</a>
</li>
<li class="platform">
<h2 class="platform-name">Ghost</h2>
<ul class="platform-docs">
<li><a href="#getting-started">Getting Started</a></li>
<li><a href="#expressions">Expressions</a></li>
<li><a href="#contexts">Contexts</a></li>
<li><a href="#helpers">Helpers</a></li>
<li><a href="#data-helpers">Data Helpers</a></li>
<li><a href="#functional-helpers">Functional Helpers</a></li>
<li><a href="#more-functional">Greater Functionality</a></li>
<li><a href="#utility-helpers">Utility Helpers</a></li>
<li><a href="#custom-helpers">Custom Helpers?</a></li>
<li><a href="#partials">Partials</a></li>
<li><a href="#routing">Routing</a></li>
<li><a href="#example">Complete Example</a></li>
</ul>
</li>
<li class="platform">
<h2 class="platform-name">Metalsmith</h2>
<ul class="platform-docs">
<li><a href="#metalsmith-introduction">Introduction</a></li>
<li>
<a href="#metalsmith-project-structure"
>Project Structure</a
>
</li>
<li><a href="#metalsmith-setup">Setup</a></li>
<li>
<a href="#metalsmith-build-script">The Build Script</a>
</li>
<li>
<a href="#metalsmith-running-the-build"
>Running the Build</a
>
</li>
<li>
<a href="#metalsmith-blog-example">Creating a Blog</a>
</li>
<li>
<a href="#metalsmith-more-plugins">More Plugins</a>
</li>
<li>
<a href="#metalsmith-custom-plugins">Custom Plugins</a>
</li>
</ul>
</li>
<li class="platform">
<h2 class="platform-name">Eleventy</h2>
<ul class="platform-docs">
<li><a href="#eleventy-introduction">Introduction</a></li>
<li>
<a href="#eleventy-project-structure">Project Structure</a>
</li>
<li>
<a href="#eleventy-building-a-blog">Building a Blog</a>
</li>
<li>
<a href="#eleventy-nunjucks">Pivoting to Nunjucks</a>
</li>
<li>
<a href="#eleventy-nunjucks-setup">Configuration & Setup</a>
</li>
<li>
<a href="#eleventy-markdown-nunjucks"
>Markdown + Nunjucks</a
>
</li>
<li>
<a href="#eleventy-advanced-topics">Advanced Topics</a>
</li>
</ul>
</li>
<li class="platform">
<h2 class="platform-name">Next.js</h2>
<ul class="platform-docs">
<li><a href="#nextjs-introduction">Introduction</a></li>
<li><a href="#nextjs-setup">Setup & Config</a></li>
<li>
<a href="#nextjs-environment">The Nunjucks Environment</a>
</li>
<li>
<a href="#nextjs-home-page">Root Layout & Home Page</a>
</li>
<li>
<a href="#nextjs-static-page">Static Page (Products)</a>
</li>
<li><a href="#nextjs-cms-page">CMS Page (Articles)</a></li>
<li>
<a href="#nextjs-client-page">Client Page (Dashboard)</a>
</li>
<li><a href="#nextjs-running">Running the Project</a></li>
</ul>
</li>
<li class="platform">
<ul class="platform-docs">
<li class="first-last">
<a href="#honorable-mentions">Honorable Mentions</a>
</li>
</ul>
</li>
</ul>
</nav>
</aside>
<main class="main-content" id="main-content">
<section id="introduction">
<h2 class="first-last">Welcome to the Templating Guide</h2>
<div class="hero-icons-group">
<img src="/handlebarsdotjs.svg" alt="Handlebarsjs icon" />
<img src="/ghost.webp" alt="Ghost icon" />
<img src="/metalsmith.webp" alt="Metalsmith icon" />
<img src="/eleventy.svg" alt="Eleventy icon" />
<img src="/react.webp" alt="React icon" />
<img src="/next.webp" alt="Next icon" />
<img src="/strapi.webp" alt="Strapi icon" />
<img src="/nunjucks.svg" alt="Nunjucks icon" />
</div>
<p>
Here we will explore how to use Handlebars and Nunjucks in various
environments: Ghost, Metalsmith, Eleventy, and Next.js. This guide
covers how to create reusable templates, manage data and even
extend the functionality of these engines to fit specific needs.
</p>
<p>
It is quite a common occurrence in programming that one needs to
maintain a structure in code, while changing content found
throughout it.
</p>
<p>
Imagine one has a recipe for a nice cake. A template is akin to
such a recipe: it possesses a structure and placeholders (exempli
gratia,
<em>add <span class="underscores-color">__</span> eggs,</em>
<em>bake for <span class="underscores-color">__</span> minutes</em
>).
</p>
<p>
A templating language, such as
<a href="https://handlebarsjs.com/" target="_blank">Handlebars</a
>, fulfills the <em>recipe</em> role for <abbr>HTML</abbr>. It
enables one to compose a base HTML structure and subsequently
inject dynamic data therein.
</p>
<p>
In other words, Handlebars functions as a <em>bridge</em>,
retrieving <span class="underscores-color">content</span> from
various sources (such as a database or
<code>.<abbr>md</abbr></code> files for blog posts) and
integrating it into the HTML structure.
</p>
<p>
Fundamentally, this process involves:
<em>setting</em>
<span class="underscores-color underline"> data</span
><em> somewhere.</em>
</p>
</section>
<hr />
<section id="getting-started">
<h2>Templating with Ghost</h2>
<p>
One might consider using a <abbr>VPS</abbr> for self-hosting a
Ghost instance. However, it is also pertinent to note the
alternative of employing a <abbr>JAMstack</abbr> approach, for
which ample information and resources are available online,
specifically regarding Ghost.
</p>
<p>
The latter option, however, presents a challenge: many of Ghost's
inherent strengths (subscriptions, user content management, mail
forwarding for updates, post visit tracking; all managed via a
good <abbr>UI</abbr>) would necessitate integration from scratch,
or through separate third-party services if Ghost were to operate
headless with an <abbr>SSG</abbr>.
</p>
<p>
Should the reader require a general overview of SSGs without the
particularities of Ghost, please navigate to the other platform
sections via the sidebar. The decision to commence with
<a href="https://docs.ghost.org/introduction">Ghost</a> stems from
its relative flexibility and comprehensive feature set concerning
custom Handlebars helpers, even if it could be considered
<em>bloated</em> for certain applications.
</p>
<p class="has-border-under">
The installation process will be omitted for brevity, just procure
the prerequisite programs and correct version of
<a href="https://nodejs.org/">Node.js</a>. Do note that building a
Ghost instance does take a good amount of time; even to start a
development server is too lenghty a time for my liking. There also
is no <abbr>HMR</abbr>, having one to manually refresh the page
after file changes and rebuilds.
</p>
<p>
Templating proves ideal for a <abbr>CMS</abbr>
platform like
<a href="https://ghost.org/" target="_blank">Ghost</a>, where
content (posts, pages, and general
<span class="underscores-color">data</span>) is separate from the
overall design, yet requires periodic addition or modification.
</p>
<p>
A "Ghost theme" comprises a collection of
<code>.<abbr>hbs</abbr></code> files, which are essentially
<abbr>HTML</abbr> files, plus Handlebars expressions for Ghost's
particular implementation of the language.
</p>
<p>The most important files in a Ghost theme are:</p>
<ul class="moustache-list">
<li>
<code>index.hbs</code>: Fallback for
<code>{tag,author,default}.hbs</code>. Extends
<code>default.hbs</code>. If you lack one, Ghost will set one
regardless.
</li>
<li>
<code>default.hbs</code>: The <strong>main layout file</strong>.
The frame for the entire site (header, main, footer, etcetera).
One <em>could</em> just use <code>index.hbs</code> for this, as
the concept is similar.
</li>
<li> <code>post.hbs</code>: Displays a single post.</li>
</ul>
</section>
<hr />
<section id="expressions">
<h2>Basic Syntax: Expressions</h2>
<p>
Handlebars expressions are straightforward, encapsulated within
double curly braces: <code>{{expression}}</code>.
</p>
<p>
Ghost supplies the
<span class="underscores-color">data</span> from its own database
edited through its web <abbr>UI</abbr> at
<code>https://WEBSITE_URL/ghost</code>, while as Handlebars
renders it in the expression's place.
</p>
<p>
For instance, within the <code>post.hbs</code> file, one could
define a document title as follows:
</p>
<pre><code><title>{{title}}</title></code></pre>
<p>
Upon a user's page view, Ghost will have replaced
<code>{{title}}</code> with the actual title of that post, as set
by an administrator of a Ghost instance for any particular post.
</p>
</section>
<hr />
<section id="contexts">
<h2>Understanding Contexts</h2>
<p>
The <span class="underscores-color">data</span> accessible to
Handlebars expressions varies depending on the page being viewed.
This phenomenon is termed "context." For example:
</p>
<ul class="moustache-list">
<li>
On a single post page (<code>post.hbs</code>), one has the
<code>post</code> <em>context</em>. This grants access to all
data pertinent to that specific post, such as
<code>{{title}}</code>, <code>{{content}}</code> and
<code>{{published_at}}</code>.
</li>
<li>
On the homepage (<code>index.hbs</code> or
<code>default.hbs</code>), one operates within the
<code>index</code> <em>context</em>. Here, individual post data
is not directly available; instead, a collection of all posts is
provided and could be looped through.
</li>
</ul>
<p>
A thorough understanding of <em>context</em> is paramount for
discerning which data works at which parts of the Ghost theme.
Sometimes, mostly with custom routing on complex themes, it could
get confusing or seem broken.
</p>
</section>
<hr />
<section id="helpers">
<h2>Ghost Helpers</h2>
<p>
Helpers are JavaScript functions embedded within templates. Their
primary purpose is to facilitate logical operations within a
logic-less templating language such as Handlebars.
</p>
<p>
Ghost provides several custom template types by default. May you
think of them like complex snippets evoked by simple keywords.
</p>
<p>
A non-exhaustive enumeration of these helpers begins in the next
section. The definition of custom helpers and/or altrernatives
will be addressed later.
</p>
</section>
<hr />
<section id="data-helpers">
<h2>Data Helpers</h2>
<p>
This category represents the most frequently employed helpers for
outputting content:
</p>
<ul class="moustache-list">
<li> <code>{{title}}</code>: Title of the post.</li>
<li>
<code>{{content}}</code>: Displays the complete content of the
post, often formatted within
<code><p></p></code> tags, thereby including
paragraph breaks on newlines. It could even include a
<abbr>CTA</abbr> to sign up or upgrade for determinate users if
configuration is set for restricted and/or limited access.
</li>
<li>
<code>{{excerpt}}</code>: Provides a shortened version of the
post's content.
</li>
<li>
<code>{{author.name}}</code>: Outputs the name of the post's
author, corresponding to a Ghost user.
</li>
<li>
<code>{{tags}}</code>: Presents a literal list of tags
associated with the post (e.g., news, technology,
entertainment), which may be iterated through.
</li>
</ul>
</section>
<hr />
<section id="functional-helpers">
<h2>Functional Helpers</h2>
<p>
Ghost additionally furnishes more advanced helpers, enabling
greater control over template logic.
</p>
<h3>The <code>{{#foreach}}</code> Helper</h3>
<p>
In the homepage, or wherever the post context is defined within
<code>routes.yaml</code>, one will most likely need to iterate
through the published posts. The <code>{{#foreach}}</code> helper
is ideal for this.
</p>
<pre><code>{{#foreach posts}}
<article>
<h2>{{title}}</h2>
<p>{{excerpt}}</p>
<a href="{{url}}">Open post itself!</a>
</article>
{{/foreach}}</code></pre>
<p>
It is important to note that the link to the post itself opens in
<code>post.hbs</code>.
</p>
<p>
Let us see a simple example of such a file, where the excerpt is
rather the full content, and there also is an image from the Ghost
admin panel.
</p>
<div class="filepath">
<code>post.hbs</code>
</div>
<pre class="has-filepath-above"><code><div>
<h1>{{title}}</h1>
<p>{{content}}</p>
<img src="{{img_url feature_image size="big" format="webp"}}" />
</div></code></pre>
<h3>
The <code>{{#post}}</code> Helper for Accessing
<code>post</code> Context
</h3>
<p>
When operating within the <code>post</code> context on a dedicated
<code>post.hbs</code> page, one merely and practically requires
<code>{{content}}</code> to retrieve the post's content. The
<code>{{#post}}...{{/post}}</code> block is implicitly available
in this scenario.
</p>
<p>
However, note that the
<a
href="https://docs.ghost.org/themes/contexts/post"
target="_blank"
>documentation</a
>
uses the <code>{{#post}}</code> helper to make explicit that we
are "dropping into" such context.
</p>
<p>
The technically correct way to access this
<span class="underscores-color">data</span> would be to write:
<code>{{post.title}}</code>, <code>{{post.content}}</code>, etc.,
even if it works either way.
</p>
<p>
I do suggest to follow the documentation and access the
<code>post</code> context within a
<code>{{#post}}...{{/post}}</code> block.
</p>
<pre><code>{{#post}}
{{!-- rest of ... --}}
<p>{{content}}</p>
{{!-- ... the post --}}
{{/post}}</code></pre>
<p>
Just as when at the <code>index</code> context we have a list of
posts to loop through with the use of the
<code>{{#foreach}}</code> helper, the <code>post</code> context
provides a more particular one.
</p>
<p>
What if we are on any other page, such as
<code>page.hbs</code> or similar?
</p>
<h3>The <code>{{#get}}</code> Helper</h3>
<p>
The <code>{{#get}}</code> helper is a more widely-encompassing
instrument for retrieving
<span class="underscores-color">data</span> not available in the
current context.
</p>
<p>
For example, one could fetch the three most recent posts
associated with a specific tag from any
<code>.<abbr>hbs</abbr></code> file within the theme.
</p>
<pre><code>{{#get "posts" filter="tags:specific-tag" limit="3"}}
{{#foreach posts}}
<h2>{{title}}</h2>
{{/foreach}}
{{/get}}</code></pre>
<h3>The <code>{{#if}}</code> Helper</h3>
<p>
The <code>{{#if}}</code> helper enables conditional rendering of
<abbr>HTML</abbr> blocks. This is particularly useful, for
instance, when verifying the existence of a feature image prior to
attempting its display.
</p>
<pre><code>{{#if feature_image}}
<img src="{{img_url feature_image}}" alt="{{title}}">
{{else}}
{{!-- Do a barrel roll! (Z or R twice) --}}
{{/if}}</code></pre>
<p>
Keeping in mind that <code>{{feature_image}}</code> is part of the
<span class="underscores-color">data</span> Ghost provides within
the <code>post</code> context.
</p>
</section>
<hr />
<section id="more-functional">
<h2>Adding Extra Functionality to Ghost</h2>
<p>
It is pertinent to acknowledge the potential limitations of Ghost
without the provision of custom fields for posts. While the
platform offers extensive customization and openness compared to
many counterparts, one is restricted to
<code>{{title}}</code>, <code>{{content}}</code>, and
<code>tags</code> for dynamic
<span class="underscores-color">data</span> assignment.
</p>
<p>
An alternative approach involves employing a script with its type
attribute set to <code>"application/json"</code>, containing
<span class="underscores-color">data</span> fetched from the Ghost
admin via helpers. This data can then be parsed by a
<abbr>JS</abbr>
script to programmatically create elements and assign their
<code>textContent</code>, with organization facilitated by
<code>tags</code>. As previously mentioned, these posts
accommodate only two text inputs: title and content; therefore, we
would at most have two fields per tag.
</p>
<p>
Here, we construct a <abbr>JSON</abbr> array of objects for each
post having a specific tag, also incorporating all other tags for
extra <span class="underscores-color">data</span>:
</p>
<pre><code>{{!-- Fetch posts tagged as "custom" into a JSON array of objects --}}
{{!-- Include their comma-separated tags for further use as well --}}
{{#get "posts" filter="tag:custom" include="tags"}}
<script type="application/json" id="post-data">
[
{{#foreach posts}}
{
"title": "{{title}}",
"content": "{{content}}",
"tags": [
{{#foreach tags}}
"{{slug}}"{{#unless @last}},{{/unless}}
{{/foreach}}
]
}{{#unless @last}},{{/unless}}
{{/foreach}}
]
</script>
{{/get}}</code></pre>
<p>
Observe how the <code>{{#unless}}</code> helper facilitates the
proper comma separation of each object, resulting in valid JSON.
The array of tags is delimited the same.
</p>
<p>
Regarding the <code>{{slug}}</code> helper, it is used to output a
<abbr>URL</abbr>-safe version of the tags. One could also use
<code>{{name}}</code> to retrieve the tag precisely as entered in
the Ghost admin panel irrespective of URL-friendliness, or even
<code>{{url}}</code>, which is still less suitable in this
example.
</p>
<pre><code>const dataElement = document.getElementById("post-data");
const postData = JSON.parse(dataElement.textContent);
// Once the data is parsed, one can access postData.title, etc.
postData.forEach((post) => {
const divElement = document.createElement("div");
// Ascertain if the post contains a specific tag.
const hasSpecificTag = post.tags.includes("specific-tag");
divElement.textContent = hasSpecificTag ? post.title : post.content;
document.body.appendChild(divElement);
});</code></pre>
<p>
It is noteworthy that <code>tags</code> are what makes
customization possible here.
</p>
</section>
<hr />
<section id="utility-helpers">
<h2>Utility Helpers</h2>
<p>This last type are very nice features!</p>
<ul class="moustache-list">
<li>
<code>{{asset "sub-dir/file.ext"}}</code>: A crucial one for
linking to the theme's <abbr>CSS</abbr>, <abbr>JS</abbr>, or any
other static asset, such as images within the
<code>assets/</code> directory, providing cache functionality.
</li>
<li>
<code>{{pagination}}</code>: Generates formatted
<abbr>HTML</abbr> for pagination links, which are customizable
in <code>pagination.hbs</code>. This implies one would limit the
ammount of posts shown per page and be within the
<code>post</code> context. It does not work when using the
<code>{{#get}}</code> helper (in my experience).
</li>
<li>
<code>{{search}}</code>: Renders a functional, pre-styled
<strong>search</strong> button and its icon.
</li>
</ul>
</section>
<hr />
<section id="custom-helpers">
<h2>Custom Helpers: Are They Possible?</h2>
<p>These are not officially supported by Ghost.</p>
<p>
Should one genuinely require custom helpers, although more robust
(and safer) alternatives are presented shortly, one could either
modify Ghost's core files (which would get removed upon updating
Ghost) or implement a reverse proxy with middleware, as
<a
href="https://dev.to/piotrbednarski/make-custom-handlebar-helpers-in-ghost-48nh"
target="_blank"
>shown here</a
>.
</p>
<h3>Officially Supported Alternatives</h3>
<p>
It is entirely feasible to implement extra functionality without
registering new helpers:
</p>
<ul class="moustache-list">
<li>
One may leverage client-side JavaScript, as previously
<a href="#more-functional">illustrated</a>.
</li>
<li>
Another related option involves the
<a href="https://docs.ghost.org/content-api" target="_blank"
>Content <abbr>API</abbr></a
>, the suitability of which is contingent upon the specific
requirements of one's application.
</li>
</ul>
<p>
For the majority of use cases, the built-in helpers suffice.
However, it is important to note that Handlebars' inherent
logic-less nature could be an issue. The second major section of
this website will pivot to Nunjucks to address this same
limitation.
</p>
</section>
<hr />
<section id="partials">
<h2>Structuring Themes with Partials</h2>
<p>
Partials are modular, reusable segments of a template. Their
utility lies in promoting the <abbr>DRY</abbr>
principle. For example, one could create a partial dedicated to
post meta-information (e.g., author, date, tags).
</p>
<p>
To implement, one could create a template file within the
<code>partials/</code> directory and subsequently incorporate it
into another template using the following syntax:
</p>
<pre><code class="language-handlebars">{{> "post-card"}}</code></pre>
<div class="filepath">
<code>partials/post-card.hbs</code>
</div>
<pre
class="has-filepath-above"
><code class="language-html"><div class="card">
<h2>Hello from Somewhere</h2>
<p>
The weather is great and the views are amazing.
Sending you a little postcard through the web.
</p>
<p class="signature">
— Yours Truly.
</p>
</div></code></pre>
</section>
<p>
Templates not only are reusable pieces of HTML inside a Handlebars
file, as one could also use helpers inside them. I omit this in this
example.
</p>
<hr />
<section id="routing">
<h2>Routing and Layouts</h2>
<p>
In Ghost, the <code>default.hbs</code> file functions as the
primary template for one's site. It typically incorporates the
header, footer and other elements common to all pages. The
<code>{{{body}}}</code> helper serves as a specialized placeholder
that instructs Ghost where to inject the content of the
<strong>current page</strong>.
</p>
<p>
For instance, when a user accesses a single post, Ghost will
render the
<code>post.hbs</code> template and embed its content into the
<code>{{{body}}}</code> of the <code>default.hbs</code> file. This
mechanism ensures a consistent layout across the entire site.
</p>
<p>
Ghost's routing system facilitates the creation of custom URLs and
their mapping to specific templates. This configuration is managed
within a
<code>routes.yaml</code> file, which can be uploaded via the Ghost
admin panel. As an example, one could establish a static page
featuring an "easter egg" by defining a custom route.
</p>
<div class="filepath"><code>routes.yaml</code></div>
<pre class="has-filepath-above"><code>routes:
/easter-egg/:
template: extra-page
</code></pre>
<p>
This configuration directs Ghost to render the
<code>extra-page.hbs</code> template when a user navigates to the
<code>/easter-egg</code> path.
</p>
<div class="filepath"><code>extra-page.hbs</code></div>
<pre class="has-filepath-above"><code>{{!< default}}
<div class="easter-egg">
<p>Here is the easter egg!</p>
</div>
</code></pre>
<p>
<code>{{!< default}}</code> <em>sends</em> our page to
<code>default.hbs</code>, placing it at <code>{{{body}}}</code>.
</p>
<h3>One-off templates</h3>
<p>
One is able to create custom routing by adding the slug of a page
to a template file. This necessitates no actual routing in
<code>routes.yaml</code>. Some examples, including their
<abbr>URL</abbr> path:
</p>
<ul class="moustache-list">
<li>
<code>page-about.hbs</code>: Custom template for an
<code>about</code> page at <code>/about</code>.
</li>
<li>
<code>tag-news.hbs</code>: Custom template for a
<code>news</code> archival page at <code>/tag/news</code>.
</li>
<li>
<code>author-staff.hbs</code>: Custom template for a staff
member at <code>/author/staff</code>. This could be used to add
a special <abbr>UI</abbr> badge, for instance.
</li>
</ul>
</section>
<hr />
<section id="example">
<h2>Complete Example: A Feature-Rich Homepage</h2>
<p>
This section elucidates the integration of previous concepts. The
example will encompass partials for the header, footer and post
cards, leveraging built-in Ghost helpers to construct a
comprehensive layout.
</p>
<h3>Creating Partials within <code>partials/</code></h3>
<p>
Several partials will be created to maintain code organization and
reusability.
</p>
<div class="filepath"><code>partials/header.hbs</code></div>
<pre
class="has-filepath-above"
><code class="language-handlebars"><header class="site-header">
<div class="site-header-content">
<h1 class="site-title">{{@site.title}}</h1>
<p class="site-description">{{@site.description}}</p>
</div>
</header>
</code></pre>
<p>
Data helpers starting with <code>@</code> indicate
<strong>global</strong>
<span class="underscores-color">data</span> is provided, available
anywhere within the theme.
</p>
<div class="filepath"><code>partials/post-card.hbs</code></div>
<pre
class="has-filepath-above"
><code class="language-handlebars"><article class="post-card">
{{#if feature_image}}
<a class="post-card-image-link" href="{{url}}">
<img class="post-card-image" src="{{img_url feature_image}}" alt="{{title}}" />
</a>
{{/if}}
<div class="post-card-content">
<a class="post-card-content-link" href="{{url}}">
<header class="post-card-header">
<h2 class="post-card-title">{{title}}</h2>
</header>
<section class="post-card-excerpt">
<p>{{excerpt}}</p>
</section>
</a>
<footer class="post-card-meta">
<span class="post-card-author">{{author.name}}</span>
</footer>
</div>
</article>
</code></pre>
<div class="filepath"><code>partials/footer.hbs</code></div>
<pre
class="has-filepath-above"
><code class="language-handlebars"><footer class="site-footer">
<a class="site-footer-link" href="{{@site.url}}">{{@site.title}}</a>
© {{date format="YYYY"}} — All rights reserved.
</footer>
</code></pre>
<h3>We are at the index, homepage or root</h3>
<p>
This content will be outputted at the
<code>{{{body}}}</code> helper.
</p>
<div class="filepath"><code>index.hbs</code></div>
<pre
class="has-filepath-above"
><code class="language-handlebars">{{!< default}}
{{#foreach posts}}
{{> "post-card"}}
{{/foreach}}</code></pre>
<p>
Here is the conclusive example of the file where everything goes:
</p>
<div class="filepath"><code>default.hbs</code></div>
<pre
class="has-filepath-above"
><code class="language-handlebars"><!doctype html>
<html>
<head>
<title>{{@site.title}}</title>
<link rel="stylesheet" type="text/css" href="{{asset "css/style.css"}}" />
</head>
<body>
{{> "header"}}
{{{body}}}
<a href="/easter-egg">Where the routing goes</a>
{{> "footer"}}
</body>
</html>
</code></pre>
<h3>Analysis of the Homepage Example</h3>
<ul class="moustache-list">
<li>
<strong>Inclusion of Header and Footer:</strong> It incorporates
<code>{{> "header"}}</code> and
<code>{{> "footer"}}</code> to integrate the respective
partials.
</li>
<li>
<strong>Iteration through Posts:</strong> The
<code>{{#foreach posts}} ... {{/foreach}}</code> block iterates
over the collection of posts provided by Ghost, which are
typically authored by an administrator within the Admin panel.
</li>
<li>
<strong>Rendering of Post Cards:</strong> Within the loop,
<code>{{> "post-card"}}</code> is invoked for each post. The
context inside the partial is programmatically set to the
current post in the iteration, thereby ensuring correct
functionality of helpers such as <code>{{title}}</code> and
<code>{{excerpt}}</code>.
</li>
</ul>
</section>
<hr />
<section id="metalsmith-introduction">
<h2>Introduction to Metalsmith</h2>
<p>
Metalsmith is an exceptionally straightforward, pluggable
<abbr>SSG</abbr>. Its distinctive characteristic lies in its core
philosophy: <strong>every component is a plugin</strong>.
</p>
<p>
Unlike other SSGs that offer a fixed set of features, Metalsmith
provides a minimalist framework, delegating further functionality
to its plugin ecosystem.
</p>
<h3>The Core Pipeline</h3>
<p>Metalsmith operates on a fundamental, three-step pipeline.</p>
<ul class="moustache-list">
<li>
<strong>Read:</strong> Ingests all files from a designated
source directory (e.g., <code>src/</code>) and loads them into
memory. Each file is represented as a JavaScript object
containing its main data and associated metadata.
</li>
<li>
<strong>Process:</strong> This collection of file objects then
undergoes a series of transformations via plugins. Each plugin
possesses the capability to manipulate the files by modifying
their content or adding and removing files from the collection.
</li>
<li>
<strong>Write:</strong> Upon completion of all plugin
operations, Metalsmith outputs the resulting files to a
specified destination directory (e.g., <code>build/</code>).
</li>
</ul>
<h3>The Efficacy of Metadata</h3>
<p>
Metadata is handled in quite neat a manner in our upcoming
examples: when Metalsmith processes a file, it parses any
front-matter (id est, <abbr>YAML</abbr> at the apex of a Markdown
file) and exposes it as an associated metadata object. This
metadata is then propagated through the plugin chain.
</p>
<p>
For example, a Markdown plugin would read the raw content of a
file, convert it to <abbr>HTML</abbr>, and update the file
object's <code>contents</code> property. A layouts plugin would
then receive that HTML content and inject it into a Handlebars
template, utilizing the file's metadata to populate variables such
as <code>{{title}}</code> or <code>{{author}}</code>.
</p>
</section>
<hr />
<section id="metalsmith-project-structure">
<h2>Project Structure</h2>
<p>
A typical directory structure for a Metalsmith project provides a
clear separation of concerns and facilitates a clean build
process.
</p>
<pre><code class="language-plaintext">.
├── build/
├── layouts/
│ ├── post.hbs
│ └── index.hbs
├── src/
│ ├── posts/
│ │ ├── first-post.md
│ │ ├── second-post.md
│ │ └── third-post.md
│ └── index.md
└── metalsmith.js</code></pre>
<p>In case you would like to follow along:</p>
<pre><code class="language-bash">mkdir -p layouts src/posts</code></pre>
<ul class="moustache-list">
<li>
<code>build/</code>: The destination for the built site. It is
created by Metalsmith and contains the final
<abbr>HTML</abbr> files and any other assets.
</li>
<li>
<code>layouts/</code>: It holds Handlebars templates; these
define the structure and layout of different pages.
</li>
<li>
<code>src/</code>: The source directory for the content.
Metalsmith reads all files from here to begin the build process.
</li>
<li>
<code>metalsmith.js</code>: Build script where the Metalsmith
pipeline is defined and plugins are configured.
</li>
</ul>
</section>