UNPKG

25.3 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/visionmedia/express
13[Handlebars]: https://github.com/wycats/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
67## Usage
68
69This 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:
70
71### Basic Usage
72
73**Directory Structure:**
74
75```
76.
77├── app.js
78└── views
79 ├── home.handlebars
80 └── layouts
81 └── main.handlebars
82
832 directories, 3 files
84```
85
86**app.js:**
87
88Creates a super simple Express app which shows the basic way to register a Handlebars view engine using this package.
89
90```javascript
91var express = require('express');
92var exphbs = require('express-handlebars');
93
94var app = express();
95
96app.engine('handlebars', exphbs());
97app.set('view engine', 'handlebars');
98
99app.get('/', function (req, res) {
100 res.render('home');
101});
102
103app.listen(3000);
104```
105
106**views/layouts/main.handlebars:**
107
108The 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.
109
110```handlebars
111<!DOCTYPE html>
112<html>
113<head>
114 <meta charset="utf-8">
115 <title>Example App</title>
116</head>
117<body>
118
119 {{{body}}}
120
121</body>
122</html>
123```
124
125**views/home.handlebars:**
126
127The content for the app's home view which will be rendered into the layout's `{{{body}}}`.
128
129```handlebars
130<h1>Example App: Home</h1>
131```
132
133#### Running the Example
134
135The above example is bundled in this package's [examples directory][], where it can be run by:
136
137```shell
138$ cd examples/basic/
139$ npm install
140$ npm start
141```
142
143### Using Instances
144
145Another way to use this view engine is to create an instance(s) of `ExpressHandlebars`, allowing access to the full API:
146
147```javascript
148var express = require('express');
149var exphbs = require('express-handlebars');
150
151var app = express();
152var hbs = exphbs.create({ /* config */ });
153
154// Register `hbs.engine` with the Express app.
155app.engine('handlebars', hbs.engine);
156app.set('view engine', 'handlebars');
157
158// ...still have a reference to `hbs`, on which methods like `loadPartials()`
159// can be called.
160```
161
162**Note:** The [Advanced Usage][] example demonstrates how `ExpressHandlebars` instances can be leveraged.
163
164### Template Caching
165
166This 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.
167
168The easiest way to control template/view caching is through Express' [view cache setting][]:
169
170```javascript
171app.enable('view cache');
172```
173
174Express enables this setting by default when in production mode, i.e.:
175
176```
177process.env.NODE_ENV === "production"
178```
179
180**Note:** All of the public API methods accept `options.cache`, which gives control over caching when calling these methods directly.
181
182### Layouts
183
184A layout is simply a Handlebars template with a `{{{body}}}` placeholder. Usually it will be an HTML page wrapper into which views will be rendered.
185
186This 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/`
187
188There are two ways to set a default layout: configuring the view engine's `defaultLayout` property, or setting [Express locals][] `app.locals.layout`.
189
190The 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:
191
192```javascript
193app.get('/', function (req, res, next) {
194 res.render('home', {layout: false});
195});
196```
197
198### Helpers
199
200Helper 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.
201
202Handlebars 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.
203
204The following example shows helpers being specified at each level:
205
206**app.js:**
207
208Creates a super simple Express app which shows the basic way to register `ExpressHandlebars` instance-level helpers, and override one at the render-level.
209
210```javascript
211var express = require('express');
212var exphbs = require('express-handlebars');
213
214var app = express();
215
216var hbs = exphbs.create({
217 // Specify helpers which are only registered on this instance.
218 helpers: {
219 foo: function () { return 'FOO!'; },
220 bar: function () { return 'BAR!'; }
221 }
222});
223
224app.engine('handlebars', hbs.engine);
225app.set('view engine', 'handlebars');
226
227app.get('/', function (req, res, next) {
228 res.render('home', {
229 showTitle: true,
230
231 // Override `foo` helper only for this rendering.
232 helpers: {
233 foo: function () { return 'foo.'; }
234 }
235 });
236});
237
238app.listen(3000);
239```
240
241**views/home.handlebars:**
242
243The app's home view which uses helper functions to help render the contents.
244
245```handlebars
246<!DOCTYPE html>
247<html>
248<head>
249 <meta charset="utf-8">
250 <title>Example App - Home</title>
251</head>
252<body>
253
254 <!-- Uses built-in `if` helper. -->
255 {{#if showTitle}}
256 <h1>Home</h1>
257 {{/if}}
258
259 <!-- Calls `foo` helper, overridden at render-level. -->
260 <p>{{foo}}</p>
261
262 <!-- Calls `bar` helper, defined at instance-level. -->
263 <p>{{bar}}</p>
264
265</body>
266</html>
267```
268
269#### More on Helpers
270
271Refer to the [Handlebars website][] for more information on defining helpers:
272
273* [Expression Helpers][]
274* [Block Helpers][]
275
276### Metadata
277
278Handlebars 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}}`.
279
280The following is the list of metadata that's accessible on the `{{@exphbs}}` data object:
281
282* `cache`: Boolean whether or not the template is cached.
283* `view`: String name of the view passed to `res.render()`.
284* `layout`: String name of the layout view.
285* `data`: Original data object passed when rendering the template.
286* `helpers`: Collection of helpers used when rendering the template.
287* `partials`: Collection of partials used when rendering the template.
288
289
290[examples directory]: https://github.com/express-handlebars/express-handlebars/tree/master/examples
291[view cache setting]: http://expressjs.com/api.html#app-settings
292[Express locals]: http://expressjs.com/api.html#app.locals
293[registered with Handlebars]: https://github.com/wycats/handlebars.js/#registering-helpers
294[built-in helpers]: http://handlebarsjs.com/#builtins
295[Handlebars website]: http://handlebarsjs.com/
296[Expression Helpers]: http://handlebarsjs.com/expressions.html#helpers
297[Block Helpers]: http://handlebarsjs.com/block_helpers.html
298
299
300## API
301
302### Configuration and Defaults
303
304There 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.
305
306```javascript
307var exphbs = require('express-handlebars');
308
309// Using the engine factory:
310exphbs({ /* config */ });
311
312// Create an instance:
313exphbs.create({ /* config */ });
314```
315
316The following is the list of configuration properties and their default values (if any):
317
318#### `handlebars=require('handlebars')`
319The Handlebars module/implementation. This allows for the `ExpressHandlebars` instance to use a different Handlebars module/implementation than that provided by the Handlebars npm package.
320
321#### `extname=".handlebars"`
322The 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()`.
323
324The following example sets up an Express app to use `.hbs` as the file extension for views:
325
326```javascript
327var express = require('express');
328var exphbs = require('express-handlebars');
329
330var app = express();
331
332app.engine('.hbs', exphbs({extname: '.hbs'}));
333app.set('view engine', '.hbs');
334```
335
336**Note:** Setting the app's `"view engine"` setting will make that value the default file extension used for looking up views.
337
338#### `layoutsDir`
339Default layouts directory is relative to `express settings.view` + `layouts/`
340The string path to the directory where the layout templates reside.
341
342**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` + `layouts/`, you will need to reflect that by passing an updated path as the `layoutsDir` property in your configuration.
343
344#### `partialsDir`
345Default partials directory is relative to `express settings.view` + `partials/`
346The string path to the directory where the partials templates reside or object with the following properties:
347
348* `dir`: The string path to the directory where the partials templates reside.
349* `namespace`: Optional string namespace to prefix the partial names.
350* `templates`: Optional collection (or promise of a collection) of templates in the form: `{filename: template}`.
351
352**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.
353
354**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.
355
356#### `defaultLayout`
357The 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});`.
358
359#### `helpers`
360An 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.
361
362#### `compilerOptions`
363An object which holds options that will be passed along to the Handlebars compiler functions: `Handlebars.compile()` and `Handlebars.precompile()`.
364
365### Properties
366
367The 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:
368
369#### `engine`
370A 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.
371
372#### `extname`
373The normalized `extname` which will _always_ start with `.` and defaults to `.handlebars`.
374
375#### `compiled`
376An object cache which holds compiled Handlebars template functions in the format: `{"path/to/template": [Function]}`.
377
378#### `precompiled`
379An object cache which holds precompiled Handlebars template strings in the format: `{"path/to/template": [String]}`.
380
381### Methods
382
383The following is the list of public API methods provided via `ExpressHandlebars` instances:
384
385**Note:** All of the public methods return a [`Promise`][promise] (with the exception of `renderView()` which is the interface with Express.)
386
387#### `getPartials([options])`
388Retrieves the partials in the `partialsDir` and returns a Promise for an object mapping the partials in the form `{name: partial}`.
389
390By 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.
391
392**Parameters:**
393
394* `[options]`: Optional object containing any of the following properties:
395
396 * `[cache]`: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.
397
398 * `[precompiled=false]`: Whether precompiled templates should be provided, instead of compiled Handlebars template functions.
399
400The name of each partial corresponds to its location in `partialsDir`. For example, consider the following directory structure:
401
402```
403views
404└── partials
405 ├── foo
406 │   └── bar.handlebars
407 └── title.handlebars
408
4092 directories, 2 files
410```
411
412`getPartials()` would produce the following result:
413
414```javascript
415var hbs = require('express-handlebars').create();
416
417hbs.getPartials().then(function (partials) {
418 console.log(partials);
419 // => { 'foo/bar': [Function],
420 // => title: [Function] }
421});
422```
423
424#### `getTemplate(filePath, [options])`
425Retrieves the template at the specified `filePath` and returns a Promise for the compiled Handlebars template function.
426
427Use `options.precompiled` to receive a precompiled Handlebars template.
428
429**Parameters:**
430
431* `filePath`: String path to the Handlebars template file.
432
433* `[options]`: Optional object containing any of the following properties:
434
435 * `[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.
436
437 * `[precompiled=false]`: Whether a precompiled template should be provided, instead of a compiled Handlebars template function.
438
439#### `getTemplates(dirPath, [options])`
440Retrieves all the templates in the specified `dirPath` and returns a Promise for an object mapping the compiled templates in the form `{filename: template}`.
441
442Use `options.precompiled` to receive precompiled Handlebars templates — this is useful for sharing templates with client code.
443
444**Parameters:**
445
446* `dirPath`: String path to the directory containing Handlebars template files.
447
448* `[options]`: Optional object containing any of the following properties:
449
450 * `[cache]`: Whether cached templates can be used if it have already been requested. This is recommended for production to avoid necessary file I/O.
451
452 * `[precompiled=false]`: Whether precompiled templates should be provided, instead of a compiled Handlebars template function.
453
454#### `render(filePath, context, [options])`
455Renders 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.
456
457**Parameters:**
458
459* `filePath`: String path to the Handlebars template file.
460
461* `context`: Object in which the template will be executed. This contains all of the values to fill into the template.
462
463* `[options]`: Optional object which can contain any of the following properties which affect this view engine's behavior:
464
465 * `[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.
466
467 * `[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.
468
469 * `[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.
470
471 * `[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.
472
473#### `renderView(viewPath, options|callback, [callback])`
474Renders 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`.
475
476This 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.
477
478The `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 load _always_ load the templates from disk.
479
480**Parameters:**
481
482* `viewPath`: String path to the Handlebars template file which should serve as the `{{{body}}}` when using a layout.
483
484* `[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:
485
486 * `[cache]`: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.
487
488 * `[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.
489
490 * `[helpers]`: Render-level helpers that will be merged with (and will override) instance and global helper functions.
491
492 * `[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.
493
494 * `[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).
495
496* `callback`: Function to call once the template is retrieved.
497
498### Hooks
499
500The 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.
501
502#### `_compileTemplate(template, options)`
503This 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.
504
505By 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.
506
507**Parameters:**
508
509* `template`: String Handlebars template that needs to be compiled.
510
511* `options`: Object `compilerOptions` that were specified when the `ExpressHandlebars` instance as created. This object should be passed along to the `Handlebars.compile()` function.
512
513#### `_precompileTemplate(template, options)`
514This 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.
515
516By 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.
517
518**Parameters:**
519
520* `template`: String Handlebars template that needs to be precompiled.
521
522* `options`: Object `compilerOptions` that were specified when the `ExpressHandlebars` instance as created. This object should be passed along to the `Handlebars.compile()` function.
523
524#### `_renderTemplate(template, context, options)`
525This 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.
526
527By 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.
528
529**Parameters:**
530
531* `template`: Compiled Handlebars template function to call.
532
533* `context`: The context object in which to render the `template`.
534
535* `options`: Object that contains options and metadata for rendering the template:
536
537 * `data`: Object to define custom `@variable` private variables.
538
539 * `helpers`: Object to provide custom helpers in addition to the globally defined helpers.
540
541 * `partials`: Object to provide custom partials in addition to the globally defined partials.
542
543
544[promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
545
546
547## Examples
548
549### [Basic Usage][]
550
551This example shows the most basic way to use this view engine.
552
553### [Advanced Usage][]
554
555This example is more comprehensive and shows how to use many of the features of this view engine, including helpers, partials, multiple layouts, etc.
556
557As 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.
558
559
560[Basic Usage]: https://github.com/express-handlebars/express-handlebars/tree/master/examples/basic
561[Advanced Usage]: https://github.com/express-handlebars/express-handlebars/tree/master/examples/advanced
562
563
564License
565-------
566
567This software is free to use under the Yahoo! Inc. BSD license. See the [LICENSE file][] for license text and copyright information.
568
569
570[LICENSE file]: https://github.com/express-handlebars/express-handlebars/blob/master/LICENSE