1 | # express-hbs
|
2 |
|
3 | Express handlebars template engine with multiple layouts, blocks and cached partials.
|
4 |
|
5 | ## v2.0.0
|
6 |
|
7 | Version 2 was a rewrite and cleanup, with no known breaking changes. Lots of bugs were fixed which may have subtly changed behaviour.
|
8 |
|
9 | Full details: https://github.com/TryGhost/express-hbs/releases/tag/2.0.0
|
10 |
|
11 | ## v1.0.0 Breaking Changes
|
12 |
|
13 | If you're upgrading from v0.8.4 to v1.0.0 there are some potentially breaking changes to be aware of:
|
14 |
|
15 | 1. Handlebars @v4.0.5 - please see the [handlebars v4.0 compatibility notes](https://github.com/wycats/handlebars.js/blob/master/release-notes.md#v400---september-1st-2015)
|
16 | 2. The file extension for partial files must now match the extension configured in `extname` - please see [the PR](https://github.com/TryGhost/express-hbs/pull/88)
|
17 |
|
18 | ## Usage
|
19 |
|
20 | To use with express 4.
|
21 | ```js
|
22 | var hbs = require('express-hbs');
|
23 |
|
24 | // Use `.hbs` for extensions and find partials in `views/partials`.
|
25 | app.engine('hbs', hbs.express4({
|
26 | partialsDir: __dirname + '/views/partials'
|
27 | }));
|
28 | app.set('view engine', 'hbs');
|
29 | app.set('views', __dirname + '/views');
|
30 | ```
|
31 | To use with express 3 is the same as above, except use hbs.express3
|
32 |
|
33 | ```js
|
34 | app.engine('hbs', hbs.express3({
|
35 | partialsDir: __dirname + '/views/partials'
|
36 | }));
|
37 | ```
|
38 |
|
39 | Options for `#express3` and `#express4`
|
40 |
|
41 | ```js
|
42 | hbs.express4({
|
43 | partialsDir: "{String/Array} [Required] Path to partials templates, one or several directories",
|
44 |
|
45 | // OPTIONAL settings
|
46 | blockHelperName: "{String} Override 'block' helper name.",
|
47 | contentHelperName: "{String} Override 'contentFor' helper name.",
|
48 | defaultLayout: "{String} Absolute path to default layout template",
|
49 | extname: "{String} Extension for templates & partials, defaults to `.hbs`",
|
50 | handlebars: "{Module} Use external handlebars instead of express-hbs dependency",
|
51 | i18n: "{Object} i18n object",
|
52 | layoutsDir: "{String} Path to layout templates",
|
53 | templateOptions: "{Object} options to pass to template()",
|
54 | beautify: "{Boolean} whether to pretty print HTML, see github.com/einars/js-beautify .jsbeautifyrc",
|
55 |
|
56 | // override the default compile
|
57 | onCompile: function(exhbs, source, filename) {
|
58 | var options;
|
59 | if (filename && filename.indexOf('partials') > -1) {
|
60 | options = {preventIndent: true};
|
61 | }
|
62 | return exhbs.handlebars.compile(source, options);
|
63 | }
|
64 | });
|
65 | ```
|
66 |
|
67 | ## Syntax
|
68 |
|
69 | To mark where layout should insert page
|
70 |
|
71 | {{{body}}}
|
72 |
|
73 | To declare a block placeholder in layout
|
74 |
|
75 | {{{block "pageScripts"}}}
|
76 |
|
77 | To define block content in a page
|
78 |
|
79 | {{#contentFor "pageScripts"}}
|
80 | CONTENT HERE
|
81 | {{/contentFor}}
|
82 |
|
83 | ## Layouts
|
84 |
|
85 | There are three ways to use a layout, listed in precedence order
|
86 |
|
87 | 1. Declarative within a page. Use handlebars comment
|
88 |
|
89 | {{!< LAYOUT}}
|
90 |
|
91 | Layout file resolution:
|
92 |
|
93 | If path starts with '.'
|
94 | LAYOUT is relative to template
|
95 | Else If `layoutsDir` is set
|
96 | LAYOUT is relative to `layoutsDir`
|
97 | Else
|
98 | LAYOUT from path.resolve(dirname(template), LAYOUT)
|
99 |
|
100 | 2. As an option to render
|
101 |
|
102 | ## ⚠️ This creates a potential security vulnerability:
|
103 |
|
104 | Do not use this option in conjunction with passing user submitted data to res.render e.g. `res.render('index', req.query)`. This allows users to read arbitrary files from your filesystem!
|
105 |
|
106 | ```js
|
107 | res.render('veggies', {
|
108 | title: 'My favorite veggies',
|
109 | veggies: veggies,
|
110 | layout: 'layout/veggie'
|
111 | });
|
112 | ```
|
113 |
|
114 | This option also allows for layout suppression (both the default layout and when specified declaratively in a page) by passing in a falsey Javascript value as the value of the `layout` property:
|
115 |
|
116 | ```js
|
117 | res.render('veggies', {
|
118 | title: 'My favorite veggies',
|
119 | veggies: veggies,
|
120 | layout: null // render without using a layout template
|
121 | });
|
122 | ```
|
123 |
|
124 | Layout file resolution:
|
125 |
|
126 | If path starts with '.'
|
127 | layout is relative to template
|
128 | Else If `layoutsDir` is set
|
129 | layout is relative to `layoutsDir`
|
130 | Else
|
131 | layout from path.resolve(viewsDir, layout)
|
132 |
|
133 | 3. Lastly, use `defaultLayout` if specified in hbs configuration options.
|
134 |
|
135 | Layouts can be nested: just include a declarative layout tag within any layout
|
136 | template to have its content included in the declared "parent" layout. Be
|
137 | aware that too much nesting can impact performances, and stay away from
|
138 | infinite loops!
|
139 |
|
140 | ## Helpers
|
141 |
|
142 | ### Synchronous helpers
|
143 |
|
144 | ```js
|
145 | hbs.registerHelper('link', function(text, options) {
|
146 | var attrs = [];
|
147 | for(var prop in options.hash) {
|
148 | attrs.push(prop + '="' + options.hash[prop] + '"');
|
149 | }
|
150 | return new hbs.SafeString(
|
151 | "<a " + attrs.join(" ") + ">" + text + "</a>"
|
152 | );
|
153 | });
|
154 | ```
|
155 |
|
156 | in markup
|
157 | ```
|
158 | {{{link 'barc.com' href='http://barc.com'}}}
|
159 | ```
|
160 |
|
161 | ### Asynchronous helpers
|
162 |
|
163 | ```js
|
164 | hbs.registerAsyncHelper('readFile', function(filename, cb) {
|
165 | fs.readFile(path.join(viewsDir, filename), 'utf8', function(err, content) {
|
166 | cb(new hbs.SafeString(content));
|
167 | });
|
168 | });
|
169 | ```
|
170 |
|
171 | in markup
|
172 | ```
|
173 | {{{readFile 'tos.txt'}}}
|
174 | ```
|
175 |
|
176 |
|
177 | ## i18n support
|
178 |
|
179 | Express-hbs supports [i18n](https://github.com/mashpie/i18n-node)
|
180 |
|
181 | ```js
|
182 | var i18n = require('i18n');
|
183 |
|
184 | // minimal config
|
185 | i18n.configure({
|
186 | locales: ['en', 'fr'],
|
187 | cookie: 'locale',
|
188 | directory: __dirname + "/locales"
|
189 | });
|
190 |
|
191 | app.engine('hbs', hbs.express3({
|
192 | // ... options from above
|
193 | i18n: i18n, // registers __ and __n helpers
|
194 | }));
|
195 | app.set('view engine', 'hbs');
|
196 | app.set('views', viewsDir);
|
197 |
|
198 | // cookies are needed
|
199 | app.use(express.cookieParser());
|
200 |
|
201 | // init i18n module
|
202 | app.use(i18n.init);
|
203 | ```
|
204 |
|
205 | ## Engine Instances
|
206 |
|
207 | Create isolated engine instances with their own cache system and handlebars engine.
|
208 |
|
209 | ```js
|
210 | var hbs = require('express-hbs');
|
211 | var instance1 = hbs.create();
|
212 | var instance2 = hbs.create();
|
213 | ```
|
214 |
|
215 | ## Template options
|
216 |
|
217 | The main use case for template options is setting the handlebars "data" object - this creates global template variables accessible with an `@` prefix.
|
218 |
|
219 | Template options can be set in 3 ways. When setting global template options they can be [passed as config on creation of an instance](https://github.com/barc/express-hbs#usage), and they can also be updated used the `updateTemplateOptions(templateOptions)` method of an instance. To set template options for an individual request they can be set on `res.locals` using the helper method `updateLocalTemplateOptions(locals, templateOptions)`.
|
220 |
|
221 | Both of these methods have a companion method `getTemplateOptions()` and `getLocalTemplateOptions(locals)`, which should be used when extending or merging the current options.
|
222 |
|
223 | ## Example
|
224 |
|
225 | in File `app.js`
|
226 |
|
227 | ```js
|
228 | // http://expressjs.com/api.html#app.locals
|
229 | app.locals({
|
230 | 'PROD_MODE': 'production' === app.get('env')
|
231 | });
|
232 |
|
233 | ```
|
234 |
|
235 | File `views/layout/default.hbs`
|
236 |
|
237 | ```html
|
238 | <html>
|
239 | <head>
|
240 | <title>{{title}}</title>
|
241 | <link type="text/css" rel="stylesheet" href="/css/style.css"/>
|
242 | {{{block "pageStyles"}}}
|
243 | </head>
|
244 | <body>
|
245 | {{{body}}}
|
246 |
|
247 | {{> scripts}}
|
248 |
|
249 | {{#if PROD_MODE}}
|
250 | {{{block 'googleAnalyticsScripts'}}}
|
251 | {{/if}}
|
252 |
|
253 | </body>
|
254 | </html>
|
255 | ```
|
256 |
|
257 |
|
258 | File `views/index.hbs`
|
259 |
|
260 | ```html
|
261 | {{!< default}}
|
262 |
|
263 | {{#contentFor 'pageStyles'}}
|
264 | <style>
|
265 | .clicker {
|
266 | color: blue;
|
267 | };
|
268 | </style>
|
269 | {{/contentFor}}
|
270 |
|
271 | <h1>{{title}}</h1>
|
272 | <p class="clicker">Click me!</p>
|
273 | ```
|
274 |
|
275 | To run example project
|
276 |
|
277 | npm install -d
|
278 | node example/app.js
|
279 |
|
280 |
|
281 | ## Testing
|
282 |
|
283 | The test suite requires the `grunt-cli` package:
|
284 |
|
285 | npm install -g grunt-cli
|
286 | npm install -d
|
287 |
|
288 | Once everything's installed, just run:
|
289 |
|
290 | npm test
|
291 |
|
292 |
|
293 | ## Credits
|
294 |
|
295 | Inspiration and code from [donpark/hbs](https://github.com/donpark/hbs)
|
296 |
|
297 | Big thanks to all [CONTRIBUTORS](https://github.com/TryGhost/express-hbs/contributors)
|
298 |
|
299 |
|
300 | ## License
|
301 |
|
302 | The MIT License (MIT)
|
303 |
|
304 | Copyright (c) 2012-2021 Barc, Inc., Ghost Foundation - Released under the [MIT license](LICENSE).
|