Revert "permet l'ajout des frameworks et des routes"

This reverts commit 361112699c
This commit is contained in:
Dario Duchateau-weinberger
2023-09-25 09:44:12 +02:00
parent 361112699c
commit 20cb812095
2787 changed files with 0 additions and 864804 deletions

View File

@@ -1,4 +0,0 @@
node_modules/
npm-debug.log
coverage/
dist/

View File

@@ -1,42 +0,0 @@
module.exports = {
root: true,
env: {
node: true,
jest: true,
},
extends: [
"standard",
"plugin:@typescript-eslint/recommended",
],
plugins: [
"@typescript-eslint",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2018,
},
rules: {
quotes: ["error", "double"],
semi: ["error", "always"],
"no-warning-comments": "warn",
"comma-dangle": ["error", "always-multiline"],
indent: ["error", "tab", { SwitchCase: 1 }],
"no-tabs": "off",
"no-var": "error",
"prefer-const": "error",
"object-shorthand": "error",
"no-restricted-globals": [
"error",
{
name: "fit",
message: "Do not commit focused tests.",
},
{
name: "fdescribe",
message: "Do not commit focused tests.",
},
],
"@typescript-eslint/ban-ts-comment": "warn",
"@typescript-eslint/no-var-requires": "warn",
},
};

View File

@@ -1,71 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 15 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -1,115 +0,0 @@
name: "Tests"
on:
pull_request:
push:
branches:
- master
env:
CI: true
jobs:
TestOS:
name: Test
strategy:
matrix:
os: [macos-latest, windows-latest]
node_version: ['lts/*']
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
check-latest: true
- name: Install dependencies
run: npm ci
- name: Run tests 👩🏾‍💻
run: npm run test
TestNode:
name: Test
strategy:
matrix:
os: [ubuntu-latest]
node_version: ['lts/*', '*']
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
check-latest: true
- name: Install dependencies
run: npm ci
- name: Run tests 👩🏾‍💻
run: npm run test
Coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
check-latest: true
- name: Install dependencies
run: npm ci
- name: Run tests 👩🏾‍💻
run: npm run test:cover
Lint:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
check-latest: true
- name: NPM install
run: npm ci
- name: Lint ✨
run: npm run lint
Build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
check-latest: true
- name: NPM install
run: npm ci
- name: Build 🧱
run: npm run build
Release:
needs: [TestOS, TestNode, Coverage, Lint, Build]
if: |
github.ref == 'refs/heads/master' &&
github.event.repository.fork == false
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
check-latest: true
- name: NPM install
run: npm ci
- name: NPM build
run: npm run build
- name: Release 🎉
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release

View File

@@ -1,259 +0,0 @@
## [7.1.2](https://github.com/express-handlebars/express-handlebars/compare/v7.1.1...v7.1.2) (2023-08-08)
### Bug Fixes
* use types from handlebars for helpers ([#617](https://github.com/express-handlebars/express-handlebars/issues/617)) ([bc38da4](https://github.com/express-handlebars/express-handlebars/commit/bc38da4199cdc450dd84537c0515da475ef0d6ad))
## [7.1.1](https://github.com/express-handlebars/express-handlebars/compare/v7.1.0...v7.1.1) (2023-08-02)
### Bug Fixes
* **deps:** update dependency handlebars to ^4.7.8 ([#616](https://github.com/express-handlebars/express-handlebars/issues/616)) ([54ef900](https://github.com/express-handlebars/express-handlebars/commit/54ef9006ad1dd425a166bd4f1fdd08aa0911dc19))
# [7.1.0](https://github.com/express-handlebars/express-handlebars/compare/v7.0.7...v7.1.0) (2023-07-20)
### Features
* add resetCache ([#554](https://github.com/express-handlebars/express-handlebars/issues/554)) ([868e9b4](https://github.com/express-handlebars/express-handlebars/commit/868e9b4ac9690de5000385c1fecdef858cf8d504))
## [7.0.7](https://github.com/express-handlebars/express-handlebars/compare/v7.0.6...v7.0.7) (2023-04-15)
### Bug Fixes
* **deps:** update dependency glob to ^10.1.0 ([#555](https://github.com/express-handlebars/express-handlebars/issues/555)) ([196c925](https://github.com/express-handlebars/express-handlebars/commit/196c9255716856ae741a9c6672821ff8e2aeb2a9))
## [7.0.6](https://github.com/express-handlebars/express-handlebars/compare/v7.0.5...v7.0.6) (2023-04-12)
### Bug Fixes
* replace backslash in partials with forward slash ([#553](https://github.com/express-handlebars/express-handlebars/issues/553)) ([2ecd784](https://github.com/express-handlebars/express-handlebars/commit/2ecd7840cb16d0e5013469b84183211e02212053))
## [7.0.5](https://github.com/express-handlebars/express-handlebars/compare/v7.0.4...v7.0.5) (2023-04-11)
### Bug Fixes
* **deps:** update dependency glob to v10 ([#551](https://github.com/express-handlebars/express-handlebars/issues/551)) ([7817150](https://github.com/express-handlebars/express-handlebars/commit/7817150a1c06b9cb50826574a2e2127d5730c268))
## [7.0.4](https://github.com/express-handlebars/express-handlebars/compare/v7.0.3...v7.0.4) (2023-03-23)
### Bug Fixes
* **deps:** update dependency graceful-fs to ^4.2.11 ([#531](https://github.com/express-handlebars/express-handlebars/issues/531)) ([570e73d](https://github.com/express-handlebars/express-handlebars/commit/570e73da862694ee3b760c3616eb23ace7b0d4af))
## [7.0.3](https://github.com/express-handlebars/express-handlebars/compare/v7.0.2...v7.0.3) (2023-03-23)
### Bug Fixes
* **deps:** update dependency glob to ^9.3.2 ([#526](https://github.com/express-handlebars/express-handlebars/issues/526)) ([2aa9c4a](https://github.com/express-handlebars/express-handlebars/commit/2aa9c4aa58cf43d25ae7dd4b21110523451ebaac))
## [7.0.2](https://github.com/express-handlebars/express-handlebars/compare/v7.0.1...v7.0.2) (2023-03-09)
### Bug Fixes
* **deps:** update dependency glob to ^9.2.1 ([#520](https://github.com/express-handlebars/express-handlebars/issues/520)) ([16a9bc3](https://github.com/express-handlebars/express-handlebars/commit/16a9bc356ba333586bb1626d2402d9aa7df948fd))
## [7.0.1](https://github.com/express-handlebars/express-handlebars/compare/v7.0.0...v7.0.1) (2023-03-01)
### Bug Fixes
* **deps:** update dependency glob to ^9.1.0 ([#518](https://github.com/express-handlebars/express-handlebars/issues/518)) ([635922c](https://github.com/express-handlebars/express-handlebars/commit/635922c746c76b0f36fe7061fcecc7c9f64d3b1b))
* fix cannot use import ([0f45477](https://github.com/express-handlebars/express-handlebars/commit/0f45477b822a9d7814e1d05c493e4b827279717c))
# [7.0.0](https://github.com/express-handlebars/express-handlebars/compare/v6.0.7...v7.0.0) (2023-03-01)
### Bug Fixes
* **deps:** update dependency glob to v9 ([#514](https://github.com/express-handlebars/express-handlebars/issues/514)) ([3b08bbb](https://github.com/express-handlebars/express-handlebars/commit/3b08bbb05fe195a855a090fadc11fc183f5c2292))
* minimum Node v16 ([#516](https://github.com/express-handlebars/express-handlebars/issues/516)) ([86da3b2](https://github.com/express-handlebars/express-handlebars/commit/86da3b229b9c3bdf1c0e5924a796199a861ec260))
### BREAKING CHANGES
* minimum node version is v16
## [6.0.7](https://github.com/express-handlebars/express-handlebars/compare/v6.0.6...v6.0.7) (2023-01-25)
### Bug Fixes
* **deps:** update dependency glob to ^8.1.0 ([#489](https://github.com/express-handlebars/express-handlebars/issues/489)) ([1bb2a2f](https://github.com/express-handlebars/express-handlebars/commit/1bb2a2f3dae7148afc5468bc916f6abe08381937))
## [6.0.6](https://github.com/express-handlebars/express-handlebars/compare/v6.0.5...v6.0.6) (2022-05-13)
### Bug Fixes
* **deps:** update dependency glob to ^8.0.2 ([8202ea1](https://github.com/express-handlebars/express-handlebars/commit/8202ea19fb6e4354edd05dc457d2f3a14a5c29d9))
## [6.0.5](https://github.com/express-handlebars/express-handlebars/compare/v6.0.4...v6.0.5) (2022-04-11)
### Bug Fixes
* **deps:** update dependency glob to v8 ([4025b58](https://github.com/express-handlebars/express-handlebars/commit/4025b58534b794863b2f51dcdc779d347a46c4a6))
## [6.0.4](https://github.com/express-handlebars/express-handlebars/compare/v6.0.3...v6.0.4) (2022-04-06)
### Bug Fixes
* **deps:** update dependency graceful-fs to ^4.2.10 ([2d6e89c](https://github.com/express-handlebars/express-handlebars/commit/2d6e89c219b11000125f7bc2630f6ddaf241987d))
## [6.0.3](https://github.com/express-handlebars/express-handlebars/compare/v6.0.2...v6.0.3) (2022-03-03)
### Bug Fixes
* allow false for defaultLayout ([#303](https://github.com/express-handlebars/express-handlebars/issues/303)) ([d6180fe](https://github.com/express-handlebars/express-handlebars/commit/d6180fe7ad8ab74e60f58b4ced1b6d6af2d68c42))
* **deps:** update dependency graceful-fs to ^4.2.9 ([#271](https://github.com/express-handlebars/express-handlebars/issues/271)) ([ea0f1f5](https://github.com/express-handlebars/express-handlebars/commit/ea0f1f563488d67202d7d6067116a4fe67eddf18))
## [6.0.2](https://github.com/express-handlebars/express-handlebars/compare/v6.0.1...v6.0.2) (2021-11-25)
### Bug Fixes
* fix typescript in strict mode ([6833d8d](https://github.com/express-handlebars/express-handlebars/commit/6833d8dd4532e45790e04940b646e33f5fd07429))
## [6.0.1](https://github.com/express-handlebars/express-handlebars/compare/v6.0.0...v6.0.1) (2021-11-13)
### Bug Fixes
* fix types ([f4de857](https://github.com/express-handlebars/express-handlebars/commit/f4de8577d5ad4510f4c5286cdee300dd27c6abfc))
* remove default export ([a7f38a1](https://github.com/express-handlebars/express-handlebars/commit/a7f38a1d3127d63450b10b3f3539e3ce8131b677))
* update examples ([1b1f5f7](https://github.com/express-handlebars/express-handlebars/commit/1b1f5f7b818985d433f6dc0398f7866c62b6cdea))
# [6.0.0](https://github.com/express-handlebars/express-handlebars/compare/v5.3.5...v6.0.0) (2021-11-13)
### Features
* rewrite in typescript ([188d3c4](https://github.com/express-handlebars/express-handlebars/commit/188d3c48526499143b7e1787accd230150a200d3))
### BREAKING CHANGES
* Change minimum node version to 12
## [5.3.5](https://github.com/express-handlebars/express-handlebars/compare/v5.3.4...v5.3.5) (2021-11-13)
### Bug Fixes
* update deps ([b516cff](https://github.com/express-handlebars/express-handlebars/commit/b516cff30ba3de90db02b3a3682c9ffbcfb10091))
## [5.3.4](https://github.com/express-handlebars/express-handlebars/compare/v5.3.3...v5.3.4) (2021-09-23)
### Bug Fixes
* **deps:** update dependency glob to ^7.2.0 ([15c77f5](https://github.com/express-handlebars/express-handlebars/commit/15c77f5e7cf31168942adaee8d021870719d9cd8))
## [5.3.3](https://github.com/express-handlebars/express-handlebars/compare/v5.3.2...v5.3.3) (2021-08-05)
### Bug Fixes
* **deps:** update dependency graceful-fs to ^4.2.7 ([94a4073](https://github.com/express-handlebars/express-handlebars/commit/94a4073bbea4591b57ea5e3cdae03c8fd861d50e))
## [5.3.2](https://github.com/express-handlebars/express-handlebars/compare/v5.3.1...v5.3.2) (2021-05-06)
### Bug Fixes
* **deps:** update dependency glob to ^7.1.7 ([8222f00](https://github.com/express-handlebars/express-handlebars/commit/8222f0015805b1287f62a1c66747a7f831a976db))
## [5.3.1](https://github.com/express-handlebars/express-handlebars/compare/v5.3.0...v5.3.1) (2021-05-04)
### Bug Fixes
* add note about security ([78c47a2](https://github.com/express-handlebars/express-handlebars/commit/78c47a235c4ad7bc2674bddd8ec2721567ed8c72))
# [5.3.0](https://github.com/express-handlebars/express-handlebars/compare/v5.2.1...v5.3.0) (2021-03-30)
### Features
* Add partialsDir.rename option ([#151](https://github.com/express-handlebars/express-handlebars/issues/151)) ([1a6771b](https://github.com/express-handlebars/express-handlebars/commit/1a6771b0f9a3db1cbd516faf79cb5e20a779e456))
## [5.2.1](https://github.com/express-handlebars/express-handlebars/compare/v5.2.0...v5.2.1) (2021-02-16)
### Bug Fixes
* **deps:** update dependency handlebars to ^4.7.7 ([1930523](https://github.com/express-handlebars/express-handlebars/commit/1930523103e6c97a3f3e41d6e7b5d6dc329c66f9))
# [5.2.0](https://github.com/express-handlebars/express-handlebars/compare/v5.1.0...v5.2.0) (2020-10-23)
### Features
* allow views to be an array ([a9f4aaa](https://github.com/express-handlebars/express-handlebars/commit/a9f4aaabd657221236b7321a4f87df7c9eb9a1bd))
# [5.1.0](https://github.com/express-handlebars/express-handlebars/compare/v5.0.0...v5.1.0) (2020-07-16)
### Features
* add encoding option ([9e516c3](https://github.com/express-handlebars/express-handlebars/commit/9e516c382269b3ab586a6ab0dbd586b3c23110c4))
# [5.0.0](https://github.com/express-handlebars/express-handlebars/compare/v4.0.6...v5.0.0) (2020-07-06)
### Bug Fixes
* update code to es2015+ ([e5a08ee](https://github.com/express-handlebars/express-handlebars/commit/e5a08eed844f177b0f365f882a20c7b229715bdd))
* update node support ([ea30d53](https://github.com/express-handlebars/express-handlebars/commit/ea30d531b2f458c37f65b50bddc504180e774f8f))
### BREAKING CHANGES
* Drop support for node versions below v10
## [4.0.6](https://github.com/express-handlebars/express-handlebars/compare/v4.0.5...v4.0.6) (2020-07-06)
### Bug Fixes
* add runtimeOptions ([b64284f](https://github.com/express-handlebars/express-handlebars/commit/b64284f6f6eab2d184671736c33fc45df5b26246))
## [4.0.5](https://github.com/express-handlebars/express-handlebars/compare/v4.0.4...v4.0.5) (2020-07-03)
### Bug Fixes
* overwrite past settings.views ([c27f1b0](https://github.com/express-handlebars/express-handlebars/commit/c27f1b0e8dcf2be974584861433cfb01a10ce1f6))
* renderView returns promise when no callback given ([c39ed87](https://github.com/express-handlebars/express-handlebars/commit/c39ed87f2478ed64211821a6ffe1dca7212fb21b))
## [4.0.4](https://github.com/express-handlebars/express-handlebars/compare/v4.0.3...v4.0.4) (2020-04-29)
### Bug Fixes
* **deps:** update dependency graceful-fs to ^4.2.4 ([c01661b](https://github.com/express-handlebars/express-handlebars/commit/c01661be5193ea77d9914b71aedcb71d6ad4ab92))
## [4.0.3](https://github.com/express-handlebars/express-handlebars/compare/v4.0.2...v4.0.3) (2020-04-05)
### Bug Fixes
* **deps:** update dependency handlebars to ^4.7.6 ([2aa29ab](https://github.com/express-handlebars/express-handlebars/commit/2aa29ab29d5db9becccb5690a6fdef4a46055906))
## [4.0.2](https://github.com/express-handlebars/express-handlebars/compare/v4.0.1...v4.0.2) (2020-04-03)
### Bug Fixes
* **deps:** update dependency handlebars to ^4.7.5 ([#6](https://github.com/express-handlebars/express-handlebars/issues/6)) ([e597254](https://github.com/express-handlebars/express-handlebars/commit/e59725426cd6c6ab261127fd96065f30009ea1e1))

View File

@@ -1,402 +0,0 @@
Express Handlebars Change History
=================================
4.0.1 (2020-04-01)
------------------
* Update handlebars to fix mimist vulnerability.
4.0.0 (2020-03-25)
------------------
* Move to repo https://github.com/express-handlebars/express-handlebars/
* Update all deps.
3.1.0 (2019-05-14)
------------------
* `defaultLayout` defaults to main ([#249][])
* Upgrade Handlebars to v4.1.2 ([#250][])
[#249]: https://github.com/ericf/express-handlebars/issues/249
[#250]: https://github.com/ericf/express-handlebars/issues/250
3.0.2 (2019-02-24)
------------------
* Fix configuration `layoutsDir` & `partialsDir`. ([#244][])
[#244]: https://github.com/ericf/express-handlebars/issues/244
3.0.1 (2019-02-20)
------------------
* Updated dependencies that are long over due
3.0.0 (2016-01-26)
------------------
* Upgraded to Handlebars 4.0. ([#142][])
[#142]: https://github.com/ericf/express-handlebars/issues/142
2.0.1 (2015-04-23)
------------------
* Guarded against unexpected Handlebars API change that was released in a patch.
([#125][])
[#125]: https://github.com/ericf/express-handlebars/issues/125
2.0.0 (2015-03-22)
------------------
* __[!]__ Upgraded to Handlebars 3.0 by default, but still works with Handlebars
2.x by using the `handlebars` config option. ([#105][])
* __[!]__ Removed using prototype properties for default config values. The
default values are now embedded in the constructor. ([#105][])
* __[!]__ Removed `handlebarsVersion` instance property and
`getHandlebarsSemver()` static function on the `ExpressHandlebars`
constructor. ([#105][])
* __[!]__ Replaced undocumented `compileTemplate()` hook with the protected but
supported `_compileTemplate()` and `_precompileTemplate()` hooks. ([#95][])
* Fixed layout path resolution on Windows. ([#113][] @Tineler)
* Added `compilerOptions` config property which is passed along to
`Handlebars.compile()` and `Handlebars.precompile()`. ([#95][])
* Exposed Express Handlebars metadata to the data channel during render. This
metadata is accessible via `{{@exphbs.*}}` ([#89][], [#101][])
* Added new "protected" hooks for AOP-ing template compilation and rendering,
all of which can optionally return a Promise: ([#105][])
* `_compileTemplate()`
* `_precompileTemplate()`
* `_renderTemplate()`
[#89]: https://github.com/ericf/express-handlebars/issues/89
[#95]: https://github.com/ericf/express-handlebars/issues/95
[#101]: https://github.com/ericf/express-handlebars/issues/101
[#105]: https://github.com/ericf/express-handlebars/issues/105
[#113]: https://github.com/ericf/express-handlebars/issues/113
1.2.2 (2015-03-06)
------------------
* Upgraded `glob` dependency to v5 which now officially supports symlinks via
the new `follow` option. ([#98][])
1.2.1 (2015-02-17)
------------------
* Locked down `glob` dependency to a v4 version range that is known to work with
this package _and_ support symlinks. The `glob` version can be updated when
[isaacs/node-glob#139](https://github.com/isaacs/node-glob/issues/139) is
resolved. ([#98][] @adgad)
[#98]: https://github.com/ericf/express-handlebars/issues/98
1.2.0 (2015-02-17)
------------------
* Added support for render-level `partials` to be specified when calling
`renderView()` (which is the method Express calls). The `options.partials`
value matches what Handlebars accepts during template rendering: it should
have the shape `{partialName: fn}` or be a Promise for such an object.
([#82][])
[#82]: https://github.com/ericf/express-handlebars/issues/82
1.1.0 (2014-09-14)
------------------
* __[!]__ Upgraded Handlebars to 2.0.0 final, it was beta before.
* Added support for `partialsDir` to be configured with a collection (or promise
for a collection) of templates, via the new `templates` prop in `partialDir`
config objects. This allows developers to hand Express Handlebars the compiled
partials templates to use for a specific partials dir.
([#81][] @joanniclaborde)
* Upgraded Promise dependency.
[#81]: https://github.com/ericf/express-handlebars/issues/81
1.0.3 (2014-09-05)
------------------
* Fixed issue with namespaced partials dirs not actually being namespaces.
([#76][] @inerte)
[#76]: https://github.com/ericf/express-handlebars/issues/76
1.0.2 (2014-09-05)
------------------
* Fixed `engines` entry in `package.json` to Node `>=0.10` to reflect this
package's requirements. ([#78][])
[#78]: https://github.com/ericf/express-handlebars/issues/78
1.0.1 (2014-08-08)
------------------
* Fixed bug where rendered content was only be returned if a layout template was
being used. Now a layout-less render will actually return content. ([#73][])
[#73]: https://github.com/ericf/express-handlebars/issues/73
1.0.0 (2014-08-07)
------------------
* __[!]__ Renamed to: `express-handlebars`. ([#65][])
* __[!]__ Rewritten to use Promises instead of `async` for asynchronous code.
([#68][]) This resulted in the following public API changes:
* `loadPartials()` --> `getPartials()`, returns a Promise.
* `loadTemplate()` --> `getTemplate()`, returns a Promise.
* `loadTemplates()` --> `getTemplates()`, returns a Promise.
* `render(file, context, [options])`, returns a Promise.
* `partialsDir` can now be set with an array of objects in the following form to
support namespaced partials: ([#70][] @joanniclaborde)
{ dir: 'foo/bar/', namespace: 'bar' }
* Added support for Handlebars' `data` channel via `options.data`. ([#62][])
* Added `compileTemplate()` hook for the pre/post compile process, this also
supports returning a Promise. ([#39][], [#41][])
* Added `_renderTemplate()` hook that supports returning a Promise.
([#39][], [#41][])
* Upgraded all dependencies, including Handlebars to 2.x. ([#59][])
* Added `graceful-fs` dependency to support large numbers of files to avoid
EMFILE errors.
* Reduced complexity of cache code.
* Updated examples to each be self-contained and have `package.json` files.
[#39]: https://github.com/ericf/express-handlebars/issues/39
[#41]: https://github.com/ericf/express-handlebars/issues/41
[#59]: https://github.com/ericf/express-handlebars/issues/59
[#62]: https://github.com/ericf/express-handlebars/issues/62
[#65]: https://github.com/ericf/express-handlebars/issues/65
[#68]: https://github.com/ericf/express-handlebars/issues/68
[#70]: https://github.com/ericf/express-handlebars/issues/70
0.5.1 (2014-08-05)
------------------
* __[!]__ Last release before `v1.0` which will have breaking changes.
* Improved `extname` docs in README and added example. ([#30][] @Crashthatch)
* `extname` can now be specified _without_ the leading `"."`.
([#51][] @calvinmetcalf)
[#30]: https://github.com/ericf/express-handlebars/issues/30
[#51]: https://github.com/ericf/express-handlebars/issues/51
0.5.0 (2013-07-25)
------------------
* Added `loadTemplates()` method which will load all the templates in a
specified directory. ([#21][])
* Added support for multiple partials directories. This enables the
`partialsDir` configuration property to be specified as an *array* of
directories, and loads all of the templates in each one.
This feature allows an app's partials to be split up in multiple directories,
which is common if an app has some shared partials which will also be exposed
to the client, and some server-side-only partials. ([#20][])
* Added runnable code examples in this package's "examples/" directory.
([#22][])
* Improved optional argument handling in public methods to treat Express
`locals` function objects as `options` and not `callback` params to the method
being invoked. ([#27][])
[#20]: https://github.com/ericf/express-handlebars/issues/20
[#21]: https://github.com/ericf/express-handlebars/issues/21
[#22]: https://github.com/ericf/express-handlebars/issues/22
[#27]: https://github.com/ericf/express-handlebars/issues/27
0.4.1 (2013-04-06)
------------------
* Updated `async` dependency to the latest stable minor version: "~0.2".
0.4.0 (2013-03-24)
------------------
* __[!]__ Removed the following "get" -> "load" aliases which kept in v0.2.0 for
back-compat:
* `getPartials()` -> `loadPartials()`
* `getTemplate()` -> `loadTemplate()`
This is the future version where these aliases have been removed.
* __[!]__ Renamed `lib/express3-handlebars.js` -> `lib/express-handlebars.js`.
* Exposed `getHandlebarsSemver()` function as a static property on the
`ExpressHandlebars` constructor.
* Rearranged module exports by moving the engine factory function to `index.js`,
making the `lib/express3-handlebars.js` module only responsible for exporting
the `ExpressHandlebars` constructor.
0.3.3 (2013-03-22)
------------------
* Updated internal `_resolveLayoutPath()` method to take the full
`options`/locals objects which the view is rendered with. This makes it easier
to override. ([#14][])
[#14]: https://github.com/ericf/express-handlebars/issues/14
0.3.2 (2013-02-20)
------------------
* Transfered ownership and copyright to Yahoo! Inc. This software is still free
to use, and is now licensed under the Yahoo! Inc. BSD license.
0.3.1 (2013-02-18)
------------------
* Updated README with info about `options.helpers` for `render()` and
`renderView()` docs. ([#7][])
[#7]: https://github.com/ericf/express-handlebars/issues/7
0.3.0 (2013-02-18)
------------------
* Added support for render-level helpers, via `options.helpers`, to the
`render()` and `renderView()` methods. Handlebars' `registerHelper()` function
now works as expected and does not have to be called before the
`ExpressHandlebars` instance is created. Helpers are now merged from:
`handlebars.helpers` (global), `helpers` (instance), and `options.helpers`
(render-level) before a template is rendered; this provides flexibility at
all levels. ([#3][], [#11][])
* Added `handlebarsVersion` property which is the version number of `handlebars`
as a semver. This is used internally to branch on certain operations which
differ between Handlebars releases.
[#3]: https://github.com/ericf/express-handlebars/issues/3
[#11]: https://github.com/ericf/express-handlebars/issues/11
0.2.3 (2013-02-13)
------------------
* Fixed issue with naming nested partials when using the latest version of
Handlebars (1.0.rc.2). Previous versions require a hack to replace "/"s with
"."s in partial names, and the latest version of Handlebars fixes that bug.
This hack will only be applied to old versions of Handlebars. ([#9][])
[#9]: https://github.com/ericf/express-handlebars/issues/9
0.2.2 (2013-02-04)
------------------
* Updated README with the public method renames which happened v0.2.0.
0.2.1 (2013-02-04)
------------------
* `extname`, `layoutsDir`, and `partialsDir` property values will now reference
the values on the prototype unless an `ExpressHandlebars` instance is
constructed with config values for these properties.
* Improved clarity of method implementations, and exposed more override "hooks"
via new private methods: `_getPartialName()`, `_renderTemplate()`, and
`_resolveLayoutPath()`.
0.2.0 (2013-02-01)
------------------
* __[!]__ Renamed methods prefixed with "get" to "load" for clarity:
* `getPartials()` -> `loadPartials()`
* `getTemplate()` -> `loadTemplate()`
Aliases for these methods have been created to maintain back-compat, but the
old method names are now deprecated will be removed in the future. ([#5][])
* All paths are resolved before checking in or adding to caches. ([#1][])
* Force `{precompiled: false}` option within `render()` and `renderView()`
methods to prevent trying to render with precompiled templates. ([#2][])
[#1]: https://github.com/ericf/express-handlebars/issues/1
[#2]: https://github.com/ericf/express-handlebars/issues/2
[#5]: https://github.com/ericf/express-handlebars/issues/5
0.1.2 (2013-01-10)
------------------
* Tweaked formatting of README documentation.
0.1.1 (2013-01-10)
------------------
* Added README documentation.
0.1.0 (2013-01-07)
------------------
* Initial release.

View File

@@ -1,31 +0,0 @@
Copyright (c) 2014, Yahoo Inc. All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of Yahoo Inc. nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of Yahoo Inc.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,611 +0,0 @@
Express Handlebars
==================
A [Handlebars][] view engine for [Express][] which doesn't suck.
[![npm version][npm-badge]][npm]
**This package used to be named `express3-handlebars`. The previous `express-handlebars` package by @jneen can be found [here][jneen-exphbs].**
[Express]: https://github.com/expressjs/express
[Handlebars]: https://github.com/handlebars-lang/handlebars.js
[npm]: https://www.npmjs.org/package/express-handlebars
[npm-badge]: https://img.shields.io/npm/v/express-handlebars.svg?style=flat-square
[jneen-exphbs]: https://github.com/jneen/express-handlebars
## Goals & Design
I 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.
### Goals and Features
After 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:
* Add back the concept of "layout", which was removed in Express 3.x.
* Add back the concept of "partials" via Handlebars' partials mechanism.
* Support a directory of partials; e.g., `{{> foo/bar}}` which exists on the file system at `views/partials/foo/bar.handlebars`, by default.
* 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.
* 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.
* Ability to easily precompile templates and partials for use on the client, enabling template sharing and reuse.
* Ability to use a different Handlebars module/implementation other than the Handlebars npm package.
### Package Design
This 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.
The 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.
This exported engine factory has two properties which expose the underlying implementation:
* `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.
* `create()`: A convenience factory function for creating `ExpressHandlebars` instances.
An instance-based approach is used so that multiple `ExpressHandlebars` instances can be created with their own configuration, templates, partials, and helpers.
## Installation
Install using npm:
```shell
$ npm install express-handlebars
```
## Danger 🔥
Never 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.
## Usage
This 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:
### Basic Usage
**Directory Structure:**
```
.
├── app.js
└── views
├── home.handlebars
└── layouts
└── main.handlebars
2 directories, 3 files
```
**app.js:**
Creates a super simple Express app which shows the basic way to register a Handlebars view engine using this package.
```javascript
import express from 'express';
import { engine } from 'express-handlebars';
const app = express();
app.engine('handlebars', engine());
app.set('view engine', 'handlebars');
app.set('views', './views');
app.get('/', (req, res) => {
res.render('home');
});
app.listen(3000);
```
**views/layouts/main.handlebars:**
The 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.
```handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Example App</title>
</head>
<body>
{{{body}}}
</body>
</html>
```
**views/home.handlebars:**
The content for the app's home view which will be rendered into the layout's `{{{body}}}`.
```handlebars
<h1>Example App: Home</h1>
```
#### Running the Example
The above example is bundled in this package's [examples directory][], where it can be run by:
```shell
$ cd examples/basic/
$ npm install
$ npm start
```
### Using Instances
Another way to use this view engine is to create an instance(s) of `ExpressHandlebars`, allowing access to the full API:
```javascript
import express from 'express';
import { create } from 'express-handlebars';
const app = express();
const hbs = create({ /* config */ });
// Register `hbs.engine` with the Express app.
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.set('views', './views');
// ...still have a reference to `hbs`, on which methods like `getPartials()`
// can be called.
```
**Note:** The [Advanced Usage][] example demonstrates how `ExpressHandlebars` instances can be leveraged.
### Template Caching
This 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.
The easiest way to control template/view caching is through Express' [view cache setting][]:
```javascript
app.enable('view cache');
```
Express enables this setting by default when in production mode, i.e.:
```
process.env.NODE_ENV === "production"
```
**Note:** All of the public API methods accept `options.cache`, which gives control over caching when calling these methods directly.
### Layouts
A layout is simply a Handlebars template with a `{{{body}}}` placeholder. Usually it will be an HTML page wrapper into which views will be rendered.
This 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/`
There are two ways to set a default layout: configuring the view engine's `defaultLayout` property, or setting [Express locals][] `app.locals.layout`.
The 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:
```javascript
app.get('/', (req, res, next) => {
res.render('home', {layout: false});
});
```
### Helpers
Helper 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.
Handlebars ships with some [built-in helpers][], such as: `with`, `if`, `each`, etc. Most applications 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.
The following example shows helpers being specified at each level:
**app.js:**
Creates a super simple Express app which shows the basic way to register `ExpressHandlebars` instance-level helpers, and override one at the render-level.
```javascript
import express from 'express';
import { create } from 'express-handlebars';
const app = express();
const hbs = create({
// Specify helpers which are only registered on this instance.
helpers: {
foo() { return 'FOO!'; },
bar() { return 'BAR!'; }
}
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.set('views', './views');
app.get('/', (req, res, next) => {
res.render('home', {
showTitle: true,
// Override `foo` helper only for this rendering.
helpers: {
foo() { return 'foo.'; }
}
});
});
app.listen(3000);
```
**views/home.handlebars:**
The app's home view which uses helper functions to help render the contents.
```handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Example App - Home</title>
</head>
<body>
<!-- Uses built-in `if` helper. -->
{{#if showTitle}}
<h1>Home</h1>
{{/if}}
<!-- Calls `foo` helper, overridden at render-level. -->
<p>{{foo}}</p>
<!-- Calls `bar` helper, defined at instance-level. -->
<p>{{bar}}</p>
</body>
</html>
```
#### More on Helpers
Refer to the [Handlebars website][] for more information on defining helpers:
* [Expression Helpers][]
* [Block Helpers][]
### Metadata
Handlebars 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}}`.
The following is the list of metadata that's accessible on the `{{@exphbs}}` data object:
* `cache`: Boolean whether or not the template is cached.
* `encoding`: String name of encoding for files.
* `view`: String name of the view passed to `res.render()`.
* `layout`: String name of the layout view.
* `data`: Original data object passed when rendering the template.
* `helpers`: Collection of helpers used when rendering the template.
* `partials`: Collection of partials used when rendering the template.
* `runtimeOptions`: Runtime Options used to render the template.
[examples directory]: https://github.com/express-handlebars/express-handlebars/tree/master/examples
[view cache setting]: https://expressjs.com/en/api.html#app.settings.table
[Express locals]: https://expressjs.com/en/api.html#app.locals
[registered with Handlebars]: https://github.com/wycats/handlebars.js/#registering-helpers
[built-in helpers]: https://handlebarsjs.com/guide/builtin-helpers.html
[Handlebars website]: https://handlebarsjs.com/
[Expression Helpers]: https://handlebarsjs.com/guide/#custom-helpers
[Block Helpers]: https://handlebarsjs.com/guide/#block-helpers
## API
### Configuration and Defaults
There 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.
```javascript
import { engine, create, ExpressHandlebars } from 'express-handlebars';
// Using the engine factory:
engine({ /* config */ });
// Create an instance:
create({ /* config */ });
// Using the class:
new ExpressHandlebars({ /* config */})
```
The following is the list of configuration properties and their default values (if any):
#### `handlebars=require('handlebars')`
The Handlebars module/implementation. This allows for the `ExpressHandlebars` instance to use a different Handlebars module/implementation than that provided by the Handlebars npm package.
#### `extname=".handlebars"`
The 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()`.
The following example sets up an Express app to use `.hbs` as the file extension for views:
```javascript
import express from 'express';
import { engine } from 'express-handlebars';
const app = express();
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set('views', './views');
```
**Note:** Setting the app's `"view engine"` setting will make that value the default file extension used for looking up views.
#### `encoding="utf8"`
Default encoding when reading files.
#### `layoutsDir`
Default layouts directory is relative to `express settings.view` + `layouts/`
The string path to the directory where the layout templates reside.
**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.
#### `partialsDir`
Default partials directory is relative to `express settings.view` + `partials/`
The string path to the directory where the partials templates reside or object with the following properties:
* `dir`: The string path to the directory where the partials templates reside.
* `namespace`: Optional string namespace to prefix the partial names.
* `templates`: Optional collection (or promise of a collection) of templates in the form: `{filename: template}`.
* `rename(filePath, namespace)`: Optional function to rename the partials. Takes two arguments: `filePath`, e.g., `partials/some/path/template.handlebars` and `namespace`.
**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.
**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.
#### `defaultLayout`
The 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}));`.
#### `helpers`
An 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.
#### `compilerOptions`
An object which holds options that will be passed along to the Handlebars compiler functions: `Handlebars.compile()` and `Handlebars.precompile()`.
#### `runtimeOptions`
An 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.
### Properties
The 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:
#### `engine`
A 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.
#### `extname`
The normalized `extname` which will _always_ start with `.` and defaults to `.handlebars`.
#### `compiled`
An object cache which holds compiled Handlebars template functions in the format: `{"path/to/template": [Function]}`.
#### `precompiled`
An object cache which holds precompiled Handlebars template strings in the format: `{"path/to/template": [String]}`.
### Methods
The following is the list of public API methods provided via `ExpressHandlebars` instances:
**Note:** All of the public methods return a [`Promise`][promise] (with the exception of `renderView()` which is the interface with Express.)
#### `getPartials([options])`
Retrieves the partials in the `partialsDir` and returns a Promise for an object mapping the partials in the form `{name: partial}`.
By 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.
**Parameters:**
* `[options]`: Optional object containing any of the following properties:
* `[cache]`: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.
* `[encoding]`: File encoding.
* `[precompiled=false]`: Whether precompiled templates should be provided, instead of compiled Handlebars template functions.
The name of each partial corresponds to its location in `partialsDir`. For example, consider the following directory structure:
```
views
└── partials
├── foo
│   └── bar.handlebars
└── title.handlebars
2 directories, 2 files
```
`getPartials()` would produce the following result:
```javascript
import { create } from 'express-handlebars';
const hbs = create();
hbs.getPartials().then(function (partials) {
console.log(partials);
// => { 'foo/bar': [Function],
// => title: [Function] }
});
```
#### `getTemplate(filePath, [options])`
Retrieves the template at the specified `filePath` and returns a Promise for the compiled Handlebars template function.
Use `options.precompiled` to receive a precompiled Handlebars template.
**Parameters:**
* `filePath`: String path to the Handlebars template file.
* `[options]`: Optional object containing any of the following properties:
* `[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.
* `[encoding]`: File encoding.
* `[precompiled=false]`: Whether a precompiled template should be provided, instead of a compiled Handlebars template function.
#### `getTemplates(dirPath, [options])`
Retrieves all the templates in the specified `dirPath` and returns a Promise for an object mapping the compiled templates in the form `{filename: template}`.
Use `options.precompiled` to receive precompiled Handlebars templates — this is useful for sharing templates with client code.
**Parameters:**
* `dirPath`: String path to the directory containing Handlebars template files.
* `[options]`: Optional object containing any of the following properties:
* `[cache]`: Whether cached templates can be used if it have already been requested. This is recommended for production to avoid necessary file I/O.
* `[encoding]`: File encoding.
* `[precompiled=false]`: Whether precompiled templates should be provided, instead of a compiled Handlebars template function.
#### `resetCache([filePathsOrFilter])`
Reset template cache. The cache can be partially reset by providing a filter argument. If no argument is given the whole cache will be reset.
**Parameters:**
* `[filePathsOrFilter]`: Optional filter to reset part of the cache. This can be a file path, an array of file paths, or a filter function based on file path.
#### `render(filePath, context, [options])`
Renders 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.
**Parameters:**
* `filePath`: String path to the Handlebars template file.
* `context`: Object in which the template will be executed. This contains all of the values to fill into the template.
* `[options]`: Optional object which can contain any of the following properties which affect this view engine's behavior:
* `[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.
* `[encoding]`: File encoding.
* `[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.
* `[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.
* `[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.
* `[runtimeOptions]`: Optional object which can contain options passed to the template function.
#### `renderView(viewPath, options|callback, [callback])`
Renders 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`.
This 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.
The `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.
**Parameters:**
* `viewPath`: String path to the Handlebars template file which should serve as the `{{{body}}}` when using a layout.
* `[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:
* `[cache]`: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.
* `[encoding]`: File encoding.
* `[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.
* `[helpers]`: Render-level helpers that will be merged with (and will override) instance and global helper functions.
* `[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.
* `[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).
* `[runtimeOptions]`: Optional object which can contain options passed to the template function.
* `callback`: Function to call once the template is retrieved.
### Hooks
The 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.
#### `_compileTemplate(template, options)`
This 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.
By 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.
**Parameters:**
* `template`: String Handlebars template that needs to be compiled.
* `options`: Object `compilerOptions` that were specified when the `ExpressHandlebars` instance as created. This object should be passed along to the `Handlebars.compile()` function.
#### `_precompileTemplate(template, options)`
This 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.
By 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.
**Parameters:**
* `template`: String Handlebars template that needs to be precompiled.
* `options`: Object `compilerOptions` that were specified when the `ExpressHandlebars` instance as created. This object should be passed along to the `Handlebars.compile()` function.
#### `_renderTemplate(template, context, options)`
This 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.
By 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.
**Parameters:**
* `template`: Compiled Handlebars template function to call.
* `context`: The context object in which to render the `template`.
* `options`: Object that contains options and metadata for rendering the template:
* `data`: Object to define custom `@variable` private variables.
* `helpers`: Object to provide custom helpers in addition to the globally defined helpers.
* `partials`: Object to provide custom partials in addition to the globally defined partials.
* `...runtimeOptions`: Other options specified by the `runtimeOptions` value.
[promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[Runtime Options]: https://handlebarsjs.com/api-reference/runtime-options.html
## Examples
### [Basic Usage][]
This example shows the most basic way to use this view engine.
### [Advanced Usage][]
This example is more comprehensive and shows how to use many of the features of this view engine, including helpers, partials, multiple layouts, etc.
As 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.
[Basic Usage]: https://github.com/express-handlebars/express-handlebars/tree/master/examples/basic
[Advanced Usage]: https://github.com/express-handlebars/express-handlebars/tree/master/examples/advanced
License
-------
This software is free to use under the Yahoo! Inc. BSD license. See the [LICENSE file][] for license text and copyright information.
[LICENSE file]: https://github.com/express-handlebars/express-handlebars/blob/master/LICENSE

View File

@@ -1,37 +0,0 @@
/// <reference types="node" />
/// <reference types="handlebars" />
import type { UnknownObject, HelperDelegateObject, ConfigOptions, Engine, TemplateSpecificationObject, TemplateDelegateObject, FsCache, PartialTemplateOptions, PartialsDirObject, RenderOptions, RenderViewOptions, RenderCallback, HandlebarsImport, CompiledCache, PrecompiledCache } from "../types";
export default class ExpressHandlebars {
config: ConfigOptions;
engine: Engine;
encoding: BufferEncoding;
layoutsDir: string;
extname: string;
compiled: CompiledCache;
precompiled: PrecompiledCache;
_fsCache: FsCache;
partialsDir: string | PartialsDirObject | (string | PartialsDirObject)[];
compilerOptions: CompileOptions;
runtimeOptions: RuntimeOptions;
helpers: HelperDelegateObject;
defaultLayout: string;
handlebars: HandlebarsImport;
constructor(config?: ConfigOptions);
getPartials(options?: PartialTemplateOptions): Promise<TemplateSpecificationObject | TemplateDelegateObject>;
getTemplate(filePath: string, options?: PartialTemplateOptions): Promise<HandlebarsTemplateDelegate | TemplateSpecification>;
getTemplates(dirPath: string, options?: PartialTemplateOptions): Promise<HandlebarsTemplateDelegate | TemplateSpecification>;
render(filePath: string, context?: UnknownObject, options?: RenderOptions): Promise<string>;
renderView(viewPath: string): Promise<string>;
renderView(viewPath: string, options: RenderViewOptions): Promise<string>;
renderView(viewPath: string, callback: RenderCallback): Promise<null>;
renderView(viewPath: string, options: RenderViewOptions, callback: RenderCallback): Promise<null>;
resetCache(filePathsOrFilter?: string | string[] | ((template: string) => boolean)): void;
protected _compileTemplate(template: string, options?: RuntimeOptions): HandlebarsTemplateDelegate;
protected _precompileTemplate(template: string, options?: RuntimeOptions): TemplateSpecification;
protected _renderTemplate(template: HandlebarsTemplateDelegate, context?: UnknownObject, options?: RuntimeOptions): string;
private _getDir;
private _getFile;
private _getTemplateName;
private _resolveViewsPath;
private _resolveLayoutPath;
}

View File

@@ -1,337 +0,0 @@
"use strict";
/*
* Copyright (c) 2015, Yahoo Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const Handlebars = require("handlebars");
const fs = require("graceful-fs");
const path = require("node:path");
const node_util_1 = require("node:util");
const glob_1 = require("glob");
const readFile = (0, node_util_1.promisify)(fs.readFile);
// -----------------------------------------------------------------------------
const defaultConfig = {
handlebars: Handlebars,
extname: ".handlebars",
encoding: "utf8",
layoutsDir: undefined,
partialsDir: undefined,
defaultLayout: "main",
helpers: undefined,
compilerOptions: undefined,
runtimeOptions: undefined,
};
class ExpressHandlebars {
constructor(config = {}) {
// Config properties with defaults.
Object.assign(this, defaultConfig, config);
// save given config to override other settings.
this.config = config;
// Express view engine integration point.
this.engine = this.renderView.bind(this);
// Normalize `extname`.
if (this.extname.charAt(0) !== ".") {
this.extname = "." + this.extname;
}
// Internal caches of compiled and precompiled templates.
this.compiled = {};
this.precompiled = {};
// Private internal file system cache.
this._fsCache = {};
}
getPartials(options = {}) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof this.partialsDir === "undefined") {
return {};
}
const partialsDirs = Array.isArray(this.partialsDir) ? this.partialsDir : [this.partialsDir];
const dirs = yield Promise.all(partialsDirs.map((dir) => __awaiter(this, void 0, void 0, function* () {
let dirPath;
let dirTemplates;
let dirNamespace;
let dirRename;
// Support `partialsDir` collection with object entries that contain a
// templates promise and a namespace.
if (typeof dir === "string") {
dirPath = dir;
}
else if (typeof dir === "object") {
dirTemplates = dir.templates;
dirNamespace = dir.namespace;
dirRename = dir.rename;
dirPath = dir.dir;
}
// We must have some path to templates, or templates themselves.
if (!dirPath && !dirTemplates) {
throw new Error("A partials dir must be a string or config object");
}
const templates = dirTemplates || (yield this.getTemplates(dirPath, options));
return {
templates: templates,
namespace: dirNamespace,
rename: dirRename,
};
})));
const partials = {};
for (const dir of dirs) {
const { templates, namespace, rename } = dir;
const filePaths = Object.keys(templates);
const getTemplateNameFn = typeof rename === "function"
? rename
: this._getTemplateName.bind(this);
for (const filePath of filePaths) {
const partialName = getTemplateNameFn(filePath, namespace);
partials[partialName] = templates[filePath];
}
}
return partials;
});
}
getTemplate(filePath, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
filePath = path.resolve(filePath);
const encoding = options.encoding || this.encoding;
const cache = options.precompiled ? this.precompiled : this.compiled;
const template = options.cache && cache[filePath];
if (template) {
return template;
}
// Optimistically cache template promise to reduce file system I/O, but
// remove from cache if there was a problem.
try {
cache[filePath] = this._getFile(filePath, { cache: options.cache, encoding })
.then((file) => {
const compileTemplate = (options.precompiled ? this._precompileTemplate : this._compileTemplate).bind(this);
return compileTemplate(file, this.compilerOptions);
});
return yield cache[filePath];
}
catch (err) {
delete cache[filePath];
throw err;
}
});
}
getTemplates(dirPath, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const cache = options.cache;
const filePaths = yield this._getDir(dirPath, { cache });
const templates = yield Promise.all(filePaths.map(filePath => {
return this.getTemplate(path.join(dirPath, filePath), options);
}));
const hash = {};
for (let i = 0; i < filePaths.length; i++) {
hash[filePaths[i]] = templates[i];
}
return hash;
});
}
render(filePath, context = {}, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const encoding = options.encoding || this.encoding;
const [template, partials] = yield Promise.all([
this.getTemplate(filePath, { cache: options.cache, encoding }),
(options.partials || this.getPartials({ cache: options.cache, encoding })),
]);
const helpers = Object.assign(Object.assign({}, this.helpers), options.helpers);
const runtimeOptions = Object.assign(Object.assign({}, this.runtimeOptions), options.runtimeOptions);
// Add ExpressHandlebars metadata to the data channel so that it's
// accessible within the templates and helpers, namespaced under:
// `@exphbs.*`
const data = Object.assign(Object.assign({}, options.data), { exphbs: Object.assign(Object.assign({}, options), { filePath,
helpers,
partials,
runtimeOptions }) });
const html = this._renderTemplate(template, context, Object.assign(Object.assign({}, runtimeOptions), { data,
helpers,
partials }));
return html;
});
}
renderView(viewPath, options = {}, callback = null) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof options === "function") {
callback = options;
options = {};
}
const context = options;
let promise = null;
if (!callback) {
promise = new Promise((resolve, reject) => {
callback = (err, value) => { err !== null ? reject(err) : resolve(value); };
});
}
// Express provides `settings.views` which is the path to the views dir that
// the developer set on the Express app. When this value exists, it's used
// to compute the view's name. Layouts and Partials directories are relative
// to `settings.view` path
let view;
const views = options.settings && options.settings.views;
const viewsPath = this._resolveViewsPath(views, viewPath);
if (viewsPath) {
view = this._getTemplateName(path.relative(viewsPath, viewPath));
this.partialsDir = this.config.partialsDir || path.join(viewsPath, "partials/");
this.layoutsDir = this.config.layoutsDir || path.join(viewsPath, "layouts/");
}
const encoding = options.encoding || this.encoding;
// Merge render-level and instance-level helpers together.
const helpers = Object.assign(Object.assign({}, this.helpers), options.helpers);
// Merge render-level and instance-level partials together.
const partials = Object.assign(Object.assign({}, yield this.getPartials({ cache: options.cache, encoding })), (options.partials || {}));
// Pluck-out ExpressHandlebars-specific options and Handlebars-specific
// rendering options.
const renderOptions = {
cache: options.cache,
encoding,
view,
layout: "layout" in options ? options.layout : this.defaultLayout,
data: options.data,
helpers,
partials,
runtimeOptions: options.runtimeOptions,
};
try {
let html = yield this.render(viewPath, context, renderOptions);
const layoutPath = this._resolveLayoutPath(renderOptions.layout);
if (layoutPath) {
html = yield this.render(layoutPath, Object.assign(Object.assign({}, context), { body: html }), Object.assign(Object.assign({}, renderOptions), { layout: undefined }));
}
callback(null, html);
}
catch (err) {
callback(err);
}
return promise;
});
}
resetCache(filePathsOrFilter) {
let filePaths = [];
if (typeof filePathsOrFilter === "undefined") {
filePaths = Object.keys(this._fsCache);
}
else if (typeof filePathsOrFilter === "string") {
filePaths = [filePathsOrFilter];
}
else if (typeof filePathsOrFilter === "function") {
filePaths = Object.keys(this._fsCache).filter(filePathsOrFilter);
}
else if (Array.isArray(filePathsOrFilter)) {
filePaths = filePathsOrFilter;
}
for (const filePath of filePaths) {
delete this._fsCache[filePath];
}
}
// -- Protected Hooks ----------------------------------------------------------
_compileTemplate(template, options = {}) {
return this.handlebars.compile(template.trim(), options);
}
_precompileTemplate(template, options = {}) {
return this.handlebars.precompile(template.trim(), options);
}
_renderTemplate(template, context = {}, options = {}) {
return template(context, options).trim();
}
// -- Private ------------------------------------------------------------------
_getDir(dirPath, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
dirPath = path.resolve(dirPath);
const cache = this._fsCache;
let dir = options.cache && cache[dirPath];
if (dir) {
return [...yield dir];
}
const pattern = "**/*" + this.extname;
// Optimistically cache dir promise to reduce file system I/O, but remove
// from cache if there was a problem.
try {
dir = cache[dirPath] = (0, glob_1.glob)(pattern, {
cwd: dirPath,
follow: true,
posix: true,
});
// @ts-expect-error FIXME: not sure how to throw error in glob for test coverage
if (options._throwTestError) {
throw new Error("test");
}
return [...yield dir];
}
catch (err) {
delete cache[dirPath];
throw err;
}
});
}
_getFile(filePath, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
filePath = path.resolve(filePath);
const cache = this._fsCache;
const encoding = options.encoding || this.encoding;
const file = options.cache && cache[filePath];
if (file) {
return file;
}
// Optimistically cache file promise to reduce file system I/O, but remove
// from cache if there was a problem.
try {
cache[filePath] = readFile(filePath, { encoding: encoding || "utf8" });
return yield cache[filePath];
}
catch (err) {
delete cache[filePath];
throw err;
}
});
}
_getTemplateName(filePath, namespace = null) {
let name = filePath;
if (name.endsWith(this.extname)) {
name = name.substring(0, name.length - this.extname.length);
}
if (namespace) {
name = namespace + "/" + name;
}
return name;
}
_resolveViewsPath(views, file) {
if (!Array.isArray(views)) {
return views;
}
let lastDir = path.resolve(file);
let dir = path.dirname(lastDir);
const absoluteViews = views.map(v => path.resolve(v));
// find the closest parent
while (dir !== lastDir) {
const index = absoluteViews.indexOf(dir);
if (index >= 0) {
return views[index];
}
lastDir = dir;
dir = path.dirname(lastDir);
}
// cannot resolve view
return null;
}
_resolveLayoutPath(layoutPath) {
if (!layoutPath) {
return null;
}
if (!path.extname(layoutPath)) {
layoutPath += this.extname;
}
return path.resolve(this.layoutsDir || "", layoutPath);
}
}
exports.default = ExpressHandlebars;
//# sourceMappingURL=express-handlebars.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
import ExpressHandlebars from "./express-handlebars";
import type { ConfigOptions, Engine } from "../types";
export { ExpressHandlebars };
export declare function create(config?: ConfigOptions): ExpressHandlebars;
export declare function engine(config?: ConfigOptions): Engine;

View File

@@ -1,19 +0,0 @@
"use strict";
/*
* Copyright (c) 2014, Yahoo Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.engine = exports.create = exports.ExpressHandlebars = void 0;
const express_handlebars_1 = require("./express-handlebars");
exports.ExpressHandlebars = express_handlebars_1.default;
function create(config = {}) {
return new express_handlebars_1.default(config);
}
exports.create = create;
function engine(config = {}) {
return create(config).engine;
}
exports.engine = engine;
//# sourceMappingURL=index.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,6DAAqD;AAM5C,4BANF,4BAAiB,CAME;AAE1B,SAAgB,MAAM,CAAE,SAAwB,EAAE;IACjD,OAAO,IAAI,4BAAiB,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAFD,wBAEC;AAED,SAAgB,MAAM,CAAE,SAAwB,EAAE;IACjD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;AAC9B,CAAC;AAFD,wBAEC"}

View File

@@ -1,27 +0,0 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
restoreMocks: true,
clearMocks: true,
collectCoverageFrom: [
"lib/**/*.ts",
],
coverageDirectory: "coverage",
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
testRegex: /\.test\.tsx?/.source,
transform: {
[/\.test\.tsx?/.source]: [
"ts-jest", {
diagnostics: false,
},
],
},
moduleFileExtensions: ["js", "json", "jsx", "d.ts", "ts", "tsx", "node"],
};

View File

@@ -1,428 +0,0 @@
/*
* Copyright (c) 2015, Yahoo Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
import * as Handlebars from "handlebars";
import * as fs from "graceful-fs";
import * as path from "node:path";
import { promisify } from "node:util";
import { glob } from "glob";
import type {
UnknownObject,
HelperDelegateObject,
ConfigOptions,
Engine,
TemplateSpecificationObject,
TemplateDelegateObject,
FsCache,
PartialTemplateOptions,
PartialsDirObject,
RenderOptions,
RenderViewOptions,
RenderCallback,
HandlebarsImport,
CompiledCache,
PrecompiledCache,
RenameFunction,
} from "../types";
const readFile = promisify(fs.readFile);
// -----------------------------------------------------------------------------
const defaultConfig: ConfigOptions = {
handlebars: Handlebars,
extname: ".handlebars",
encoding: "utf8",
layoutsDir: undefined, // Default layouts directory is relative to `express settings.view` + `layouts/`
partialsDir: undefined, // Default partials directory is relative to `express settings.view` + `partials/`
defaultLayout: "main",
helpers: undefined,
compilerOptions: undefined,
runtimeOptions: undefined,
};
export default class ExpressHandlebars {
config: ConfigOptions;
engine: Engine;
encoding: BufferEncoding;
layoutsDir: string;
extname: string;
compiled: CompiledCache;
precompiled: PrecompiledCache;
_fsCache: FsCache;
partialsDir: string|PartialsDirObject|(string|PartialsDirObject)[];
compilerOptions: CompileOptions;
runtimeOptions: RuntimeOptions;
helpers: HelperDelegateObject;
defaultLayout: string;
handlebars: HandlebarsImport;
constructor (config: ConfigOptions = {}) {
// Config properties with defaults.
Object.assign(this, defaultConfig, config);
// save given config to override other settings.
this.config = config;
// Express view engine integration point.
this.engine = this.renderView.bind(this);
// Normalize `extname`.
if (this.extname.charAt(0) !== ".") {
this.extname = "." + this.extname;
}
// Internal caches of compiled and precompiled templates.
this.compiled = {};
this.precompiled = {};
// Private internal file system cache.
this._fsCache = {};
}
async getPartials (options: PartialTemplateOptions = {}): Promise<TemplateSpecificationObject|TemplateDelegateObject> {
if (typeof this.partialsDir === "undefined") {
return {};
}
const partialsDirs = Array.isArray(this.partialsDir) ? this.partialsDir : [this.partialsDir];
const dirs = await Promise.all(partialsDirs.map(async dir => {
let dirPath: string;
let dirTemplates: TemplateDelegateObject;
let dirNamespace: string;
let dirRename: RenameFunction;
// Support `partialsDir` collection with object entries that contain a
// templates promise and a namespace.
if (typeof dir === "string") {
dirPath = dir;
} else if (typeof dir === "object") {
dirTemplates = dir.templates;
dirNamespace = dir.namespace;
dirRename = dir.rename;
dirPath = dir.dir;
}
// We must have some path to templates, or templates themselves.
if (!dirPath && !dirTemplates) {
throw new Error("A partials dir must be a string or config object");
}
const templates: HandlebarsTemplateDelegate|TemplateSpecification = dirTemplates || await this.getTemplates(dirPath, options);
return {
templates: templates as HandlebarsTemplateDelegate|TemplateSpecification,
namespace: dirNamespace,
rename: dirRename,
};
}));
const partials: TemplateDelegateObject|TemplateSpecificationObject = {};
for (const dir of dirs) {
const { templates, namespace, rename } = dir;
const filePaths = Object.keys(templates);
const getTemplateNameFn = typeof rename === "function"
? rename
: this._getTemplateName.bind(this);
for (const filePath of filePaths) {
const partialName = getTemplateNameFn(filePath, namespace);
partials[partialName] = templates[filePath];
}
}
return partials;
}
async getTemplate (filePath: string, options: PartialTemplateOptions = {}): Promise<HandlebarsTemplateDelegate|TemplateSpecification> {
filePath = path.resolve(filePath);
const encoding = options.encoding || this.encoding;
const cache: PrecompiledCache|CompiledCache = options.precompiled ? this.precompiled : this.compiled;
const template: Promise<HandlebarsTemplateDelegate|TemplateSpecification> = options.cache && cache[filePath];
if (template) {
return template;
}
// Optimistically cache template promise to reduce file system I/O, but
// remove from cache if there was a problem.
try {
cache[filePath] = this._getFile(filePath, { cache: options.cache, encoding })
.then((file: string) => {
const compileTemplate: (file: string, options: RuntimeOptions) => TemplateSpecification|HandlebarsTemplateDelegate = (options.precompiled ? this._precompileTemplate : this._compileTemplate).bind(this);
return compileTemplate(file, this.compilerOptions);
});
return await cache[filePath];
} catch (err) {
delete cache[filePath];
throw err;
}
}
async getTemplates (dirPath: string, options: PartialTemplateOptions = {}): Promise<HandlebarsTemplateDelegate|TemplateSpecification> {
const cache = options.cache;
const filePaths = await this._getDir(dirPath, { cache });
const templates = await Promise.all(filePaths.map(filePath => {
return this.getTemplate(path.join(dirPath, filePath), options);
}));
const hash = {};
for (let i = 0; i < filePaths.length; i++) {
hash[filePaths[i]] = templates[i];
}
return hash;
}
async render (filePath: string, context: UnknownObject = {}, options: RenderOptions = {}): Promise<string> {
const encoding = options.encoding || this.encoding;
const [template, partials] = await Promise.all([
this.getTemplate(filePath, { cache: options.cache, encoding }) as Promise<HandlebarsTemplateDelegate>,
(options.partials || this.getPartials({ cache: options.cache, encoding })) as Promise<TemplateDelegateObject>,
]);
const helpers: HelperDelegateObject = { ...this.helpers, ...options.helpers };
const runtimeOptions = { ...this.runtimeOptions, ...options.runtimeOptions };
// Add ExpressHandlebars metadata to the data channel so that it's
// accessible within the templates and helpers, namespaced under:
// `@exphbs.*`
const data = {
...options.data,
exphbs: {
...options,
filePath,
helpers,
partials,
runtimeOptions,
},
};
const html = this._renderTemplate(template, context, {
...runtimeOptions,
data,
helpers,
partials,
});
return html;
}
async renderView (viewPath: string): Promise<string>;
async renderView (viewPath: string, options: RenderViewOptions): Promise<string>;
async renderView (viewPath: string, callback: RenderCallback): Promise<null>;
async renderView (viewPath: string, options: RenderViewOptions, callback: RenderCallback): Promise<null>;
async renderView (viewPath: string, options: RenderViewOptions|RenderCallback = {}, callback: RenderCallback|null = null): Promise<string|null> {
if (typeof options === "function") {
callback = options;
options = {};
}
const context = options as UnknownObject;
let promise: Promise<string>|null = null;
if (!callback) {
promise = new Promise((resolve, reject) => {
callback = (err, value) => { err !== null ? reject(err) : resolve(value); };
});
}
// Express provides `settings.views` which is the path to the views dir that
// the developer set on the Express app. When this value exists, it's used
// to compute the view's name. Layouts and Partials directories are relative
// to `settings.view` path
let view: string;
const views = options.settings && options.settings.views;
const viewsPath = this._resolveViewsPath(views, viewPath);
if (viewsPath) {
view = this._getTemplateName(path.relative(viewsPath, viewPath));
this.partialsDir = this.config.partialsDir || path.join(viewsPath, "partials/");
this.layoutsDir = this.config.layoutsDir || path.join(viewsPath, "layouts/");
}
const encoding = options.encoding || this.encoding;
// Merge render-level and instance-level helpers together.
const helpers = { ...this.helpers, ...options.helpers };
// Merge render-level and instance-level partials together.
const partials: TemplateDelegateObject = {
...await this.getPartials({ cache: options.cache, encoding }) as TemplateDelegateObject,
...(options.partials || {}),
};
// Pluck-out ExpressHandlebars-specific options and Handlebars-specific
// rendering options.
const renderOptions = {
cache: options.cache,
encoding,
view,
layout: "layout" in options ? options.layout : this.defaultLayout,
data: options.data,
helpers,
partials,
runtimeOptions: options.runtimeOptions,
};
try {
let html = await this.render(viewPath, context, renderOptions);
const layoutPath = this._resolveLayoutPath(renderOptions.layout);
if (layoutPath) {
html = await this.render(
layoutPath,
{ ...context, body: html },
{ ...renderOptions, layout: undefined },
);
}
callback(null, html);
} catch (err) {
callback(err);
}
return promise;
}
resetCache (filePathsOrFilter?: string | string[] | ((template: string) => boolean)) {
let filePaths: string[] = [];
if (typeof filePathsOrFilter === "undefined") {
filePaths = Object.keys(this._fsCache);
} else if (typeof filePathsOrFilter === "string") {
filePaths = [filePathsOrFilter];
} else if (typeof filePathsOrFilter === "function") {
filePaths = Object.keys(this._fsCache).filter(filePathsOrFilter);
} else if (Array.isArray(filePathsOrFilter)) {
filePaths = filePathsOrFilter;
}
for (const filePath of filePaths) {
delete this._fsCache[filePath];
}
}
// -- Protected Hooks ----------------------------------------------------------
protected _compileTemplate (template: string, options: RuntimeOptions = {}): HandlebarsTemplateDelegate {
return this.handlebars.compile(template.trim(), options);
}
protected _precompileTemplate (template: string, options: RuntimeOptions = {}): TemplateSpecification {
return this.handlebars.precompile(template.trim(), options);
}
protected _renderTemplate (template: HandlebarsTemplateDelegate, context: UnknownObject = {}, options: RuntimeOptions = {}): string {
return template(context, options).trim();
}
// -- Private ------------------------------------------------------------------
private async _getDir (dirPath: string, options: PartialTemplateOptions = {}): Promise<string[]> {
dirPath = path.resolve(dirPath);
const cache = this._fsCache;
let dir = options.cache && (cache[dirPath] as Promise<string[]>);
if (dir) {
return [...await dir];
}
const pattern = "**/*" + this.extname;
// Optimistically cache dir promise to reduce file system I/O, but remove
// from cache if there was a problem.
try {
dir = cache[dirPath] = glob(pattern, {
cwd: dirPath,
follow: true,
posix: true,
});
// @ts-expect-error FIXME: not sure how to throw error in glob for test coverage
if (options._throwTestError) {
throw new Error("test");
}
return [...await dir];
} catch (err) {
delete cache[dirPath];
throw err;
}
}
private async _getFile (filePath: string, options: PartialTemplateOptions = {}): Promise<string> {
filePath = path.resolve(filePath);
const cache = this._fsCache;
const encoding = options.encoding || this.encoding;
const file = options.cache && (cache[filePath] as Promise<string>);
if (file) {
return file;
}
// Optimistically cache file promise to reduce file system I/O, but remove
// from cache if there was a problem.
try {
cache[filePath] = readFile(filePath, { encoding: encoding || "utf8" });
return await cache[filePath] as string;
} catch (err) {
delete cache[filePath];
throw err;
}
}
private _getTemplateName (filePath: string, namespace: string = null): string {
let name = filePath;
if (name.endsWith(this.extname)) {
name = name.substring(0, name.length - this.extname.length);
}
if (namespace) {
name = namespace + "/" + name;
}
return name;
}
private _resolveViewsPath (views: string|string[], file: string): string|null {
if (!Array.isArray(views)) {
return views;
}
let lastDir = path.resolve(file);
let dir = path.dirname(lastDir);
const absoluteViews = views.map(v => path.resolve(v));
// find the closest parent
while (dir !== lastDir) {
const index = absoluteViews.indexOf(dir);
if (index >= 0) {
return views[index];
}
lastDir = dir;
dir = path.dirname(lastDir);
}
// cannot resolve view
return null;
}
private _resolveLayoutPath (layoutPath: string): string|null {
if (!layoutPath) {
return null;
}
if (!path.extname(layoutPath)) {
layoutPath += this.extname;
}
return path.resolve(this.layoutsDir || "", layoutPath);
}
}

View File

@@ -1,21 +0,0 @@
/*
* Copyright (c) 2014, Yahoo Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
import ExpressHandlebars from "./express-handlebars";
import type {
ConfigOptions,
Engine,
} from "../types";
export { ExpressHandlebars };
export function create (config: ConfigOptions = {}): ExpressHandlebars {
return new ExpressHandlebars(config);
}
export function engine (config: ConfigOptions = {}): Engine {
return create(config).engine;
}

View File

@@ -1,104 +0,0 @@
{
"_from": "express-handlebars@^7.1.2",
"_id": "express-handlebars@7.1.2",
"_inBundle": false,
"_integrity": "sha512-ss9d3mBChOLTEtyfzXCsxlItUxpgS3i4cb/F70G6Q5ohQzmD12XB4x/Y9U6YboeeYBJZt7WQ5yUNu7ZSQ/EGyQ==",
"_location": "/express-handlebars",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "express-handlebars@^7.1.2",
"name": "express-handlebars",
"escapedName": "express-handlebars",
"rawSpec": "^7.1.2",
"saveSpec": null,
"fetchSpec": "^7.1.2"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-7.1.2.tgz",
"_shasum": "2471673d11af46f496cba4098a705f0217232fda",
"_spec": "express-handlebars@^7.1.2",
"_where": "/mnt/c/Users/docto/Downloads/Rappaurio",
"author": {
"name": "Eric Ferraiuolo",
"email": "eferraiuolo@gmail.com",
"url": "http://ericf.me/"
},
"bugs": {
"url": "https://github.com/express-handlebars/express-handlebars/issues"
},
"bundleDependencies": false,
"dependencies": {
"glob": "^10.3.3",
"graceful-fs": "^4.2.11",
"handlebars": "^4.7.8"
},
"deprecated": false,
"description": "A Handlebars view engine for Express which doesn't suck.",
"devDependencies": {
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^10.0.1",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^9.0.4",
"@semantic-release/npm": "^10.0.4",
"@semantic-release/release-notes-generator": "^11.0.4",
"@types/glob": "^8.1.0",
"@types/jest": "^29.5.3",
"@types/node": "^18.17.3",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"eslint": "^8.46.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-n": "^16.0.1",
"eslint-plugin-promise": "^6.1.1",
"jest-cli": "^29.6.2",
"semantic-release": "^21.0.7",
"ts-jest": "^29.1.1",
"typescript": "^5.1.6"
},
"directories": {
"example": "examples"
},
"engines": {
"node": ">=v16"
},
"homepage": "https://github.com/express-handlebars/express-handlebars",
"keywords": [
"express",
"express3",
"handlebars",
"view",
"layout",
"partials",
"templates"
],
"license": "BSD-3-Clause",
"main": "dist/index.js",
"name": "express-handlebars",
"release": {
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/github",
"@semantic-release/git"
]
},
"repository": {
"type": "git",
"url": "git://github.com/express-handlebars/express-handlebars.git"
},
"scripts": {
"build": "tsc",
"lint": "eslint .",
"test": "jest --verbose",
"test:cover": "jest --coverage"
},
"version": "7.1.2"
}

View File

@@ -1,10 +0,0 @@
{
"extends": [
"config:base"
],
"devDependencies": {
"automerge": true,
"commitMessageTopic": "devDependency {{depName}}"
},
"rangeStrategy": "bump"
}

View File

@@ -1,880 +0,0 @@
import * as path from "node:path";
import * as expressHandlebars from "../lib/index";
import type {
TemplateDelegateObject,
EngineOptions,
} from "../types";
function fixturePath (filePath = "") {
return path.resolve(__dirname, "./fixtures", filePath);
}
// allow access to private functions for testing
// https://github.com/microsoft/TypeScript/issues/19335
/* eslint-disable dot-notation, @typescript-eslint/no-empty-function */
describe("express-handlebars", () => {
test("ExpressHandlebars instance", () => {
const exphbs = new expressHandlebars.ExpressHandlebars();
expect(exphbs).toBeDefined();
});
test("should nomalize extname", () => {
const exphbs1 = expressHandlebars.create({ extname: "ext" });
const exphbs2 = expressHandlebars.create({ extname: ".ext" });
expect(exphbs1.extname).toBe(".ext");
expect(exphbs2.extname).toBe(".ext");
});
describe("getPartials", () => {
test("should throw if partialsDir is not correct type", async () => {
// @ts-expect-error partialsDir is invalid
const exphbs = expressHandlebars.create({ partialsDir: 1 });
let error: Error | undefined;
try {
await exphbs.getPartials();
} catch (e) {
error = e;
}
expect(error).toEqual(expect.any(Error));
expect(error?.message).toBe("A partials dir must be a string or config object");
});
test("should return empty object if no partialsDir is defined", async () => {
const exphbs = expressHandlebars.create();
const partials = await exphbs.getPartials();
expect(partials).toEqual({});
});
test("should return empty object partialsDir does not exist", async () => {
const exphbs = expressHandlebars.create({ partialsDir: "does-not-exist" });
const partials = await exphbs.getPartials();
expect(partials).toEqual({});
});
test("should return partials on string", async () => {
const exphbs = expressHandlebars.create({ partialsDir: fixturePath("partials") });
const partials = await exphbs.getPartials();
expect(partials).toEqual({
partial: expect.any(Function),
"partial-latin1": expect.any(Function),
"subdir/partial-subdir": expect.any(Function),
});
});
test("should return partials on array", async () => {
const exphbs = expressHandlebars.create({ partialsDir: [fixturePath("partials")] });
const partials = await exphbs.getPartials();
expect(partials).toEqual({
partial: expect.any(Function),
"partial-latin1": expect.any(Function),
"subdir/partial-subdir": expect.any(Function),
});
});
test("should return partials on object", async () => {
const fn = jest.fn();
const exphbs = expressHandlebars.create({
partialsDir: {
templates: { "partial template": fn },
namespace: "partial namespace",
dir: fixturePath("partials"),
},
});
const partials = await exphbs.getPartials();
expect(partials).toEqual({
"partial namespace/partial template": fn,
});
});
test("should return renamed partials with rename function", async () => {
const fn = jest.fn();
const exphbs = expressHandlebars.create({
partialsDir: {
templates: { "partial/template": fn },
namespace: "partial namespace",
dir: fixturePath("partials"),
rename: (filePath, namespace) => {
return `${namespace}/${filePath.split("/")[0]}`;
},
},
});
const partials = await exphbs.getPartials();
expect(partials).toEqual({
"partial namespace/partial": fn,
});
});
test("should return partials on path relative to cwd", async () => {
const exphbs = expressHandlebars.create({ partialsDir: "spec/fixtures/partials" });
const partials = await exphbs.getPartials();
expect(partials).toEqual({
partial: expect.any(Function),
"partial-latin1": expect.any(Function),
"subdir/partial-subdir": expect.any(Function),
});
});
test("should return template function", async () => {
const exphbs = expressHandlebars.create({ partialsDir: "spec/fixtures/partials" });
const partials = await exphbs.getPartials() as TemplateDelegateObject;
const html = partials.partial({ text: "test text" });
expect(html).toBe("partial test text");
});
test("should return a template with encoding", async () => {
const exphbs = expressHandlebars.create({ partialsDir: "spec/fixtures/partials" });
const partials = await exphbs.getPartials({ encoding: "latin1" }) as TemplateDelegateObject;
const html = partials["partial-latin1"]({});
expect(html).toContain("ñáéíóú");
});
test("should return a template with default encoding", async () => {
const exphbs = expressHandlebars.create({
encoding: "latin1",
partialsDir: "spec/fixtures/partials",
});
const partials = await exphbs.getPartials() as TemplateDelegateObject;
const html = partials["partial-latin1"]({});
expect(html).toContain("ñáéíóú");
});
});
describe("getTemplate", () => {
test("should return cached template", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("templates/template.handlebars");
const compiledCachedFunction = (() => "compiled") as Handlebars.TemplateDelegate;
exphbs.compiled[filePath] = Promise.resolve(compiledCachedFunction);
const precompiledCachedFunction = (() => "precompiled") as TemplateSpecification;
exphbs.precompiled[filePath] = Promise.resolve(precompiledCachedFunction);
const template = await exphbs.getTemplate(filePath, { cache: true });
expect(template).toBe(compiledCachedFunction);
});
test("should return precompiled cached template", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("templates/template.handlebars");
const compiledCachedFunction = (() => "compiled") as Handlebars.TemplateDelegate;
exphbs.compiled[filePath] = Promise.resolve(compiledCachedFunction);
const precompiledCachedFunction = (() => "precompiled") as TemplateSpecification;
exphbs.precompiled[filePath] = Promise.resolve(precompiledCachedFunction);
const template = await exphbs.getTemplate(filePath, { precompiled: true, cache: true });
expect(template).toBe(precompiledCachedFunction);
});
test("should store in precompiled cache", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("templates/template.handlebars");
expect(exphbs.compiled[filePath]).toBeUndefined();
expect(exphbs.precompiled[filePath]).toBeUndefined();
await exphbs.getTemplate(filePath, { precompiled: true });
expect(exphbs.compiled[filePath]).toBeUndefined();
expect(exphbs.precompiled[filePath]).toBeDefined();
});
test("should store in compiled cache", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("templates/template.handlebars");
expect(exphbs.compiled[filePath]).toBeUndefined();
expect(exphbs.precompiled[filePath]).toBeUndefined();
await exphbs.getTemplate(filePath);
expect(exphbs.compiled[filePath]).toBeDefined();
expect(exphbs.precompiled[filePath]).toBeUndefined();
});
test("should return a template", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("templates/template.handlebars");
const template = await exphbs.getTemplate(filePath) as HandlebarsTemplateDelegate;
const html = template({ text: "test text" });
expect(html).toBe("<p>test text</p>");
});
test("should return a template with encoding", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("templates/template-latin1.handlebars");
const template = await exphbs.getTemplate(filePath, { encoding: "latin1" }) as HandlebarsTemplateDelegate;
const html = template({});
expect(html).toContain("ñáéíóú");
});
test("should return a template with default encoding", async () => {
const exphbs = expressHandlebars.create({ encoding: "latin1" });
const filePath = fixturePath("templates/template-latin1.handlebars");
const template = await exphbs.getTemplate(filePath) as HandlebarsTemplateDelegate;
const html = template({});
expect(html).toContain("ñáéíóú");
});
test("should not store in cache on error", async () => {
const exphbs = expressHandlebars.create();
const filePath = "does-not-exist";
expect(exphbs.compiled[filePath]).toBeUndefined();
let error: Error | undefined;
try {
await exphbs.getTemplate(filePath);
} catch (e) {
error = e;
}
expect(error?.message).toEqual(expect.stringContaining("no such file or directory"));
expect(exphbs.compiled[filePath]).toBeUndefined();
});
});
describe("getTemplates", () => {
test("should return cached templates", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const fsCache = Promise.resolve([]);
exphbs._fsCache[dirPath] = fsCache;
const templates = await exphbs.getTemplates(dirPath, { cache: true });
expect(templates).toEqual({});
});
test("should return templates", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const templates = await exphbs.getTemplates(dirPath);
const html = templates["template.handlebars"]({ text: "test text" });
expect(html).toBe("<p>test text</p>");
});
test("should get templates in sub directories", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const templates = await exphbs.getTemplates(dirPath);
const paths = Object.keys(templates);
expect(paths).toEqual([
"template.handlebars",
"template-latin1.handlebars",
"subdir/template.handlebars",
]);
});
});
describe("render", () => {
test("should return cached templates", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("render-cached.handlebars");
exphbs.compiled[filePath] = Promise.resolve(() => "cached");
const html = await exphbs.render(filePath, undefined, { cache: true });
expect(html).toBe("cached");
});
test("should use helpers", async () => {
const exphbs = expressHandlebars.create({
helpers: {
help: () => "help",
},
});
const filePath = fixturePath("render-helper.handlebars");
const html = await exphbs.render(filePath, { text: "test text" });
expect(html).toBe("<p>help</p>");
});
test("should override helpers", async () => {
const exphbs = expressHandlebars.create({
helpers: {
help: () => "help",
},
});
const filePath = fixturePath("render-helper.handlebars");
const html = await exphbs.render(filePath, { text: "test text" }, {
helpers: {
help: (text: string) => text,
},
});
expect(html).toBe("<p>test text</p>");
});
test("should return html", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("render-text.handlebars");
const html = await exphbs.render(filePath, { text: "test text" });
expect(html).toBe("<p>test text</p>");
});
test("should return html with encoding", async () => {
const exphbs = expressHandlebars.create({
partialsDir: fixturePath("partials"),
});
const filePath = fixturePath("render-latin1.handlebars");
const html = await exphbs.render(filePath, undefined, { encoding: "latin1" });
expect(html).toContain("partial ñáéíóú");
expect(html).toContain("render ñáéíóú");
});
test("should return html with default encoding", async () => {
const exphbs = expressHandlebars.create({
encoding: "latin1",
partialsDir: fixturePath("partials"),
});
const filePath = fixturePath("render-latin1.handlebars");
const html = await exphbs.render(filePath);
expect(html).toContain("partial ñáéíóú");
expect(html).toContain("render ñáéíóú");
});
test("should render with partial", async () => {
const exphbs = expressHandlebars.create({
partialsDir: fixturePath("partials"),
});
const filePath = fixturePath("render-partial.handlebars");
const html = await exphbs.render(filePath, { text: "test text" });
expect(html.replace(/\r/g, "")).toBe("<h1>partial test text</h1>\n<p>test text</p>");
});
test("should render with subdir/partial", async () => {
const exphbs = expressHandlebars.create({
partialsDir: fixturePath("partials"),
});
const filePath = fixturePath("render-subdir-partial.handlebars");
const html = await exphbs.render(filePath, { text: "test text" });
expect(html.replace(/\r/g, "")).toBe("<h1>subdir partial test text</h1>\n<p>test text</p>");
});
test("should render with runtimeOptions", async () => {
const exphbs = expressHandlebars.create({
runtimeOptions: { allowProtoPropertiesByDefault: true },
});
const filePath = fixturePath("test");
const spy = jest.fn(() => { return "test"; }) as Handlebars.TemplateDelegate;
exphbs.compiled[filePath] = Promise.resolve(spy);
await exphbs.render(filePath, undefined, { cache: true });
expect(spy).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({ allowProtoPropertiesByDefault: true }));
});
test("should override runtimeOptions", async () => {
const exphbs = expressHandlebars.create({
runtimeOptions: { allowProtoPropertiesByDefault: true },
});
const filePath = fixturePath("test");
const spy = jest.fn(() => { return "test"; }) as Handlebars.TemplateDelegate;
exphbs.compiled[filePath] = Promise.resolve(spy);
await exphbs.render(filePath, undefined, {
cache: true,
runtimeOptions: { allowProtoPropertiesByDefault: false },
});
expect(spy).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({ allowProtoPropertiesByDefault: false }));
});
});
describe("engine", () => {
test("should call renderView", async () => {
jest.spyOn(expressHandlebars.ExpressHandlebars.prototype, "renderView").mockImplementation(() => Promise.resolve(null));
const exphbs = expressHandlebars.create();
const cb = () => { /* empty */ };
exphbs.engine("view", {}, cb);
expect(expressHandlebars.ExpressHandlebars.prototype.renderView).toHaveBeenCalledWith("view", {}, cb);
});
test("should call engine", async () => {
jest.spyOn(expressHandlebars.ExpressHandlebars.prototype, "renderView").mockImplementation(() => Promise.resolve(null));
const cb = () => { /* empty */ };
expressHandlebars.engine()("view", {}, cb);
expect(expressHandlebars.ExpressHandlebars.prototype.renderView).toHaveBeenCalledWith("view", {}, cb);
});
test("should render html", async () => {
const renderView = expressHandlebars.engine({ defaultLayout: undefined });
const viewPath = fixturePath("render-text.handlebars");
const html = await renderView(viewPath, { text: "test text" } as EngineOptions);
expect(html).toBe("<p>test text</p>");
});
});
describe("renderView", () => {
test("should use settings.views", async () => {
const exphbs = expressHandlebars.create();
const viewPath = fixturePath("render-partial.handlebars");
const viewsPath = fixturePath();
const html = await exphbs.renderView(viewPath, {
text: "test text",
settings: { views: viewsPath },
});
expect(html.replace(/\r/g, "")).toBe("<body>\n<h1>partial test text</h1>\n<p>test text</p>\n</body>");
});
test("should use settings.views array", async () => {
const exphbs = expressHandlebars.create();
const viewPath = fixturePath("render-partial.handlebars");
const viewsPath = fixturePath();
const html = await exphbs.renderView(viewPath, {
text: "test text",
settings: { views: [viewsPath] },
});
expect(html.replace(/\r/g, "")).toBe("<body>\n<h1>partial test text</h1>\n<p>test text</p>\n</body>");
});
test("should not use settings.views array when no parent found", async () => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const viewPath = fixturePath("render-text.handlebars");
const viewsPath = "does-not-exist";
const html = await exphbs.renderView(viewPath, {
text: "test text",
settings: { views: [viewsPath] },
});
expect(html).toBe("<p>test text</p>");
});
test("should use settings.views when it changes", async () => {
const exphbs = expressHandlebars.create();
const viewPath = fixturePath("render-partial.handlebars");
const viewsPath = fixturePath();
const html = await exphbs.renderView(viewPath, {
text: "test text",
settings: { views: viewsPath },
});
expect(html.replace(/\r/g, "")).toBe("<body>\n<h1>partial test text</h1>\n<p>test text</p>\n</body>");
const otherViewsPath = fixturePath("other-views");
const otherhtml = await exphbs.renderView(viewPath, {
text: "test text",
settings: { views: otherViewsPath },
});
expect(otherhtml.replace(/\r/g, "")).toBe("<body>\nother layout\n<h1>other partial test text</h1>\n<p>test text</p>\n</body>");
});
test("should not overwrite config with settings.views", async () => {
const exphbs = expressHandlebars.create({
layoutsDir: fixturePath("layouts"),
partialsDir: fixturePath("partials"),
});
const viewPath = fixturePath("render-partial.handlebars");
const viewsPath = fixturePath("other-views");
const html = await exphbs.renderView(viewPath, {
text: "test text",
settings: { views: viewsPath },
});
expect(html.replace(/\r/g, "")).toBe("<body>\n<h1>partial test text</h1>\n<p>test text</p>\n</body>");
});
test("should merge helpers", async () => {
const exphbs = expressHandlebars.create({
defaultLayout: undefined,
helpers: {
help: () => "help",
},
});
const viewPath = fixturePath("render-helper.handlebars");
const html = await exphbs.renderView(viewPath, {
text: "test text",
helpers: {
help: (text: string) => text,
},
});
expect(html).toBe("<p>test text</p>");
});
test("should use layout option", async () => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const layoutPath = fixturePath("layouts/main.handlebars");
const viewPath = fixturePath("render-text.handlebars");
const html = await exphbs.renderView(viewPath, {
text: "test text",
layout: layoutPath,
});
expect(html.replace(/\r/g, "")).toBe("<body>\n<p>test text</p>\n</body>");
});
test("should render html", async () => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const viewPath = fixturePath("render-text.handlebars");
const html = await exphbs.renderView(viewPath, { text: "test text" });
expect(html).toBe("<p>test text</p>");
});
test("should render html with encoding", async () => {
const exphbs = expressHandlebars.create({
defaultLayout: "main-latin1",
partialsDir: fixturePath("partials"),
layoutsDir: fixturePath("layouts"),
});
const viewPath = fixturePath("render-latin1.handlebars");
const html = await exphbs.renderView(viewPath, { encoding: "latin1" });
expect(html).toContain("layout ñáéíóú");
expect(html).toContain("partial ñáéíóú");
expect(html).toContain("render ñáéíóú");
});
test("should render html with default encoding", async () => {
const exphbs = expressHandlebars.create({
encoding: "latin1",
defaultLayout: "main-latin1",
partialsDir: fixturePath("partials"),
layoutsDir: fixturePath("layouts"),
});
const viewPath = fixturePath("render-latin1.handlebars");
const html = await exphbs.renderView(viewPath);
expect(html).toContain("layout ñáéíóú");
expect(html).toContain("partial ñáéíóú");
expect(html).toContain("render ñáéíóú");
});
test("should call callback with html", (done) => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const viewPath = fixturePath("render-text.handlebars");
exphbs.renderView(viewPath, { text: "test text" }, (err: Error|null, html: string|undefined) => {
expect(err).toBe(null);
expect(html).toBe("<p>test text</p>");
done();
});
});
test("should call callback as second parameter", (done) => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const viewPath = fixturePath("render-text.handlebars");
exphbs.renderView(viewPath, (err: Error|null, html: string|undefined) => {
expect(err).toBe(null);
expect(html).toBe("<p></p>");
done();
});
});
test("should call callback with error", (done) => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const viewPath = "does-not-exist";
exphbs.renderView(viewPath, {}, (err: Error|null, html: string | undefined) => {
expect(err?.message).toEqual(expect.stringContaining("no such file or directory"));
expect(html).toBeUndefined();
done();
});
});
test("should reject with error", async () => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const viewPath = "does-not-exist";
let error: Error | undefined;
try {
await exphbs.renderView(viewPath);
} catch (e) {
error = e;
}
expect(error?.message).toEqual(expect.stringContaining("no such file or directory"));
});
test("should use runtimeOptions", async () => {
const exphbs = expressHandlebars.create({ defaultLayout: undefined });
const filePath = fixturePath("test");
const spy = jest.fn(() => { return "test"; }) as Handlebars.TemplateDelegate;
exphbs.compiled[filePath] = Promise.resolve(spy);
await exphbs.renderView(filePath, {
cache: true,
runtimeOptions: { allowProtoPropertiesByDefault: true },
});
expect(spy).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({ allowProtoPropertiesByDefault: true }));
});
});
describe("resetCache", () => {
test("should reset all cache", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const template = fixturePath("templates/template.handlebars");
await exphbs.getTemplates(dirPath);
expect(exphbs._fsCache[template]).toBeDefined();
exphbs.resetCache();
expect(exphbs._fsCache).toEqual({});
});
test("should reset all cache with undefined", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const template = fixturePath("templates/template.handlebars");
await exphbs.getTemplates(dirPath);
expect(exphbs._fsCache[template]).toBeDefined();
let undef: undefined;
exphbs.resetCache(undef);
expect(exphbs._fsCache).toEqual({});
});
test("should reset cached file path", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const template = fixturePath("templates/template.handlebars");
await exphbs.getTemplates(dirPath);
expect(Object.keys(exphbs._fsCache).length).toEqual(4);
expect(exphbs._fsCache[template]).toBeDefined();
exphbs.resetCache(template);
expect(Object.keys(exphbs._fsCache).length).toEqual(3);
expect(exphbs._fsCache[template]).toBeUndefined();
});
test("should reset cached file paths", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const template = fixturePath("templates/template.handlebars");
const templateLatin1 = fixturePath("templates/template-latin1.handlebars");
await exphbs.getTemplates(dirPath);
expect(Object.keys(exphbs._fsCache).length).toEqual(4);
expect(exphbs._fsCache[template]).toBeDefined();
expect(exphbs._fsCache[templateLatin1]).toBeDefined();
exphbs.resetCache([template, templateLatin1]);
expect(Object.keys(exphbs._fsCache).length).toEqual(2);
expect(exphbs._fsCache[template]).toBeUndefined();
expect(exphbs._fsCache[templateLatin1]).toBeUndefined();
});
test("should reset cached file based on filter", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const templateLatin1 = fixturePath("templates/template-latin1.handlebars");
await exphbs.getTemplates(dirPath);
expect(Object.keys(exphbs._fsCache).length).toEqual(4);
expect(exphbs._fsCache[templateLatin1]).toBeDefined();
exphbs.resetCache((f) => f.includes("latin1"));
expect(Object.keys(exphbs._fsCache).length).toEqual(3);
expect(exphbs._fsCache[templateLatin1]).toBeUndefined();
});
test("should not error on invalid file path", async () => {
const exphbs = expressHandlebars.create();
const dirPath = fixturePath("templates");
const template = fixturePath("templates/invalid.handlebars");
await exphbs.getTemplates(dirPath);
expect(Object.keys(exphbs._fsCache).length).toEqual(4);
expect(exphbs._fsCache[template]).toBeUndefined();
exphbs.resetCache(template);
expect(Object.keys(exphbs._fsCache).length).toEqual(4);
expect(exphbs._fsCache[template]).toBeUndefined();
});
});
describe("hooks", () => {
describe("_compileTemplate", () => {
test("should call template with context and options", () => {
const exphbs = expressHandlebars.create();
// @ts-expect-error empty function
jest.spyOn(exphbs.handlebars, "compile").mockImplementation(() => {});
const template = "template";
const options = {};
exphbs["_compileTemplate"](template, options);
expect(exphbs.handlebars.compile).toHaveBeenCalledWith(template, options);
});
test("should trim template", () => {
const exphbs = expressHandlebars.create();
// @ts-expect-error empty function
jest.spyOn(exphbs.handlebars, "compile").mockImplementation(() => {});
const template = " template\n";
const options = {};
exphbs["_compileTemplate"](template, options);
expect(exphbs.handlebars.compile).toHaveBeenCalledWith("template", options);
});
});
describe("_precompileTemplate", () => {
test("should call template with context and options", () => {
const exphbs = expressHandlebars.create();
// @ts-expect-error empty function
jest.spyOn(exphbs.handlebars, "precompile").mockImplementation(() => {});
const template = "template";
const options = {};
exphbs["_precompileTemplate"](template, options);
expect(exphbs.handlebars.precompile).toHaveBeenCalledWith(template, options);
});
test("should trim template", () => {
const exphbs = expressHandlebars.create();
// @ts-expect-error empty function
jest.spyOn(exphbs.handlebars, "precompile").mockImplementation(() => {});
const template = " template\n";
const options = {};
exphbs["_precompileTemplate"](template, options);
expect(exphbs.handlebars.precompile).toHaveBeenCalledWith("template", options);
});
});
describe("_renderTemplate", () => {
test("should call template with context and options", () => {
const exphbs = expressHandlebars.create();
const template = jest.fn(() => "");
const context = {};
const options = {};
exphbs["_renderTemplate"](template, context, options);
expect(template).toHaveBeenCalledWith(context, options);
});
test("should trim html", () => {
const exphbs = expressHandlebars.create();
const template = () => " \n";
const html = exphbs["_renderTemplate"](template);
expect(html).toBe("");
});
});
describe("_getDir", () => {
test("should get from cache", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("test");
exphbs._fsCache[filePath] = Promise.resolve(["test"]);
const file = await exphbs["_getDir"](filePath, { cache: true });
expect(file).toEqual(["test"]);
});
test("should store in cache", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("templates");
expect(exphbs._fsCache[filePath]).toBeUndefined();
const expected = await exphbs["_getDir"](filePath);
expect(exphbs._fsCache[filePath]).toBeInstanceOf(Promise);
expect(await exphbs._fsCache[filePath]).toEqual(expected);
});
test("should not store in cache on error", async () => {
const exphbs = expressHandlebars.create();
const filePath = "test";
expect(exphbs._fsCache[filePath]).toBeUndefined();
let error: Error | undefined;
try {
await exphbs["_getDir"](filePath, {
// @ts-expect-error Add this just for testing
_throwTestError: true,
});
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
expect(exphbs._fsCache[filePath]).toBeUndefined();
});
});
describe("_getFile", () => {
test("should get from cache", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("test");
exphbs._fsCache[filePath] = "test";
const file = await exphbs["_getFile"](filePath, { cache: true });
expect(file).toBe("test");
});
test("should store in cache", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("render-text.handlebars");
expect(exphbs._fsCache[filePath]).toBeUndefined();
await exphbs["_getFile"](filePath);
expect(exphbs._fsCache[filePath]).toBeDefined();
});
test("should not store in cache on error", async () => {
const exphbs = expressHandlebars.create();
const filePath = "does-not-exist";
expect(exphbs._fsCache[filePath]).toBeUndefined();
let error: Error | undefined;
try {
await exphbs["_getFile"](filePath);
} catch (e) {
error = e;
}
expect(error?.message).toEqual(expect.stringContaining("no such file or directory"));
expect(exphbs._fsCache[filePath]).toBeUndefined();
});
test("should read as utf8", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("render-text.handlebars");
const text = await exphbs["_getFile"](filePath);
expect(text.trim()).toBe("<p>{{text}}</p>");
});
test("should read as utf8 by default", async () => {
const exphbs = expressHandlebars.create({ encoding: undefined });
const filePath = fixturePath("render-text.handlebars");
const text = await exphbs["_getFile"](filePath);
expect(text.trim()).toBe("<p>{{text}}</p>");
});
test("should read as latin1", async () => {
const exphbs = expressHandlebars.create();
const filePath = fixturePath("render-latin1.handlebars");
const text = await exphbs["_getFile"](filePath, { encoding: "latin1" });
expect(text).toContain("ñáéíóú");
});
test("should read as default encoding", async () => {
const exphbs = expressHandlebars.create({ encoding: "latin1" });
const filePath = fixturePath("render-latin1.handlebars");
const text = await exphbs["_getFile"](filePath);
expect(text).toContain("ñáéíóú");
});
});
describe("_getTemplateName", () => {
test("should remove extension", () => {
const exphbs = expressHandlebars.create();
const name = exphbs["_getTemplateName"]("filePath.handlebars");
expect(name).toBe("filePath");
});
test("should leave if no extension", () => {
const exphbs = expressHandlebars.create();
const name = exphbs["_getTemplateName"]("filePath");
expect(name).toBe("filePath");
});
test("should add namespace", () => {
const exphbs = expressHandlebars.create();
const name = exphbs["_getTemplateName"]("filePath.handlebars", "namespace");
expect(name).toBe("namespace/filePath");
});
});
describe("_resolveViewsPath", () => {
test("should return closest parent", () => {
const file = "/root/views/file.hbs";
const exphbs = expressHandlebars.create();
const viewsPath = exphbs["_resolveViewsPath"]([
"/root",
"/root/views",
"/root/views/file",
], file);
expect(viewsPath).toBe("/root/views");
});
test("should return string views", () => {
const exphbs = expressHandlebars.create();
const viewsPath = exphbs["_resolveViewsPath"]("./views", "filePath.hbs");
expect(viewsPath).toBe("./views");
});
test("should return null views", () => {
const exphbs = expressHandlebars.create();
// @ts-expect-error shouldn't expect null parameter
const viewsPath = exphbs["_resolveViewsPath"](null, "filePath.hbs");
expect(viewsPath).toBe(null);
});
test("should return null if not found", () => {
const file = "/file.hbs";
const exphbs = expressHandlebars.create();
const viewsPath = exphbs["_resolveViewsPath"]([
"/views",
], file);
expect(viewsPath).toBe(null);
});
});
describe("_resolveLayoutPath", () => {
test("should add extension", () => {
const exphbs = expressHandlebars.create();
const layoutPath = exphbs["_resolveLayoutPath"]("filePath");
expect(layoutPath).toEqual(expect.stringMatching(/filePath\.handlebars$/));
});
test("should use layoutsDir", () => {
const layoutsDir = fixturePath("layouts");
const filePath = "filePath.handlebars";
const exphbs = expressHandlebars.create({ layoutsDir });
const layoutPath = exphbs["_resolveLayoutPath"](filePath);
expect(layoutPath).toBe(path.resolve(layoutsDir, filePath));
});
test("should return null", () => {
const exphbs = expressHandlebars.create();
// @ts-expect-error shouldn't expect null parameter
const layoutPath = exphbs["_resolveLayoutPath"](null);
expect(layoutPath).toBe(null);
});
});
});
});

View File

@@ -1,3 +0,0 @@
file encoding: Windows 1252 (latin1)
<p>layout <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></p>
{{{body}}}

View File

@@ -1,3 +0,0 @@
<body>
{{{body}}}
</body>

View File

@@ -1,4 +0,0 @@
<body>
other layout
{{{body}}}
</body>

View File

@@ -1 +0,0 @@
other partial {{text}}

View File

@@ -1,2 +0,0 @@
file encoding: Windows 1252 (latin1)
<p>partial <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></p>

View File

@@ -1 +0,0 @@
partial {{text}}

View File

@@ -1 +0,0 @@
subdir partial {{text}}

View File

@@ -1 +0,0 @@
not cached

View File

@@ -1 +0,0 @@
<p>{{help text}}</p>

View File

@@ -1,3 +0,0 @@
{{> partial-latin1}}
file encoding: Windows 1252 (latin1)
<p>render <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></p>

View File

@@ -1,2 +0,0 @@
<h1>{{> partial}}</h1>
<p>{{text}}</p>

View File

@@ -1,2 +0,0 @@
<h1>{{> subdir/partial-subdir}}</h1>
<p>{{text}}</p>

View File

@@ -1 +0,0 @@
<p>{{text}}</p>

View File

@@ -1 +0,0 @@
<p>{{text}}</p>

View File

@@ -1,2 +0,0 @@
file encoding: Windows 1252 (latin1)
<p><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></p>

View File

@@ -1 +0,0 @@
<p>{{text}}</p>

View File

@@ -1,12 +0,0 @@
{
"compilerOptions": {
"outDir": "./dist",
"target": "ES2015",
"declaration": true,
"sourceMap": true,
"module": "commonjs",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
},
"include": ["./lib/**/*"]
}

View File

@@ -1,92 +0,0 @@
/// <reference types="handlebars" />
export interface UnknownObject {
[index: string]: unknown
}
export interface HelperDelegateObject {
[index: string]: Handlebars.HelperDelegate;
}
export interface TemplateDelegateObject {
[index: string]: Handlebars.TemplateDelegate;
}
export interface TemplateSpecificationObject {
[index: string]: TemplateSpecification;
}
export interface CompiledCache {
[index: string]: Promise<Handlebars.TemplateDelegate>;
}
export interface PrecompiledCache {
[index: string]: Promise<TemplateSpecification>;
}
export interface FsCache {
[index: string]: string|string[]|Promise<string|string[]>;
}
export type RenameFunction = (filePath: string, namespace?: string) => string
export interface PartialsDirObject {
templates: TemplateDelegateObject;
namespace: string;
dir: string;
rename?: RenameFunction | undefined;
}
export interface PartialTemplateOptions {
encoding?: BufferEncoding;
cache?: boolean;
precompiled?: boolean;
}
export interface RenderOptions {
cache?: boolean;
data?: UnknownObject;
encoding?: BufferEncoding;
helpers?: HelperDelegateObject;
layout?: string;
partials?: TemplateDelegateObject;
runtimeOptions?: Handlebars.RuntimeOptions;
}
export interface RenderViewOptions extends RenderOptions {
[index: string]: unknown;
settings?: {
views: string|string[]
}
}
export type HandlebarsCompile = (input: unknown, options: CompileOptions) => Handlebars.TemplateDelegate;
export type HandlebarsPrecompile = (input: unknown, options: PrecompileOptions) => TemplateSpecification;
export interface HandlebarsImport {
[index: string]: unknown;
compile: HandlebarsCompile;
precompile: HandlebarsPrecompile;
}
export interface ConfigOptions {
handlebars?: HandlebarsImport;
extname?: string;
encoding?: BufferEncoding;
layoutsDir?: string;
partialsDir?: string|string[]|PartialsDirObject|PartialsDirObject[];
defaultLayout?: string|false;
helpers?: UnknownObject;
compilerOptions?: CompileOptions;
runtimeOptions?: Handlebars.RuntimeOptions;
}
export interface EngineOptions extends ConfigOptions {
[index: string]: unknown;
}
export interface RenderCallback {
(err: Error|null, content?: string): void;
}
export type Engine = (viewPath: string, options: ConfigOptions, callback?: RenderCallback) => Promise<string>