UNPKG

26.9 kBMarkdownView Raw
1Express Handlebars
2==================
3
4A [Handlebars][] view engine for [Express][] which doesn't suck.
5
6[![npm version][npm-badge]][npm]
7[![dependency status][dep-badge]][dep-status]
8
9**This package used to be named `express3-handlebars`. The previous `express-handlebars` package by @jneen can be found [here][jneen-exphbs].**
10
11
12[Express]: https://github.com/expressjs/express
13[Handlebars]: https://github.com/handlebars-lang/handlebars.js
14[npm]: https://www.npmjs.org/package/express-handlebars
15[npm-badge]: https://img.shields.io/npm/v/express-handlebars.svg?style=flat-square
16[dep-status]: https://david-dm.org/express-handlebars/express-handlebars
17[dep-badge]: https://img.shields.io/david/express-handlebars/express-handlebars.svg?style=flat-square
18[jneen-exphbs]: https://github.com/jneen/express-handlebars
19
20
21## Goals & Design
22
23I created this project out of frustration with the existing Handlebars view engines for Express. As of version 3.x, Express got out of the business of being a generic view engine — this was a great decision — leaving developers to implement the concepts of layouts, partials, and doing file I/O for their template engines of choice.
24
25### Goals and Features
26
27After building a half-dozen Express apps, I developed requirements and opinions about what a Handlebars view engine should provide and how it should be implemented. The following is that list:
28
29* Add back the concept of "layout", which was removed in Express 3.x.
30
31* Add back the concept of "partials" via Handlebars' partials mechanism.
32
33* Support a directory of partials; e.g., `{{> foo/bar}}` which exists on the file system at `views/partials/foo/bar.handlebars`, by default.
34
35* Smart file system I/O and template caching. When in development, templates are always loaded from disk. In production, raw files and compiled templates are cached, including partials.
36
37* All async and non-blocking. File system I/O is slow and servers should not be blocked from handling requests while reading from disk. I/O queuing is used to avoid doing unnecessary work.
38
39* Ability to easily precompile templates and partials for use on the client, enabling template sharing and reuse.
40
41* Ability to use a different Handlebars module/implementation other than the Handlebars npm package.
42
43### Package Design
44
45This package was designed to work great for both the simple and complex use cases. I _intentionally_ made sure the full implementation is exposed and is easily overridable.
46
47The package exports a function which can be invoked with no arguments or with a `config` object and it will return a function (closed over sensible defaults) which can be registered with an Express app. It's an engine factory function.
48
49This exported engine factory has two properties which expose the underlying implementation:
50
51* `ExpressHandlebars()`: The constructor function which holds the internal implementation on its `prototype`. This produces instance objects which store their configuration, `compiled` and `precompiled` templates, and expose an `engine()` function which can be registered with an Express app.
52
53* `create()`: A convenience factory function for creating `ExpressHandlebars` instances.
54
55An instance-based approach is used so that multiple `ExpressHandlebars` instances can be created with their own configuration, templates, partials, and helpers.
56
57
58## Installation
59
60Install using npm:
61
62```shell
63$ npm install express-handlebars
64```
65
66## Danger 🔥
67
68Never put objects on the `req` object straight in as the data, this can allow hackers to run XSS attacks. Always make sure you are destructuring the values on objects like `req.query` and `req.params`. See https://blog.shoebpatel.com/2021/01/23/The-Secret-Parameter-LFR-and-Potential-RCE-in-NodeJS-Apps/ for more details.
69
70## Usage
71
72This view engine uses sensible defaults that leverage the "Express-way" of structuring an app's views. This makes it trivial to use in basic apps:
73
74### Basic Usage
75
76**Directory Structure:**
77
78```
79.
80├── app.js
81└── views
82 ├── home.handlebars
83 └── layouts
84 └── main.handlebars
85
862 directories, 3 files
87```
88
89**app.js:**
90
91Creates a super simple Express app which shows the basic way to register a Handlebars view engine using this package.
92
93```javascript
94var express = require('express');
95var exphbs = require('express-handlebars');
96
97var app = express();
98
99app.engine('handlebars', exphbs());
100app.set('view engine', 'handlebars');
101
102app.get('/', function (req, res) {
103 res.render('home');
104});
105
106app.listen(3000);
107```
108
109**views/layouts/main.handlebars:**
110
111The main layout is the HTML page wrapper which can be reused for the different views of the app. `{{{body}}}` is used as a placeholder for where the main content should be rendered.
112
113```handlebars
114<!DOCTYPE html>
115<html>
116<head>
117 <meta charset="utf-8">
118 <title>Example App</title>
119</head>
120<body>
121
122 {{{body}}}
123
124</body>
125</html>
126```
127
128**views/home.handlebars:**
129
130The content for the app's home view which will be rendered into the layout's `{{{body}}}`.
131
132```handlebars
133<h1>Example App: Home</h1>
134```
135
136#### Running the Example
137
138The above example is bundled in this package's [examples directory][], where it can be run by:
139
140```shell
141$ cd examples/basic/
142$ npm install
143$ npm start
144```
145
146### Using Instances
147
148Another way to use this view engine is to create an instance(s) of `ExpressHandlebars`, allowing access to the full API:
149
150```javascript
151var express = require('express');
152var exphbs = require('express-handlebars');
153
154var app = express();
155var hbs = exphbs.create({ /* config */ });
156
157// Register `hbs.engine` with the Express app.
158app.engine('handlebars', hbs.engine);
159app.set('view engine', 'handlebars');
160
161// ...still have a reference to `hbs`, on which methods like `getPartials()`
162// can be called.
163```
164
165**Note:** The [Advanced Usage][] example demonstrates how `ExpressHandlebars` instances can be leveraged.
166
167### Template Caching
168
169This view engine uses a smart template caching strategy. In development, templates will always be loaded from disk, i.e., no caching. In production, raw files and compiled Handlebars templates are aggressively cached.
170
171The easiest way to control template/view caching is through Express' [view cache setting][]:
172
173```javascript
174app.enable('view cache');
175```
176
177Express enables this setting by default when in production mode, i.e.:
178
179```
180process.env.NODE_ENV === "production"
181```
182
183**Note:** All of the public API methods accept `options.cache`, which gives control over caching when calling these methods directly.
184
185### Layouts
186
187A layout is simply a Handlebars template with a `{{{body}}}` placeholder. Usually it will be an HTML page wrapper into which views will be rendered.
188
189This view engine adds back the concept of "layout", which was removed in Express 3.x. It can be configured with a path to the layouts directory, by default it's set to relative to `express settings.view` + `layouts/`
190
191There are two ways to set a default layout: configuring the view engine's `defaultLayout` property, or setting [Express locals][] `app.locals.layout`.
192
193The layout into which a view should be rendered can be overridden per-request by assigning a different value to the `layout` request local. The following will render the "home" view with no layout:
194
195```javascript
196app.get('/', function (req, res, next) {
197 res.render('home', {layout: false});
198});
199```
200
201### Helpers
202
203Helper functions, or "helpers" are functions that can be [registered with Handlebars][] and can be called within a template. Helpers can be used for transforming output, iterating over data, etc. To keep with the spirit of *logic-less* templates, helpers are the place where logic should be defined.
204
205Handlebars ships with some [built-in helpers][], such as: `with`, `if`, `each`, etc. Most application will need to extend this set of helpers to include app-specific logic and transformations. Beyond defining global helpers on `Handlebars`, this view engine supports `ExpressHandlebars` instance-level helpers via the `helpers` configuration property, and render-level helpers via `options.helpers` when calling the `render()` and `renderView()` methods.
206
207The following example shows helpers being specified at each level:
208
209**app.js:**
210
211Creates a super simple Express app which shows the basic way to register `ExpressHandlebars` instance-level helpers, and override one at the render-level.
212
213```javascript
214var express = require('express');
215var exphbs = require('express-handlebars');
216
217var app = express();
218
219var hbs = exphbs.create({
220 // Specify helpers which are only registered on this instance.
221 helpers: {
222 foo: function () { return 'FOO!'; },
223 bar: function () { return 'BAR!'; }
224 }
225});
226
227app.engine('handlebars', hbs.engine);
228app.set('view engine', 'handlebars');
229
230app.get('/', function (req, res, next) {
231 res.render('home', {
232 showTitle: true,
233
234 // Override `foo` helper only for this rendering.
235 helpers: {
236 foo: function () { return 'foo.'; }
237 }
238 });
239});
240
241app.listen(3000);
242```
243
244**views/home.handlebars:**
245
246The app's home view which uses helper functions to help render the contents.
247
248```handlebars
249<!DOCTYPE html>
250<html>
251<head>
252 <meta charset="utf-8">
253 <title>Example App - Home</title>
254</head>
255<body>
256
257 <!-- Uses built-in `if` helper. -->
258 {{#if showTitle}}
259 <h1>Home</h1>
260 {{/if}}
261
262 <!-- Calls `foo` helper, overridden at render-level. -->
263 <p>{{foo}}</p>
264
265 <!-- Calls `bar` helper, defined at instance-level. -->
266 <p>{{bar}}</p>
267
268</body>
269</html>
270```
271
272#### More on Helpers
273
274Refer to the [Handlebars website][] for more information on defining helpers:
275
276* [Expression Helpers][]
277* [Block Helpers][]
278
279### Metadata
280
281Handlebars has a data channel feature that propagates data through all scopes, including helpers and partials. Values in the data channel can be accessed via the `{{@variable}}` syntax. Express Handlebars provides metadata about a template it renders on a `{{@exphbs}}` object allowing access to things like the view name passed to `res.render()` via `{{@exphbs.view}}`.
282
283The following is the list of metadata that's accessible on the `{{@exphbs}}` data object:
284
285* `cache`: Boolean whether or not the template is cached.
286* `encoding`: String name of encoding for files.
287* `view`: String name of the view passed to `res.render()`.
288* `layout`: String name of the layout view.
289* `data`: Original data object passed when rendering the template.
290* `helpers`: Collection of helpers used when rendering the template.
291* `partials`: Collection of partials used when rendering the template.
292* `runtimeOptions`: Runtime Options used to render the template.
293
294
295[examples directory]: https://github.com/express-handlebars/express-handlebars/tree/master/examples
296[view cache setting]: https://expressjs.com/en/api.html#app.settings.table
297[Express locals]: https://expressjs.com/en/api.html#app.locals
298[registered with Handlebars]: https://github.com/wycats/handlebars.js/#registering-helpers
299[built-in helpers]: https://handlebarsjs.com/guide/builtin-helpers.html
300[Handlebars website]: https://handlebarsjs.com/
301[Expression Helpers]: https://handlebarsjs.com/guide/#custom-helpers
302[Block Helpers]: https://handlebarsjs.com/guide/#block-helpers
303
304
305## API
306
307### Configuration and Defaults
308
309There are two main ways to use this package: via its engine factory function, or creating `ExpressHandlebars` instances; both use the same configuration properties and defaults.
310
311```javascript
312var exphbs = require('express-handlebars');
313
314// Using the engine factory:
315exphbs({ /* config */ });
316
317// Create an instance:
318exphbs.create({ /* config */ });
319```
320
321The following is the list of configuration properties and their default values (if any):
322
323#### `handlebars=require('handlebars')`
324The Handlebars module/implementation. This allows for the `ExpressHandlebars` instance to use a different Handlebars module/implementation than that provided by the Handlebars npm package.
325
326#### `extname=".handlebars"`
327The string name of the file extension used by the templates. This value should correspond with the `extname` under which this view engine is registered with Express when calling `app.engine()`.
328
329The following example sets up an Express app to use `.hbs` as the file extension for views:
330
331```javascript
332var express = require('express');
333var exphbs = require('express-handlebars');
334
335var app = express();
336
337app.engine('.hbs', exphbs({extname: '.hbs'}));
338app.set('view engine', '.hbs');
339```
340
341**Note:** Setting the app's `"view engine"` setting will make that value the default file extension used for looking up views.
342
343#### `encoding="utf8"`
344Default encoding when reading files.
345
346#### `layoutsDir`
347Default layouts directory is relative to `express settings.view` + `layouts/`
348The string path to the directory where the layout templates reside.
349
350**Note:** If you configure Express to look for views in a custom location (e.g., `app.set('views', 'some/path/')`), and if your `layoutsDir` is not relative to `express settings.view` + `layouts/`, you will need to reflect that by passing an updated path as the `layoutsDir` property in your configuration.
351
352#### `partialsDir`
353Default partials directory is relative to `express settings.view` + `partials/`
354The string path to the directory where the partials templates reside or object with the following properties:
355
356* `dir`: The string path to the directory where the partials templates reside.
357* `namespace`: Optional string namespace to prefix the partial names.
358* `templates`: Optional collection (or promise of a collection) of templates in the form: `{filename: template}`.
359* `rename(filePath, namespace)`: Optional function to rename the partials. Takes two arguments: `filePath`, e.g., `partials/some/path/template.handlebars` and `namespace`.
360
361**Note:** If you configure Express to look for views in a custom location (e.g., `app.set('views', 'some/path/')`), and if your `partialsDir` is not relative to `express settings.view` + `partials/`, you will need to reflect that by passing an updated path as the `partialsDir` property in your configuration.
362
363**Note:** Multiple partials dirs can be used by making `partialsDir` an array of strings, and/or config objects as described above. The namespacing feature is useful if multiple partials dirs are used and their file paths might clash.
364
365#### `defaultLayout`
366The string name or path of a template in the `layoutsDir` to use as the default layout. `main` is used as the default. This is overridden by a `layout` specified in the app or response `locals`. **Note:** A falsy value will render without a layout; e.g., `res.render('home', {layout: false});`. You can also use a falsy value when creating the engine to make using no layout a default e.g. `app.engine('.hbs', exphbs({defaultLayout: false}));`.
367
368#### `helpers`
369An object which holds the helper functions used when rendering templates with this `ExpressHandlebars` instance. When rendering a template, a collection of helpers will be generated by merging: `handlebars.helpers` (global), `helpers` (instance), and `options.helpers` (render-level). This allows Handlebars' `registerHelper()` function to operate as expected, will providing two extra levels over helper overrides.
370
371#### `compilerOptions`
372An object which holds options that will be passed along to the Handlebars compiler functions: `Handlebars.compile()` and `Handlebars.precompile()`.
373
374#### `runtimeOptions`
375An object which holds options that will be passed along to the template function in addition to the `data`, `helpers`, and `partials` options. See [Runtime Options][] for a list of available options.
376
377### Properties
378
379The public API properties are provided via `ExpressHandlebars` instances. In additional to the properties listed in the **Configuration and Defaults** section, the following are additional public properties:
380
381#### `engine`
382A function reference to the `renderView()` method which is bound to `this` `ExpressHandlebars` instance. This bound function should be used when registering this view engine with an Express app.
383
384#### `extname`
385The normalized `extname` which will _always_ start with `.` and defaults to `.handlebars`.
386
387#### `compiled`
388An object cache which holds compiled Handlebars template functions in the format: `{"path/to/template": [Function]}`.
389
390#### `precompiled`
391An object cache which holds precompiled Handlebars template strings in the format: `{"path/to/template": [String]}`.
392
393### Methods
394
395The following is the list of public API methods provided via `ExpressHandlebars` instances:
396
397**Note:** All of the public methods return a [`Promise`][promise] (with the exception of `renderView()` which is the interface with Express.)
398
399#### `getPartials([options])`
400Retrieves the partials in the `partialsDir` and returns a Promise for an object mapping the partials in the form `{name: partial}`.
401
402By default each partial will be a compiled Handlebars template function. Use `options.precompiled` to receive the partials as precompiled templates — this is useful for sharing templates with client code.
403
404**Parameters:**
405
406* `[options]`: Optional object containing any of the following properties:
407
408 * `[cache]`: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.
409
410 * `[encoding]`: File encoding.
411
412 * `[precompiled=false]`: Whether precompiled templates should be provided, instead of compiled Handlebars template functions.
413
414The name of each partial corresponds to its location in `partialsDir`. For example, consider the following directory structure:
415
416```
417views
418└── partials
419 ├── foo
420 │   └── bar.handlebars
421 └── title.handlebars
422
4232 directories, 2 files
424```
425
426`getPartials()` would produce the following result:
427
428```javascript
429var hbs = require('express-handlebars').create();
430
431hbs.getPartials().then(function (partials) {
432 console.log(partials);
433 // => { 'foo/bar': [Function],
434 // => title: [Function] }
435});
436```
437
438#### `getTemplate(filePath, [options])`
439Retrieves the template at the specified `filePath` and returns a Promise for the compiled Handlebars template function.
440
441Use `options.precompiled` to receive a precompiled Handlebars template.
442
443**Parameters:**
444
445* `filePath`: String path to the Handlebars template file.
446
447* `[options]`: Optional object containing any of the following properties:
448
449 * `[cache]`: Whether a cached template can be used if it have already been requested. This is recommended for production to avoid necessary file I/O.
450
451 * `[encoding]`: File encoding.
452
453 * `[precompiled=false]`: Whether a precompiled template should be provided, instead of a compiled Handlebars template function.
454
455#### `getTemplates(dirPath, [options])`
456Retrieves all the templates in the specified `dirPath` and returns a Promise for an object mapping the compiled templates in the form `{filename: template}`.
457
458Use `options.precompiled` to receive precompiled Handlebars templates — this is useful for sharing templates with client code.
459
460**Parameters:**
461
462* `dirPath`: String path to the directory containing Handlebars template files.
463
464* `[options]`: Optional object containing any of the following properties:
465
466 * `[cache]`: Whether cached templates can be used if it have already been requested. This is recommended for production to avoid necessary file I/O.
467
468 * `[encoding]`: File encoding.
469
470 * `[precompiled=false]`: Whether precompiled templates should be provided, instead of a compiled Handlebars template function.
471
472#### `render(filePath, context, [options])`
473Renders the template at the specified `filePath` with the `context`, using this instance's `helpers` and partials by default, and returns a Promise for the resulting string.
474
475**Parameters:**
476
477* `filePath`: String path to the Handlebars template file.
478
479* `context`: Object in which the template will be executed. This contains all of the values to fill into the template.
480
481* `[options]`: Optional object which can contain any of the following properties which affect this view engine's behavior:
482
483 * `[cache]`: Whether a cached template can be used if it have already been requested. This is recommended for production to avoid unnecessary file I/O.
484
485 * `[encoding]`: File encoding.
486
487 * `[data]`: Optional object which can contain any data that Handlebars will pipe through the template, all helpers, and all partials. This is a side data channel.
488
489 * `[helpers]`: Render-level helpers that will be used instead of any instance-level helpers; these will be merged with (and will override) any global Handlebars helper functions.
490
491 * `[partials]`: Render-level partials that will be used instead of any instance-level partials. This is used internally as an optimization to avoid re-loading all the partials.
492
493 * `[runtimeOptions]`: Optional object which can contain options passed to the template function.
494
495#### `renderView(viewPath, options|callback, [callback])`
496Renders the template at the specified `viewPath` as the `{{{body}}}` within the layout specified by the `defaultLayout` or `options.layout`. Rendering will use this instance's `helpers` and partials, and passes the resulting string to the `callback`.
497
498This method is called by Express and is the main entry point into this Express view engine implementation. It adds the concept of a "layout" and delegates rendering to the `render()` method.
499
500The `options` will be used both as the context in which the Handlebars templates are rendered, and to signal this view engine on how it should behave, e.g., `options.cache=false` will _always_ load the templates from disk.
501
502**Parameters:**
503
504* `viewPath`: String path to the Handlebars template file which should serve as the `{{{body}}}` when using a layout.
505
506* `[options]`: Optional object which will serve as the context in which the Handlebars templates are rendered. It may also contain any of the following properties which affect this view engine's behavior:
507
508 * `[cache]`: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.
509
510 * `[encoding]`: File encoding.
511
512 * `[data]`: Optional object which can contain any data that Handlebars will pipe through the template, all helpers, and all partials. This is a side data channel.
513
514 * `[helpers]`: Render-level helpers that will be merged with (and will override) instance and global helper functions.
515
516 * `[partials]`: Render-level partials will be merged with (and will override) instance and global partials. This should be a `{partialName: fn}` hash or a Promise of an object with this shape.
517
518 * `[layout]`: Optional string path to the Handlebars template file to be used as the "layout". This overrides any `defaultLayout` value. Passing a falsy value will render with no layout (even if a `defaultLayout` is defined).
519
520 * `[runtimeOptions]`: Optional object which can contain options passed to the template function.
521
522* `callback`: Function to call once the template is retrieved.
523
524### Hooks
525
526The following is the list of protected methods that are called internally and serve as _hooks_ to override functionality of `ExpressHandlebars` instances. A value or a promise can be returned from these methods which allows them to perform async operations.
527
528#### `_compileTemplate(template, options)`
529This hook will be called when a Handlebars template needs to be compiled. This function needs to return a compiled Handlebars template function, or a promise for one.
530
531By default this hook calls `Handlebars.compile()`, but it can be overridden to preform operations before and/or after Handlebars compiles the template. This is useful if you wanted to first process Markdown within a Handlebars template.
532
533**Parameters:**
534
535* `template`: String Handlebars template that needs to be compiled.
536
537* `options`: Object `compilerOptions` that were specified when the `ExpressHandlebars` instance as created. This object should be passed along to the `Handlebars.compile()` function.
538
539#### `_precompileTemplate(template, options)`
540This hook will be called when a Handlebars template needs to be precompiled. This function needs to return a serialized Handlebars template spec. string, or a promise for one.
541
542By default this hook calls `Handlebars.precompile()`, but it can be overridden to preform operations before and/or after Handlebars precompiles the template. This is useful if you wanted to first process Markdown within a Handlebars template.
543
544**Parameters:**
545
546* `template`: String Handlebars template that needs to be precompiled.
547
548* `options`: Object `compilerOptions` that were specified when the `ExpressHandlebars` instance as created. This object should be passed along to the `Handlebars.compile()` function.
549
550#### `_renderTemplate(template, context, options)`
551This hook will be called when a compiled Handlebars template needs to be rendered. This function needs to returned the rendered output string, or a promise for one.
552
553By default this hook simply calls the passed-in `template` with the `context` and `options` arguments, but it can be overridden to perform operations before and/or after rendering the template.
554
555**Parameters:**
556
557* `template`: Compiled Handlebars template function to call.
558
559* `context`: The context object in which to render the `template`.
560
561* `options`: Object that contains options and metadata for rendering the template:
562
563 * `data`: Object to define custom `@variable` private variables.
564
565 * `helpers`: Object to provide custom helpers in addition to the globally defined helpers.
566
567 * `partials`: Object to provide custom partials in addition to the globally defined partials.
568
569 * `...runtimeOptions`: Other options specified by the `runtimeOptions` value.
570
571
572[promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
573[Runtime Options]: https://handlebarsjs.com/api-reference/runtime-options.html
574
575
576## Examples
577
578### [Basic Usage][]
579
580This example shows the most basic way to use this view engine.
581
582### [Advanced Usage][]
583
584This example is more comprehensive and shows how to use many of the features of this view engine, including helpers, partials, multiple layouts, etc.
585
586As noted in the **Package Design** section, this view engine's implementation is instance-based, and more advanced usages can take advantage of this. The Advanced Usage example demonstrates how to use an `ExpressHandlebars` instance to share templates with the client, among other features.
587
588
589[Basic Usage]: https://github.com/express-handlebars/express-handlebars/tree/master/examples/basic
590[Advanced Usage]: https://github.com/express-handlebars/express-handlebars/tree/master/examples/advanced
591
592
593License
594-------
595
596This software is free to use under the Yahoo! Inc. BSD license. See the [LICENSE file][] for license text and copyright information.
597
598
599[LICENSE file]: https://github.com/express-handlebars/express-handlebars/blob/master/LICENSE