express-hbs 2.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +73 -0
- package/.github/workflows/test.yml +32 -8
- package/.nvmrc +1 -0
- package/.nycrc.json +16 -0
- package/.oxfmtrc.json +19 -0
- package/.oxlintrc.json +32 -0
- package/AGENTS.md +40 -0
- package/LICENSE +1 -1
- package/README.md +50 -22
- package/example/app-layoutsDir.js +32 -39
- package/example/app.js +27 -34
- package/lib/hbs.js +82 -90
- package/lib/resolver.js +10 -9
- package/package.json +29 -22
- package/renovate.json +9 -2
- package/test/apps/async/index.js +54 -42
- package/test/apps/i18n/index.js +9 -7
- package/test/asyncSpecs.js +12 -12
- package/test/cacheSpecs.js +18 -21
- package/test/customExtension.js +15 -16
- package/test/handlebarsSpecs.js +16 -19
- package/test/helperSpecs.js +22 -20
- package/test/helpers.js +3 -3
- package/test/i18nSpecs.js +10 -8
- package/test/issues.js +136 -122
- package/test/layoutSpecs.js +23 -25
- package/test/localTemplateOptions.js +40 -42
- package/test/multiple.js +30 -34
- package/test/nonExpress.js +9 -10
- package/test/optionsSpecs.js +5 -7
- package/test/resolverSpecs.js +54 -0
- package/.eslintrc +0 -42
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- 'package.json'
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
dry-run:
|
|
12
|
+
description: 'Preview what would be published without actually publishing'
|
|
13
|
+
required: false
|
|
14
|
+
type: boolean
|
|
15
|
+
default: true
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
publish:
|
|
22
|
+
name: Publish to npm
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
# OIDC trusted publishing to npm. Provenance is enabled because this
|
|
27
|
+
# repository is public; public repos can generate a provenance
|
|
28
|
+
# attestation at publish time.
|
|
29
|
+
id-token: write
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
env:
|
|
33
|
+
FORCE_COLOR: 1
|
|
34
|
+
CI: true
|
|
35
|
+
NPM_CONFIG_PROVENANCE: true
|
|
36
|
+
NPM_CONFIG_REGISTRY: 'https://registry.npmjs.org'
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
40
|
+
with:
|
|
41
|
+
persist-credentials: false
|
|
42
|
+
|
|
43
|
+
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
|
44
|
+
with:
|
|
45
|
+
node-version: '24.14.1'
|
|
46
|
+
registry-url: 'https://registry.npmjs.org'
|
|
47
|
+
|
|
48
|
+
- name: Install Corepack
|
|
49
|
+
run: npm install --global corepack@0.34.6
|
|
50
|
+
- name: Enable corepack
|
|
51
|
+
run: corepack enable
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: pnpm install --frozen-lockfile --prefer-offline
|
|
55
|
+
|
|
56
|
+
- name: Determine whether to publish
|
|
57
|
+
id: pub
|
|
58
|
+
run: |
|
|
59
|
+
name=$(jq -r .name package.json)
|
|
60
|
+
version=$(jq -r .version package.json)
|
|
61
|
+
if [ -n "$(npm view "$name@$version" version 2>/dev/null)" ]; then
|
|
62
|
+
echo "$name@$version is already published; nothing to do"
|
|
63
|
+
echo "publish=false" >> "$GITHUB_OUTPUT"
|
|
64
|
+
else
|
|
65
|
+
echo "queueing $name@$version"
|
|
66
|
+
echo "publish=true" >> "$GITHUB_OUTPUT"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
- name: Publish to npm
|
|
70
|
+
if: steps.pub.outputs.publish == 'true'
|
|
71
|
+
# --no-git-checks is required because actions/checkout leaves the repo on
|
|
72
|
+
# a detached HEAD, which pnpm's default publish branch check rejects.
|
|
73
|
+
run: pnpm publish --no-git-checks ${{ (github.event.inputs['dry-run'] == 'true' && '--dry-run') || '' }}
|
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
name: Test
|
|
2
|
+
|
|
2
3
|
on:
|
|
3
4
|
pull_request:
|
|
4
5
|
push:
|
|
5
6
|
branches:
|
|
6
7
|
- main
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
8
15
|
|
|
9
16
|
jobs:
|
|
10
17
|
build:
|
|
11
18
|
runs-on: ubuntu-latest
|
|
12
|
-
|
|
19
|
+
timeout-minutes: 20
|
|
13
20
|
strategy:
|
|
21
|
+
fail-fast: false
|
|
14
22
|
matrix:
|
|
15
|
-
node: [
|
|
23
|
+
node: [20, 22, 24]
|
|
16
24
|
name: Node ${{ matrix.node }}
|
|
17
25
|
steps:
|
|
18
|
-
- uses: actions/checkout@
|
|
19
|
-
|
|
26
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
27
|
+
with:
|
|
28
|
+
persist-credentials: false
|
|
29
|
+
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
|
30
|
+
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
20
31
|
with:
|
|
21
32
|
node-version: ${{ matrix.node }}
|
|
22
|
-
|
|
23
|
-
- run:
|
|
24
|
-
- run:
|
|
33
|
+
cache: pnpm
|
|
34
|
+
- run: pnpm install --frozen-lockfile
|
|
35
|
+
- run: pnpm lint
|
|
36
|
+
- name: Run tests with coverage
|
|
37
|
+
run: pnpm coverage
|
|
38
|
+
|
|
39
|
+
required-checks-pass:
|
|
40
|
+
name: Required checks pass
|
|
41
|
+
if: always()
|
|
42
|
+
needs: [build]
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
timeout-minutes: 5
|
|
45
|
+
steps:
|
|
46
|
+
- name: Verify all required jobs succeeded
|
|
47
|
+
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
|
48
|
+
run: exit 1
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|
package/.nycrc.json
ADDED
package/.oxfmtrc.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
|
3
|
+
"singleQuote": true,
|
|
4
|
+
"tabWidth": 2,
|
|
5
|
+
"sortPackageJson": false,
|
|
6
|
+
"ignorePatterns": [
|
|
7
|
+
"**/node_modules/**",
|
|
8
|
+
"**/coverage/**",
|
|
9
|
+
"pnpm-lock.yaml",
|
|
10
|
+
"**/*.json",
|
|
11
|
+
"**/*.jsonc",
|
|
12
|
+
"**/*.yml",
|
|
13
|
+
"**/*.yaml",
|
|
14
|
+
"**/*.md",
|
|
15
|
+
"**/*.hbs",
|
|
16
|
+
"**/*.html",
|
|
17
|
+
"**/*.css"
|
|
18
|
+
]
|
|
19
|
+
}
|
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"categories": {
|
|
4
|
+
"correctness": "error",
|
|
5
|
+
"suspicious": "warn"
|
|
6
|
+
},
|
|
7
|
+
"env": {
|
|
8
|
+
"node": true,
|
|
9
|
+
"es2024": true
|
|
10
|
+
},
|
|
11
|
+
"rules": {
|
|
12
|
+
"no-console": "error"
|
|
13
|
+
},
|
|
14
|
+
"overrides": [
|
|
15
|
+
{
|
|
16
|
+
"files": ["test/**/*.js"],
|
|
17
|
+
"env": {
|
|
18
|
+
"mocha": true
|
|
19
|
+
},
|
|
20
|
+
"rules": {
|
|
21
|
+
"no-console": "off",
|
|
22
|
+
"no-unused-vars": "off"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"files": ["lib/hbs.js", "example/**/*.js"],
|
|
27
|
+
"rules": {
|
|
28
|
+
"no-console": "off"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# express-hbs agent notes
|
|
2
|
+
|
|
3
|
+
`express-hbs` is a CommonJS Express view-engine library. Read `README.md` for
|
|
4
|
+
the public API and usage examples before changing behavior.
|
|
5
|
+
|
|
6
|
+
## Commands
|
|
7
|
+
|
|
8
|
+
Use pnpm 10 on Node.js 20 or later.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pnpm install --frozen-lockfile
|
|
12
|
+
pnpm lint
|
|
13
|
+
pnpm test
|
|
14
|
+
pnpm coverage
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`pnpm test` runs `mocha` and then `pnpm lint` through the `posttest` script.
|
|
18
|
+
`pnpm coverage` runs the same test suite under `nyc` and enforces the coverage
|
|
19
|
+
thresholds in `package.json`.
|
|
20
|
+
|
|
21
|
+
## Boundaries
|
|
22
|
+
|
|
23
|
+
- Do not edit `pnpm-lock.yaml` by hand. Regenerate it with `pnpm install`.
|
|
24
|
+
- Keep `lib/hbs.js` and `lib/resolver.js` CommonJS; this package does not use
|
|
25
|
+
ESM.
|
|
26
|
+
- Treat files under `test/views/**`, `test/issues/**`, and `example/views/**`
|
|
27
|
+
as rendering fixtures. Seemingly odd whitespace, malformed markup, or partial
|
|
28
|
+
names may be intentional test coverage.
|
|
29
|
+
- Keep the security warning around user-controlled `layout` values and
|
|
30
|
+
`restrictLayoutsTo` in sync with behavior.
|
|
31
|
+
|
|
32
|
+
## Style notes
|
|
33
|
+
|
|
34
|
+
- Formatting is enforced by `oxfmt`; run `pnpm lint:fix` rather than doing large
|
|
35
|
+
manual formatting sweeps.
|
|
36
|
+
- The formatter intentionally ignores Handlebars, HTML, CSS, lockfile, and
|
|
37
|
+
fixture data files. Preserve their existing shape unless the test expectation
|
|
38
|
+
itself is being changed.
|
|
39
|
+
- Add or update tests for layout resolution, partial loading, async helpers,
|
|
40
|
+
template options, and caching changes.
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2012-
|
|
3
|
+
Copyright (c) 2012-2025 Barc, Inc., Ghost Foundation
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
6
|
this software and associated documentation files (the "Software"), to deal in
|
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# express-hbs
|
|
2
2
|
|
|
3
|
-
Express
|
|
3
|
+
Express Handlebars template engine for Express apps, with nested layouts,
|
|
4
|
+
named content blocks, cached partials, i18n helpers, and asynchronous helpers.
|
|
5
|
+
|
|
6
|
+
`express-hbs` exposes Express-compatible view engine functions and a shared
|
|
7
|
+
Handlebars instance. It is used by apps that want Handlebars templates with
|
|
8
|
+
layout inheritance and block-style content regions while keeping the familiar
|
|
9
|
+
`app.engine()` integration.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Node.js 20 or later
|
|
14
|
+
- pnpm 10 when working on this repository
|
|
4
15
|
|
|
5
16
|
## v2.0.0
|
|
6
17
|
|
|
@@ -17,7 +28,8 @@ If you're upgrading from v0.8.4 to v1.0.0 there are some potentially breaking ch
|
|
|
17
28
|
|
|
18
29
|
## Usage
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
Register the engine with Express using `hbs.express4()`.
|
|
32
|
+
|
|
21
33
|
```js
|
|
22
34
|
var hbs = require('express-hbs');
|
|
23
35
|
|
|
@@ -28,7 +40,9 @@ app.engine('hbs', hbs.express4({
|
|
|
28
40
|
app.set('view engine', 'hbs');
|
|
29
41
|
app.set('views', __dirname + '/views');
|
|
30
42
|
```
|
|
31
|
-
|
|
43
|
+
|
|
44
|
+
`hbs.express3()` is still available for legacy apps and accepts the same
|
|
45
|
+
options:
|
|
32
46
|
|
|
33
47
|
```js
|
|
34
48
|
app.engine('hbs', hbs.express3({
|
|
@@ -36,7 +50,7 @@ app.engine('hbs', hbs.express3({
|
|
|
36
50
|
}));
|
|
37
51
|
```
|
|
38
52
|
|
|
39
|
-
Options for
|
|
53
|
+
Options for `express3()` and `express4()`:
|
|
40
54
|
|
|
41
55
|
```js
|
|
42
56
|
hbs.express4({
|
|
@@ -67,15 +81,15 @@ hbs.express4({
|
|
|
67
81
|
|
|
68
82
|
## Syntax
|
|
69
83
|
|
|
70
|
-
To mark where layout should insert page
|
|
84
|
+
To mark where a layout should insert page content:
|
|
71
85
|
|
|
72
86
|
{{{body}}}
|
|
73
87
|
|
|
74
|
-
To declare a block placeholder in layout
|
|
88
|
+
To declare a block placeholder in a layout:
|
|
75
89
|
|
|
76
90
|
{{{block "pageScripts"}}}
|
|
77
91
|
|
|
78
|
-
To define block content in a page
|
|
92
|
+
To define block content in a page:
|
|
79
93
|
|
|
80
94
|
{{#contentFor "pageScripts"}}
|
|
81
95
|
CONTENT HERE
|
|
@@ -83,7 +97,7 @@ To define block content in a page
|
|
|
83
97
|
|
|
84
98
|
## Layouts
|
|
85
99
|
|
|
86
|
-
There are three ways to use a layout, listed in precedence order
|
|
100
|
+
There are three ways to use a layout, listed in precedence order:
|
|
87
101
|
|
|
88
102
|
1. Declarative within a page. Use handlebars comment
|
|
89
103
|
|
|
@@ -100,9 +114,10 @@ There are three ways to use a layout, listed in precedence order
|
|
|
100
114
|
|
|
101
115
|
2. As an option to render
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
⚠️ Passing user-controlled data into the `layout` option can read arbitrary
|
|
118
|
+
files unless `restrictLayoutsTo` confines layout resolution to a safe
|
|
119
|
+
directory. Do not call `res.render('index', req.query)` or similar without
|
|
120
|
+
setting `restrictLayoutsTo`.
|
|
106
121
|
|
|
107
122
|
```js
|
|
108
123
|
res.render('veggies', {
|
|
@@ -112,7 +127,8 @@ There are three ways to use a layout, listed in precedence order
|
|
|
112
127
|
});
|
|
113
128
|
```
|
|
114
129
|
|
|
115
|
-
This option also allows
|
|
130
|
+
This option also allows layout suppression, including the default layout and
|
|
131
|
+
template-declared layouts, by passing a falsey JavaScript value:
|
|
116
132
|
|
|
117
133
|
```js
|
|
118
134
|
res.render('veggies', {
|
|
@@ -215,9 +231,12 @@ var instance2 = hbs.create();
|
|
|
215
231
|
|
|
216
232
|
## Template options
|
|
217
233
|
|
|
218
|
-
The main use case for template options is setting the
|
|
234
|
+
The main use case for template options is setting the Handlebars `data` object,
|
|
235
|
+
which creates global template variables accessible with an `@` prefix.
|
|
219
236
|
|
|
220
|
-
Template options can be
|
|
237
|
+
Template options can be passed when creating an engine instance, updated with
|
|
238
|
+
`updateTemplateOptions(templateOptions)`, or set for one request with
|
|
239
|
+
`updateLocalTemplateOptions(locals, templateOptions)` on `res.locals`.
|
|
221
240
|
|
|
222
241
|
Both of these methods have a companion method `getTemplateOptions()` and `getLocalTemplateOptions(locals)`, which should be used when extending or merging the current options.
|
|
223
242
|
|
|
@@ -275,20 +294,29 @@ File `views/index.hbs`
|
|
|
275
294
|
|
|
276
295
|
To run example project
|
|
277
296
|
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
```sh
|
|
298
|
+
pnpm install
|
|
299
|
+
node example/app.js
|
|
300
|
+
```
|
|
280
301
|
|
|
302
|
+
The example server listens on <http://localhost:3000>.
|
|
281
303
|
|
|
282
304
|
## Testing
|
|
283
305
|
|
|
284
|
-
|
|
306
|
+
Install dependencies and run the test suite:
|
|
285
307
|
|
|
286
|
-
|
|
287
|
-
|
|
308
|
+
```sh
|
|
309
|
+
pnpm install
|
|
310
|
+
pnpm test
|
|
311
|
+
```
|
|
288
312
|
|
|
289
|
-
|
|
313
|
+
Useful maintainer commands:
|
|
290
314
|
|
|
291
|
-
|
|
315
|
+
```sh
|
|
316
|
+
pnpm lint # oxlint plus oxfmt --check
|
|
317
|
+
pnpm lint:fix # apply safe lint fixes and formatting
|
|
318
|
+
pnpm coverage # mocha under nyc with enforced coverage thresholds
|
|
319
|
+
```
|
|
292
320
|
|
|
293
321
|
|
|
294
322
|
## Credits
|
|
@@ -302,4 +330,4 @@ Big thanks to all [CONTRIBUTORS](https://github.com/TryGhost/express-hbs/contrib
|
|
|
302
330
|
|
|
303
331
|
The MIT License (MIT)
|
|
304
332
|
|
|
305
|
-
Copyright (c) 2012-
|
|
333
|
+
Copyright (c) 2012-2026 Barc, Inc., Ghost Foundation - Released under the [MIT license](LICENSE).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// pnpm add express express-hbs
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -16,18 +16,20 @@ var viewsDir = relative('views');
|
|
|
16
16
|
app.use(express.static(relative('public')));
|
|
17
17
|
|
|
18
18
|
// Hook in express-hbs and tell it where known directories reside
|
|
19
|
-
app.engine(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
app.engine(
|
|
20
|
+
'hbs',
|
|
21
|
+
hbs.express4({
|
|
22
|
+
partialsDir: [relative('views/partials'), relative('views/partials-other')],
|
|
23
|
+
layoutsDir: relative('views/layout'),
|
|
24
|
+
defaultLayout: relative('views/layout/default.hbs'),
|
|
25
|
+
restrictLayoutsTo: relative('views/layout'),
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
25
28
|
app.set('view engine', 'hbs');
|
|
26
29
|
app.set('views', relative('views'));
|
|
27
30
|
|
|
28
|
-
|
|
29
31
|
// Register sync helper
|
|
30
|
-
hbs.registerHelper('link', function(text, options) {
|
|
32
|
+
hbs.registerHelper('link', function (text, options) {
|
|
31
33
|
var attrs = [];
|
|
32
34
|
for (var prop in options.hash) {
|
|
33
35
|
attrs.push(prop + '="' + options.hash[prop] + '"');
|
|
@@ -36,66 +38,57 @@ hbs.registerHelper('link', function(text, options) {
|
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
// Register Async helpers
|
|
39
|
-
hbs.registerAsyncHelper('readFile', function(filename, cb) {
|
|
40
|
-
fs.readFile(fp.join(viewsDir, filename), 'utf8', function(err, content) {
|
|
41
|
+
hbs.registerAsyncHelper('readFile', function (filename, cb) {
|
|
42
|
+
fs.readFile(fp.join(viewsDir, filename), 'utf8', function (err, content) {
|
|
41
43
|
if (err) console.error(err);
|
|
42
44
|
cb(new hbs.SafeString(content));
|
|
43
45
|
});
|
|
44
46
|
});
|
|
45
47
|
|
|
46
|
-
var fruits = [
|
|
47
|
-
{name: 'apple'},
|
|
48
|
-
{name: 'orange'},
|
|
49
|
-
{name: 'pear'}
|
|
50
|
-
];
|
|
51
|
-
|
|
48
|
+
var fruits = [{ name: 'apple' }, { name: 'orange' }, { name: 'pear' }];
|
|
52
49
|
|
|
53
|
-
var veggies = [
|
|
54
|
-
{name: 'asparagus'},
|
|
55
|
-
{name: 'carrot'},
|
|
56
|
-
{name: 'spinach'}
|
|
57
|
-
];
|
|
50
|
+
var veggies = [{ name: 'asparagus' }, { name: 'carrot' }, { name: 'spinach' }];
|
|
58
51
|
|
|
59
|
-
app.get('/', function(req, res) {
|
|
52
|
+
app.get('/', function (req, res) {
|
|
60
53
|
res.render('index', {
|
|
61
|
-
title: 'express-hbs example'
|
|
54
|
+
title: 'express-hbs example',
|
|
62
55
|
});
|
|
63
56
|
});
|
|
64
57
|
|
|
65
|
-
app.get('/fruits', function(req, res) {
|
|
58
|
+
app.get('/fruits', function (req, res) {
|
|
66
59
|
res.render('fruits/index-layoutsDir', {
|
|
67
60
|
title: 'My favorite fruits',
|
|
68
|
-
fruits: fruits
|
|
61
|
+
fruits: fruits,
|
|
69
62
|
});
|
|
70
63
|
});
|
|
71
64
|
|
|
72
|
-
app.get('/fruits/:name', function(req, res) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
app.get('/fruits/:name', function (req, res) {
|
|
66
|
+
res.render('fruits/details-layoutsDir', {
|
|
67
|
+
fruit: req.params.name,
|
|
68
|
+
});
|
|
76
69
|
});
|
|
77
70
|
|
|
78
|
-
app.get('/veggies', function(req, res) {
|
|
71
|
+
app.get('/veggies', function (req, res) {
|
|
79
72
|
res.render('veggies', {
|
|
80
73
|
title: 'My favorite veggies',
|
|
81
74
|
veggies: veggies,
|
|
82
|
-
layout: 'veggie'
|
|
75
|
+
layout: 'veggie',
|
|
83
76
|
});
|
|
84
77
|
});
|
|
85
78
|
|
|
86
|
-
app.get('/veggies/explicit-dir', function(req, res) {
|
|
79
|
+
app.get('/veggies/explicit-dir', function (req, res) {
|
|
87
80
|
res.render('veggies', {
|
|
88
81
|
title: 'My favorite veggies',
|
|
89
82
|
veggies: veggies,
|
|
90
|
-
layout: relative('views/layout/veggie')
|
|
83
|
+
layout: relative('views/layout/veggie'),
|
|
91
84
|
});
|
|
92
85
|
});
|
|
93
86
|
|
|
94
|
-
app.get('/veggies/:name', function(req, res) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
app.get('/veggies/:name', function (req, res) {
|
|
88
|
+
res.render('veggies/details', {
|
|
89
|
+
veggie: req.params.name,
|
|
90
|
+
layout: 'veggie-details',
|
|
91
|
+
});
|
|
99
92
|
});
|
|
100
93
|
|
|
101
94
|
if (require.main === module) {
|
package/example/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
//
|
|
2
|
+
// pnpm add express express-hbs
|
|
3
3
|
|
|
4
4
|
function create(hbs, env) {
|
|
5
5
|
if (env) process.env.NODE_ENV = env;
|
|
@@ -18,82 +18,75 @@ function create(hbs, env) {
|
|
|
18
18
|
app.use(express.static(relative('public')));
|
|
19
19
|
|
|
20
20
|
// Hook in express-hbs and tell it where known directories reside
|
|
21
|
-
app.engine(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
app.engine(
|
|
22
|
+
'hbs',
|
|
23
|
+
hbs.express4({
|
|
24
|
+
partialsDir: [relative('views/partials'), relative('views/partials-other')],
|
|
25
|
+
defaultLayout: relative('views/layout/default.hbs'),
|
|
26
|
+
restrictLayoutsTo: viewsDir,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
26
29
|
app.set('view engine', 'hbs');
|
|
27
30
|
app.set('views', viewsDir);
|
|
28
31
|
|
|
29
32
|
// Register sync helper
|
|
30
|
-
hbs.registerHelper('link', function(text, options) {
|
|
33
|
+
hbs.registerHelper('link', function (text, options) {
|
|
31
34
|
var attrs = [];
|
|
32
35
|
for (var prop in options.hash) {
|
|
33
36
|
attrs.push(prop + '="' + options.hash[prop] + '"');
|
|
34
37
|
}
|
|
35
|
-
return new hbs.SafeString(
|
|
36
|
-
'<a ' + attrs.join(' ') + '>' + text + '</a>'
|
|
37
|
-
);
|
|
38
|
+
return new hbs.SafeString('<a ' + attrs.join(' ') + '>' + text + '</a>');
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
// Register Async helpers
|
|
41
|
-
hbs.registerAsyncHelper('readFile', function(filename, cb) {
|
|
42
|
-
fs.readFile(fp.join(viewsDir, filename), 'utf8', function(err, content) {
|
|
42
|
+
hbs.registerAsyncHelper('readFile', function (filename, cb) {
|
|
43
|
+
fs.readFile(fp.join(viewsDir, filename), 'utf8', function (err, content) {
|
|
43
44
|
if (err) console.error(err);
|
|
44
45
|
cb(new hbs.SafeString(content));
|
|
45
46
|
});
|
|
46
47
|
});
|
|
47
48
|
|
|
48
|
-
var fruits = [
|
|
49
|
-
{name: 'apple'},
|
|
50
|
-
{name: 'orange'},
|
|
51
|
-
{name: 'pear'}
|
|
52
|
-
];
|
|
49
|
+
var fruits = [{ name: 'apple' }, { name: 'orange' }, { name: 'pear' }];
|
|
53
50
|
|
|
54
|
-
var veggies = [
|
|
55
|
-
{name: 'asparagus'},
|
|
56
|
-
{name: 'carrot'},
|
|
57
|
-
{name: 'spinach'}
|
|
58
|
-
];
|
|
51
|
+
var veggies = [{ name: 'asparagus' }, { name: 'carrot' }, { name: 'spinach' }];
|
|
59
52
|
|
|
60
|
-
app.get('/', function(req, res) {
|
|
53
|
+
app.get('/', function (req, res) {
|
|
61
54
|
res.render('index', {
|
|
62
|
-
title: 'express-hbs example'
|
|
55
|
+
title: 'express-hbs example',
|
|
63
56
|
});
|
|
64
57
|
});
|
|
65
58
|
|
|
66
|
-
app.get('/replace', function(req, res) {
|
|
59
|
+
app.get('/replace', function (req, res) {
|
|
67
60
|
res.render('replace', {
|
|
68
|
-
title: 'express-hbs example'
|
|
61
|
+
title: 'express-hbs example',
|
|
69
62
|
});
|
|
70
63
|
});
|
|
71
64
|
|
|
72
|
-
app.get('/fruits', function(req, res) {
|
|
65
|
+
app.get('/fruits', function (req, res) {
|
|
73
66
|
res.render('fruits/index', {
|
|
74
67
|
title: 'My favorite fruits',
|
|
75
|
-
fruits: fruits
|
|
68
|
+
fruits: fruits,
|
|
76
69
|
});
|
|
77
70
|
});
|
|
78
71
|
|
|
79
|
-
app.get('/fruits/:name', function(req, res) {
|
|
72
|
+
app.get('/fruits/:name', function (req, res) {
|
|
80
73
|
res.render('fruits/details', {
|
|
81
|
-
fruit: req.params.name
|
|
74
|
+
fruit: req.params.name,
|
|
82
75
|
});
|
|
83
76
|
});
|
|
84
77
|
|
|
85
|
-
app.get('/veggies', function(req, res) {
|
|
78
|
+
app.get('/veggies', function (req, res) {
|
|
86
79
|
res.render('veggies', {
|
|
87
80
|
title: 'My favorite veggies',
|
|
88
81
|
veggies: veggies,
|
|
89
|
-
layout: 'layout/veggie'
|
|
82
|
+
layout: 'layout/veggie',
|
|
90
83
|
});
|
|
91
84
|
});
|
|
92
85
|
|
|
93
|
-
app.get('/veggies/:name', function(req, res) {
|
|
86
|
+
app.get('/veggies/:name', function (req, res) {
|
|
94
87
|
res.render('veggies/details', {
|
|
95
88
|
veggie: req.params.name,
|
|
96
|
-
layout: 'layout/veggie-details'
|
|
89
|
+
layout: 'layout/veggie-details',
|
|
97
90
|
});
|
|
98
91
|
});
|
|
99
92
|
|