Showing preview only (675K chars total). Download the full file or copy to clipboard to get everything.
Repository: handlebars-lang/handlebars.js
Branch: master
Commit: b5ad7304736a
Files: 166
Total size: 631.6 KB
Directory structure:
gitextract_nws7_sbo/
├── .editorconfig
├── .git-blame-ignore-revs
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── .oxfmtrc.json
├── .oxlintrc.json
├── .swcrc
├── CONTRIBUTING.md
├── FAQ.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│ └── handlebars.mjs
├── components/
│ ├── bower.json
│ ├── component.json
│ ├── composer.json
│ ├── handlebars-source.gemspec
│ ├── handlebars.js.nuspec
│ ├── lib/
│ │ └── handlebars/
│ │ └── source.rb
│ └── package.json
├── docs/
│ ├── compiler-api.md
│ └── decorators-api.md
├── eslint.config.mjs
├── lib/
│ ├── handlebars/
│ │ ├── base.js
│ │ ├── compiler/
│ │ │ ├── ast.js
│ │ │ ├── code-gen.js
│ │ │ ├── compiler.js
│ │ │ └── javascript-compiler.js
│ │ ├── decorators/
│ │ │ └── inline.js
│ │ ├── decorators.js
│ │ ├── helpers/
│ │ │ ├── block-helper-missing.js
│ │ │ ├── each.js
│ │ │ ├── helper-missing.js
│ │ │ ├── if.js
│ │ │ ├── log.js
│ │ │ ├── lookup.js
│ │ │ └── with.js
│ │ ├── helpers.js
│ │ ├── internal/
│ │ │ ├── proto-access.js
│ │ │ └── wrapHelper.js
│ │ ├── logger.js
│ │ ├── no-conflict.js
│ │ ├── runtime.js
│ │ ├── safe-string.js
│ │ └── utils.js
│ ├── handlebars.js
│ ├── handlebars.runtime.js
│ ├── index.js
│ └── precompiler.js
├── package.json
├── release-notes.md
├── rspack.config.js
├── runtime.d.ts
├── runtime.js
├── spec/
│ ├── artifacts/
│ │ ├── bom.handlebars
│ │ ├── empty.handlebars
│ │ ├── example_1.handlebars
│ │ ├── example_2.hbs
│ │ ├── known.helpers.handlebars
│ │ ├── non.default.extension.hbs
│ │ └── partial.template.handlebars
│ ├── ast.js
│ ├── basic.js
│ ├── blocks.js
│ ├── builtins.js
│ ├── compiler.js
│ ├── data.js
│ ├── env/
│ │ ├── browser-vitest-pre.js
│ │ ├── browser-vitest.js
│ │ ├── browser.js
│ │ ├── common.js
│ │ └── node.js
│ ├── expected/
│ │ ├── bom.amd.js
│ │ ├── compiled.string.txt
│ │ ├── empty.amd.js
│ │ ├── empty.amd.namespace.js
│ │ ├── empty.amd.simple.js
│ │ ├── empty.common.js
│ │ ├── empty.name.amd.js
│ │ ├── empty.root.amd.js
│ │ ├── handlebar.path.amd.js
│ │ ├── help.menu.txt
│ │ ├── namespace.amd.js
│ │ ├── non.default.extension.amd.js
│ │ ├── non.empty.amd.known.helper.js
│ │ ├── partial.template.js
│ │ └── source.map.amd.js
│ ├── helpers.js
│ ├── index.html
│ ├── javascript-compiler.js
│ ├── partials.js
│ ├── precompiler.js
│ ├── regressions.js
│ ├── require.js
│ ├── runtime.js
│ ├── security.js
│ ├── source-map.js
│ ├── spec.js
│ ├── strict.js
│ ├── subexpressions.js
│ ├── tokenizer.js
│ ├── umd-runtime.html
│ ├── umd.html
│ ├── utils.js
│ ├── vendor/
│ │ └── require.js
│ └── whitespace-control.js
├── tasks/
│ ├── publish-to-aws.js
│ ├── tests/
│ │ ├── README.md
│ │ ├── cli.test.js
│ │ └── git.test.js
│ ├── util/
│ │ ├── async-grunt-task.js
│ │ ├── exec-file.js
│ │ └── git.js
│ └── version.js
├── tests/
│ ├── bench/
│ │ ├── compare.mjs
│ │ ├── perf.mjs
│ │ ├── report.mjs
│ │ ├── size.mjs
│ │ └── templates.mjs
│ ├── browser/
│ │ ├── README.md
│ │ ├── playwright.config.js
│ │ └── tests/
│ │ └── lib.spec.js
│ ├── integration/
│ │ ├── README.md
│ │ ├── multi-nodejs-test/
│ │ │ ├── .gitignore
│ │ │ ├── package.json
│ │ │ ├── precompile-test-template.txt.hbs
│ │ │ ├── run-handlebars.js
│ │ │ └── test.sh
│ │ ├── rollup-test/
│ │ │ ├── .gitignore
│ │ │ ├── package.json
│ │ │ ├── rollup.config.js
│ │ │ ├── src/
│ │ │ │ └── index.js
│ │ │ └── test.sh
│ │ ├── run-integration-tests.sh
│ │ ├── webpack-babel-test/
│ │ │ ├── .babelrc
│ │ │ ├── .gitignore
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── handlebars-inline-precompile-test.js
│ │ │ │ └── lib/
│ │ │ │ └── assert.js
│ │ │ ├── test.sh
│ │ │ └── webpack.config.js
│ │ └── webpack-test/
│ │ ├── .gitignore
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── handlebars-default-import-test.js
│ │ │ ├── handlebars-esm-import-test.js
│ │ │ ├── handlebars-loader-test.js
│ │ │ ├── handlebars-register-helper-test.js
│ │ │ ├── handlebars-runtime-test.js
│ │ │ ├── lib/
│ │ │ │ └── assert.js
│ │ │ └── test-template.handlebars
│ │ ├── test.sh
│ │ └── webpack.config.js
│ ├── print-script.js
│ └── rspack/
│ └── rspack.test.js
├── tstyche.config.json
├── types/
│ ├── __typetests__/
│ │ └── handlebars.tst.ts
│ ├── index.d.ts
│ └── tsconfig.json
└── vitest.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*.js]
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.yml]
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .git-blame-ignore-revs
================================================
# Upgrade to Prettier 2.7
3d228334530860a6e3f99dc10777c84bf22292c1
# Format markdown files with Prettier
dfe2eaaf20f0b679d94e5a799757c4394d80f1cc
# migrate to oxlint and oxfmt
0c1d00282ca619c3416ad819bdf53c0852b32415
================================================
FILE: .gitattributes
================================================
# Handlebars-template fixtures in test cases need deterministic eol
*.handlebars text eol=lf
*.hbs text eol=lf
# Lexer files as well
*.l text eol=lf
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
Before filing issues, please check the following points first:
- [ ] Please don't open issues for security issues. Instead, file a report at https://www.npmjs.com/advisories/report?package=handlebars
- [ ] Have a look at https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md
- [ ] Read the FAQ at https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md
- [ ] Use the jsfiddle-template at https://jsfiddle.net/4nbwjaqz/4/ to reproduce problems or bugs
This will probably help you to get a solution faster.
For bugs, it would be great to have a PR with a failing test-case.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
Before creating a pull-request, please check https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md first.
Generally we like to see pull requests that
- [ ] Please don't start pull requests for security issues. Instead, file a report at https://www.npmjs.com/advisories/report?package=handlebars
- [ ] Maintain the existing code style
- [ ] Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
- [ ] Have good commit messages
- [ ] Have tests
- [ ] Have the [typings](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) (types/index.d.ts) updated on every API change. If you need help, updating those, please mention that in the PR description.
- [ ] Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
- [ ] Please target the `master` branch in the PR.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
directory: '/'
open-pull-requests-limit: 0
schedule:
interval: weekly
allow:
- dependency-type: production
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches:
- master
pull_request: {}
jobs:
lint:
name: Lint
runs-on: 'ubuntu-latest'
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
test:
name: Test (Node)
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: ['ubuntu-latest', 'windows-latest']
# https://nodejs.org/en/about/releases/
node-version: ['20', '22', '24']
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Test
run: npm run test
- name: Test (Integration)
if: matrix.operating-system == 'ubuntu-latest'
run: npm run test:integration
browser:
name: Test (Browser)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: |
npx playwright install-deps
npx playwright install
- name: Build
run: npm run build
- name: Test
run: |
npm run test:browser-smoke
npm run test:browser
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
workflow_dispatch:
push:
branches:
- master
tags:
- '*'
jobs:
publish-aws-s3:
name: Publish to AWS S3
runs-on: 'ubuntu-latest'
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
run: npm ci
- name: Publish
run: |
git config --global user.email "release@handlebarsjs.com"
git config --global user.name "handlebars-lang"
npm run publish:aws
env:
S3_BUCKET_NAME: 'builds.handlebarsjs.com'
S3_REGION: 'us-east-1'
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
================================================
FILE: .gitignore
================================================
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
.idea
/yarn-error.log
/yarn.lock
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
/coverage/
/dist/
/tests/bench/results/
/tests/integration/*/dist/
/spec/tmp/*
================================================
FILE: .gitmodules
================================================
[submodule "spec/mustache"]
path = spec/mustache
url = https://github.com/mustache/spec.git
================================================
FILE: .oxfmtrc.json
================================================
{
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxfmt-config-schema/refs/heads/main/schema.json",
"singleQuote": true,
"tabWidth": 2,
"semi": true,
"trailingComma": "es5",
"printWidth": 80,
"ignorePatterns": [
".rvmrc",
".DS_Store",
"/tmp/",
"*.sublime-project",
"*.sublime-workspace",
"npm-debug.log",
"sauce_connect.log*",
".idea",
"yarn-error.log",
"/coverage/",
".nyc_output/",
"/dist/",
"/tests/integration/*/dist/",
"/spec/expected/",
"/spec/mustache",
"/spec/vendor",
"*.handlebars",
"*.hbs"
]
}
================================================
FILE: .oxlintrc.json
================================================
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "node", "vitest"],
"categories": {
"correctness": "error"
},
"rules": {
"no-console": "warn",
"no-func-assign": "off",
"no-sparse-arrays": "off",
"default-case": "warn",
"guard-for-in": "warn",
"no-alert": "error",
"no-caller": "error",
"no-div-regex": "warn",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-implied-eval": "error",
"no-iterator": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-loop-func": "error",
"no-multi-str": "warn",
"no-global-assign": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-proto": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unused-expressions": "error",
"no-warning-comments": "warn",
"no-with": "error",
"radix": "error",
"no-label-var": "error",
"no-use-before-define": ["error", { "functions": false }],
"no-var": "error",
"node/no-process-env": "error"
},
"ignorePatterns": [
"tmp/",
"dist/",
"coverage/",
".nyc_output/",
"handlebars-release.tgz",
"tests/integration/*/dist/",
"spec/expected/",
"spec/mustache",
"spec/vendor",
"node_modules",
"types/"
],
"overrides": [
{
"files": ["lib/**/*.js"],
"env": {
"node": false,
"browser": true
}
},
{
"files": ["spec/**/*.js"],
"globals": {
"CompilerContext": "readonly",
"Handlebars": "writable",
"handlebarsEnv": "readonly",
"expectTemplate": "readonly",
"suite": "readonly",
"test": "readonly",
"testBoth": "readonly",
"raises": "readonly",
"deepEqual": "readonly",
"start": "readonly",
"stop": "readonly",
"ok": "readonly",
"vi": "readonly",
"strictEqual": "readonly",
"define": "readonly",
"expect": "readonly",
"beforeEach": "readonly",
"afterEach": "readonly",
"describe": "readonly",
"it": "readonly"
},
"rules": {
"no-var": "off",
"dot-notation": "off",
"vitest/no-conditional-tests": "off"
}
},
{
"files": ["tasks/**/*.js"],
"rules": {
"node/no-process-env": "off",
"prefer-const": "warn",
"dot-notation": "error"
}
},
{
"files": ["tasks/tests/**/*.js"],
"globals": {
"describe": "readonly",
"it": "readonly",
"expect": "readonly",
"beforeEach": "readonly",
"afterEach": "readonly",
"vi": "readonly"
}
},
{
"files": ["tests/bench/**/*.mjs"],
"rules": {
"no-console": "off"
}
},
{
"files": ["tests/integration/multi-nodejs-test/**/*.js"],
"rules": {
"no-console": "off",
"no-var": "off"
}
},
{
"files": ["tests/browser/**/*.js"],
"env": {
"browser": true
}
},
{
"files": [
"tests/integration/webpack-babel-test/src/**/*.js",
"tests/integration/webpack-test/src/**/*.js"
],
"env": {
"browser": true
},
"rules": {
"no-var": "off"
}
}
]
}
================================================
FILE: .swcrc
================================================
{
"$schema": "https://swc.rs/schema.json",
"module": {
"type": "commonjs",
"importInterop": "swc"
},
"sourceMaps": "inline"
}
================================================
FILE: CONTRIBUTING.md
================================================
# How to Contribute
## Reporting Security Issues
Please refer to our [Security Policy](https://github.com/handlebars-lang/handlebars.js/blob/master/SECURITY.md).
## Reporting Issues
Please refer to our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]!
In general, we are going to ask for an **example** of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle **[template][jsfiddle]** to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here).
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
## Branches
- The branch `master` contains the current development version (v5).
- The branch `4.x` contains the previous stable version. Only critical bugfixes are backported there.
## Pull Requests
We also accept [pull requests][pull-request]!
Generally we like to see pull requests that
- Maintain the existing code style
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
- Have tests
- Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
## Building
To build Handlebars.js you'll need Node.js installed.
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
Project dependencies may be installed via `npm install`.
To build Handlebars.js from scratch, run `npm run build` in the root of the project. That will compile CJS modules via SWC and bundle UMD distributions via rspack, outputting results to the dist/ folder. To run tests, use `npm test`.
If you notice any problems, please report them to the GitHub issue tracker at
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
## Running Tests
To run tests locally, first install all dependencies.
```sh
npm install
```
Clone the mustache specs into the spec/mustache folder.
```sh
cd spec
rm -r mustache
git clone https://github.com/mustache/spec.git mustache
```
From the root directory, run the tests.
```sh
npm test
```
## Linting and Formatting
Handlebars uses `oxlint` for linting, `oxfmt` for formatting, and `eslint` (with `eslint-plugin-compat`) for browser API compatibility checks.
Committed files are linted and formatted in a pre-commit hook.
You can use the following scripts to make sure that the CI job does not fail:
- **npm run lint** will run all linters and fail on warnings
- **npm run format** will format all files
- **npm run check-before-pull-request** will perform all checks that our CI job does, excluding integration tests.
- **npm run test:integration** will run integration tests (bundler compatibility with webpack, rollup, etc.)
These tests only work on Linux.
## Releasing the latest version
Before attempting the release Handlebars, please make sure that you have the following authorizations:
- Push-access to `handlebars-lang/handlebars.js`
- Publishing rights on npmjs.com for the `handlebars` package
- Publishing rights on gemfury for the `handlebars-source` package
- Push-access to the repo for legacy package managers: `components/handlebars`
- Push-access to the production-repo of the handlebars site: `handlebars-lang/handlebarsjs.com-github-pages`
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
A full release may be completed with the following:
```
npm ci
npm run build
npm publish
```
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
in those places still point to the latest version
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
- [The bower package](https://github.com/components/handlebars.js) (check the package.json)
- [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
- [RubyGems](https://rubygems.org/gems/handlebars-source)
When everything is OK, the **handlebars site** needs to be updated.
Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
and make a minimal change to the README. This will invoke a github-action that redeploys
the site, fetching the latest version-number from the npm-registry.
(note that the default-branch of this repo is not the master and regular changes are done
in the `handlebars-lang/docs`-repo).
[generator-release]: https://github.com/walmartlabs/generator-release
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
[jsfiddle]: https://jsfiddle.net/4nbwjaqz/4/
================================================
FILE: FAQ.md
================================================
# Frequently Asked Questions
## How can I file a bug report:
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
## Why isn't my Mustache template working?
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache).
## Why is it slower when compiling?
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
## Why doesn't this work with Content Security Policy restrictions?
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
## How can I include script tags in my template?
If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
```html
<script type="text/x-handlebars">
foo
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
</script>
```
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
## Why are my precompiled scripts throwing exceptions?
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
```sh
handlebars --version
```
If using the integrated precompiler and
```javascript
console.log(Handlebars.VERSION);
```
On the client side.
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
## How do I load the runtime library when using AMD?
The `handlebars.runtime.js` file includes a UMD build, which exposes the library as both the module root and the `default` field for compatibility.
================================================
FILE: Gruntfile.js
================================================
// Legacy Gruntfile — kept only for the 'metrics' and 'version' tasks.
// The main build pipeline uses rspack + swc (see rspack.config.js and .swcrc).
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
});
grunt.task.loadTasks('tasks');
};
================================================
FILE: LICENSE
================================================
Copyright (C) 2011-2019 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
[](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
[](https://www.jsdelivr.com/package/npm/handlebars)
[](https://www.npmjs.com/package/handlebars)
[](https://www.npmjs.com/package/handlebars)
[](https://bundlephobia.com/package/handlebars)
[](https://packagephobia.com/result?p=handlebars)
# Handlebars.js
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
Checkout the official Handlebars docs site at
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
## Installing
See our [installation documentation](https://handlebarsjs.com/guide/installation/).
## Usage
In general, the syntax of Handlebars.js templates is a superset
of Mustache templates. For basic syntax, check out the [Mustache
manpage](https://mustache.github.io/mustache.5.html).
Once you have a template, use the `Handlebars.compile` method to compile
the template into a function. The generated function takes a context
argument, which will be used to render the template.
```js
var source =
'<p>Hello, my name is {{name}}. I am from {{hometown}}. I have ' +
'{{kids.length}} kids:</p>' +
'<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>';
var template = Handlebars.compile(source);
var data = {
name: 'Alan',
hometown: 'Somewhere, TX',
kids: [
{ name: 'Jimmy', age: '12' },
{ name: 'Sally', age: '4' },
],
};
var result = template(data);
// Would render:
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
// <ul>
// <li>Jimmy is 12</li>
// <li>Sally is 4</li>
// </ul>
```
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
## Precompiling Templates
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/guide/installation/precompilation.html).
## Differences Between Handlebars.js and Mustache
Handlebars.js adds a couple of additional features to make writing
templates easier and also changes a tiny detail of how partials work.
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions)
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers)
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks)
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
### Compatibility
There are a few Mustache behaviors that Handlebars does not implement.
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
- Handlebars does not allow space between the opening `{{` and a command character such as `#`, `/` or `>`. The command character must immediately follow the braces, so for example `{{> partial }}` is allowed but `{{ > partial }}` is not.
- Alternative delimiters are not supported.
## Supported Environments
Handlebars has been designed to work in any ECMAScript 2020 environment. This includes
- Node.js
- Chrome
- Firefox
- Safari
- Edge
If you need to support older environments, use Handlebars version 4.
## Performance
In a rough performance test, precompiled Handlebars.js templates (in
the original version of Handlebars.js) rendered in about half the
time of Mustache templates. It would be a shame if it were any other
way, since they were precompiled, but the difference in architecture
does have some big performance advantages. Justin Marney, a.k.a.
[gotascii](http://github.com/gotascii), confirmed that with an
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
rewritten Handlebars (current version) is faster than the old version,
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
### Benchmarks
The project includes a comprehensive benchmark suite (powered by [tinybench](https://github.com/tinylibs/tinybench)) that measures compilation, execution, precompilation, and end-to-end performance across templates of varying size and complexity.
```bash
# Run benchmarks (auto-labels with current git branch)
npm run bench
# Run with a custom label
npm run bench -- --label my-optimization
# Filter templates by name (regex, case-insensitive)
npm run bench -- --grep "complex|recursive"
# Run only specific sections (regex, case-insensitive)
npm run bench -- --section precompil
npm run bench -- --section "compilation|precompil"
# Compare results
npm run bench:compare
# Or specify files explicitly
npm run bench:compare -- bench/results/bench-*-main.md bench/results/bench-*-feat.md
```
Results are saved as timestamped Markdown files in `bench/results/`. Each report includes ops/sec, avg latency, p50/p75/p99 percentiles, and sample counts.
Typical workflow for comparing branches:
```bash
git checkout main && npm run bench
git checkout my-feature && npm run bench
npm run bench:compare
```
When run without arguments, `bench:compare` auto-selects two result files: if a file labelled "main" exists it is always used as the baseline, otherwise the older file is the baseline. The comparison uses p75 latency for the diff to filter outliers, and marks changes with `!` (>2%) and `!!` (>5%).
## Upgrading
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
If you are using Handlebars in production, please regularly look for issues labeled
[possibly breaking](https://github.com/handlebars-lang/handlebars.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22possibly+breaking%22).
If this label is applied to an issue, it means that the requested change is probably not a breaking change,
but since Handlebars is widely in use by a lot of people, there's always a chance that it breaks somebody's build.
## Known Issues
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
## Handlebars in the Wild
- [apiDoc](https://github.com/apidoc/apidoc) apiDoc uses handlebars as parsing engine for api documentation view generation.
- [Assemble](https://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert) and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js as its template engine.
- [CoSchedule](https://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js.
- [Ember.js](https://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
- [express-handlebars](https://github.com/express-handlebars/express-handlebars) A Handlebars view engine for Express which doesn't suck.
- [express-hbs](https://github.com/TryGhost/express-hbs) Express Handlebars template engine with inheritance, partials, i18n and async helpers.
- [Ghost](https://ghost.org/) Just a blogging platform.
- [handlebars-action](https://github.com/marketplace/actions/handlebars-action) A GitHub action to transform files in your repository with Handlebars templating.
- [handlebars_assets](https://github.com/leshill/handlebars_assets) A Rails Asset Pipeline gem from Les Hill (@leshill).
- [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library with 100+ handlebars helpers.
- [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extensible and embeddable layout blocks as seen in other popular templating languages.
- [handlebars-loader](https://github.com/pcardune/handlebars-loader) A handlebars template loader for webpack.
- [handlebars-wax](https://github.com/shannonmoeller/handlebars-wax) The missing Handlebars API. Effortless registration of data, partials, helpers, and decorators using file-system globs, modules, and plain-old JavaScript objects.
- [hbs](https://github.com/pillarjs/hbs) An Express.js view engine adapter for Handlebars.js, from Don Park.
- [html-bundler-webpack-plugin](https://github.com/webdiscus/html-bundler-webpack-plugin) The webpack plugin to compile templates, [supports Handlebars](https://github.com/webdiscus/html-bundler-webpack-plugin#using-template-handlebars).
- [incremental-bars](https://github.com/atomictag/incremental-bars) adds support for [incremental-dom](https://github.com/google/incremental-dom) as template target to Handlebars.
- [jQuery plugin](https://71104.github.io/jquery-handlebars/) allows you to use Handlebars.js with [jQuery](http://jquery.com/).
- [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
- [koa-hbs](https://github.com/jwilm/koa-hbs) [koa](https://github.com/koajs/koa) generator based renderer for Handlebars.js.
- [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
- [openVALIDATION](https://github.com/openvalidation/openvalidation) a natural language compiler for validation rules. Generates program code in Java, JavaScript, C#, Python and Rust with handlebars.
- [Plop](https://plopjs.com/) is a micro-generator framework that makes it easy to create files with a level of uniformity.
- [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
- [sammy.js](https://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
- [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
- [SproutCore](https://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
- [vite-plugin-handlebars](https://github.com/alexlafroscia/vite-plugin-handlebars) A package for Vite 2. Allows for running your HTML files through the Handlebars compiler.
- [YUI](https://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars.
## External Resources
- [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
Have a project using Handlebars? Send us a [pull request][pull-request]!
## License
Handlebars.js is released under the MIT license.
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
================================================
FILE: SECURITY.md
================================================
# Security Policy
We recommend always using the latest versions of Handlebars and its official companion libraries to ensure your application remains as secure as possible.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 5.0.x | :white_check_mark: |
| 4.7.x | :white_check_mark: |
| < 4.7 | :x: |
## Reporting a Vulnerability
To report a vulnerability, please visit https://github.com/handlebars-lang/handlebars.js/security.
================================================
FILE: bin/handlebars.mjs
================================================
#!/usr/bin/env node
import { createRequire } from 'node:module';
import yargs from 'yargs';
const require = createRequire(import.meta.url);
const Precompiler = require('../dist/cjs/precompiler');
const parser = yargs(process.argv.slice(2))
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...')
.help(false)
.version(false)
.option('f', {
type: 'string',
description: 'Output File',
alias: 'output',
})
.option('map', {
type: 'string',
description: 'Source Map File',
})
.option('a', {
type: 'boolean',
description: 'Exports amd style (require.js)',
alias: 'amd',
})
.option('c', {
type: 'string',
description: 'Exports CommonJS style, path to Handlebars module',
alias: 'commonjs',
default: null,
})
.option('h', {
type: 'string',
description: 'Path to handlebar.js (only valid for amd-style)',
alias: 'handlebarPath',
default: '',
})
.option('k', {
type: 'string',
description: 'Known helpers',
alias: 'known',
})
.option('o', {
type: 'boolean',
description: 'Known helpers only',
alias: 'knownOnly',
})
.option('m', {
type: 'boolean',
description: 'Minimize output',
alias: 'min',
})
.option('n', {
type: 'string',
description: 'Template namespace',
alias: 'namespace',
default: 'Handlebars.templates',
})
.option('s', {
type: 'boolean',
description: 'Output template function only.',
alias: 'simple',
})
.option('N', {
type: 'string',
description:
'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
alias: 'name',
})
.option('i', {
type: 'string',
description:
'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
alias: 'string',
})
.option('r', {
type: 'string',
description:
'Template root. Base value that will be stripped from template names.',
alias: 'root',
})
.option('p', {
type: 'boolean',
description: 'Compiling a partial template',
alias: 'partial',
})
.option('d', {
type: 'boolean',
description: 'Include data when compiling',
alias: 'data',
})
.option('e', {
type: 'string',
description: 'Template extension.',
alias: 'extension',
default: 'handlebars',
})
.option('b', {
type: 'boolean',
description:
'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
alias: 'bom',
})
.option('v', {
type: 'boolean',
description: 'Prints the current compiler version',
alias: 'version',
})
.option('help', {
type: 'boolean',
description: 'Outputs this message',
})
.wrap(120);
const argv = parser.parseSync();
argv.files = argv._;
delete argv._;
Precompiler.loadTemplates(argv, function (err, opts) {
if (err) {
throw err;
}
if (opts.help || (!opts.templates.length && !opts.version)) {
parser.showHelp('log');
} else {
// cli() is async (returns a Promise), so errors would become unhandled
// rejections. Re-throw via nextTick to surface them as uncaught exceptions.
Promise.resolve(Precompiler.cli(opts)).catch((error) => {
process.nextTick(() => {
throw error;
});
});
}
});
================================================
FILE: components/bower.json
================================================
{
"name": "handlebars",
"version": "5.0.0-alpha.1",
"main": "handlebars.js",
"license": "MIT",
"dependencies": {}
}
================================================
FILE: components/component.json
================================================
{
"name": "handlebars",
"repo": "components/handlebars.js",
"version": "1.0.0",
"main": "handlebars.js",
"scripts": ["handlebars.js"]
}
================================================
FILE: components/composer.json
================================================
{
"name": "components/handlebars.js",
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
"homepage": "http://handlebarsjs.com",
"license": "MIT",
"type": "component",
"keywords": [
"handlebars",
"mustache",
"html"
],
"authors": [
{
"name": "Chris Wanstrath",
"homepage": "http://chriswanstrath.com"
}
],
"require": {
"robloach/component-installer": "*"
},
"extra": {
"component": {
"name": "handlebars",
"scripts": [
"handlebars.js"
],
"files": [
"handlebars.runtime.js"
],
"shim": {
"exports": "Handlebars"
}
}
}
}
================================================
FILE: components/handlebars-source.gemspec
================================================
# -*- encoding: utf-8 -*-
require 'json'
package = JSON.parse(File.read('bower.json'))
Gem::Specification.new do |gem|
gem.name = "handlebars-source"
gem.authors = ["Yehuda Katz"]
gem.email = ["wycats@gmail.com"]
gem.date = Time.now.strftime("%Y-%m-%d")
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
gem.summary = %q{Handlebars.js source code wrapper}
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
gem.version = package["version"].sub "-", "."
gem.license = "MIT"
gem.files = [
'handlebars.js',
'handlebars.runtime.js',
'lib/handlebars/source.rb'
]
end
================================================
FILE: components/handlebars.js.nuspec
================================================
<?xml version="1.0"?>
<package>
<metadata>
<id>handlebars.js</id>
<version>5.0.0-alpha.1</version>
<authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Extension of the Mustache logicless template language</description>
<releaseNotes></releaseNotes>
<tags>handlebars mustache template html</tags>
</metadata>
<files>
<file src="handlebars.js" target="Content\Scripts" />
</files>
</package>
================================================
FILE: components/lib/handlebars/source.rb
================================================
module Handlebars
module Source
def self.bundled_path
File.expand_path("../../../handlebars.js", __FILE__)
end
def self.runtime_bundled_path
File.expand_path("../../../handlebars.runtime.js", __FILE__)
end
end
end
================================================
FILE: components/package.json
================================================
{
"name": "handlebars",
"version": "5.0.0-alpha.1",
"license": "MIT",
"jspm": {
"main": "handlebars",
"shim": {
"handlebars": {
"exports": "Handlebars"
}
},
"files": [
"handlebars.js",
"handlebars.runtime.js"
],
"buildConfig": {
"minify": true
}
}
}
================================================
FILE: docs/compiler-api.md
================================================
# Handlebars Compiler APIs
There are a number of formal APIs that tool implementors may interact with.
## AST
Other tools may interact with the formal AST as defined below. Any JSON structure matching this pattern may be used and passed into the `compile` and `precompile` methods in the same way as the text for a template.
AST structures may be generated either with the `Handlebars.parse` method and then manipulated, via the `Handlebars.AST` objects of the same name, or constructed manually as a generic JavaScript object matching the structure defined below.
```javascript
var ast = Handlebars.parse(myTemplate);
// Modify ast
Handlebars.precompile(ast);
```
### Parsing
There are two primary APIs that are used to parse an existing template into the AST:
#### parseWithoutProcessing
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
Example:
```js
let ast = Handlebars.parseWithoutProcessing(myTemplate);
```
#### parse
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
- Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
- Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
Example:
```js
let ast = Handlebars.parse(myTemplate);
```
### Basic
```java
interface Node {
type: string;
loc: SourceLocation | null;
}
interface SourceLocation {
source: string | null;
start: Position;
end: Position;
}
interface Position {
line: uint >= 1;
column: uint >= 0;
}
```
### Programs
```java
interface Program <: Node {
type: "Program";
body: [ Statement ];
blockParams: [ string ];
}
```
### Statements
```java
interface Statement <: Node { }
interface MustacheStatement <: Statement {
type: "MustacheStatement";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
escaped: boolean;
strip: StripFlags | null;
}
interface BlockStatement <: Statement {
type: "BlockStatement";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
program: Program | null;
inverse: Program | null;
openStrip: StripFlags | null;
inverseStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
interface PartialStatement <: Statement {
type: "PartialStatement";
name: PathExpression | SubExpression;
params: [ Expression ];
hash: Hash;
indent: string;
strip: StripFlags | null;
}
interface PartialBlockStatement <: Statement {
type: "PartialBlockStatement";
name: PathExpression | SubExpression;
params: [ Expression ];
hash: Hash;
program: Program | null;
indent: string;
openStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
```
`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
```java
interface ContentStatement <: Statement {
type: "ContentStatement";
value: string;
original: string;
}
interface CommentStatement <: Statement {
type: "CommentStatement";
value: string;
strip: StripFlags | null;
}
```
```java
interface Decorator <: Statement {
type: "Decorator";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
strip: StripFlags | null;
}
interface DecoratorBlock <: Statement {
type: "DecoratorBlock";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
program: Program | null;
openStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
```
Decorator paths only utilize the `path.original` value and as a consequence do not support depthed evaluation.
### Expressions
```java
interface Expression <: Node { }
```
##### SubExpressions
```java
interface SubExpression <: Expression {
type: "SubExpression";
path: PathExpression;
params: [ Expression ];
hash: Hash;
}
```
##### Paths
```java
interface PathExpression <: Expression {
type: "PathExpression";
data: boolean;
depth: uint >= 0;
parts: [ string ];
original: string;
}
```
- `data` is true when the given expression is a `@data` reference.
- `depth` is an integer representation of which context the expression references. `0` represents the current context, `1` would be `../`, etc.
- `parts` is an array of the names in the path. `foo.bar` would be `['foo', 'bar']`. Scope references, `.`, `..`, and `this` should be omitted from this array.
- `original` is the path as entered by the user. Separator and scope references are left untouched.
##### Literals
```java
interface Literal <: Expression { }
interface StringLiteral <: Literal {
type: "StringLiteral";
value: string;
original: string;
}
interface BooleanLiteral <: Literal {
type: "BooleanLiteral";
value: boolean;
original: boolean;
}
interface NumberLiteral <: Literal {
type: "NumberLiteral";
value: number;
original: number;
}
interface UndefinedLiteral <: Literal {
type: "UndefinedLiteral";
}
interface NullLiteral <: Literal {
type: "NullLiteral";
}
```
### Miscellaneous
```java
interface Hash <: Node {
type: "Hash";
pairs: [ HashPair ];
}
interface HashPair <: Node {
type: "HashPair";
key: string;
value: Expression;
}
interface StripFlags {
open: boolean;
close: boolean;
}
```
`StripFlags` are used to signify whitespace control character that may have been entered on a given statement.
## AST Visitor
`Handlebars.Visitor` is available as a base class for general interaction with AST structures. This will by default traverse the entire tree and individual methods may be overridden to provide specific responses to particular nodes.
Recording all referenced partial names:
```javascript
var Visitor = Handlebars.Visitor;
function ImportScanner() {
this.partials = [];
}
ImportScanner.prototype = new Visitor();
ImportScanner.prototype.PartialStatement = function (partial) {
this.partials.push({ request: partial.name.original });
Visitor.prototype.PartialStatement.call(this, partial);
};
var scanner = new ImportScanner();
scanner.accept(ast);
```
The current node's ancestors will be maintained in the `parents` array, with the most recent parent listed first.
The visitor may also be configured to operate in mutation mode by setting the `mutating` field to true. When in this mode, handler methods may return any valid AST node and it will replace the one they are currently operating on. Returning `false` will remove the given value (if valid) and returning `undefined` will leave the node intact. This return structure only apply to mutation mode and non-mutation mode visitors are free to return whatever values they wish.
Implementors that may need to support mutation mode are encouraged to utilize the `acceptKey`, `acceptRequired` and `acceptArray` helpers which provide the conditional overwrite behavior as well as implement sanity checks where pertinent.
## JavaScript Compiler
The `Handlebars.JavaScriptCompiler` object has a number of methods that may be customized to alter the output of the compiler:
- `nameLookup(parent, name, type)`
Used to generate the code to resolve a given path component.
- `parent` is the existing code in the path resolution
- `name` is the current path component
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
- `depthedLookup(name)`
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
- `compilerInfo()`
Allows for custom compiler flags used in the runtime version checking logic.
- `appendToBuffer(source, location, explicit)`
Allows for code buffer emitting code. Defaults behavior is string concatenation.
- `source` is the source code whose result is to be appending
- `location` is the location of the source in the source map.
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
- `initializeBuffer()`
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
### Example for the compiler api.
This example changes all lookups of properties are performed by a helper (`lookupLowerCase`) which looks for `test` if `{{Test}}` occurs in the template. This is just to illustrate how compiler behavior can be change.
There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it.
```javascript
function MyCompiler() {
Handlebars.JavaScriptCompiler.apply(this, arguments);
}
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
// Use this compile to compile BlockStatment-Blocks
MyCompiler.prototype.compiler = MyCompiler;
MyCompiler.prototype.nameLookup = function (parent, name, type) {
if (type === 'context') {
return this.source.functionCall('helpers.lookupLowerCase', '', [
parent,
JSON.stringify(name),
]);
} else {
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(
this,
parent,
name,
type
);
}
};
var env = Handlebars.create();
env.registerHelper('lookupLowerCase', function (parent, name) {
return parent[name.toLowerCase()];
});
env.JavaScriptCompiler = MyCompiler;
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
console.log(
template({
test: [{ value: 'a' }, { value: 'b' }, { value: 'c' }],
})
);
```
================================================
FILE: docs/decorators-api.md
================================================
# Decorators
**Decorators are deprecated, please join the discussion at [#1574](https://github.com/handlebars-lang/handlebars.js/issues/1574) to see what we can do about it.**
Decorators allow for blocks to be annotated with metadata or wrapped in functionality prior to execution of the block. This may be used to communicate with the containing helper or to set up a particular state in the system prior to running the block.
Decorators are registered through similar methods as helpers, `registerDecorators` and `unregisterDecorators`. These can then be referenced via the friendly name in the template using the `{{* decorator}}` and `{{#* decorator}}{/decorator}}` syntaxes. These syntaxes are derivatives of the normal mustache syntax and as such have all of the same argument and whitespace behaviors.
Decorators are executed when the block program is instantiated and are passed `(program, props, container, context, data, blockParams, depths)`.
- `program`: The block to wrap
- `props`: Object used to set metadata on the final function. Any values set on this object will be set on the function, regardless of if the original function is replaced or not. Metadata should be applied using this object as values applied to `program` may be masked by subsequent decorators that may wrap `program`.
- `container`: The current runtime container
- `context`: The current context. Since the decorator is run before the block that contains it, this is the parent context.
- `data`: The current `@data` values
- `blockParams`: The current block parameters stack
- `depths`: The current context stack
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
================================================
FILE: eslint.config.mjs
================================================
import compat from 'eslint-plugin-compat';
export default [
{
// Ignore everything except lib/
ignores: ['**', '!lib/**'],
},
{
// Only check browser API compat in the runtime library code.
// All other linting is handled by oxlint.
...compat.configs['flat/recommended'],
files: ['lib/**/*.js'],
linterOptions: {
reportUnusedDisableDirectives: 'off',
},
},
];
================================================
FILE: lib/handlebars/base.js
================================================
import { Exception } from '@handlebars/parser';
import { createFrame, extend, toString } from './utils';
import { registerDefaultHelpers } from './helpers';
import { registerDefaultDecorators } from './decorators';
import logger from './logger';
import { resetLoggedProperties } from './internal/proto-access';
export const VERSION = '4.7.7';
export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
export const REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '== 1.x.x',
5: '== 2.0.0-alpha.x',
6: '>= 2.0.0-beta.1',
7: '>= 4.0.0 <4.3.0',
8: '>= 4.3.0',
};
const objectType = '[object Object]';
export function HandlebarsEnvironment(helpers, partials, decorators) {
this.helpers = helpers || {};
this.partials = partials || {};
this.decorators = decorators || {};
registerDefaultHelpers(this);
registerDefaultDecorators(this);
}
HandlebarsEnvironment.prototype = {
constructor: HandlebarsEnvironment,
logger: logger,
log: logger.log,
registerHelper: function (name, fn) {
if (toString.call(name) === objectType) {
if (fn) {
throw new Exception('Arg not supported with multiple helpers');
}
extend(this.helpers, name);
} else {
this.helpers[name] = fn;
}
},
unregisterHelper: function (name) {
delete this.helpers[name];
},
registerPartial: function (name, partial) {
if (toString.call(name) === objectType) {
extend(this.partials, name);
} else {
if (typeof partial === 'undefined') {
throw new Exception(
`Attempting to register a partial called "${name}" as undefined`
);
}
this.partials[name] = partial;
}
},
unregisterPartial: function (name) {
delete this.partials[name];
},
registerDecorator: function (name, fn) {
if (toString.call(name) === objectType) {
if (fn) {
throw new Exception('Arg not supported with multiple decorators');
}
extend(this.decorators, name);
} else {
this.decorators[name] = fn;
}
},
unregisterDecorator: function (name) {
delete this.decorators[name];
},
/**
* Reset the memory of illegal property accesses that have already been logged.
* @deprecated should only be used in handlebars test-cases
*/
resetLoggedPropertyAccesses() {
resetLoggedProperties();
},
};
export let log = logger.log;
export { createFrame, logger };
================================================
FILE: lib/handlebars/compiler/ast.js
================================================
let AST = {
// Public API used to evaluate derived attributes regarding AST nodes
helpers: {
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
helperExpression: function (node) {
return (
node.type === 'SubExpression' ||
((node.type === 'MustacheStatement' ||
node.type === 'BlockStatement') &&
!!((node.params && node.params.length) || node.hash))
);
},
scopedId: function (path) {
return /^\.|this\b/.test(path.original);
},
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
simpleId: function (path) {
return (
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
);
},
},
};
// Must be exported as an object rather than the root of the module as the jison lexer
// must modify the object to operate properly.
export default AST;
================================================
FILE: lib/handlebars/compiler/code-gen.js
================================================
/* global define */
import { isArray } from '../utils';
let SourceNode;
try {
/* v8 ignore next */
if (typeof define !== 'function' || !define.amd) {
// We don't support this in AMD environments. For these environments, we assume that
// they are running on the browser and thus have no need for the source-map library.
// The variable indirection prevents bundlers from statically resolving and bundling
// source-map (which requires Node-built-ins). The stub SourceNode below handles
// browser/bundled usage. Bundlers may emit a "Critical dependency" warning — this
// is expected and harmless.
let mod = 'source-map';
let SourceMap = require(mod);
SourceNode = SourceMap.SourceNode;
}
// oxlint-disable-next-line no-unused-vars -- Babel 5 requires named catch param
} catch (err) {
/* NOP */
}
/* v8 ignore next -- tested but not covered due to dist build */
if (!SourceNode) {
SourceNode = function (line, column, srcFile, chunks) {
this.src = '';
if (chunks) {
this.add(chunks);
}
};
/* v8 ignore next */
SourceNode.prototype = {
add: function (chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src += chunks;
},
prepend: function (chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src = chunks + this.src;
},
toStringWithSourceMap: function () {
return { code: this.toString() };
},
toString: function () {
return this.src;
},
};
}
function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) {
let ret = [];
for (let i = 0, len = chunk.length; i < len; i++) {
ret.push(codeGen.wrap(chunk[i], loc));
}
return ret;
} else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
// Handle primitives that the SourceNode will throw up on
return chunk + '';
}
return chunk;
}
function CodeGen(srcFile) {
this.srcFile = srcFile;
this.source = [];
}
CodeGen.prototype = {
isEmpty() {
return !this.source.length;
},
prepend: function (source, loc) {
this.source.unshift(this.wrap(source, loc));
},
push: function (source, loc) {
this.source.push(this.wrap(source, loc));
},
merge: function () {
let source = this.empty();
this.each(function (line) {
source.add([' ', line, '\n']);
});
return source;
},
each: function (iter) {
for (let i = 0, len = this.source.length; i < len; i++) {
iter(this.source[i]);
}
},
empty: function () {
let loc = this.currentLocation || { start: {} };
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
},
wrap: function (chunk, loc = this.currentLocation || { start: {} }) {
if (chunk instanceof SourceNode) {
return chunk;
}
chunk = castChunk(chunk, this, loc);
return new SourceNode(
loc.start.line,
loc.start.column,
this.srcFile,
chunk
);
},
functionCall: function (fn, type, params) {
params = this.generateList(params);
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
},
quotedString: function (str) {
return (
'"' +
(str + '')
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') +
'"'
);
},
objectLiteral: function (obj) {
let pairs = [];
Object.keys(obj).forEach((key) => {
let value = castChunk(obj[key], this);
if (value !== 'undefined') {
pairs.push([this.quotedString(key), ':', value]);
}
});
let ret = this.generateList(pairs);
ret.prepend('{');
ret.add('}');
return ret;
},
generateList: function (entries) {
let ret = this.empty();
for (let i = 0, len = entries.length; i < len; i++) {
if (i) {
ret.add(',');
}
ret.add(castChunk(entries[i], this));
}
return ret;
},
generateArray: function (entries) {
let ret = this.generateList(entries);
ret.prepend('[');
ret.add(']');
return ret;
},
};
export default CodeGen;
================================================
FILE: lib/handlebars/compiler/compiler.js
================================================
import { Exception } from '@handlebars/parser';
import { isArray, indexOf, extend } from '../utils';
import AST from './ast';
const slice = [].slice;
export function Compiler() {}
// the foundHelper register will disambiguate helper lookup from finding a
// function in a context. This is necessary for mustache compatibility, which
// requires that context functions in blocks are evaluated by blockHelperMissing,
// and then proceed as if the resulting value was provided to blockHelperMissing.
Compiler.prototype = {
compiler: Compiler,
equals: function (other) {
let len = this.opcodes.length;
if (other.opcodes.length !== len) {
return false;
}
for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false;
}
}
// We know that length is the same between the two arrays because they are directly tied
// to the opcode behavior above.
len = this.children.length;
for (let i = 0; i < len; i++) {
if (!this.children[i].equals(other.children[i])) {
return false;
}
}
return true;
},
guid: 0,
compile: function (program, options) {
this.sourceNode = [];
this.opcodes = [];
this.children = [];
this.options = options;
options.blockParams = options.blockParams || [];
options.knownHelpers = extend(
Object.create(null),
{
helperMissing: true,
blockHelperMissing: true,
each: true,
if: true,
unless: true,
with: true,
log: true,
lookup: true,
},
options.knownHelpers
);
return this.accept(program);
},
compileProgram: function (program) {
let childCompiler = new this.compiler(),
result = childCompiler.compile(program, this.options),
guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial;
this.children[guid] = result;
this.useDepths = this.useDepths || result.useDepths;
return guid;
},
accept: function (node) {
/* v8 ignore next -- Sanity code */
if (!this[node.type]) {
throw new Exception('Unknown type: ' + node.type, node);
}
this.sourceNode.unshift(node);
let ret = this[node.type](node);
this.sourceNode.shift();
return ret;
},
Program: function (program) {
this.options.blockParams.unshift(program.blockParams);
let body = program.body,
bodyLength = body.length;
for (let i = 0; i < bodyLength; i++) {
this.accept(body[i]);
}
this.options.blockParams.shift();
this.isSimple = bodyLength === 1;
this.blockParams = program.blockParams ? program.blockParams.length : 0;
return this;
},
BlockStatement: function (block) {
transformLiteralToPath(block);
let program = block.program,
inverse = block.inverse;
program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse);
let type = this.classifySexpr(block);
if (type === 'helper') {
this.helperSexpr(block, program, inverse);
} else if (type === 'simple') {
this.simpleSexpr(block);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('blockValue', block.path.original);
} else {
this.ambiguousSexpr(block, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('ambiguousBlockValue');
}
this.opcode('append');
},
DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path;
this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original);
},
PartialStatement: function (partial) {
this.usePartial = true;
let program = partial.program;
if (program) {
program = this.compileProgram(partial.program);
}
let params = partial.params;
if (params.length > 1) {
throw new Exception(
'Unsupported number of partial arguments: ' + params.length,
partial
);
} else if (!params.length) {
if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined');
} else {
params.push({ type: 'PathExpression', parts: [], depth: 0 });
}
}
let partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) {
this.accept(partial.name);
}
this.setupFullMustacheParams(partial, program, undefined, true);
let indent = partial.indent || '';
if (this.options.preventIndent && indent) {
this.opcode('appendContent', indent);
indent = '';
}
this.opcode('invokePartial', isDynamic, partialName, indent);
this.opcode('append');
},
PartialBlockStatement: function (partialBlock) {
this.PartialStatement(partialBlock);
},
MustacheStatement: function (mustache) {
this.SubExpression(mustache);
if (mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
Decorator(decorator) {
this.DecoratorBlock(decorator);
},
ContentStatement: function (content) {
if (content.value) {
this.opcode('appendContent', content.value);
}
},
CommentStatement: function () {},
SubExpression: function (sexpr) {
transformLiteralToPath(sexpr);
let type = this.classifySexpr(sexpr);
if (type === 'simple') {
this.simpleSexpr(sexpr);
} else if (type === 'helper') {
this.helperSexpr(sexpr);
} else {
this.ambiguousSexpr(sexpr);
}
},
ambiguousSexpr: function (sexpr, program, inverse) {
let path = sexpr.path,
name = path.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', path.depth);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
path.strict = true;
this.accept(path);
this.opcode('invokeAmbiguous', name, isBlock);
},
simpleSexpr: function (sexpr) {
let path = sexpr.path;
path.strict = true;
this.accept(path);
this.opcode('resolvePossibleLambda');
},
helperSexpr: function (sexpr, program, inverse) {
let params = this.setupFullMustacheParams(sexpr, program, inverse),
path = sexpr.path,
name = path.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new Exception(
'You specified knownHelpersOnly, but used the unknown helper ' + name,
sexpr
);
} else {
path.strict = true;
path.falsy = true;
this.accept(path);
this.opcode(
'invokeHelper',
params.length,
path.original,
AST.helpers.simpleId(path)
);
}
},
PathExpression: function (path) {
this.addDepth(path.depth);
this.opcode('getContext', path.depth);
let name = path.parts[0],
scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
if (blockParamId) {
this.opcode('lookupBlockParam', blockParamId, path.parts);
} else if (!name) {
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
this.opcode('pushContext');
} else if (path.data) {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict);
} else {
this.opcode(
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
}
},
StringLiteral: function (string) {
this.opcode('pushString', string.value);
},
NumberLiteral: function (number) {
this.opcode('pushLiteral', number.value);
},
BooleanLiteral: function (bool) {
this.opcode('pushLiteral', bool.value);
},
UndefinedLiteral: function () {
this.opcode('pushLiteral', 'undefined');
},
NullLiteral: function () {
this.opcode('pushLiteral', 'null');
},
Hash: function (hash) {
let pairs = hash.pairs,
i = 0,
l = pairs.length;
this.opcode('pushHash');
for (; i < l; i++) {
this.pushParam(pairs[i].value);
}
while (i--) {
this.opcode('assignToHash', pairs[i].key);
}
this.opcode('popHash');
},
// HELPERS
opcode: function (name) {
this.opcodes.push({
opcode: name,
args: slice.call(arguments, 1),
loc: this.sourceNode[0].loc,
});
},
addDepth: function (depth) {
if (!depth) {
return;
}
this.useDepths = true;
},
classifySexpr: function (sexpr) {
let isSimple = AST.helpers.simpleId(sexpr.path);
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
let isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
let isEligible = !isBlockParam && (isHelper || isSimple);
// if ambiguous, we can possibly resolve the ambiguity now
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
if (isEligible && !isHelper) {
let name = sexpr.path.parts[0],
options = this.options;
if (options.knownHelpers[name]) {
isHelper = true;
} else if (options.knownHelpersOnly) {
isEligible = false;
}
}
if (isHelper) {
return 'helper';
} else if (isEligible) {
return 'ambiguous';
} else {
return 'simple';
}
},
pushParams: function (params) {
for (let i = 0, l = params.length; i < l; i++) {
this.pushParam(params[i]);
}
},
pushParam: function (val) {
this.accept(val);
},
setupFullMustacheParams: function (sexpr, program, inverse, omitEmpty) {
let params = sexpr.params;
this.pushParams(params);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
if (sexpr.hash) {
this.accept(sexpr.hash);
} else {
this.opcode('emptyHash', omitEmpty);
}
return params;
},
blockParamIndex: function (name) {
for (
let depth = 0, len = this.options.blockParams.length;
depth < len;
depth++
) {
let blockParams = this.options.blockParams[depth],
param = blockParams && indexOf(blockParams, name);
if (blockParams && param >= 0) {
return [depth, param];
}
}
},
};
export function precompile(input, options = {}, env) {
validateInput(input, options);
let environment = compileEnvironment(input, options, env);
return new env.JavaScriptCompiler().compile(environment, options);
}
export function compile(input, options = {}, env) {
options = extend({}, options);
validateInput(input, options);
let compiled;
function compileInput() {
let environment = compileEnvironment(input, options, env),
templateSpec = new env.JavaScriptCompiler().compile(
environment,
options,
undefined,
true
);
return env.template(templateSpec);
}
// Template is only compiled on first use and cached after that point.
return function (context, execOptions) {
if (!compiled) {
compiled = compileInput();
}
return compiled.call(this, context, execOptions);
};
}
function validateInput(input, options) {
if (
input == null ||
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
input
);
}
if (options.trackIds || options.stringParams) {
throw new Exception(
'TrackIds and stringParams are no longer supported. See Github #1145'
);
}
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}
}
function compileEnvironment(input, options, env) {
let ast = env.parse(input, options);
return new env.Compiler().compile(ast, options);
}
function argEquals(a, b) {
if (a === b) {
return true;
}
if (isArray(a) && isArray(b) && a.length === b.length) {
for (let i = 0; i < a.length; i++) {
if (!argEquals(a[i], b[i])) {
return false;
}
}
return true;
}
}
function transformLiteralToPath(sexpr) {
if (!sexpr.path.parts) {
let literal = sexpr.path;
// Casting to string here to make false and 0 literal values play nicely with the rest
// of the system.
sexpr.path = {
type: 'PathExpression',
data: false,
depth: 0,
parts: [literal.original + ''],
original: literal.original + '',
loc: literal.loc,
};
}
}
================================================
FILE: lib/handlebars/compiler/javascript-compiler.js
================================================
import { Exception } from '@handlebars/parser';
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
import { isArray } from '../utils';
import CodeGen from './code-gen';
function Literal(value) {
this.value = value;
}
function JavaScriptCompiler() {}
JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function (parent, name /*, type */) {
return this.internalNameLookup(parent, name);
},
depthedLookup: function (name) {
return [
this.aliasable('container.lookup'),
'(depths, ',
JSON.stringify(name),
')',
];
},
compilerInfo: function () {
const revision = COMPILER_REVISION,
versions = REVISION_CHANGES[revision];
return [revision, versions];
},
appendToBuffer: function (source, location, explicit) {
// Force a source as this simplifies the merge logic.
if (!isArray(source)) {
source = [source];
}
source = this.source.wrap(source, location);
if (this.environment.isSimple) {
return ['return ', source, ';'];
} else if (explicit) {
// This is a case where the buffer operation occurs as a child of another
// construct, generally braces. We have to explicitly output these buffer
// operations to ensure that the emitted code goes in the correct location.
return ['buffer += ', source, ';'];
} else {
source.appendToBuffer = true;
return source;
}
},
initializeBuffer: function () {
return this.quotedString('');
},
// END PUBLIC API
internalNameLookup: function (parent, name) {
this.lookupPropertyFunctionIsUsed = true;
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
},
lookupPropertyFunctionIsUsed: false,
compile: function (environment, options, context, asObject) {
this.environment = environment;
this.options = options;
this.precompile = !asObject;
this.name = this.environment.name;
this.isChild = !!context;
this.context = context || {
decorators: [],
programs: [],
environments: [],
};
this.preamble();
this.stackSlot = 0;
this.stackVars = [];
this.aliases = {};
this.registers = { list: [] };
this.hashes = [];
this.compileStack = [];
this.inlineStack = [];
this.blockParams = [];
this.compileChildren(environment, options);
this.useDepths =
this.useDepths ||
environment.useDepths ||
environment.useDecorators ||
this.options.compat;
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
let opcodes = environment.opcodes,
opcode,
firstLoc,
i,
l;
for (i = 0, l = opcodes.length; i < l; i++) {
opcode = opcodes[i];
this.source.currentLocation = opcode.loc;
firstLoc = firstLoc || opcode.loc;
this[opcode.opcode].apply(this, opcode.args);
}
// Flush any trailing content that might be pending.
this.source.currentLocation = firstLoc;
this.pushSource('');
/* v8 ignore next */
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
throw new Exception('Compile completed with content left on stack');
}
if (!this.decorators.isEmpty()) {
this.useDecorators = true;
this.decorators.prepend([
'var decorators = container.decorators, ',
this.lookupPropertyFunctionVarDeclaration(),
';\n',
]);
this.decorators.push('return fn;');
if (asObject) {
// eslint-disable-next-line no-new-func
this.decorators = Function.apply(this, [
'fn',
'props',
'container',
'depth0',
'data',
'blockParams',
'depths',
this.decorators.merge(),
]);
} else {
this.decorators.prepend(
'function(fn, props, container, depth0, data, blockParams, depths) {\n'
);
this.decorators.push('}\n');
this.decorators = this.decorators.merge();
}
} else {
this.decorators = undefined;
}
let fn = this.createFunctionContext(asObject);
if (!this.isChild) {
let ret = {
compiler: this.compilerInfo(),
main: fn,
};
if (this.decorators) {
ret.main_d = this.decorators;
ret.useDecorators = true;
}
let { programs, decorators } = this.context;
for (i = 0, l = programs.length; i < l; i++) {
if (programs[i]) {
ret[i] = programs[i];
if (decorators[i]) {
ret[i + '_d'] = decorators[i];
ret.useDecorators = true;
}
}
}
// Release AST/compiler references only needed during compilation for dedup
this.context.environments.length = 0;
if (this.environment.usePartial) {
ret.usePartial = true;
}
if (this.options.data) {
ret.useData = true;
}
if (this.useDepths) {
ret.useDepths = true;
}
if (this.useBlockParams) {
ret.useBlockParams = true;
}
if (this.options.compat) {
ret.compat = true;
}
if (!asObject) {
ret.compiler = JSON.stringify(ret.compiler);
this.source.currentLocation = { start: { line: 1, column: 0 } };
ret = this.objectLiteral(ret);
if (options.srcName) {
ret = ret.toStringWithSourceMap({ file: options.destName });
ret.map = ret.map && ret.map.toString();
} else {
ret = ret.toString();
}
} else {
ret.compilerOptions = this.options;
}
return ret;
} else {
return fn;
}
},
preamble: function () {
// track the last context pushed into place to allow skipping the
// getContext opcode when it would be a noop
this.lastContext = 0;
this.source = new CodeGen(this.options.srcName);
this.decorators = new CodeGen(this.options.srcName);
},
createFunctionContext: function (asObject) {
let varDeclarations = '';
let locals = this.stackVars.concat(this.registers.list);
if (locals.length > 0) {
varDeclarations += ', ' + locals.join(', ');
}
// Generate minimizer alias mappings
//
// When using true SourceNodes, this will update all references to the given alias
// as the source nodes are reused in situ. For the non-source node compilation mode,
// aliases will not be used, but this case is already being run on the client and
// we aren't concern about minimizing the template size.
let aliasCount = 0;
Object.keys(this.aliases).forEach((alias) => {
let node = this.aliases[alias];
if (node.children && node.referenceCount > 1) {
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
node.children[0] = 'alias' + aliasCount;
}
});
if (this.lookupPropertyFunctionIsUsed) {
varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration();
}
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
if (this.useBlockParams || this.useDepths) {
params.push('blockParams');
}
if (this.useDepths) {
params.push('depths');
}
// Perform a second pass over the output to merge content when possible
let source = this.mergeSource(varDeclarations);
if (asObject) {
params.push(source);
return Function.apply(this, params); // eslint-disable-line no-new-func
} else {
return this.source.wrap([
'function(',
params.join(','),
') {\n ',
source,
'}',
]);
}
},
mergeSource: function (varDeclarations) {
let isSimple = this.environment.isSimple,
appendOnly = !this.forceBuffer,
appendFirst,
sourceSeen,
bufferStart,
bufferEnd;
this.source.each((line) => {
if (line.appendToBuffer) {
if (bufferStart) {
line.prepend(' + ');
} else {
bufferStart = line;
}
bufferEnd = line;
} else {
if (bufferStart) {
if (!sourceSeen) {
appendFirst = true;
} else {
bufferStart.prepend('buffer += ');
}
bufferEnd.add(';');
bufferStart = bufferEnd = undefined;
}
sourceSeen = true;
if (!isSimple) {
appendOnly = false;
}
}
});
if (appendOnly) {
if (bufferStart) {
bufferStart.prepend('return ');
bufferEnd.add(';');
} else if (!sourceSeen) {
this.source.push('return "";');
}
} else {
varDeclarations +=
', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
if (bufferStart) {
bufferStart.prepend('return buffer + ');
bufferEnd.add(';');
} else {
this.source.push('return buffer;');
}
}
if (varDeclarations) {
this.source.prepend(
'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')
);
}
return this.source.merge();
},
lookupPropertyFunctionVarDeclaration: function () {
return `
lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
}
`.trim();
},
// [blockValue]
//
// On stack, before: hash, inverse, program, value
// On stack, after: return value of blockHelperMissing
//
// The purpose of this opcode is to take a block of the form
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
// replace it on the stack with the result of properly
// invoking blockHelperMissing.
blockValue: function (name) {
let blockHelperMissing = this.aliasable(
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params);
let blockName = this.popStack();
params.splice(1, 0, blockName);
this.push(this.source.functionCall(blockHelperMissing, 'call', params));
},
// [ambiguousBlockValue]
//
// On stack, before: hash, inverse, program, value
// Compiler value, before: lastHelper=value of last found helper, if any
// On stack, after, if no lastHelper: same as [blockValue]
// On stack, after, if lastHelper: value
ambiguousBlockValue: function () {
// We're being a bit cheeky and reusing the options value from the prior exec
let blockHelperMissing = this.aliasable(
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true);
this.flushInline();
let current = this.topStack();
params.splice(1, 0, current);
this.pushSource([
'if (!',
this.lastHelper,
') { ',
current,
' = ',
this.source.functionCall(blockHelperMissing, 'call', params),
'}',
]);
},
// [appendContent]
//
// On stack, before: ...
// On stack, after: ...
//
// Appends the string value of `content` to the current buffer
appendContent: function (content) {
if (this.pendingContent) {
content = this.pendingContent + content;
} else {
this.pendingLocation = this.source.currentLocation;
}
this.pendingContent = content;
},
// [append]
//
// On stack, before: value, ...
// On stack, after: ...
//
// Coerces `value` to a String and appends it to the current buffer.
//
// If `value` is truthy, or 0, it is coerced into a string and appended
// Otherwise, the empty string is appended
append: function () {
if (this.isInline()) {
this.replaceStack((current) => [' != null ? ', current, ' : ""']);
this.pushSource(this.appendToBuffer(this.popStack()));
} else {
let local = this.popStack();
this.pushSource([
'if (',
local,
' != null) { ',
this.appendToBuffer(local, undefined, true),
' }',
]);
if (this.environment.isSimple) {
this.pushSource([
'else { ',
this.appendToBuffer("''", undefined, true),
' }',
]);
}
}
},
// [appendEscaped]
//
// On stack, before: value, ...
// On stack, after: ...
//
// Escape `value` and append it to the buffer
appendEscaped: function () {
this.pushSource(
this.appendToBuffer([
this.aliasable('container.escapeExpression'),
'(',
this.popStack(),
')',
])
);
},
// [getContext]
//
// On stack, before: ...
// On stack, after: ...
// Compiler value, after: lastContext=depth
//
// Set the value of the `lastContext` compiler value to the depth
getContext: function (depth) {
this.lastContext = depth;
},
// [pushContext]
//
// On stack, before: ...
// On stack, after: currentContext, ...
//
// Pushes the value of the current context onto the stack.
pushContext: function () {
this.pushStackLiteral(this.contextName(this.lastContext));
},
// [lookupOnContext]
//
// On stack, before: ...
// On stack, after: currentContext[name], ...
//
// Looks up the value of `name` on the current context and pushes
// it onto the stack.
lookupOnContext: function (parts, falsy, strict, scoped) {
let i = 0;
if (!scoped && this.options.compat && !this.lastContext) {
// The depthed query is expected to handle the undefined logic for the root level that
// is implemented below, so we evaluate that directly in compat mode
this.push(this.depthedLookup(parts[i++]));
} else {
this.pushContext();
}
this.resolvePath('context', parts, i, falsy, strict);
},
// [lookupBlockParam]
//
// On stack, before: ...
// On stack, after: blockParam[name], ...
//
// Looks up the value of `parts` on the given block param and pushes
// it onto the stack.
lookupBlockParam: function (blockParamId, parts) {
this.useBlockParams = true;
this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
this.resolvePath('context', parts, 1);
},
// [lookupData]
//
// On stack, before: ...
// On stack, after: data, ...
//
// Push the data lookup operator
lookupData: function (depth, parts, strict) {
if (!depth) {
this.pushStackLiteral('data');
} else {
this.pushStackLiteral('container.data(data, ' + depth + ')');
}
this.resolvePath('data', parts, 0, true, strict);
},
resolvePath: function (type, parts, startPartIndex, falsy, strict) {
if (this.options.strict || this.options.assumeObjects) {
this.push(
strictLookup(
this.options.strict && strict,
this,
parts,
startPartIndex,
type
)
);
return;
}
let len = parts.length;
for (let i = startPartIndex; i < len; i++) {
/* eslint-disable no-loop-func */
this.replaceStack((current) => {
let lookup = this.nameLookup(current, parts[i], type);
// We want to ensure that zero and false are handled properly if the context (falsy flag)
// needs to have the special handling for these values.
if (!falsy) {
return [' != null ? ', lookup, ' : ', current];
} else {
// Otherwise we can use generic falsy handling
return [' && ', lookup];
}
});
/* eslint-enable no-loop-func */
}
},
// [resolvePossibleLambda]
//
// On stack, before: value, ...
// On stack, after: resolved value, ...
//
// If the `value` is a lambda, replace it on the stack by
// the return value of the lambda
resolvePossibleLambda: function () {
this.push([
this.aliasable('container.lambda'),
'(',
this.popStack(),
', ',
this.contextName(0),
')',
]);
},
emptyHash: function (omitEmpty) {
this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
},
pushHash: function () {
if (this.hash) {
this.hashes.push(this.hash);
}
this.hash = { values: {} };
},
popHash: function () {
let hash = this.hash;
this.hash = this.hashes.pop();
this.push(this.objectLiteral(hash.values));
},
// [pushString]
//
// On stack, before: ...
// On stack, after: quotedString(string), ...
//
// Push a quoted version of `string` onto the stack
pushString: function (string) {
this.pushStackLiteral(this.quotedString(string));
},
// [pushLiteral]
//
// On stack, before: ...
// On stack, after: value, ...
//
// Pushes a value onto the stack. This operation prevents
// the compiler from creating a temporary variable to hold
// it.
pushLiteral: function (value) {
this.pushStackLiteral(value);
},
// [pushProgram]
//
// On stack, before: ...
// On stack, after: program(guid), ...
//
// Push a program expression onto the stack. This takes
// a compile-time guid and converts it into a runtime-accessible
// expression.
pushProgram: function (guid) {
if (guid != null) {
this.pushStackLiteral(this.programExpression(guid));
} else {
this.pushStackLiteral(null);
}
},
// [registerDecorator]
//
// On stack, before: hash, program, params..., ...
// On stack, after: ...
//
// Pops off the decorator's parameters, invokes the decorator,
// and inserts the decorator into the decorators list.
registerDecorator(paramSize, name) {
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
options = this.setupHelperArgs(name, paramSize);
this.decorators.push([
'fn = ',
this.decorators.functionCall(foundDecorator, '', [
'fn',
'props',
'container',
options,
]),
' || fn;',
]);
},
// [invokeHelper]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
//
// Pops off the helper's parameters, invokes the helper,
// and pushes the helper's return value onto the stack.
//
// If the helper is not found, `helperMissing` is called.
invokeHelper: function (paramSize, name, isSimple) {
let nonHelper = this.popStack(),
helper = this.setupHelper(paramSize, name);
let possibleFunctionCalls = [];
if (isSimple) {
// direct call to helper
possibleFunctionCalls.push(helper.name);
}
// call a function from the input object
possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) {
possibleFunctionCalls.push(
this.aliasable('container.hooks.helperMissing')
);
}
let functionLookupCode = [
'(',
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
')',
];
let functionCall = this.source.functionCall(
functionLookupCode,
'call',
helper.callParams
);
this.push(functionCall);
},
itemsSeparatedBy: function (items, separator) {
let result = [];
result.push(items[0]);
for (let i = 1; i < items.length; i++) {
result.push(separator, items[i]);
}
return result;
},
// [invokeKnownHelper]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
//
// This operation is used when the helper is known to exist,
// so a `helperMissing` fallback is not required.
invokeKnownHelper: function (paramSize, name) {
let helper = this.setupHelper(paramSize, name);
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
},
// [invokeAmbiguous]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of disambiguation
//
// This operation is used when an expression like `{{foo}}`
// is provided, but we don't know at compile-time whether it
// is a helper or a path.
//
// This operation emits more code than the other options,
// and can be avoided by passing the `knownHelpers` and
// `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function (name, helperCall) {
this.useRegister('helper');
let nonHelper = this.popStack();
this.emptyHash();
let helper = this.setupHelper(0, name, helperCall);
let helperName = (this.lastHelper = this.nameLookup(
'helpers',
name,
'helper'
));
let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
if (!this.options.strict) {
lookup[0] = '(helper = ';
lookup.push(
' != null ? helper : ',
this.aliasable('container.hooks.helperMissing')
);
}
this.push([
'(',
lookup,
helper.paramsInit ? ['),(', helper.paramsInit] : [],
'),',
'(typeof helper === ',
this.aliasable('"function"'),
' ? ',
this.source.functionCall('helper', 'call', helper.callParams),
' : helper))',
]);
},
// [invokePartial]
//
// On stack, before: context, ...
// On stack after: result of partial invocation
//
// This operation pops off a context, invokes a partial with that context,
// and pushes the result of the invocation back.
invokePartial: function (isDynamic, name, indent) {
let params = [],
options = this.setupParams(name, 1, params);
if (isDynamic) {
name = this.popStack();
delete options.name;
}
if (indent) {
options.indent = JSON.stringify(indent);
}
options.helpers = 'helpers';
options.partials = 'partials';
options.decorators = 'container.decorators';
if (!isDynamic) {
params.unshift(this.nameLookup('partials', name, 'partial'));
} else {
params.unshift(name);
}
if (this.options.compat) {
options.depths = 'depths';
}
options = this.objectLiteral(options);
params.push(options);
this.push(this.source.functionCall('container.invokePartial', '', params));
},
// [assignToHash]
//
// On stack, before: value, ..., hash, ...
// On stack, after: ..., hash, ...
//
// Pops a value off the stack and assigns it to the current hash
assignToHash: function (key) {
this.hash.values[key] = this.popStack();
},
// HELPERS
compiler: JavaScriptCompiler,
compileChildren: function (environment, options) {
let children = environment.children,
child,
compiler;
for (let i = 0, l = children.length; i < l; i++) {
child = children[i];
compiler = new this.compiler();
let existing = this.matchExistingProgram(child);
if (existing == null) {
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
let index = this.context.programs.length;
child.index = index;
child.name = 'program' + index;
this.context.programs[index] = compiler.compile(
child,
options,
this.context,
!this.precompile
);
this.context.decorators[index] = compiler.decorators;
this.context.environments[index] = child;
this.useDepths = this.useDepths || compiler.useDepths;
this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
child.useDepths = this.useDepths;
child.useBlockParams = this.useBlockParams;
} else {
child.index = existing.index;
child.name = 'program' + existing.index;
this.useDepths = this.useDepths || existing.useDepths;
this.useBlockParams = this.useBlockParams || existing.useBlockParams;
}
}
},
matchExistingProgram: function (child) {
for (let i = 0, len = this.context.environments.length; i < len; i++) {
let environment = this.context.environments[i];
if (environment && environment.equals(child)) {
return environment;
}
}
},
programExpression: function (guid) {
let child = this.environment.children[guid],
programParams = [child.index, 'data', child.blockParams];
if (this.useBlockParams || this.useDepths) {
programParams.push('blockParams');
}
if (this.useDepths) {
programParams.push('depths');
}
return 'container.program(' + programParams.join(', ') + ')';
},
useRegister: function (name) {
if (!this.registers[name]) {
this.registers[name] = true;
this.registers.list.push(name);
}
},
push: function (expr) {
if (!(expr instanceof Literal)) {
expr = this.source.wrap(expr);
}
this.inlineStack.push(expr);
return expr;
},
pushStackLiteral: function (item) {
this.push(new Literal(item));
},
pushSource: function (source) {
if (this.pendingContent) {
this.source.push(
this.appendToBuffer(
this.source.quotedString(this.pendingContent),
this.pendingLocation
)
);
this.pendingContent = undefined;
}
if (source) {
this.source.push(source);
}
},
replaceStack: function (callback) {
let prefix = ['('],
stack,
createdStack,
usedLiteral;
/* v8 ignore next */
if (!this.isInline()) {
throw new Exception('replaceStack on non-inline');
}
// We want to merge the inline statement into the replacement statement via ','
let top = this.popStack(true);
if (top instanceof Literal) {
// Literals do not need to be inlined
stack = [top.value];
prefix = ['(', stack];
usedLiteral = true;
} else {
// Get or create the current stack name for use by the inline
createdStack = true;
let name = this.incrStack();
prefix = ['((', this.push(name), ' = ', top, ')'];
stack = this.topStack();
}
let item = callback.call(this, stack);
if (!usedLiteral) {
this.popStack();
}
if (createdStack) {
this.stackSlot--;
}
this.push(prefix.concat(item, ')'));
},
incrStack: function () {
this.stackSlot++;
if (this.stackSlot > this.stackVars.length) {
this.stackVars.push('stack' + this.stackSlot);
}
return this.topStackName();
},
topStackName: function () {
return 'stack' + this.stackSlot;
},
flushInline: function () {
let inlineStack = this.inlineStack;
this.inlineStack = [];
for (let i = 0, len = inlineStack.length; i < len; i++) {
let entry = inlineStack[i];
/* v8 ignore next */
if (entry instanceof Literal) {
this.compileStack.push(entry);
} else {
let stack = this.incrStack();
this.pushSource([stack, ' = ', entry, ';']);
this.compileStack.push(stack);
}
}
},
isInline: function () {
return this.inlineStack.length;
},
popStack: function (wrapped) {
let inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop();
if (!wrapped && item instanceof Literal) {
return item.value;
} else {
if (!inline) {
/* v8 ignore next */
if (!this.stackSlot) {
throw new Exception('Invalid stack pop');
}
this.stackSlot--;
}
return item;
}
},
topStack: function () {
let stack = this.isInline() ? this.inlineStack : this.compileStack,
item = stack[stack.length - 1];
/* v8 ignore next */
if (item instanceof Literal) {
return item.value;
} else {
return item;
}
},
contextName: function (context) {
if (this.useDepths && context) {
return 'depths[' + context + ']';
} else {
return 'depth' + context;
}
},
quotedString: function (str) {
return this.source.quotedString(str);
},
objectLiteral: function (obj) {
return this.source.objectLiteral(obj);
},
aliasable: function (name) {
let ret = this.aliases[name];
if (ret) {
ret.referenceCount++;
return ret;
}
ret = this.aliases[name] = this.source.wrap(name);
ret.aliasable = true;
ret.referenceCount = 1;
return ret;
},
setupHelper: function (paramSize, name, blockHelper) {
let params = [],
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
let foundHelper = this.nameLookup('helpers', name, 'helper'),
callContext = this.aliasable(
`${this.contextName(0)} != null ? ${this.contextName(
0
)} : (container.nullContext || {})`
);
return {
params: params,
paramsInit: paramsInit,
name: foundHelper,
callParams: [callContext].concat(params),
};
},
setupParams: function (helper, paramSize, params) {
let options = {},
objectArgs = !params,
param;
if (objectArgs) {
params = [];
}
options.name = this.quotedString(helper);
options.hash = this.popStack();
let inverse = this.popStack(),
program = this.popStack();
// Avoid setting fn and inverse if neither are set. This allows
// helpers to do a check for `if (options.fn)`
if (program || inverse) {
options.fn = program || 'container.noop';
options.inverse = inverse || 'container.noop';
}
// The parameters go on to the stack in order (making sure that they are evaluated in order)
// so we need to pop them off the stack in reverse order
let i = paramSize;
while (i--) {
param = this.popStack();
params[i] = param;
}
if (objectArgs) {
options.args = this.source.generateArray(params);
}
if (this.options.data) {
options.data = 'data';
}
if (this.useBlockParams) {
options.blockParams = 'blockParams';
}
return options;
},
setupHelperArgs: function (helper, paramSize, params, useRegister) {
let options = this.setupParams(helper, paramSize, params);
options.loc = JSON.stringify(this.source.currentLocation);
options = this.objectLiteral(options);
if (useRegister) {
this.useRegister('options');
params.push('options');
return ['options=', options];
} else if (params) {
params.push(options);
return '';
} else {
return options;
}
},
};
(function () {
const reservedWords = (
'break else new var' +
' case finally return void' +
' catch for switch while' +
' continue function this with' +
' default if throw' +
' delete in try' +
' do instanceof typeof' +
' abstract enum int short' +
' boolean export interface static' +
' byte extends long super' +
' char final native synchronized' +
' class float package throws' +
' const goto private transient' +
' debugger implements protected volatile' +
' double import public let yield await' +
' null true false'
).split(' ');
const compilerWords = (JavaScriptCompiler.RESERVED_WORDS = {});
for (let i = 0, l = reservedWords.length; i < l; i++) {
compilerWords[reservedWords[i]] = true;
}
})();
/**
* @deprecated May be removed in the next major version
*/
JavaScriptCompiler.isValidJavaScriptVariableName = function (name) {
return (
!JavaScriptCompiler.RESERVED_WORDS[name] &&
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
);
};
function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
let stack = compiler.popStack(),
len = parts.length;
if (requireTerminal) {
len--;
}
for (let i = startPartIndex; i < len; i++) {
stack = compiler.nameLookup(stack, parts[i], type);
}
if (requireTerminal) {
return [
compiler.aliasable('container.strict'),
'(',
stack,
', ',
compiler.quotedString(parts[len]),
', ',
JSON.stringify(compiler.source.currentLocation),
' )',
];
} else {
return stack;
}
}
export default JavaScriptCompiler;
================================================
FILE: lib/handlebars/decorators/inline.js
================================================
import { extend } from '../utils';
export default function (instance) {
instance.registerDecorator(
'inline',
function (fn, props, container, options) {
let ret = fn;
if (!props.partials) {
props.partials = {};
ret = function (context, options) {
// Create a new partials stack frame prior to exec.
let original = container.partials;
container.partials = extend({}, original, props.partials);
let ret = fn(context, options);
container.partials = original;
return ret;
};
}
props.partials[options.args[0]] = options.fn;
return ret;
}
);
}
================================================
FILE: lib/handlebars/decorators.js
================================================
import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) {
registerInline(instance);
}
================================================
FILE: lib/handlebars/helpers/block-helper-missing.js
================================================
import { isArray } from '../utils';
export default function (instance) {
instance.registerHelper('blockHelperMissing', function (context, options) {
let inverse = options.inverse,
fn = options.fn;
if (context === true) {
return fn(this);
} else if (context === false || context == null) {
return inverse(this);
} else if (isArray(context)) {
if (context.length > 0) {
return instance.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
return fn(context, options);
}
});
}
================================================
FILE: lib/handlebars/helpers/each.js
================================================
import { Exception } from '@handlebars/parser';
import { createFrame, isArray, isFunction, isMap, isSet } from '../utils';
export default function (instance) {
instance.registerHelper('each', function (context, options) {
if (!options) {
throw new Exception('Must pass iterator to #each');
}
let fn = options.fn,
inverse = options.inverse,
i = 0,
ret = '',
data;
if (isFunction(context)) {
context = context.call(this);
}
if (options.data) {
data = createFrame(options.data);
}
function execIteration(field, value, index, last) {
if (data) {
data.key = field;
data.index = index;
data.first = index === 0;
data.last = !!last;
}
ret =
ret +
fn(value, {
data: data,
blockParams: [context[field], field],
});
}
if (context && typeof context === 'object') {
if (isArray(context)) {
for (let j = context.length; i < j; i++) {
if (i in context) {
execIteration(i, context[i], i, i === context.length - 1);
}
}
} else if (isMap(context)) {
const j = context.size;
for (const [key, value] of context) {
execIteration(key, value, i++, i === j);
}
} else if (isSet(context)) {
const j = context.size;
for (const value of context) {
execIteration(i, value, i++, i === j);
}
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
const newContext = [];
const iterator = context[Symbol.iterator]();
for (let it = iterator.next(); !it.done; it = iterator.next()) {
newContext.push(it.value);
}
context = newContext;
for (let j = context.length; i < j; i++) {
execIteration(i, context[i], i, i === context.length - 1);
}
} else {
let priorKey;
Object.keys(context).forEach((key) => {
// We're running the iterations one step out of sync so we can detect
// the last iteration without have to scan the object twice and create
// an intermediate keys array.
if (priorKey !== undefined) {
execIteration(priorKey, context[priorKey], i - 1);
}
priorKey = key;
i++;
});
if (priorKey !== undefined) {
execIteration(priorKey, context[priorKey], i - 1, true);
}
}
}
if (i === 0) {
ret = inverse(this);
}
return ret;
});
}
================================================
FILE: lib/handlebars/helpers/helper-missing.js
================================================
import { Exception } from '@handlebars/parser';
export default function (instance) {
instance.registerHelper('helperMissing', function (/* [args, ]options */) {
if (arguments.length === 1) {
// A missing field in a {{foo}} construct.
return undefined;
} else {
// Someone is actually trying to call something, blow up.
throw new Exception(
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
);
}
});
}
================================================
FILE: lib/handlebars/helpers/if.js
================================================
import { Exception } from '@handlebars/parser';
import { isEmpty, isFunction } from '../utils';
export default function (instance) {
instance.registerHelper('if', function (conditional, options) {
if (arguments.length != 2) {
throw new Exception('#if requires exactly one argument');
}
if (isFunction(conditional)) {
conditional = conditional.call(this);
}
// Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the conditional as purely not empty based on the
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function (conditional, options) {
if (arguments.length != 2) {
throw new Exception('#unless requires exactly one argument');
}
return instance.helpers['if'].call(this, conditional, {
fn: options.inverse,
inverse: options.fn,
hash: options.hash,
});
});
}
================================================
FILE: lib/handlebars/helpers/log.js
================================================
export default function (instance) {
instance.registerHelper('log', function (/* message, options */) {
let args = [undefined],
options = arguments[arguments.length - 1];
for (let i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}
let level = 1;
if (options.hash.level != null) {
level = options.hash.level;
} else if (options.data && options.data.level != null) {
level = options.data.level;
}
args[0] = level;
instance.log(...args);
});
}
================================================
FILE: lib/handlebars/helpers/lookup.js
================================================
export default function (instance) {
instance.registerHelper('lookup', function (obj, field, options) {
if (!obj) {
// Note for 5.0: Change to "obj == null" in 5.0
return obj;
}
return options.lookupProperty(obj, field);
});
}
================================================
FILE: lib/handlebars/helpers/with.js
================================================
import { Exception } from '@handlebars/parser';
import { isEmpty, isFunction } from '../utils';
export default function (instance) {
instance.registerHelper('with', function (context, options) {
if (arguments.length != 2) {
throw new Exception('#with requires exactly one argument');
}
if (isFunction(context)) {
context = context.call(this);
}
let fn = options.fn;
if (!isEmpty(context)) {
let data = options.data;
return fn(context, {
data: data,
blockParams: [context],
});
} else {
return options.inverse(this);
}
});
}
================================================
FILE: lib/handlebars/helpers.js
================================================
import registerBlockHelperMissing from './helpers/block-helper-missing';
import registerEach from './helpers/each';
import registerHelperMissing from './helpers/helper-missing';
import registerIf from './helpers/if';
import registerLog from './helpers/log';
import registerLookup from './helpers/lookup';
import registerWith from './helpers/with';
export function registerDefaultHelpers(instance) {
registerBlockHelperMissing(instance);
registerEach(instance);
registerHelperMissing(instance);
registerIf(instance);
registerLog(instance);
registerLookup(instance);
registerWith(instance);
}
export function moveHelperToHooks(instance, helperName, keepHelper) {
if (instance.helpers[helperName]) {
instance.hooks[helperName] = instance.helpers[helperName];
if (!keepHelper) {
// Using delete is slow
instance.helpers[helperName] = undefined;
}
}
}
================================================
FILE: lib/handlebars/internal/proto-access.js
================================================
import { extend } from '../utils';
import logger from '../logger';
const loggedProperties = Object.create(null);
export function createProtoAccessControl(runtimeOptions) {
// Create an object with "null"-prototype to avoid truthy results on
// prototype properties.
const propertyWhiteList = Object.create(null);
// eslint-disable-next-line no-proto
propertyWhiteList['__proto__'] = false;
extend(propertyWhiteList, runtimeOptions.allowedProtoProperties);
const methodWhiteList = Object.create(null);
methodWhiteList['constructor'] = false;
methodWhiteList['__defineGetter__'] = false;
methodWhiteList['__defineSetter__'] = false;
methodWhiteList['__lookupGetter__'] = false;
extend(methodWhiteList, runtimeOptions.allowedProtoMethods);
return {
properties: {
whitelist: propertyWhiteList,
defaultValue: runtimeOptions.allowProtoPropertiesByDefault,
},
methods: {
whitelist: methodWhiteList,
defaultValue: runtimeOptions.allowProtoMethodsByDefault,
},
};
}
export function resultIsAllowed(result, protoAccessControl, propertyName) {
if (typeof result === 'function') {
return checkWhiteList(protoAccessControl.methods, propertyName);
} else {
return checkWhiteList(protoAccessControl.properties, propertyName);
}
}
function checkWhiteList(protoAccessControlForType, propertyName) {
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
return protoAccessControlForType.whitelist[propertyName] === true;
}
if (protoAccessControlForType.defaultValue !== undefined) {
return protoAccessControlForType.defaultValue;
}
logUnexpectedPropertyAccessOnce(propertyName);
return false;
}
function logUnexpectedPropertyAccessOnce(propertyName) {
if (loggedProperties[propertyName] !== true) {
loggedProperties[propertyName] = true;
logger.log(
'error',
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
`You can add a runtime option to disable the check or this warning:\n` +
`See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`
);
}
}
export function resetLoggedProperties() {
Object.keys(loggedProperties).forEach((propertyName) => {
delete loggedProperties[propertyName];
});
}
================================================
FILE: lib/handlebars/internal/wrapHelper.js
================================================
export function wrapHelper(helper, transformOptionsFn) {
if (typeof helper !== 'function') {
// This should not happen, but apparently it does in https://github.com/handlebars-lang/handlebars.js/issues/1639
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
return helper;
}
let wrapper = function (/* dynamic arguments */) {
const options = arguments[arguments.length - 1];
arguments[arguments.length - 1] = transformOptionsFn(options);
return helper.apply(this, arguments);
};
return wrapper;
}
================================================
FILE: lib/handlebars/logger.js
================================================
import { indexOf } from './utils';
let logger = {
methodMap: ['debug', 'info', 'warn', 'error'],
level: 'info',
// Maps a given level value to the `methodMap` indexes above.
lookupLevel: function (level) {
if (typeof level === 'string') {
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
if (levelMap >= 0) {
level = levelMap;
} else {
level = parseInt(level, 10);
}
}
return level;
},
// Can be overridden in the host environment
log: function (level, ...message) {
level = logger.lookupLevel(level);
if (
typeof console !== 'undefined' &&
logger.lookupLevel(logger.level) <= level
) {
let method = logger.methodMap[level];
// eslint-disable-next-line no-console
if (!console[method]) {
method = 'log';
}
console[method](...message); // eslint-disable-line no-console
}
},
};
export default logger;
================================================
FILE: lib/handlebars/no-conflict.js
================================================
export default function (Handlebars) {
let $Handlebars = globalThis.Handlebars;
/* v8 ignore next */
Handlebars.noConflict = function () {
if (globalThis.Handlebars === Handlebars) {
globalThis.Handlebars = $Handlebars;
}
return Handlebars;
};
}
================================================
FILE: lib/handlebars/runtime.js
================================================
import { Exception } from '@handlebars/parser';
import * as Utils from './utils';
import {
COMPILER_REVISION,
createFrame,
LAST_COMPATIBLE_COMPILER_REVISION,
REVISION_CHANGES,
} from './base';
import { moveHelperToHooks } from './helpers';
import { wrapHelper } from './internal/wrapHelper';
import {
createProtoAccessControl,
resultIsAllowed,
} from './internal/proto-access';
export function checkRevision(compilerInfo) {
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
currentRevision = COMPILER_REVISION;
if (
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
compilerRevision <= COMPILER_REVISION
) {
return;
}
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
const runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception(
'Template was precompiled with an older version of Handlebars than the current runtime. ' +
'Please update your precompiler to a newer version (' +
runtimeVersions +
') or downgrade your runtime to an older version (' +
compilerVersions +
').'
);
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception(
'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
'Please update your runtime to a newer version (' +
compilerInfo[1] +
').'
);
}
}
export function template(templateSpec, env) {
/* v8 ignore next */
if (!env) {
throw new Exception('No environment passed to template');
}
if (!templateSpec || !templateSpec.main) {
throw new Exception('Unknown template object: ' + typeof templateSpec);
}
templateSpec.main.decorator = templateSpec.main_d;
// Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as pseudo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
const templateWasPrecompiledWithCompilerV7 =
templateSpec.compiler && templateSpec.compiler[0] === 7;
function invokePartialWrapper(partial, context, options) {
if (options.hash) {
context = Utils.extend({}, context, options.hash);
}
partial = env.VM.resolvePartial.call(this, partial, context, options);
options.hooks = this.hooks;
options.protoAccessControl = this.protoAccessControl;
let result = env.VM.invokePartial.call(this, partial, context, options);
if (result == null && env.compile) {
options.partials[options.name] = env.compile(
partial,
templateSpec.compilerOptions,
env
);
result = options.partials[options.name](context, options);
}
if (result != null) {
if (options.indent) {
let lines = result.split('\n');
for (let i = 0, l = lines.length; i < l; i++) {
if (!lines[i] && i + 1 === l) {
break;
}
lines[i] = options.indent + lines[i];
}
result = lines.join('\n');
}
return result;
} else {
throw new Exception(
'The partial ' +
options.name +
' could not be compiled when running in runtime-only mode'
);
}
}
// Just add water
let container = {
strict: function (obj, name, loc) {
if (!obj || !(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj, {
loc: loc,
});
}
return container.lookupProperty(obj, name);
},
lookupProperty: function (parent, propertyName) {
if (Utils.isMap(parent)) {
return parent.get(propertyName);
}
let result = parent[propertyName];
if (result == null) {
return result;
}
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return result;
}
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
return result;
}
return undefined;
},
lookup: function (depths, name) {
const len = depths.length;
for (let i = 0; i < len; i++) {
let result = depths[i] && container.lookupProperty(depths[i], name);
if (result != null) {
return depths[i][name];
}
}
},
lambda: function (current, context) {
return typeof current === 'function' ? current.call(context) : current;
},
escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper,
fn: function (i) {
let ret = templateSpec[i];
ret.decorator = templateSpec[i + '_d'];
return ret;
},
programs: [],
program: function (i, data, declaredBlockParams, blockParams, depths) {
let programWrapper = this.programs[i],
fn = this.fn(i);
if (data || depths || blockParams || declaredBlockParams) {
programWrapper = wrapProgram(
this,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
);
} else if (!programWrapper) {
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
}
return programWrapper;
},
data: function (value, depth) {
while (value && depth--) {
value = value._parent;
}
return value;
},
mergeIfNeeded: function (param, common) {
let obj = param || common;
if (param && common && param !== common) {
obj = Utils.extend({}, common, param);
}
return obj;
},
// An empty object to use as replacement for null-contexts
nullContext: Object.seal({}),
noop: env.VM.noop,
compilerInfo: templateSpec.compiler,
};
function ret(context, options = {}) {
let data = options.data;
_setup(options);
if (!options.partial && templateSpec.useData) {
data = initData(context, data);
}
let depths,
blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) {
if (options.depths) {
depths =
context != options.depths[0]
? [context].concat(options.depths)
: options.depths;
} else {
depths = [context];
}
}
function main(context /*, options*/) {
return (
'' +
templateSpec.main(
container,
context,
container.helpers,
container.partials,
data,
blockParams,
depths
)
);
}
main = executeDecorators(
templateSpec.main,
main,
container,
options.depths || [],
data,
blockParams
);
return main(context, options);
}
ret.isTop = true;
function _setup(options) {
if (!options.partial) {
let mergedHelpers = {};
addHelpers(mergedHelpers, env.helpers, container);
addHelpers(mergedHelpers, options.helpers, container);
container.helpers = mergedHelpers;
if (templateSpec.usePartial) {
// Use mergeIfNeeded here to prevent compiling global partials multiple times
container.partials = container.mergeIfNeeded(
options.partials,
env.partials
);
}
if (templateSpec.usePartial || templateSpec.useDecorators) {
container.decorators = Utils.extend(
{},
env.decorators,
options.decorators
);
}
container.hooks = {};
container.protoAccessControl = createProtoAccessControl(options);
let keepHelperInHelpers =
options.allowCallsToHelperMissing ||
templateWasPrecompiledWithCompilerV7;
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
} else {
container.protoAccessControl = options.protoAccessControl; // internal option
container.helpers = options.helpers;
container.partials = options.partials;
container.decorators = options.decorators;
container.hooks = options.hooks;
}
}
return ret;
}
export function wrapProgram(
container,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
) {
function prog(context, options = {}) {
let currentDepths = depths;
if (
depths &&
context != depths[0] &&
!(context === container.nullContext && depths[0] === null)
) {
currentDepths = [context].concat(depths);
}
return fn(
container,
context,
container.helpers,
container.partials,
options.data || data,
blockParams && [options.blockParams].concat(blockParams),
currentDepths
);
}
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
prog.program = i;
prog.depth = depths ? depths.length : 0;
prog.blockParams = declaredBlockParams || 0;
return prog;
}
/**
* This is currently part of the official API, therefore implementation details should not be changed.
*/
export function resolvePartial(partial, context, options) {
if (!partial) {
if (options.name === '@partial-block') {
partial = options.data['partial-block'];
} else {
partial = options.partials[options.name];
}
} else if (!partial.call && !options.name) {
// This is a dynamic partial that returned a string
options.name = partial;
partial = options.partials[partial];
}
return partial;
}
export function invokePartial(partial, context, options) {
// Use the current closure context to save the partial-block if this partial
const currentPartialBlock = options.data && options.data['partial-block'];
options.partial = true;
let partialBlock;
if (options.fn && options.fn !== noop) {
options.data = createFrame(options.data);
// Wrapper function to get access to currentPartialBlock from the closure
let fn = options.fn;
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
context,
options = {}
) {
// Restore the partial-block from the closure for the execution of the block
// i.e. the part inside the block of the partial call.
options.data = createFrame(options.data);
options.data['partial-block'] = currentPartialBlock;
return fn(context, options);
};
if (fn.partials) {
options.partials = Utils.extend({}, options.partials, fn.partials);
}
}
if (partial === undefined && partialBlock) {
partial = partialBlock;
}
if (partial === undefined) {
throw new Exception(
'The partial "' + options.name + '" could not be found'
);
} else if (partial instanceof Function) {
return partial(context, options);
}
}
export function noop() {
return '';
}
function initData(context, data) {
if (!data || !('root' in data)) {
data = data ? createFrame(data) : {};
data.root = context;
}
return data;
}
function executeDecorators(fn, prog, container, depths, data, blockParams) {
if (fn.decorator) {
let props = {};
prog = fn.decorator(
prog,
props,
container,
depths && depths[0],
data,
blockParams,
depths
);
Utils.extend(prog, props);
}
return prog;
}
function addHelpers(mergedHelpers, helpers, container) {
if (!helpers) return;
Object.keys(helpers).forEach((helperName) => {
let helper = helpers[helperName];
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
});
}
function passLookupPropertyOption(helper, container) {
const lookupProperty = container.lookupProperty;
return wrapHelper(helper, (options) => {
options.lookupProperty = lookupProperty;
return options;
});
}
================================================
FILE: lib/handlebars/safe-string.js
================================================
// Build out our basic SafeString type
function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
return '' + this.string;
};
export default SafeString;
================================================
FILE: lib/handlebars/utils.js
================================================
const escape = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`',
'=': '=',
};
const badChars = /[&<>"'`=]/g,
possible = /[&<>"'`=]/;
function escapeChar(chr) {
return escape[chr];
}
export function extend(obj /* , ...source */) {
for (let i = 1; i < arguments.length; i++) {
for (let key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
obj[key] = arguments[i][key];
}
}
}
return obj;
}
export let toString = Object.prototype.toString;
// Sourced from lodash
// https://github.com/lodash/lodash/blob/4.17.21/LICENSE
export function isFunction(value) {
return typeof value === 'function';
}
function testTag(name) {
const tag = '[object ' + name + ']';
return function (value) {
return value && typeof value === 'object'
? toString.call(value) === tag
: false;
};
}
export const isArray = Array.isArray;
export const isMap = testTag('Map');
export const isSet = testTag('Set');
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
export function indexOf(array, value) {
for (let i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
return -1;
}
export function escapeExpression(string) {
if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe
if (string && string.toHTML) {
return string.toHTML();
} else if (string == null) {
return '';
} else if (!string) {
return string + '';
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = '' + string;
}
if (!possible.test(string)) {
return string;
}
return string.replace(badChars, escapeChar);
}
export function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
}
}
export function createFrame(object) {
let frame = extend({}, object);
frame._parent = object;
return frame;
}
================================================
FILE: lib/handlebars.js
================================================
import {
parser as Parser,
parse,
parseWithoutProcessing,
Visitor,
} from '@handlebars/parser';
import runtime from './handlebars.runtime';
// Compiler imports
import AST from './handlebars/compiler/ast';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import noConflict from './handlebars/no-conflict';
let _create = runtime.create;
function create() {
let hb = _create();
hb.compile = function (input, options) {
return compile(input, options, hb);
};
hb.precompile = function (input, options) {
return precompile(input, options, hb);
};
hb.AST = AST;
hb.Compiler = Compiler;
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
hb.parseWithoutProcessing = parseWithoutProcessing;
return hb;
}
let inst = create();
inst.create = create;
noConflict(inst);
inst.Visitor = Visitor;
inst['default'] = inst;
export default inst;
================================================
FILE: lib/handlebars.runtime.js
================================================
import { Exception } from '@handlebars/parser';
import * as base from './handlebars/base';
// Each of these augment the Handlebars object. No need to setup here.
// (This is done to easily share code between commonjs and browse envs)
import SafeString from './handlebars/safe-string';
import * as Utils from './handlebars/utils';
import * as runtime from './handlebars/runtime';
import noConflict from './handlebars/no-conflict';
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
function create() {
let hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base);
hb.SafeString = SafeString;
hb.Exception = Exception;
hb.Utils = Utils;
hb.escapeExpression = Utils.escapeExpression;
// Spread into a plain object so that runtime functions (e.g. checkRevision)
// can be overridden by consumers. ES module namespace objects are sealed with
// getter-only properties per spec, which would prevent monkey-patching.
hb.VM = { ...runtime };
hb.template = function (spec) {
return runtime.template(spec, hb);
};
return hb;
}
let inst = create();
inst.create = create;
noConflict(inst);
inst['default'] = inst;
export default inst;
================================================
FILE: lib/index.js
================================================
// USAGE:
// var handlebars = require('handlebars');
/* eslint-disable no-var */
// var local = handlebars.create();
var handlebars = require('../dist/cjs/handlebars')['default'];
var parser = require('@handlebars/parser');
handlebars.PrintVisitor = parser.PrintVisitor;
handlebars.print = parser.print;
module.exports = handlebars;
// Publish a Node.js require() handler for .handlebars and .hbs files
function extension(module, filename) {
var fs = require('fs');
var templateString = fs.readFileSync(filename, 'utf8');
module.exports = handlebars.compile(templateString);
}
/* v8 ignore next 4 */
if (typeof require !== 'undefined' && require.extensions) {
require.extensions['.handlebars'] = extension;
require.extensions['.hbs'] = extension;
}
================================================
FILE: lib/precompiler.js
================================================
/* eslint-disable no-console */
import Async from 'neo-async';
import fs from 'fs';
import Handlebars from './handlebars';
import { basename } from 'path';
import { SourceMapConsumer, SourceNode } from 'source-map';
module.exports.loadTemplates = function (opts, callback) {
loadStrings(opts, function (err, strings) {
if (err) {
callback(err);
} else {
loadFiles(opts, function (err, files) {
if (err) {
callback(err);
} else {
opts.templates = strings.concat(files);
callback(undefined, opts);
}
});
}
});
};
function loadStrings(opts, callback) {
let strings = arrayCast(opts.string),
names = arrayCast(opts.name);
if (names.length !== strings.length && strings.length > 1) {
return callback(
new Handlebars.Exception(
'Number of names did not match the number of string inputs'
)
);
}
Async.map(
strings,
function (string, callback) {
if (string !== '-') {
callback(undefined, string);
} else {
// Load from stdin
let buffer = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', function (chunk) {
buffer += chunk;
});
process.stdin.on('end', function () {
callback(undefined, buffer);
});
}
},
function (err, strings) {
strings = strings.map((string, index) => ({
name: names[index],
path: names[index],
source: string,
}));
callback(err, strings);
}
);
}
function loadFiles(opts, callback) {
// Build file extension pattern
let extension = (opts.extension || 'handlebars').replace(
/[\\^$*+?.():=!|{}\-[\]]/g,
function (arg) {
return '\\' + arg;
}
);
extension = new RegExp('\\.' + extension + '$');
let ret = [],
queue = (opts.files || []).map((template) => ({
template,
root: opts.root,
}));
Async.whilst(
() => queue.length,
function (callback) {
let { template: path, root } = queue.shift();
fs.stat(path, function (err, stat) {
if (err) {
return callback(
new Handlebars.Exception(`Unable to open template file "${path}"`)
);
}
if (stat.isDirectory()) {
opts.hasDirectory = true;
fs.readdir(path, function (err, children) {
/* v8 ignore next -- Race condition that being too lazy to test */
if (err) {
return callback(err);
}
children.forEach(function (file) {
let childPath = path + '/' + file;
if (
extension.test(childPath) ||
fs.statSync(childPath).isDirectory()
) {
queue.push({ template: childPath, root: root || path });
}
});
callback();
});
} else {
fs.readFile(path, 'utf8', function (err, data) {
/* v8 ignore next -- Race condition that being too lazy to test */
if (err) {
return callback(err);
}
if (opts.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
// Clean the template name
let name = path;
if (!root) {
name = basename(name);
} else if (name.indexOf(root) === 0) {
name = name.substring(root.length + 1);
}
name = name.replace(extension, '');
ret.push({
path: path,
name: name,
source: data,
});
callback();
});
}
});
},
function (err) {
if (err) {
callback(err);
} else {
callback(undefined, ret);
}
}
);
}
module.exports.cli = async function (opts) {
if (opts.version) {
console.log(Handlebars.VERSION);
return;
}
if (!opts.templates.length && !opts.hasDirectory) {
throw new Handlebars.Exception(
'Must define at least one template or directory.'
);
}
if (opts.simple && opts.min) {
throw new Handlebars.Exception('Unable to minimize simple output');
}
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
if (opts.simple && multiple) {
throw new Handlebars.Exception(
'Unable to output multiple templates in simple mode'
);
}
// Force simple mode if we have only one template and it's unnamed.
if (
!opts.amd &&
!opts.commonjs &&
opts.templates.length === 1 &&
!opts.templates[0].name
) {
opts.simple = true;
}
// Convert the known list into a hash
let known = {};
if (opts.known && !Array.isArray(opts.known)) {
opts.known = [opts.known];
}
if (opts.known) {
for (let i = 0, len = opts.known.length; i < len; i++) {
known[opts.known[i]] = true;
}
}
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
let output = new SourceNode();
if (!opts.simple) {
if (opts.amd) {
output.add(
"define(['" +
opts.handlebarPath +
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
);
} else if (opts.commonjs) {
output.add('var Handlebars = require("' + opts.commonjs + '");');
} else {
output.add('(function() {\n');
}
output.add(' var template = Handlebars.template, templates = ');
if (opts.namespace) {
output.add(opts.namespace);
output.add(' = ');
output.add(opts.namespace);
output.add(' || ');
}
output.add('{};\n');
}
for (const template of opts.templates) {
let options = {
knownHelpers: known,
knownHelpersOnly: opts.o,
};
if (opts.map) {
options.srcName = template.path;
}
if (opts.data) {
options.data = true;
}
let precompiled = Handlebars.precompile(template.source, options);
// If we are generating a source map, we have to reconstruct the SourceNode object
if (opts.map) {
let consumer = await new SourceMapConsumer(precompiled.map);
precompiled = SourceNode.fromStringWithSourceMap(
precompiled.code,
consumer
);
consumer.destroy();
}
if (opts.simple) {
output.add([precompiled, '\n']);
} else {
if (!template.name) {
throw new Handlebars.Exception('Name missing for template');
}
if (opts.amd && !multiple) {
output.add('return ');
}
output.add([
objectName,
"['",
template.name,
"'] = template(",
precompiled,
');\n',
]);
}
}
// Output the content
if (!opts.simple) {
if (opts.amd) {
if (multiple) {
output.add(['return ', objectName, ';\n']);
}
output.add('});');
} else if (!opts.commonjs) {
output.add('})();');
}
}
if (opts.map) {
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
}
output = output.toStringWithSourceMap();
output.map = output.map + '';
if (opts.min) {
output = minify(output, opts.map);
}
if (opts.map) {
fs.writeFileSync(opts.map, output.map, 'utf8');
}
output = output.code;
if (opts.output) {
fs.writeFileSync(opts.output, output, 'utf8');
} else {
console.log(output);
}
};
function arrayCast(value) {
value = value != null ? value : [];
if (!Array.isArray(value)) {
value = [value];
}
return value;
}
/**
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
*
* We are using `require` instead of `import` here, because es6-modules do not allow
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
* should not be a problem.
*
* @param {string} output the compiled template
* @param {string} sourceMapFile the file to write the source map to.
*/
function minify(output, sourceMapFile) {
try {
// Try to resolve uglify-js in order to see if it does exist
require.resolve('uglify-js');
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
// Something else seems to be wrong
throw e;
}
// it does not exist!
console.error(
'Code minimization is disabled due to missing uglify-js dependency'
);
return output;
}
return require('uglify-js').minify(output.code, {
sourceMap: {
content: output.map,
url: sourceMapFile,
},
});
}
================================================
FILE: package.json
================================================
{
"name": "handlebars",
"version": "5.0.0-alpha.1",
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
"keywords": [
"handlebars",
"html",
"mustache",
"template"
],
"homepage": "https://handlebarsjs.com/",
"license": "MIT",
"author": "Yehuda Katz",
"repository": {
"type": "git",
"url": "https://github.com/handlebars-lang/handlebars.js.git"
},
"bin": {
"handlebars": "bin/handlebars.mjs"
},
"files": [
"bin",
"dist/*.js",
"dist/cjs/**/*.js",
"lib",
"release-notes.md",
"runtime.js",
"types/*.d.ts",
"runtime.d.ts"
],
"main": "lib/index.js",
"browser": "./dist/cjs/handlebars.js",
"types": "types/index.d.ts",
"scripts": {
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('tmp',{recursive:true,force:true})\"",
"build:cjs": "swc lib --out-dir dist/cjs --strip-leading-paths --ignore \"**/index.js\"",
"build:bundles": "rspack build",
"build": "npm run clean && npm run build:cjs && npm run build:bundles",
"release": "npm run build",
"publish:aws": "npm run build && npm run test:tasks && node tasks/publish-to-aws.js",
"format": "oxfmt --write . && oxlint --fix .",
"lint": "npm run lint:oxlint && npm run lint:format && npm run lint:types && npm run lint:compat",
"lint:oxlint": "oxlint --max-warnings 0 .",
"lint:format": "oxfmt --check .",
"lint:types": "tsc --noEmit --project types && tstyche",
"lint:compat": "eslint",
"test": "npm run build && vitest run --project node --project tasks --project rspack --coverage",
"test:browser": "vitest run --project browser",
"test:unit": "vitest run --project node",
"test:tasks": "vitest run --project tasks",
"test:browser-smoke": "playwright test --config tests/browser/playwright.config.js",
"test:serve": "npx serve -l 9999 .",
"test:integration": "npm run build && ./tests/integration/run-integration-tests.sh",
"bench": "node tests/bench/perf.mjs",
"bench:compare": "node tests/bench/compare.mjs",
"bench:size": "node tests/bench/size.mjs",
"--- combined tasks ---": "",
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
},
"dependencies": {
"@handlebars/parser": "^2.1.0",
"neo-async": "^2.6.2",
"source-map": "^0.7.6",
"yargs": "^18.0.0"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.1011.0",
"@playwright/test": "^1.58.2",
"@rspack/cli": "^1.7.8",
"@rspack/core": "^1.7.8",
"@swc/cli": "^0.8.0",
"@swc/core": "^1.15.18",
"@vitest/browser": "^4.0.18",
"@vitest/browser-playwright": "^4.0.18",
"@vitest/coverage-v8": "^4.0.18",
"cli-testlab": "^6.0.0",
"concurrently": "^5.0.0",
"eslint": "^10.0.3",
"eslint-plugin-compat": "^7.0.1",
"fs-extra": "^8.1.0",
"husky": "^3.1.0",
"lint-staged": "^16.3.2",
"mock-stdin": "^0.3.0",
"oxfmt": "^0.36.0",
"oxlint": "^1.51.0",
"semver": "^5.0.1",
"tinybench": "^6.0.0",
"tstyche": "^6.2.0",
"typescript": "^5.9.3",
"uglify-js": "^3.19.3",
"vitest": "^4.0.18"
},
"peerDependencies": {
"uglify-js": "^3.19.3"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,css,json,md}": [
"oxfmt --write"
],
"*.js": [
"oxlint --fix"
]
},
"browserslist": [
"last 2 versions",
"Firefox ESR",
"not dead",
"not IE 11",
"maintained node versions"
],
"engines": {
"node": ">=20"
},
"jspm": {
"main": "handlebars",
"directories": {
"lib": "dist/cjs"
},
"buildConfig": {
"minify": true
}
}
}
================================================
FILE: release-notes.md
================================================
# Release Notes
## Development
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.7...master)
## v4.7.7 - February 15th, 2021
- fix weird error in integration tests - eb860c0
- fix: check prototype property access in strict-mode (#1736) - b6d3de7
- fix: escape property names in compat mode (#1736) - f058970
- refactor: In spec tests, use expectTemplate over equals and shouldThrow (#1683) - 77825f8
- chore: start testing on Node.js 12 and 13 - 3789a30
(POSSIBLY) BREAKING CHANGES:
- the changes from version [4.6.0](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md#v460---january-8th-2020) now also apply
in when using the compile-option "strict: true". Access to prototype properties is forbidden completely by default, specific properties or methods
can be allowed via runtime-options. See #1633 for details. If you are using Handlebars as documented, you should not be accessing prototype properties
from your template anyway, so the changes should not be a problem for you. Only the use of undocumented features can break your build.
That is why we only bump the patch version despite mentioning breaking changes.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.6...v4.7.7)
## v4.7.6 - April 3rd, 2020
Chore/Housekeeping:
- [#1672](https://github.com/handlebars-lang/handlebars.js/issues/1672) - Switch cmd parser to latest minimist ([@dougwilson](https://api.github.com/users/dougwilson)
Compatibility notes:
- Restored Node.js compatibility
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.5...v4.7.6)
## v4.7.5 - April 2nd, 2020
Chore/Housekeeping:
- ~Node.js version support has been changed to v6+~ Reverted in 4.7.6
Compatibility notes:
- ~Node.js < v6 is no longer supported~ Reverted in 4.7.6
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.4...v4.7.5)
## v4.7.4 - April 1st, 2020
Chore/Housekeeping:
- [#1666](https://github.com/handlebars-lang/handlebars.js/issues/1666) - Replaced minimist with yargs for handlebars CLI ([@aorinevo](https://api.github.com/users/aorinevo), [@AviVahl](https://api.github.com/users/AviVahl) & [@fabb](https://api.github.com/users/fabb))
Compatibility notes:
- No incompatibilities are to be expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.3...v4.7.4)
## v4.7.3 - February 5th, 2020
Chore/Housekeeping:
- [#1644](https://github.com/handlebars-lang/handlebars.js/issues/1644) - Download links to aws broken on handlebarsjs.com - access denied ([@Tea56](https://api.github.com/users/Tea56))
- Fix spelling and punctuation in changelog - d78cc73
Bugfixes:
- Add Type Definition for Handlebars.VERSION, Fixes #1647 - 4de51fe
- Include Type Definition for runtime.js in Package - a32d05f
Compatibility notes:
- No incompatibilities are to be expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.2...v4.7.3)
## v4.7.2 - January 13th, 2020
Bugfixes:
- fix: don't wrap helpers that are not functions - 9d5aa36, #1639
Chore/Build:
- chore: execute saucelabs-task only if access-key exists - a4fd391
Compatibility notes:
- No breaking changes are to be expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.1...v4.7.2)
## v4.7.1 - January 12th, 2020
Bugfixes:
- fix: fix log output in case of illegal property access - f152dfc
- fix: log error for illegal property access only once per property - 3c1e252
Compatibility notes:
- no incompatibilities are to be expected.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.0...v4.7.1)
## v4.7.0 - January 10th, 2020
Features:
- feat: default options for controlling proto access - 7af1c12, #1635
- This makes it possible to disable the prototype access restrictions added in 4.6.0
- an error is logged in the console, if access to prototype properties is attempted and denied
and no explicit configuration has taken place.
Compatibility notes:
- no compatibilities are expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.6.0...v4.7.0)
## v4.6.0 - January 8th, 2020
Features:
- feat: access control to prototype properties via whitelist (#1633)- d03b6ec
Bugfixes:
- fix(runtime.js): partials compile not caching (#1600) - 23d58e7
Chores, docs:
- various refactorings and improvements to tests - d7f0dcf, 187d611, d337f40
- modernize the build-setup
- use prettier to format and eslint to verify - c40d9f3, 8901c28, e97685e, 1f61f21
- use nyc instead of istanbul to collect coverage - 164b7ff, 1ebce2b
- update build code to use modern javascript and make it cleaner - 14b621c, 1ec1737, 3a5b65e, dde108e, 04b1984, 587e7a3
- restructur build commands - e913dc5,
- eslint rule changes - ac4655e, dc54952
- Update (C) year in the LICENSE file - d1fb07b
- chore: try to fix saucelabs credentials (#1627) -
- Update readme.md with updated links (#1620) - edcc84f
BREAKING CHANGES:
- access to prototype properties is forbidden completely by default,
specific properties or methods can be allowed via runtime-options.
See #1633 for details.
If you are using Handlebars as documented, you should not be accessing prototype
properties from your template anyway, so the changes should not be a problem
for you. Only the use of undocumented features can break your build.
That is why we only bump the minor version despite mentioning breaking changes.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.5.3...v4.6.0)
## v4.5.3 - November 18th, 2019
Bugfixes:
- fix: add "no-prototype-builtins" eslint-rule and fix all occurences - f7f05d7
- fix: add more properties required to be enumerable - 1988878
Chores / Build:
- fix: use !== 0 instead of != 0 - c02b05f
- add chai and dirty-chai and sinon, for cleaner test-assertions and spies,
deprecate old assertion-methods - 93e284e, 886ba86, 0817dad, 93516a0
Security:
- The properties `__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__`
have been added to the list of "properties that must be enumerable".
If a property by that name is found and not enumerable on its parent,
it will silently evaluate to `undefined`. This is done in both the compiled template and the "lookup"-helper.
This will prevent new Remote-Code-Execution exploits that have been
published recently.
Compatibility notes:
- Due to the security-fixes. The semantics of the templates using
`__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__` in the respect that those expression now return
`undefined` rather than their actual value from the proto.
- The semantics have not changed in cases where the properties are enumerable, as in:
```js
{
__proto__: 'some string';
}
```
- The change may be breaking in that respect, but we still only
increase the patch-version, because the incompatible use-cases
are not intended, undocumented and far less important than fixing
Remote-Code-Execution exploits on existing systems.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.5.2...v4.5.3)
## v4.5.2 - November 13th, 2019
# Bugfixes
- fix: use String(field) in lookup when checking for "constructor" - d541378
- test: add fluent API for testing Handlebars - c2ac79c
Compatibility notes:
- no incompatibility are to be expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.5.1...v4.5.2)
## v4.5.1 - October 29th, 2019
Bugfixs
- fix: move "eslint-plugin-compat" to devDependencies - 5e9d17f (#1589)
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.5.0...v4.5.1)
## v4.5.0 - October 28th, 2019
Features / Improvements
- Add method Handlebars.parseWithoutProcessing (#1584) - 62ed3c2
- add guard to if & unless helpers (#1549)
- show source location for the strict lookup exceptions - feb60f8
Bugfixes:
- Use objects for hash value tracking - 7fcf9d2
Chore:
- Resolve deprecation warning message from eslint while running eslint (#1586) - 7052e88
- chore: add eslint-plugin-compat and eslint-plugin-es5 - 088e618
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.4.5...v4.5.0)
## v4.4.5 - October 20th, 2019
Bugfixes:
- Contents of raw-blocks must be matched with non-eager regex-matching - 8d5530e, #1579
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.4.4...v4.4.5)
## v4.4.4 - October 20th, 2019
Bugfixes:
- fix: prevent zero length tokens in raw-blocks (#1577, #1578) - f1752fe
Chore:
- chore: link to s3 bucket with https, add "npm ci" to build instructions - 0b593bf
Compatibility notes:
- no compatibility issues are expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.4.3...v4.4.4)
## v4.4.3 - October 8th, 2019
Bugfixes
Typings:
- add missing type fields to AST typings and add tests for them - 0440af2
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.4.2...v4.4.3)
## v4.4.2 - October 2nd, 2019
- chore: fix grunt-saucelabs dependency - b7eada0
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.4.1...v4.4.2)
## v4.4.1 - October 2nd, 2019
- [#1562](https://github.com/handlebars-lang/handlebars.js/issues/1562) - Error message for syntax error missing location in 4.2.1+
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.4.0...v4.4.1)
## v4.4.0 - September 29th, 2019
- Added support for iterable objects in {{#each}} helper (#1557) - cf7545e
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.3.5...v4.4.0)
## v4.3.5 - October 2nd, 2019
- Error message for syntax error missing location in 4.2.1+ (#1562)
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.3.4...v4.3.5)
## v4.3.4 - September 28th, 2019
- fix: harden "propertyIsEnumerable"-check - ff4d827
Compatibility notes:
- No incompatibilities are known.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.3.3...v4.3.4)
## v4.3.3 - September 27th, 2019
- fix test case for browsers that do not support **defineGetter** - 8742bde
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.3.2...v4.3.3)
## v4.3.2 - September 26th, 2019
- Use Object.prototype.propertyIsEnumerable to check for constructors - 213c0bb, #1563
Compatibility notes:
- There are no breaking changes
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.3.1...v4.3.2)
## v4.3.1 - September 25th, 2019
Fixes:
- do not break on precompiled templates from Handlebars >=4.0.0 <4.3.0 - 1266838, #1561
- Ensure allowCallsToHelperMissing runtime option is optional in typings - 93444c5, 64ecb9e, #1560
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.3.0...v4.3.1)
## v4.3.0 - September 24th, 2019
Fixes:
- Security: Disallow calling "helperMissing" and "blockHelperMissing" directly - 2078c72
- Disallow calling "helperMissing" and "blockHelperMissing" directly - 2078c72
Features:
- Add new runtime option `allowCallsToHelperMissing` to allow calling `blockHelperMissing` and `helperMissing`.
Breaking changes:
Compatibility notes:
- Compiler revision increased - 06b7224
- This means that template compiled with versions prior to 4.3.0 will not work with runtimes >= 4.3.0
The increase was done because the "helperMissing" and "blockHelperMissing" are now moved from the helpers
to the internal "container.hooks" object, so old templates will not be able to call them anymore. We suggest
that you always recompile your templates with the latest compiler in your build pipelines.
- Disallow calling "helperMissing" and "blockHelperMissing" directly - 2078c72
- Calling "helperMissing" and "blockHelperMissing" directly from a template (like in `{{blockHelperMissing}}` was
never intended and was part of the exploits that have been revealed early in 2019
(see https://github.com/handlebars-lang/handlebars.js/issues/1495). _It is also part of a new exploit that
is not captured by the earlier fix._ In order to harden Handlebars against such exploits, calling thos helpers
is now not possible anymore. _Overriding_ those helpers is still possible.
- If you really need this behavior, you can set the runtime option `allowCallsToHelperMissing` to `true` and the
calls will again be possible
Both bullet points imly that Handlebars is not 100% percent compatible to 4.2.0, despite the minor version bump.
We consider it more important to resolve a major security issue than to maintain 100% compatibility.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.2.2...v4.3.0)
## v4.2.2 - October 2nd, 2019
- Error message for syntax error missing location in 4.2.1+ (#1562)
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.2.1...v4.2.2)
## v4.2.1 - September 20th, 2019
Bugfixes:
- The "browser" property in the package.json has been updated to use the common-js builds instead of the minified UMD - c55a7be, #1553
Compatibility notes:
- No compatibility issues should arise
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.2.0...v4.2.1)
## v4.2.0 - September 3rd, 2019
Chore/Test:
- Use custom `grunt-saucelab` with current sauce-connect proxy - f119497
- Add framework for various integration tests - f9cce4d
- Add integration test for webpack - a57b682
Bugfixes:
- [#1544](https://github.com/handlebars-lang/handlebars.js/issues/1544) - Typescript types: `knownHelpers` doesnt allow for custom helpers ([@NickCis](https://api.github.com/users/NickCis))
- [#1534](https://github.com/handlebars-lang/handlebars.js/pull/1534) - Add typings for "Handlebars.VM.resolvePartial ([@AndrewLeedham](https://api.github.com/users/AndrewLeedham))
Features:
- [#1540](https://github.com/handlebars-lang/handlebars.js/pull/1540) - added "browser"-property to package.json, resolves #1102 ([@ouijan](https://api.github.com/users/ouijan))
Compatibility notes:
- The new "browser"-property should not break anything, but you can never be sure. The integration test for webpack
shows that it works, but if it doesn't please open an issue.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.1.2-0...v4.2.0)
## v4.1.2-0 - August 25th, 2019
[#1540](https://github.com/handlebars-lang/handlebars.js/pull/1540) - added browser to package.json, resolves #1102 ([@ouijan](https://api.github.com/users/ouijan))
Compatibility notes:
- We are not sure if imports via webpack are still working, which is why this release is a pre-release
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.1.2...v4.1.2-0)
## v4.1.2 - April 13th, 2019
Chore/Test:
- [#1515](https://github.com/handlebars-lang/handlebars.js/pull/1515) - Port over linting and test for typings ([@zimmi88](https://api.github.com/users/zimmi88))
- chore: add missing typescript dependency, add package-lock.json - 594f1e3
- test: remove safari from saucelabs - 871accc
Bugfixes:
- fix: prevent RCE through the "lookup"-helper - cd38583
Compatibility notes:
Access to the constructor of a class thought `{{lookup obj "constructor" }}` is now prohibited. This closes
a leak that only half closed in versions 4.0.13 and 4.1.0, but it is a slight incompatibility.
This kind of access is not the intended use of Handlebars and leads to the vulnerability described
in #1495. We will **not** increase the major version, because such use is not intended or documented,
and because of the potential impact of the issue (we fear that most people won't use a new major version
and the issue may not be resolved on many systems).
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.1.1...v4.1.2)
## v4.1.1 - March 16th, 2019
Bugfixes:
- fix: add "runtime.d.ts" to allow "require('handlebars/runtime')" in TypeScript - 5cedd62
Refactorings:
- replace "async" with "neo-async" - 048f2ce
- use "substring"-function instead of "substr" - 445ae12
Compatibility notes:
- This is a bugfix release. There are no breaking change and no new features.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.1.0...v4.1.1)
## v4.1.0 - February 7th, 2019
New Features
- import TypeScript typings - 27ac1ee
Security fixes:
- disallow access to the constructor in templates to prevent RCE - 42841c4, #1495
Housekeeping
- chore: fix components/handlebars package.json and auto-update on release - bacd473
- chore: Use node 10 to build handlebars - 78dd89c
- chore/doc: Add more release docs - 6b87c21
Compatibility notes:
Access to class constructors (i.e. `({}).constructor`) is now prohibited to prevent
Remote Code Execution. This means that following construct will no work anymore:
```
class SomeClass {
}
SomeClass.staticProperty = 'static'
var template = Handlebars.compile('{{constructor.staticProperty}}');
document.getElementById('output').innerHTML = template(new SomeClass());
// expected: 'static', but now this is empty.
```
This kind of access is not the intended use of Handlebars and leads to the vulnerability described in #1495. We will **not** increase the major version, because such use is not intended or documented, and because of the potential impact of the issue (we fear that most people won't use a new major version and the issue may not be resolved on many systems).
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.12...v4.1.0)
## v4.0.12 - September 4th, 2018
New features:
- none
Various dependency updates
- [#1464](https://github.com/handlebars-lang/handlebars.js/pull/1464) - Bump versions of grunt-plugins to 1.x
- [#1398](https://github.com/handlebars-lang/handlebars.js/pull/1398) - Chore: updated various dev dependencies
- upgrade uglify-js - d3d3942
- Update grunt-eslint to 20.1.0 - 7729aa9
- Update dependencies "async" to 2.5.0 and "source-map" to 0.6.1 (73d5637)
Bugfixes:
- [components/handlebars.js#24](https://github.com/components/handlebars.js#24) Add package.json to components shim
- Updated `source-map`-package should work better with `rollup`[#1463](https://github.com/handlebars-lang/handlebars.js/issues/1463)
Removed obsolete code:
- unnecessary check - 0ddff8b
- Use `files` field - 69c6ca5
- Update jsfiddle to 4.0.11 - 8947dd0
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.11...v4.0.12)
## v4.0.11 - October 17th, 2017
- [#1391](https://github.com/handlebars-lang/handlebars.js/issues/1391) - `uglify-js` is unconditionally imported, but only listed as optional dependency ([@Turbo87](https://github.com/Turbo87))
- [#1233](https://github.com/handlebars-lang/handlebars.js/issues/1233) - Unable to build under windows - error at test:bin task ([@blikblum](https://github.com/blikblum))
- Update (C) year in the LICENSE file - 21386b6
Compatibility notes:
- This is a bugfix release. There are no breaking change and no new features.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.10...v4.0.11)
## v4.0.10 - May 21st, 2017
- Fix regression in 4.0.9: Replace "Object.assign" (not support in IE) by "util/extend" - 0e953d1
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.9...v4.0.10)
## v4.0.9 - May 21st, 2017
- [#1327](https://github.com/handlebars-lang/handlebars.js/issues/1327) Handlebars.compile() does not modify "options" anymore
- pending [#1331](https://github.com/handlebars-lang/handlebars.js/issues/1331) Attempts to build Handlebars in a Windows environment
- Fix build in windows - cc554a5
- Ensure LF line-edings in handlebars-template fixtures (\*.hbs) - ed879a6
- Run integration test with `node handlebars -a ...` on Windows - 2e21e2b
- Ensure LF line-edings in lexer-files (\*.l) - bdfdbea
- Force LF line-endings for spec/artifacts - b50ef03
- Use istanbul/lib/cli.js instead of node_modules/.bin/istanbul - 6e6269f
- TravisCI: Publish valid semver tags independently of the branch - 7378f85
Compatibility notes:
- No compatibility issues are expected.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.8...v4.0.9)
## v4.0.8 - May 2nd, 2017
- [#1341](https://github.com/handlebars-lang/handlebars.js/issues/1341) [#1342](https://github.com/handlebars-lang/handlebars.js/issues/1342) Allow partial-blocks to be executed without "options" ([@nknapp](https://github.com/nknapp)) - a00c598
Compatibility notes:
- No breaking changes
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.7...v4.0.8)
## v4.0.7 - April 29th, 2017
- [#1319](https://github.com/handlebars-lang/handlebars.js/issues/1319): Fix context-stack when calling block-helpers on null values ([@nknapp](https://github.com/nknapp)) - c8f4b57
- [#1315](https://github.com/handlebars-lang/handlebars.js/pull/1315) Parser: Change suffix to use ES6 default module export ([@Turbo87](https://github.com/Turbo87))- b617375
- [#1290](https://github.com/handlebars-lang/handlebars.js/pull/1290) [#1252](https://github.com/handlebars-lang/handlebars.js/issue/1290) Add more tests for partial-blocks and inline partials ([@nknapp](https://github.com/nknapp)) - 63a8e0c
- [#1252](https://github.com/handlebars-lang/handlebars.js/issue/1290) Using @partial-block twice in a template not possible ([@nknapp](https://github.com/nknapp)) - 5a164d0
- [#1310](https://github.com/handlebars-lang/handlebars.js/pull/1310) Avoid duplicate "sourceMappingURL=" lines. ([@joonas-lahtinen](https://github.com/joonas-lahtinen)) - 01b0f65
- [#1275](https://github.com/handlebars-lang/handlebars.js/pull/1275) require('sys') is deprecated, using 'util' instead ([@travnels](https://github.com/travnels)) - 406f2ee
- [#1285](https://github.com/handlebars-lang/handlebars.js/pull/1285) [#1284](https://github.com/handlebars-lang/handlebars.js/issues/1284) Make "column"-property of Errors enumerable ([@nknapp](https://github.com/nknapp)) - a023cb4
- [#1285](https://github.com/handlebars-lang/handlebars.js/pull/1285) Testcase to verify that compile-errors have a column-property ([@nknapp](https://github.com/nknapp)) - c7dc353
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.6...v4.0.7)
## v4.0.6 - November 12th, 2016
- [#1243](https://github.com/handlebars-lang/handlebars.js/pull/1243) - Walk up data frames for nested @partial-block ([@lawnsea](https://github.com/lawnsea))
- [#1210](https://github.com/handlebars-lang/handlebars.js/pull/1210) - Add a new lightweight package based on handlebars in the README ([@kabirbaidhya](https://github.com/kabirbaidhya))
- [#1187](https://github.com/handlebars-lang/handlebars.js/pull/1187) - Ensure that existing blockParams and depths are respected on dupe programs ([@charleso](https://github.com/charleso))
- [#1191](https://github.com/handlebars-lang/handlebars.js/pull/1191) - Added cory ([@leo](https://github.com/leo))
- [#1177](https://github.com/handlebars-lang/handlebars.js/pull/1177) - Preserve License info in Closure Compiler ([@gennadiylitvinyuk](https://github.com/gennadiylitvinyuk))
- [#1171](https://github.com/handlebars-lang/handlebars.js/pull/1171) - Contributing doc fix: failing thats -> failing tests ([@paulfalgout](https://github.com/paulfalgout))
- [#1166](https://github.com/handlebars-lang/handlebars.js/pull/1166) - Update license date ([@timwangdev](https://github.com/timwangdev))
- Update jsfiddle to point to latest - 959ee55 (originally dfc7554 by [@kpdecker](https://github.com/kpdecker))
- [#1163](https://github.com/handlebars-lang/handlebars.js/pull/1163) - Fix typos on decorators-api.md. ([@adjohnson916](https://github.com/adjohnson916))
- Drop extra Error params - 8c19874 (originally 63fdb92 by [@kpdecker](https://github.com/kpdecker))
- [#1153](https://github.com/handlebars-lang/handlebars.js/pull/1153) - Add documentation for running tests to contributing.md ([@ryanmurakami](https://github.com/ryanmurakami))
- Avoid error in older browsers in test - 400916c (originally a6121ca by [@kpdecker](https://github.com/kpdecker))
- Update target browser test versions - fee2334 (originally 871c32a by [@kpdecker](https://github.com/kpdecker))
- Exclude coverage check in exception conditional - 32d6363 (originally 326734b by [@kpdecker](https://github.com/kpdecker))
- Fix throw when creating exception object in Safari - 20c965c (originally 2ea6119 by [@kpdecker](https://github.com/kpdecker))
- Update build for modern node versions - 6c9f98c (originally 8289c0b by [@kpdecker](https://github.com/kpdecker))
- [#1135](https://github.com/handlebars-lang/handlebars.js/issues/1135) - Relax depth check for context push - c393c81 (originally 25458fd by [@kpdecker](https://github.com/kpdecker))
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.5...v4.0.6)
## v4.0.5 - November 19th, 2015
- [#1132](https://github.com/handlebars-lang/handlebars.js/pull/1132) - Update uglify-js to avoid vulnerability ([@plynchnlm](https://github.com/plynchnlm))
- [#1129](https://github.com/handlebars-lang/handlebars.js/issues/1129) - Minified lib returns an empty string ([@bricss](https://github.com/bricss))
- Return current handlebars instance from noConflict - 685cf92
- Add webpack to dev dependency to support npm 3 - 7a6c228
- Further relax uglify dependency - 0a3b3c2
- Include tests for minimized artifacts - c21118d
- Fix lint errors under latest eslint - 9f59de9
- Add print-script helper script - 98a6717
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.4...v4.0.5)
## v4.0.4 - October 29th, 2015
- [#1121](https://github.com/handlebars-lang/handlebars.js/pull/1121) - Include partial name in 'undefined partial' exception message ([@shinypb](https://github.com/shinypb))
- [#1125](https://github.com/handlebars-lang/handlebars.js/pull/1125) - Add promised-handlebars to "in-the-wild"-list ([@nknapp](https://github.com/nknapp))
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.3...v4.0.4)
## v4.0.3 - September 23rd, 2015
- [#1099](https://github.com/handlebars-lang/handlebars.js/issues/1099) - @partial-block is overridden ([@btmorex](https://github.com/btmorex))
- [#1093](https://github.com/handlebars-lang/handlebars.js/issues/1093) - #each skips iteration on undefined values ([@florianpilz](https://github.com/florianpilz))
- [#1092](https://github.com/handlebars-lang/handlebars.js/issues/1092) - Square braces in key name ([@distantnative](https://github.com/distantnative))
- [#1091](https://github.com/handlebars-lang/handlebars.js/pull/1091) - fix typo in release notes ([@nikolas](https://github.com/nikolas))
- [#1090](https://github.com/handlebars-lang/handlebars.js/pull/1090) - grammar fixes in 4.0.0 release notes ([@nikolas](https://github.com/nikolas))
Compatibility notes:
- `each` iteration with `undefined` values has been restored to the 3.0 behaviors. Helper calls with undefined context values will now execute against an arbitrary empty object to avoid executing against global object in non-strict mode.
- `]` can now be included in `[]` wrapped identifiers by escaping with `\`. Any `[]` identifiers that include `\` will now have to properly escape these values.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.2...v4.0.3)
## v4.0.2 - September 4th, 2015
- [#1089](https://github.com/handlebars-lang/handlebars.js/issues/1089) - "Failover content" not working in multiple levels of inline partials ([@michaellopez](https://github.com/michaellopez))
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.1...v4.0.2)
## v4.0.1 - September 2nd, 2015
- Fix failure when using decorators in partials - 05b82a2
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.0...v4.0.1)
## v4.0.0 - September 1st, 2015
- [#1082](https://github.com/handlebars-lang/handlebars.js/pull/1082) - Decorators and Inline Partials ([@kpdecker](https://github.com/kpdecker))
- [#1076](https://github.com/handlebars-lang/handlebars.js/pull/1076) - Implement partial blocks ([@kpdecker](https://github.com/kpdecker))
- [#1087](https://github.com/handlebars-lang/handlebars.js/pull/1087) - Fix #each when last object entry has empty key ([@denniskuczynski](https://github.com/denniskuczynski))
- [#1084](https://github.com/handlebars-lang/handlebars.js/pull/1084) - Bump uglify version to fix vulnerability ([@John-Steidley](https://github.com/John-Steidley))
- [#1068](https://github.com/handlebars-lang/handlebars.js/pull/1068) - Fix typo ([@0xack13](https://github.com/0xack13))
- [#1060](https://github.com/handlebars-lang/handlebars.js/pull/1060) - #1056 Fixed grammar for nested raw blocks ([@ericbn](https://github.com/ericbn))
- [#1052](https://github.com/handlebars-lang/handlebars.js/pull/1052) - Updated year in License ([@maqnouch](https://github.com/maqnouch))
- [#1037](https://github.com/handlebars-lang/handlebars.js/pull/1037) - Fix minor typos in README ([@tomxtobin](https://github.com/tomxtobin))
- [#1032](https://github.com/handlebars-lang/handlebars.js/issues/1032) - Is it possible to render a partial without the parent scope? ([@aputinski](https://github.com/aputinski))
- [#1019](https://github.com/handlebars-lang/handlebars.js/pull/1019) - Fixes typo in tests ([@aymerick](https://github.com/aymerick))
- [#1016](https://github.com/handlebars-lang/handlebars.js/issues/1016) - Version mis-match ([@mayankdedhia](https://github.com/mayankdedhia))
- [#1023](https://github.com/handlebars-lang/handlebars.js/issues/1023) - is it possible for nested custom helpers to communicate between each other?
- [#893](https://github.com/handlebars-lang/handlebars.js/issues/893) - [Proposal] Section blocks.
- [#792](https://github.com/handlebars-lang/handlebars.js/issues/792) - feature request: inline partial definitions
- [#583](https://github.com/handlebars-lang/handlebars.js/issues/583) - Parent path continues to drill down depth with multiple conditionals
- [#404](https://github.com/handlebars-lang/handlebars.js/issues/404) - Add named child helpers that can be referenced by block helpers
- Escape = in HTML content - [83b8e84](https://github.com/handlebars-lang/handlebars.js/commit/83b8e84)
- Drop AST constructors in favor of JSON - [95d84ba](https://github.com/handlebars-lang/handlebars.js/commit/95d84ba)
- Pass container rather than exec as context - [9a2d1d6](https://github.com/handlebars-lang/handlebars.js/commit/9a2d1d6)
- Add ignoreStandalone compiler option - [ea3a5a1](https://github.com/handlebars-lang/handlebars.js/commit/ea3a5a1)
- Ignore empty when iterating on sparse arrays - [06d515a](https://github.com/handlebars-lang/handlebars.js/commit/06d515a)
- Add support for string and stdin precompilation - [0de8dac](https://github.com/handlebars-lang/handlebars.js/commit/0de8dac)
- Simplify object assignment generation logic - [77e6bfc](https://github.com/handlebars-lang/handlebars.js/commit/77e6bfc)
- Bulletproof AST.helpers.helperExpression - [93b0760](https://github.com/handlebars-lang/handlebars.js/commit/93b0760)
- Always return string responses - [8e868ab](https://github.com/handlebars-lang/handlebars.js/commit/8e868ab)
- Pass undefined fields to helpers in strict mode - [5d4b8da](https://github.com/handlebars-lang/handlebars.js/commit/5d4b8da)
- Avoid depth creation when context remains the same - [279e038](https://github.com/handlebars-lang/handlebars.js/commit/279e038)
- Improve logging API - [9a49d35](https://github.com/handlebars-lang/handlebars.js/commit/9a49d35)
- Fix with operator in no @data mode - [231a8d7](https://github.com/handlebars-lang/handlebars.js/commit/231a8d7)
- Allow empty key name in each iteration - [1bb640b](https://github.com/handlebars-lang/handlebars.js/commit/1bb640b)
- Add with block parameter support - [2a85106](https://github.com/handlebars-lang/handlebars.js/commit/2a85106)
- Fix escaping of non-javascript identifiers - [410141c](https://github.com/handlebars-lang/handlebars.js/commit/410141c)
- Fix location information for programs - [93faffa](https://github.com/handlebars-lang/handlebars.js/commit/93faffa)
Compatibility notes:
- Depthed paths are now conditionally pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior that better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/handlebars-lang/handlebars.js/issues/1028).
- The `=` character is now HTML escaped. This closes a potential exploit case when using unquoted attributes, i.e. `<div foo={{bar}}>`. In general it's recommended that attributes always be quoted when their values are generated from a mustache to avoid any potential exploit surfaces.
- AST constructors have been dropped in favor of plain old javascript objects
- The runtime version has been increased. Precompiled templates will need to use runtime of at least 4.0.0.
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v3.0.3...v4.0.0)
## v3.0.3 - April 28th, 2015
- [#1004](https://github.com/handlebars-lang/handlebars.js/issues/1004) - Latest version breaks with RequireJS (global is undefined) ([@boskee](https://github.com/boskee))
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v3.0.2...v3.0.3)
## v3.0.2 - April 20th, 2015
- [#998](https://github.com/handlebars-lang/handlebars.js/pull/998) - Add full support for es6 ([@kpdecker](https://github.com/kpdecker))
- [#994](https://github.com/handlebars-lang/handlebars.js/issues/994) - Access Handlebars.Visitor in browser ([@tamlyn](https://github.com/tamlyn))
- [#990](https://github.com/handlebars-lang/handlebars.js/issues/990) - Allow passing null/undefined literals subexpressions ([@blimmer](https://github.com/blimmer))
- [#989](https://github.com/handlebars-lang/handlebars.js/issues/989) - Source-map error with requirejs ([@SteppeEagle](https://github.com/SteppeEagle))
- [#967](https://github.com/handlebars-lang/handlebars.js/issues/967) - can't access "this" property ([@75lb](https://github.com/75lb))
- Use captureStackTrace for error handler - a009a97
- Ignore branches tested without coverage monitoring - 37a664b
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v3.0.1...v3.0.2)
## v3.0.1 - March 24th, 2015
- [#984](https://github.com/handlebars-lang/handlebars.js/pull/984) - Adding documentation for passing arguments into partials ([@johneke](https://github.com/johneke))
- [#973](https://github.com/handlebars-lang/handlebars.js/issues/973) - version 3 is slower than version 2 ([@elover](https://github.com/elover))
- [#966](https://github.com/handlebars-lang/handlebars.js/issues/966) - "handlebars --version" does not work with v3.0.0 ([@abloomston](https://github.com/abloomston))
- [#964](https://github.com/handlebars-lang/handlebars.js/pull/964) - default is a reserved word ([@grassick](https://github.com/grassick))
- [#962](https://github.com/handlebars-lang/handlebars.js/pull/962) - Add dashbars' link on README. ([@pismute](https://github.com/pismute))
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v3.0.0...v3.0.1)
## v3.0.0 - February 10th, 2015
- [#941](https://github.com/handlebars-lang/handlebars.js/pull/941) - Add support for dynamic partial names ([@kpdecker](https://github.com/kpdecker))
- [#940](https://github.com/handlebars-lang/handlebars.js/pull/940) - Add missing reserved words so compiler knows to use array syntax: ([@mattflaschen](https://github.com/mattflaschen))
- [#938](https://github.com/handlebars-lang/handlebars.js/pull/938) - Fix example using #with helper ([@diwo](https://github.com/diwo))
- [#930](https://github.com/handlebars-lang/handlebars.js/pull/930) - Add parent tracking and mutation to AST visitors ([@kpdecker](https://github.com/kpdecker))
- [#926](https://github.com/handlebars-lang/handlebars.js/issues/926) - Depthed lookups fail when program duplicator runs ([@kpdecker](https://github.com/kpdecker))
- [#918](https://github.com/handlebars-lang/handlebars.js/pull/918) - Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos ([@oneeman](https://github.com/oneeman))
- [#915](https://github.com/handlebars-lang/handlebars.js/pull/915) - Ast update ([@kpdecker](https://github.com/kpdecker))
- [#910](https://github.com/handlebars-lang/handlebars.js/issues/910) - Different behavior of {{@last}} when {{#each}} in {{#each}} ([@zordius](https://github.com/zordius))
- [#907](https://github.com/handlebars-lang/handlebars.js/issues/907) - Implement named helper variable references ([@kpdecker](https://github.com/kpdecker))
- [#906](https://github.com/handlebars-lang/handlebars.js/pull/906) - Add parser support for block params ([@mmun](https://github.com/mmun))
- [#903](https://github.com/handlebars-lang/handlebars.js/issues/903) - Only provide aliases for multiple use calls ([@kpdecker](https://github.com/kpdecker))
- [#902](https://github.com/handlebars-lang/handlebars.js/pull/902) - Generate Source Maps ([@kpdecker](https://github.com/kpdecker))
- [#901](https://github.com/handlebars-lang/handlebars.js/issues/901) - Still escapes with noEscape enabled on isolated Handlebars environment ([@zedknight](https://github.com/zedknight))
- [#896](https://github.com/handlebars-lang/handlebars.js/pull/896) - Simplify BlockNode by removing intermediate MustacheNode ([@mmun](https://github.com/mmun))
- [#892](https://github.com/handlebars-lang/handlebars.js/pull/892) - Implement parser for else chaining of helpers ([@kpdecker](https://github.com/kpdecker))
- [#889](https://github.com/handlebars-lang/handlebars.js/issues/889) - Consider extensible parser API ([@kpdecker](https://github.com/kpdecker))
- [#887](https://github.com/handlebars-lang/handlebars.js/issues/887) - Handlebars.noConflict() option? ([@bradvogel](https://github.com/bradvogel))
- [#886](https://github.com/handlebars-lang/handlebars.js/issues/886) - Add SafeString to context (or use duck-typing) ([@dominicbarnes](https://github.com/dominicbarnes))
- [#870](https://github.com/handlebars-lang/handlebars.js/pull/870) - Registering undefined partial throws exception. ([@max-b](https://github.com/max-b))
- [#866](https://github.com/handlebars-lang/handlebars.js/issues/866) - comments don't respect whitespace control ([@75lb](https://github.com/75lb))
- [#863](https://github.com/handlebars-lang/handlebars.js/pull/863) - + jsDelivr CDN info ([@tomByrer](https://github.com/tomByrer))
- [#858](https://github.com/handlebars-lang/handlebars.js/issues/858) - Disable new default auto-indent at included partials ([@majodev](https://github.com/majodev))
- [#856](https://github.com/handlebars-lang/handlebars.js/pull/856) - jspm compatibility ([@MajorBreakfast](https://github.com/MajorBreakfast))
- [#805](https://github.com/handlebars-lang/handlebars.js/issues/805) - Request: "strict" lookups ([@nzakas](https://github.com/nzakas))
- Export the default object for handlebars/runtime - 5594416
- Lookup partials when undefined - 617dd57
Compatibility notes:
- Runtime breaking changes. Must match 3.x runtime and precompiler.
- The AST has been upgraded to a public API.
- There are a number of changes to this, but the format is now documented in docs/compiler-api.md
- The Visitor API has been expanded to support mutation and provide a base implementation
- The `JavaScriptCompiler` APIs have been formalized and documented. As part of the sourcemap handling these should be updated to return arrays for concatenation.
- `JavaScriptCompiler.namespace` has been removed as it was unused.
- `SafeString` is now duck typed on `toHTML`
New Features:
- noConflict
- Source Maps
- Block Params
- Strict Mode
- @last and other each changes
- Chained else blocks
- @data methods can now have helper parameters passed to them
- Dynamic partials
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v2.0.0...v3.0.0)
## v2.0.0 - September 1st, 2014
- Update jsfiddle to 2.0.0-beta.1 - 0670f65
- Add contrib note regarding handlebarsjs.com docs - 4d17e3c
- Play nice with gemspec version numbers - 64d5481
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v2.0.0-beta.1...v2.0.0)
## v2.0.0-beta.1 - August 26th, 2014
- [#787](https://github.com/handlebars-lang/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://github.com/kpdecker))
- [#827](https://github.com/handlebars-lang/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://github.com/scoot557))
- [#767](https://github.com/handlebars-lang/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://github.com/evensoul))
- Changes to 0/undefined handling
- [#731](https://github.com/handlebars-lang/handlebars.js/pull/731) - Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0 ([@kpdecker](https://github.com/kpdecker))
- [#820](https://github.com/handlebars-lang/handlebars.js/issues/820) - strange behavior for {{foo.bar}} when foo is 0 or null or false ([@zordius](https://github.com/zordius))
- [#837](https://github.com/handlebars-lang/handlebars.js/issues/837) - Strange input for custom helper ( foo.bar == false when foo is undefined ) ([@zordius](https://github.com/zordius))
- [#819](https://github.com/handlebars-lang/handlebars.js/pull/819) - Implement recursive field lookup ([@kpdecker](https://github.com/kpdecker))
- [#764](https://github.com/handlebars-lang/handlebars.js/issues/764) - This reference not working for helpers ([@kpdecker](https://github.com/kpdecker))
- [#773](https://github.com/handlebars-lang/handlebars.js/issues/773) - Implicit parameters in {{#each}} introduces a peculiarity in helpers calling convention ([@Bertrand](https://github.com/Bertrand))
- [#783](https://github.com/handlebars-lang/handlebars.js/issues/783) - helperMissing and consistency for different expression types ([@ErisDS](https://github.com/ErisDS))
- [#795](https://github.com/handlebars-lang/handlebars.js/pull/795) - Turn the precompile script into a wrapper around a module. ([@jwietelmann](https://github.com/jwietelmann))
- [#823](https://github.com/handlebars-lang/handlebars.js/pull/823) - Support inverse sections on the with helper ([@dan-manges](https://github.com/dan-manges))
- [#834](https://github.com/handlebars-lang/handlebars.js/pull/834) - Refactor blocks, programs and inverses ([@mmun](https://github.com/mmun))
- [#852](https://github.com/handlebars-lang/handlebars.js/issues/852) - {{foo~}} space control behavior is different from older version ([@zordius](https://github.com/zordius))
- [#835](https://github.com/handlebars-lang/handlebars.js/issues/835) - Templates overwritten if file is loaded twice
- Expose escapeExpression on the root object - 980c38c
- Remove nested function eval in blockHelperMissing - 6f22ec1
- Fix compiler program de-duping - 9e3f824
Compatibility notes:
- The default build now outputs a generic UMD wrapper. This should be transparent change but may cause issues in some environments.
- Runtime compatibility breaks in both directions. Ensure that both compiler and client are upgraded to 2.0.0-beta.1 or higher at the same time.
- `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups.
- `false` values are now printed to output rather than silently dropped
- Lines containing only block statements and whitespace are now removed. This matches the Mustache spec but may cause issues with code that expects whitespace to exist but would not otherwise.
- Partials that are standalone will now indent their rendered content
- `AST.ProgramNode`'s signature has changed.
- Numerious methods/features removed from pseudo-API classes
- `JavaScriptCompiler.register`
- `JavaScriptCompiler.replaceStack` no longer supports non-inline replace
- `Compiler.disassemble`
- `DECLARE` opcode
- `strip` opcode
- `lookup` opcode
- Content nodes may have their `string` values mutated over time. `original` field provides the unmodified value.
- Removed unused `Handlebars.registerHelper` `inverse` parameter
- `each` helper requires iterator parameter
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v2.0.0-alpha.4...v2.0.0-beta.1)
## v2.0.0-alpha.4 - May 19th, 2014
- Expose setup wrappers for compiled templates - 3638874
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v2.0.0-alpha.3...v2.0.0-alpha.4)
## v2.0.0-alpha.3 - May 19th, 2014
- [#797](https://github.com/handlebars-lang/handlebars.js/pull/797) - Pass full helper ID to helperMissing when options are provided ([@tomdale](https://github.com/tomdale))
- [#793](https://github.com/handlebars-lang/handlebars.js/pull/793) - Ensure isHelper is coerced to a boolean ([@mmun](https://github.com/mmun))
- Refactor template init logic - 085e5e1
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v2.0.0-alpha.2...v2.0.0-alpha.3)
## v2.0.0-alpha.2 - March 6th, 2014
- [#756](https://github.com/handlebars-lang/handlebars.js/pull/756) - fix bug in IE<=8 (no Array::map), closes #751 ([@jenseng](https://github.com/jenseng))
- [#749](https://github.com/handlebars-lang/handlebars.js/pull/749) - properly handle multiple subexpressions in the same hash, fixes #748 ([@jenseng](https://github.com/jenseng))
- [#743](https://github.com/handlebars-lang/handlebars.js/issues/743) - subexpression confusion/problem? ([@waynedpj](https://github.com/waynedpj))
- [#746](https://github.com/handlebars-lang/handlebars.js/issues/746) - [CLI] support `handlebars --version` ([@apfelbox](https://github.com/apfelbox))
- [#747](https://github.com/handlebars-lang/handlebars.js/pull/747) - updated grunt-saucelabs, failing tests revealed ([@Jonahss](https://github.com/Jonahss))
- Make JSON a requirement for the compiler. - 058c0fb
-
gitextract_nws7_sbo/ ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .oxfmtrc.json ├── .oxlintrc.json ├── .swcrc ├── CONTRIBUTING.md ├── FAQ.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── SECURITY.md ├── bin/ │ └── handlebars.mjs ├── components/ │ ├── bower.json │ ├── component.json │ ├── composer.json │ ├── handlebars-source.gemspec │ ├── handlebars.js.nuspec │ ├── lib/ │ │ └── handlebars/ │ │ └── source.rb │ └── package.json ├── docs/ │ ├── compiler-api.md │ └── decorators-api.md ├── eslint.config.mjs ├── lib/ │ ├── handlebars/ │ │ ├── base.js │ │ ├── compiler/ │ │ │ ├── ast.js │ │ │ ├── code-gen.js │ │ │ ├── compiler.js │ │ │ └── javascript-compiler.js │ │ ├── decorators/ │ │ │ └── inline.js │ │ ├── decorators.js │ │ ├── helpers/ │ │ │ ├── block-helper-missing.js │ │ │ ├── each.js │ │ │ ├── helper-missing.js │ │ │ ├── if.js │ │ │ ├── log.js │ │ │ ├── lookup.js │ │ │ └── with.js │ │ ├── helpers.js │ │ ├── internal/ │ │ │ ├── proto-access.js │ │ │ └── wrapHelper.js │ │ ├── logger.js │ │ ├── no-conflict.js │ │ ├── runtime.js │ │ ├── safe-string.js │ │ └── utils.js │ ├── handlebars.js │ ├── handlebars.runtime.js │ ├── index.js │ └── precompiler.js ├── package.json ├── release-notes.md ├── rspack.config.js ├── runtime.d.ts ├── runtime.js ├── spec/ │ ├── artifacts/ │ │ ├── bom.handlebars │ │ ├── empty.handlebars │ │ ├── example_1.handlebars │ │ ├── example_2.hbs │ │ ├── known.helpers.handlebars │ │ ├── non.default.extension.hbs │ │ └── partial.template.handlebars │ ├── ast.js │ ├── basic.js │ ├── blocks.js │ ├── builtins.js │ ├── compiler.js │ ├── data.js │ ├── env/ │ │ ├── browser-vitest-pre.js │ │ ├── browser-vitest.js │ │ ├── browser.js │ │ ├── common.js │ │ └── node.js │ ├── expected/ │ │ ├── bom.amd.js │ │ ├── compiled.string.txt │ │ ├── empty.amd.js │ │ ├── empty.amd.namespace.js │ │ ├── empty.amd.simple.js │ │ ├── empty.common.js │ │ ├── empty.name.amd.js │ │ ├── empty.root.amd.js │ │ ├── handlebar.path.amd.js │ │ ├── help.menu.txt │ │ ├── namespace.amd.js │ │ ├── non.default.extension.amd.js │ │ ├── non.empty.amd.known.helper.js │ │ ├── partial.template.js │ │ └── source.map.amd.js │ ├── helpers.js │ ├── index.html │ ├── javascript-compiler.js │ ├── partials.js │ ├── precompiler.js │ ├── regressions.js │ ├── require.js │ ├── runtime.js │ ├── security.js │ ├── source-map.js │ ├── spec.js │ ├── strict.js │ ├── subexpressions.js │ ├── tokenizer.js │ ├── umd-runtime.html │ ├── umd.html │ ├── utils.js │ ├── vendor/ │ │ └── require.js │ └── whitespace-control.js ├── tasks/ │ ├── publish-to-aws.js │ ├── tests/ │ │ ├── README.md │ │ ├── cli.test.js │ │ └── git.test.js │ ├── util/ │ │ ├── async-grunt-task.js │ │ ├── exec-file.js │ │ └── git.js │ └── version.js ├── tests/ │ ├── bench/ │ │ ├── compare.mjs │ │ ├── perf.mjs │ │ ├── report.mjs │ │ ├── size.mjs │ │ └── templates.mjs │ ├── browser/ │ │ ├── README.md │ │ ├── playwright.config.js │ │ └── tests/ │ │ └── lib.spec.js │ ├── integration/ │ │ ├── README.md │ │ ├── multi-nodejs-test/ │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── precompile-test-template.txt.hbs │ │ │ ├── run-handlebars.js │ │ │ └── test.sh │ │ ├── rollup-test/ │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── rollup.config.js │ │ │ ├── src/ │ │ │ │ └── index.js │ │ │ └── test.sh │ │ ├── run-integration-tests.sh │ │ ├── webpack-babel-test/ │ │ │ ├── .babelrc │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── handlebars-inline-precompile-test.js │ │ │ │ └── lib/ │ │ │ │ └── assert.js │ │ │ ├── test.sh │ │ │ └── webpack.config.js │ │ └── webpack-test/ │ │ ├── .gitignore │ │ ├── package.json │ │ ├── src/ │ │ │ ├── handlebars-default-import-test.js │ │ │ ├── handlebars-esm-import-test.js │ │ │ ├── handlebars-loader-test.js │ │ │ ├── handlebars-register-helper-test.js │ │ │ ├── handlebars-runtime-test.js │ │ │ ├── lib/ │ │ │ │ └── assert.js │ │ │ └── test-template.handlebars │ │ ├── test.sh │ │ └── webpack.config.js │ ├── print-script.js │ └── rspack/ │ └── rspack.test.js ├── tstyche.config.json ├── types/ │ ├── __typetests__/ │ │ └── handlebars.tst.ts │ ├── index.d.ts │ └── tsconfig.json └── vitest.config.js
SYMBOL INDEX (186 symbols across 53 files)
FILE: components/lib/handlebars/source.rb
type Handlebars (line 1) | module Handlebars
type Source (line 2) | module Source
function bundled_path (line 3) | def self.bundled_path
function runtime_bundled_path (line 7) | def self.runtime_bundled_path
FILE: lib/handlebars.js
function create (line 18) | function create() {
FILE: lib/handlebars.runtime.js
function create (line 13) | function create() {
FILE: lib/handlebars/base.js
constant VERSION (line 8) | const VERSION = '4.7.7';
constant COMPILER_REVISION (line 9) | const COMPILER_REVISION = 8;
constant LAST_COMPATIBLE_COMPILER_REVISION (line 10) | const LAST_COMPATIBLE_COMPILER_REVISION = 7;
constant REVISION_CHANGES (line 12) | const REVISION_CHANGES = {
function HandlebarsEnvironment (line 25) | function HandlebarsEnvironment(helpers, partials, decorators) {
method resetLoggedPropertyAccesses (line 87) | resetLoggedPropertyAccesses() {
FILE: lib/handlebars/compiler/ast.js
constant AST (line 1) | let AST = {
FILE: lib/handlebars/compiler/code-gen.js
function castChunk (line 55) | function castChunk(chunk, codeGen, loc) {
function CodeGen (line 70) | function CodeGen(srcFile) {
method isEmpty (line 76) | isEmpty() {
FILE: lib/handlebars/compiler/compiler.js
function Compiler (line 7) | function Compiler() {}
method DecoratorBlock (line 152) | DecoratorBlock(decorator) {
method Decorator (line 213) | Decorator(decorator) {
function precompile (line 442) | function precompile(input, options = {}, env) {
function compile (line 449) | function compile(input, options = {}, env) {
function validateInput (line 476) | function validateInput(input, options) {
function compileEnvironment (line 500) | function compileEnvironment(input, options, env) {
function argEquals (line 505) | function argEquals(a, b) {
function transformLiteralToPath (line 520) | function transformLiteralToPath(sexpr) {
FILE: lib/handlebars/compiler/javascript-compiler.js
function Literal (line 6) | function Literal(value) {
function JavaScriptCompiler (line 10) | function JavaScriptCompiler() {}
method registerDecorator (line 651) | registerDecorator(paramSize, name) {
function strictLookup (line 1167) | function strictLookup(requireTerminal, compiler, parts, startPartIndex, ...
FILE: lib/handlebars/decorators.js
function registerDefaultDecorators (line 3) | function registerDefaultDecorators(instance) {
FILE: lib/handlebars/helpers.js
function registerDefaultHelpers (line 9) | function registerDefaultHelpers(instance) {
function moveHelperToHooks (line 19) | function moveHelperToHooks(instance, helperName, keepHelper) {
FILE: lib/handlebars/helpers/each.js
function execIteration (line 24) | function execIteration(field, value, index, last) {
FILE: lib/handlebars/internal/proto-access.js
function createProtoAccessControl (line 6) | function createProtoAccessControl(runtimeOptions) {
function resultIsAllowed (line 33) | function resultIsAllowed(result, protoAccessControl, propertyName) {
function checkWhiteList (line 41) | function checkWhiteList(protoAccessControlForType, propertyName) {
function logUnexpectedPropertyAccessOnce (line 52) | function logUnexpectedPropertyAccessOnce(propertyName) {
function resetLoggedProperties (line 64) | function resetLoggedProperties() {
FILE: lib/handlebars/internal/wrapHelper.js
function wrapHelper (line 1) | function wrapHelper(helper, transformOptionsFn) {
FILE: lib/handlebars/runtime.js
function checkRevision (line 16) | function checkRevision(compilerInfo) {
function template (line 49) | function template(templateSpec, env) {
function wrapProgram (line 291) | function wrapProgram(
function resolvePartial (line 332) | function resolvePartial(partial, context, options) {
function invokePartial (line 347) | function invokePartial(partial, context, options) {
function noop (line 385) | function noop() {
function initData (line 389) | function initData(context, data) {
function executeDecorators (line 397) | function executeDecorators(fn, prog, container, depths, data, blockParam...
function addHelpers (line 414) | function addHelpers(mergedHelpers, helpers, container) {
function passLookupPropertyOption (line 422) | function passLookupPropertyOption(helper, container) {
FILE: lib/handlebars/safe-string.js
function SafeString (line 2) | function SafeString(string) {
FILE: lib/handlebars/utils.js
function escapeChar (line 14) | function escapeChar(chr) {
function extend (line 18) | function extend(obj /* , ...source */) {
function isFunction (line 34) | function isFunction(value) {
function testTag (line 38) | function testTag(name) {
function indexOf (line 52) | function indexOf(array, value) {
function escapeExpression (line 61) | function escapeExpression(string) {
function isEmpty (line 84) | function isEmpty(value) {
function createFrame (line 94) | function createFrame(object) {
FILE: lib/index.js
function extension (line 16) | function extension(module, filename) {
FILE: lib/precompiler.js
function loadStrings (line 25) | function loadStrings(opts, callback) {
function loadFiles (line 66) | function loadFiles(opts, callback) {
function arrayCast (line 305) | function arrayCast(value) {
function minify (line 323) | function minify(output, sourceMapFile) {
FILE: rspack.config.js
function createConfig (line 15) | function createConfig(entry, filename, minimize) {
FILE: spec/ast.js
function testColumns (line 111) | function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
FILE: spec/builtins.js
function Clazz (line 250) | function Clazz() {
function Iterator (line 553) | function Iterator(arr) {
function Iterable (line 565) | function Iterable(arr) {
FILE: spec/compiler.js
function compile (line 7) | function compile(string) {
FILE: spec/env/browser-vitest.js
function safeEval (line 18) | function safeEval(templateSpec) {
FILE: spec/env/browser.js
function safeEval (line 27) | function safeEval(templateSpec) {
FILE: spec/env/common.js
function HandlebarsTestBench (line 7) | function HandlebarsTestBench(templateAsString) {
FILE: spec/env/node.js
function safeEval (line 15) | function safeEval(templateSpec) {
FILE: spec/helpers.js
function runWithIdentityHelper (line 37) | function runWithIdentityHelper(template, expected) {
function link (line 187) | function link(options) {
function list (line 249) | function list(context, options) {
function goodbye (line 562) | function goodbye(options) {
function goodbye (line 618) | function goodbye(options) {
FILE: spec/partials.js
function partial (line 182) | function partial(context) {
FILE: spec/precompiler.js
function mockRequireUglify (line 36) | async function mockRequireUglify(loadError, callback) {
function loadTemplatesAsync (line 291) | function loadTemplatesAsync(inputOpts) {
FILE: spec/regressions.js
function registerTemplate (line 408) | function registerTemplate(Handlebars, compileTemplate) {
function compiledTemplateVersion7 (line 414) | function compiledTemplateVersion7() {
function compiledTemplateVersion7_usingLookupHelper (line 436) | function compiledTemplateVersion7_usingLookupHelper() {
FILE: spec/security.js
function TestClass (line 163) | function TestClass() {}
function checkProtoMethodAccess (line 185) | function checkProtoMethodAccess(compileOptions) {
function checkProtoPropertyAccess (line 318) | function checkProtoPropertyAccess(compileOptions) {
function wrapToAdjustContainer (line 452) | function wrapToAdjustContainer(precompiledTemplateFunction) {
FILE: spec/source-map.js
function grepLine (line 51) | function grepLine(token, lines) {
FILE: spec/tokenizer.js
function shouldMatchTokens (line 1) | function shouldMatchTokens(result, tokens) {
function shouldBeToken (line 6) | function shouldBeToken(result, name, text) {
function tokenize (line 16) | function tokenize(template) {
FILE: spec/utils.js
function A (line 71) | function A() {
FILE: spec/vendor/require.js
function isFunction (line 41) | function isFunction(it) {
function isArray (line 45) | function isArray(it) {
function each (line 53) | function each(ary, func) {
function eachReverse (line 68) | function eachReverse(ary, func) {
function hasProp (line 79) | function hasProp(obj, prop) {
function getOwn (line 83) | function getOwn(obj, prop) {
function eachProp (line 92) | function eachProp(obj, func) {
function mixin (line 107) | function mixin(target, source, force, deepStringMixin) {
function bind (line 127) | function bind(obj, fn) {
function scripts (line 133) | function scripts() {
function defaultOnError (line 137) | function defaultOnError(err) {
function getGlobal (line 143) | function getGlobal(value) {
function makeError (line 162) | function makeError(id, msg, err, requireModules) {
function newContext (line 194) | function newContext(contextName) {
function getInteractiveScript (line 1908) | function getInteractiveScript() {
FILE: tasks/publish-to-aws.js
function main (line 9) | async function main() {
function validateS3Env (line 36) | function validateS3Env() {
function publish (line 47) | async function publish(suffixes) {
function publishSuffix (line 52) | async function publishSuffix(suffix) {
function uploadToBucket (line 68) | async function uploadToBucket(localFile, nameInBucket) {
function getS3Client (line 80) | function getS3Client() {
function getNameInBucket (line 93) | function getNameInBucket(filename, suffix) {
function getLocalFile (line 97) | function getLocalFile(filename) {
FILE: tasks/tests/cli.test.js
method toEqualWithRelaxedSpace (line 9) | toEqualWithRelaxedSpace(received, expected) {
function expectedFile (line 34) | function expectedFile(specPath) {
FILE: tasks/tests/git.test.js
function createRepositoryThatActsAsRemote (line 26) | async function createRepositoryThatActsAsRemote() {
FILE: tasks/util/async-grunt-task.js
function createRegisterAsyncTaskFn (line 3) | function createRegisterAsyncTaskFn(grunt) {
FILE: tasks/util/exec-file.js
function execNodeJsScriptWithInheritedOutput (line 7) | async function execNodeJsScriptWithInheritedOutput(command, args) {
FILE: tasks/util/git.js
method remotes (line 4) | async remotes() {
method branches (line 7) | async branches() {
method commitInfo (line 10) | async commitInfo() {
method add (line 20) | async add(path) {
method commit (line 23) | async commit(message) {
function getHeadSha (line 29) | async function getHeadSha() {
function getMasterSha (line 34) | async function getMasterSha() {
function getTagName (line 47) | async function getTagName() {
function git (line 62) | async function git(...args) {
FILE: tasks/version.js
function replaceAndAdd (line 57) | async function replaceAndAdd(path, regex, value) {
FILE: tests/bench/compare.mjs
function parseOps (line 77) | function parseOps(str) {
function parseNs (line 84) | function parseNs(str) {
function parseReport (line 92) | function parseReport(filepath) {
function pctChange (line 142) | function pctChange(baseline, current) {
function formatPct (line 147) | function formatPct(pct) {
function indicator (line 152) | function indicator(pct, higherIsBetter) {
FILE: tests/bench/perf.mjs
constant COMPILE_BENCH_CONFIG (line 15) | const COMPILE_BENCH_CONFIG = {
constant EXEC_BENCH_CONFIG (line 21) | const EXEC_BENCH_CONFIG = {
function getGitBranch (line 29) | function getGitBranch() {
function newBench (line 89) | function newBench(config) {
function createEnv (line 93) | function createEnv(def) {
function shouldRunSection (line 106) | function shouldRunSection(title) {
function runSection (line 111) | async function runSection(title, config, setup) {
function run (line 127) | async function run() {
FILE: tests/bench/report.mjs
function formatNs (line 9) | function formatNs(ns) {
function formatOps (line 15) | function formatOps(ops) {
function taskToRow (line 21) | function taskToRow(task) {
function completedTasks (line 36) | function completedTasks(bench) {
function printResults (line 42) | function printResults(bench) {
function printSectionHeader (line 69) | function printSectionHeader(title) {
function saveMarkdownReport (line 81) | function saveMarkdownReport(
FILE: tests/bench/size.mjs
function measureDistSizes (line 15) | async function measureDistSizes() {
function measurePrecompileSizes (line 46) | function measurePrecompileSizes() {
function run (line 74) | async function run() {
FILE: tests/bench/templates.mjs
method header (line 136) | header() {
FILE: tests/browser/tests/lib.spec.js
function waitForMochaAndAssertResult (line 3) | async function waitForMochaAndAssertResult(page) {
FILE: tests/integration/webpack-babel-test/src/lib/assert.js
function assertEquals (line 1) | function assertEquals(actual, expected) {
FILE: tests/integration/webpack-test/src/lib/assert.js
function assertEquals (line 1) | function assertEquals(actual, expected) {
FILE: tests/print-script.js
function collectSource (line 46) | function collectSource(lines, lineName, colName, order) {
FILE: tests/rspack/rspack.test.js
constant EXPECTED_BUNDLES (line 8) | const EXPECTED_BUNDLES = [
FILE: types/index.d.ts
type TemplateDelegate (line 24) | interface TemplateDelegate<T = any> {
type Template (line 28) | type Template<T = any> = TemplateDelegate<T> | string;
type RuntimeOptions (line 30) | interface RuntimeOptions {
type HelperOptions (line 45) | interface HelperOptions {
type HelperDelegate (line 52) | interface HelperDelegate {
type HelperDeclareSpec (line 63) | interface HelperDeclareSpec {
class Exception (line 113) | class Exception {
class SafeString (line 127) | class SafeString {
type ICompiler (line 150) | interface ICompiler {
class Visitor (line 171) | class Visitor implements ICompiler {
type ResolvePartialOptions (line 194) | interface ResolvePartialOptions {
type HandlebarsTemplatable (line 217) | interface HandlebarsTemplatable {
type HandlebarsTemplateDelegate (line 222) | type HandlebarsTemplateDelegate<T = any> =
type HandlebarsTemplates (line 225) | interface HandlebarsTemplates {
type TemplateSpecification (line 229) | interface TemplateSpecification {}
type RuntimeOptions (line 232) | type RuntimeOptions = Handlebars.RuntimeOptions;
type CompileOptions (line 234) | interface CompileOptions {
type KnownHelpers (line 247) | type KnownHelpers = {
type BuiltinHelperName (line 251) | type BuiltinHelperName =
type CustomHelperName (line 261) | type CustomHelperName = string;
type PrecompileOptions (line 263) | interface PrecompileOptions extends CompileOptions {
type SafeString (line 270) | type SafeString = Handlebars.SafeString;
type Utils (line 272) | type Utils = typeof Handlebars.Utils;
type Logger (line 277) | interface Logger {
type CompilerInfo (line 289) | type CompilerInfo = [number /* revision */, string /* versions */];
Condensed preview — 166 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (679K chars).
[
{
"path": ".editorconfig",
"chars": 181,
"preview": "root = true\n\n[*.js]\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.yml]\nindent_size = 2"
},
{
"path": ".git-blame-ignore-revs",
"chars": 217,
"preview": "# Upgrade to Prettier 2.7\n3d228334530860a6e3f99dc10777c84bf22292c1\n# Format markdown files with Prettier\ndfe2eaaf20f0b67"
},
{
"path": ".gitattributes",
"chars": 150,
"preview": "# Handlebars-template fixtures in test cases need deterministic eol\n*.handlebars text eol=lf\n*.hbs text eol=lf\n\n# Lexer "
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 609,
"preview": "Before filing issues, please check the following points first:\n\n- [ ] Please don't open issues for security issues. Inst"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 941,
"preview": "Before creating a pull-request, please check https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.m"
},
{
"path": ".github/dependabot.yml",
"chars": 182,
"preview": "version: 2\nupdates:\n - package-ecosystem: npm\n directory: '/'\n open-pull-requests-limit: 0\n schedule:\n in"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1761,
"preview": "name: CI\n\non:\n push:\n branches:\n - master\n pull_request: {}\n\njobs:\n lint:\n name: Lint\n runs-on: 'ubuntu"
},
{
"path": ".github/workflows/release.yml",
"chars": 934,
"preview": "name: Release\n\non:\n workflow_dispatch:\n push:\n branches:\n - master\n tags:\n - '*'\n\njobs:\n publish-aws-"
},
{
"path": ".gitignore",
"chars": 255,
"preview": ".rvmrc\n.DS_Store\n/tmp/\n*.sublime-project\n*.sublime-workspace\nnpm-debug.log\n.idea\n/yarn-error.log\n/yarn.lock\nnode_modules"
},
{
"path": ".gitmodules",
"chars": 94,
"preview": "[submodule \"spec/mustache\"]\n\tpath = spec/mustache\n\turl = https://github.com/mustache/spec.git\n"
},
{
"path": ".oxfmtrc.json",
"chars": 606,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/nicolo-ribaudo/oxfmt-config-schema/refs/heads/main/schema.json\",\n \"si"
},
{
"path": ".oxlintrc.json",
"chars": 3565,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json\",\n \"plugins"
},
{
"path": ".swcrc",
"chars": 142,
"preview": "{\n \"$schema\": \"https://swc.rs/schema.json\",\n \"module\": {\n \"type\": \"commonjs\",\n \"importInterop\": \"swc\"\n },\n \"so"
},
{
"path": "CONTRIBUTING.md",
"chars": 5558,
"preview": "# How to Contribute\n\n## Reporting Security Issues\n\nPlease refer to our [Security Policy](https://github.com/handlebars-l"
},
{
"path": "FAQ.md",
"chars": 2904,
"preview": "# Frequently Asked Questions\n\n## How can I file a bug report:\n\nSee our guidelines on [reporting issues](https://github.c"
},
{
"path": "Gruntfile.js",
"chars": 297,
"preview": "// Legacy Gruntfile — kept only for the 'metrics' and 'version' tasks.\n// The main build pipeline uses rspack + swc (see"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "Copyright (C) 2011-2019 by Yehuda Katz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 12156,
"preview": "[](https://github"
},
{
"path": "SECURITY.md",
"chars": 493,
"preview": "# Security Policy\n\nWe recommend always using the latest versions of Handlebars and its official companion libraries to e"
},
{
"path": "bin/handlebars.mjs",
"chars": 3391,
"preview": "#!/usr/bin/env node\n\nimport { createRequire } from 'node:module';\nimport yargs from 'yargs';\n\nconst require = createRequ"
},
{
"path": "components/bower.json",
"chars": 126,
"preview": "{\n \"name\": \"handlebars\",\n \"version\": \"5.0.0-alpha.1\",\n \"main\": \"handlebars.js\",\n \"license\": \"MIT\",\n \"dependencies\":"
},
{
"path": "components/component.json",
"chars": 146,
"preview": "{\n \"name\": \"handlebars\",\n \"repo\": \"components/handlebars.js\",\n \"version\": \"1.0.0\",\n \"main\": \"handlebars.js\",\n \"scri"
},
{
"path": "components/composer.json",
"chars": 761,
"preview": "{\n \"name\": \"components/handlebars.js\",\n \"description\": \"Handlebars.js and Mustache are both logicless templating langu"
},
{
"path": "components/handlebars-source.gemspec",
"chars": 705,
"preview": "# -*- encoding: utf-8 -*-\nrequire 'json'\n\npackage = JSON.parse(File.read('bower.json'))\n\nGem::Specification.new do |gem|"
},
{
"path": "components/handlebars.js.nuspec",
"chars": 644,
"preview": "<?xml version=\"1.0\"?>\n<package>\n\t<metadata>\n\t\t<id>handlebars.js</id>\n\t\t<version>5.0.0-alpha.1</version>\n\t\t<authors>handl"
},
{
"path": "components/lib/handlebars/source.rb",
"chars": 247,
"preview": "module Handlebars\n module Source\n def self.bundled_path\n File.expand_path(\"../../../handlebars.js\", __FILE__)\n "
},
{
"path": "components/package.json",
"chars": 326,
"preview": "{\n \"name\": \"handlebars\",\n \"version\": \"5.0.0-alpha.1\",\n \"license\": \"MIT\",\n \"jspm\": {\n \"main\": \"handlebars\",\n \"s"
},
{
"path": "docs/compiler-api.md",
"chars": 10321,
"preview": "# Handlebars Compiler APIs\n\nThere are a number of formal APIs that tool implementors may interact with.\n\n## AST\n\nOther t"
},
{
"path": "docs/decorators-api.md",
"chars": 2010,
"preview": "# Decorators\n\n**Decorators are deprecated, please join the discussion at [#1574](https://github.com/handlebars-lang/hand"
},
{
"path": "eslint.config.mjs",
"chars": 407,
"preview": "import compat from 'eslint-plugin-compat';\n\nexport default [\n {\n // Ignore everything except lib/\n ignores: ['**'"
},
{
"path": "lib/handlebars/base.js",
"chars": 2522,
"preview": "import { Exception } from '@handlebars/parser';\nimport { createFrame, extend, toString } from './utils';\nimport { regist"
},
{
"path": "lib/handlebars/compiler/ast.js",
"chars": 988,
"preview": "let AST = {\n // Public API used to evaluate derived attributes regarding AST nodes\n helpers: {\n // a mustache is de"
},
{
"path": "lib/handlebars/compiler/code-gen.js",
"chars": 4256,
"preview": "/* global define */\nimport { isArray } from '../utils';\n\nlet SourceNode;\n\ntry {\n /* v8 ignore next */\n if (typeof defi"
},
{
"path": "lib/handlebars/compiler/compiler.js",
"chars": 13497,
"preview": "import { Exception } from '@handlebars/parser';\nimport { isArray, indexOf, extend } from '../utils';\nimport AST from './"
},
{
"path": "lib/handlebars/compiler/javascript-compiler.js",
"chars": 31908,
"preview": "import { Exception } from '@handlebars/parser';\nimport { COMPILER_REVISION, REVISION_CHANGES } from '../base';\nimport { "
},
{
"path": "lib/handlebars/decorators/inline.js",
"chars": 671,
"preview": "import { extend } from '../utils';\n\nexport default function (instance) {\n instance.registerDecorator(\n 'inline',\n "
},
{
"path": "lib/handlebars/decorators.js",
"chars": 135,
"preview": "import registerInline from './decorators/inline';\n\nexport function registerDefaultDecorators(instance) {\n registerInlin"
},
{
"path": "lib/handlebars/helpers/block-helper-missing.js",
"chars": 583,
"preview": "import { isArray } from '../utils';\n\nexport default function (instance) {\n instance.registerHelper('blockHelperMissing'"
},
{
"path": "lib/handlebars/helpers/each.js",
"chars": 2586,
"preview": "import { Exception } from '@handlebars/parser';\nimport { createFrame, isArray, isFunction, isMap, isSet } from '../utils"
},
{
"path": "lib/handlebars/helpers/helper-missing.js",
"chars": 471,
"preview": "import { Exception } from '@handlebars/parser';\n\nexport default function (instance) {\n instance.registerHelper('helperM"
},
{
"path": "lib/handlebars/helpers/if.js",
"chars": 1194,
"preview": "import { Exception } from '@handlebars/parser';\nimport { isEmpty, isFunction } from '../utils';\n\nexport default function"
},
{
"path": "lib/handlebars/helpers/log.js",
"chars": 523,
"preview": "export default function (instance) {\n instance.registerHelper('log', function (/* message, options */) {\n let args ="
},
{
"path": "lib/handlebars/helpers/lookup.js",
"chars": 255,
"preview": "export default function (instance) {\n instance.registerHelper('lookup', function (obj, field, options) {\n if (!obj) "
},
{
"path": "lib/handlebars/helpers/with.js",
"chars": 616,
"preview": "import { Exception } from '@handlebars/parser';\nimport { isEmpty, isFunction } from '../utils';\n\nexport default function"
},
{
"path": "lib/handlebars/helpers.js",
"chars": 891,
"preview": "import registerBlockHelperMissing from './helpers/block-helper-missing';\nimport registerEach from './helpers/each';\nimpo"
},
{
"path": "lib/handlebars/internal/proto-access.js",
"chars": 2393,
"preview": "import { extend } from '../utils';\nimport logger from '../logger';\n\nconst loggedProperties = Object.create(null);\n\nexpor"
},
{
"path": "lib/handlebars/internal/wrapHelper.js",
"chars": 578,
"preview": "export function wrapHelper(helper, transformOptionsFn) {\n if (typeof helper !== 'function') {\n // This should not ha"
},
{
"path": "lib/handlebars/logger.js",
"chars": 954,
"preview": "import { indexOf } from './utils';\n\nlet logger = {\n methodMap: ['debug', 'info', 'warn', 'error'],\n level: 'info',\n\n "
},
{
"path": "lib/handlebars/no-conflict.js",
"chars": 273,
"preview": "export default function (Handlebars) {\n let $Handlebars = globalThis.Handlebars;\n\n /* v8 ignore next */\n Handlebars.n"
},
{
"path": "lib/handlebars/runtime.js",
"chars": 11875,
"preview": "import { Exception } from '@handlebars/parser';\nimport * as Utils from './utils';\nimport {\n COMPILER_REVISION,\n create"
},
{
"path": "lib/handlebars/safe-string.js",
"chars": 230,
"preview": "// Build out our basic SafeString type\nfunction SafeString(string) {\n this.string = string;\n}\n\nSafeString.prototype.toS"
},
{
"path": "lib/handlebars/utils.js",
"chars": 2273,
"preview": "const escape = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '`': '`',\n '=':"
},
{
"path": "lib/handlebars.js",
"chars": 1015,
"preview": "import {\n parser as Parser,\n parse,\n parseWithoutProcessing,\n Visitor,\n} from '@handlebars/parser';\n\nimport runtime "
},
{
"path": "lib/handlebars.runtime.js",
"chars": 1214,
"preview": "import { Exception } from '@handlebars/parser';\nimport * as base from './handlebars/base';\n\n// Each of these augment the"
},
{
"path": "lib/index.js",
"chars": 764,
"preview": "// USAGE:\n// var handlebars = require('handlebars');\n/* eslint-disable no-var */\n\n// var local = handlebars.create();\n\nv"
},
{
"path": "lib/precompiler.js",
"chars": 8559,
"preview": "/* eslint-disable no-console */\nimport Async from 'neo-async';\nimport fs from 'fs';\nimport Handlebars from './handlebars"
},
{
"path": "package.json",
"chars": 3801,
"preview": "{\n \"name\": \"handlebars\",\n \"version\": \"5.0.0-alpha.1\",\n \"description\": \"Handlebars provides the power necessary to let"
},
{
"path": "release-notes.md",
"chars": 63742,
"preview": "# Release Notes\n\n## Development\n\n[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.7...master)\n\n##"
},
{
"path": "rspack.config.js",
"chars": 2188,
"preview": "const { rspack } = require('@rspack/core');\nconst path = require('path');\nconst fs = require('fs');\n\nconst pkg = require"
},
{
"path": "runtime.d.ts",
"chars": 83,
"preview": "import Handlebars = require('handlebars');\n\ndeclare module 'handlebars/runtime' {}\n"
},
{
"path": "runtime.js",
"chars": 167,
"preview": "// Create a simple path alias to allow browserify to resolve\n// the runtime on a supported path.\nmodule.exports = requir"
},
{
"path": "spec/artifacts/bom.handlebars",
"chars": 2,
"preview": "a"
},
{
"path": "spec/artifacts/empty.handlebars",
"chars": 0,
"preview": ""
},
{
"path": "spec/artifacts/example_1.handlebars",
"chars": 8,
"preview": "{{foo}}\n"
},
{
"path": "spec/artifacts/example_2.hbs",
"chars": 17,
"preview": "Hello, {{name}}!\n"
},
{
"path": "spec/artifacts/known.helpers.handlebars",
"chars": 154,
"preview": "{{#someHelper true}}\n <div>Some known helper</div>\n {{#anotherHelper true}}\n <div>Another known helper</div>\n {"
},
{
"path": "spec/artifacts/non.default.extension.hbs",
"chars": 25,
"preview": "<div>This is a test</div>"
},
{
"path": "spec/artifacts/partial.template.handlebars",
"chars": 23,
"preview": "<div>Test Partial</div>"
},
{
"path": "spec/ast.js",
"chars": 5523,
"preview": "describe('ast', function () {\n if (!Handlebars.AST) {\n return;\n }\n\n var AST = Handlebars.AST;\n\n describe('BlockSt"
},
{
"path": "spec/basic.js",
"chars": 17090,
"preview": "describe('basic context', function () {\n it('most basic', function () {\n expectTemplate('{{foo}}').withInput({ foo: "
},
{
"path": "spec/blocks.js",
"chars": 14141,
"preview": "describe('blocks', function () {\n it('array', function () {\n var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel"
},
{
"path": "spec/builtins.js",
"chars": 23879,
"preview": "describe('builtin helpers', function () {\n describe('#if', function () {\n it('if', function () {\n var string = "
},
{
"path": "spec/compiler.js",
"chars": 5587,
"preview": "describe('compiler', function () {\n if (!Handlebars.compile) {\n return;\n }\n\n describe('#equals', function () {\n "
},
{
"path": "spec/data.js",
"chars": 10054,
"preview": "describe('data', function () {\n it('passing in data to a compiled function that expects data - works with helpers', fun"
},
{
"path": "spec/env/browser-vitest-pre.js",
"chars": 290,
"preview": "// Pre-setup for browser tests. Must run before the main setup file\n// imports the Handlebars library, so that noConflic"
},
{
"path": "spec/env/browser-vitest.js",
"chars": 690,
"preview": "import './common.js';\nimport Handlebars from '../../lib/handlebars.js';\n\nglobalThis.Handlebars = Handlebars;\n\nglobalThis"
},
{
"path": "spec/env/browser.js",
"chars": 857,
"preview": "require('./common');\n\nvar fs = require('fs'),\n vm = require('vm');\n\nglobal.Handlebars = 'no-conflict';\n\nvar filename = "
},
{
"path": "spec/env/common.js",
"chars": 3634,
"preview": "var global = globalThis;\n\nglobal.expectTemplate = function (templateAsString) {\n return new HandlebarsTestBench(templat"
},
{
"path": "spec/env/node.js",
"chars": 623,
"preview": "require('./common');\n\nglobal.Handlebars = require('../../lib');\n\nglobal.CompilerContext = {\n compile: function (templat"
},
{
"path": "spec/expected/bom.amd.js",
"chars": 361,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars"
},
{
"path": "spec/expected/compiled.string.txt",
"chars": 139,
"preview": "{\"compiler\":[8,\">= 4.3.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n return \"<div>Test String</div>\""
},
{
"path": "spec/expected/empty.amd.js",
"chars": 353,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars.t"
},
{
"path": "spec/expected/empty.amd.namespace.js",
"chars": 363,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars.t"
},
{
"path": "spec/expected/empty.amd.simple.js",
"chars": 115,
"preview": "{\"compiler\":[8,\">= 4.3.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n return \"\";\n},\"useData\":true}"
},
{
"path": "spec/expected/empty.common.js",
"chars": 270,
"preview": "(function() {\n var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};\ntempl"
},
{
"path": "spec/expected/empty.name.amd.js",
"chars": 554,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars.t"
},
{
"path": "spec/expected/empty.root.amd.js",
"chars": 396,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars.t"
},
{
"path": "spec/expected/handlebar.path.amd.js",
"chars": 372,
"preview": "define(['some-path/handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = "
},
{
"path": "spec/expected/help.menu.txt",
"chars": 2581,
"preview": "Precompile handlebar templates.\nUsage: handlebars.mjs [template|directory]...\n\nOptions:\n -f, --output Output Fi"
},
{
"path": "spec/expected/namespace.amd.js",
"chars": 517,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars"
},
{
"path": "spec/expected/non.default.extension.amd.js",
"chars": 403,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars"
},
{
"path": "spec/expected/non.empty.amd.known.helper.js",
"chars": 1599,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\nHandlebars = Handlebars[\"default\"]; var template = Handlebars.temp"
},
{
"path": "spec/expected/partial.template.js",
"chars": 406,
"preview": "define(['handlebars.runtime'], function(Handlebars) {\n Handlebars = Handlebars[\"default\"]; var template = Handlebars"
},
{
"path": "spec/expected/source.map.amd.js",
"chars": 252,
"preview": "define([\"handlebars.runtime\"],function(e){var t=(e=e.default).template;return(e.templates=e.templates||{}).test=t({compi"
},
{
"path": "spec/helpers.js",
"chars": 33805,
"preview": "describe('helpers', function () {\n it('helper with complex lookup$', function () {\n expectTemplate('{{#goodbyes}}{{{"
},
{
"path": "spec/index.html",
"chars": 1687,
"preview": "<!doctype html>\n<html>\n <head>\n <title>Handlebars UMD Smoke Test</title>\n <meta http-equiv=\"Content-Type\" content"
},
{
"path": "spec/javascript-compiler.js",
"chars": 4399,
"preview": "describe('javascript-compiler api', function () {\n if (!Handlebars.JavaScriptCompiler) {\n return;\n }\n\n describe('#"
},
{
"path": "spec/partials.js",
"chars": 24145,
"preview": "describe('partials', function () {\n it('basic partials', function () {\n var string = 'Dudes: {{#dudes}}{{> dude}}{{/"
},
{
"path": "spec/precompiler.js",
"chars": 12687,
"preview": "/* eslint-disable no-console */\ndescribe('precompiler', function () {\n // NOP Under non-node environments\n if (typeof "
},
{
"path": "spec/regressions.js",
"chars": 15952,
"preview": "describe('Regressions', function () {\n it('GH-94: Cannot read property of undefined', function () {\n expectTemplate("
},
{
"path": "spec/require.js",
"chars": 745,
"preview": "if (typeof require !== 'undefined' && require.extensions['.handlebars']) {\n describe('Require', function () {\n it('L"
},
{
"path": "spec/runtime.js",
"chars": 1728,
"preview": "describe('runtime', function () {\n describe('#template', function () {\n it('should throw on invalid templates', func"
},
{
"path": "spec/security.js",
"chars": 15377,
"preview": "describe('security issues', function () {\n describe('GH-1495: Prevent Remote Code Execution via constructor', function "
},
{
"path": "spec/source-map.js",
"chars": 1639,
"preview": "try {\n if (typeof define !== 'function' || !define.amd) {\n var SourceMap = require('source-map'),\n SourceMapCon"
},
{
"path": "spec/spec.js",
"chars": 1630,
"preview": "describe('spec', function () {\n // NOP Under non-node environments\n if (typeof process === 'undefined') {\n return;\n"
},
{
"path": "spec/strict.js",
"chars": 5280,
"preview": "var Exception = Handlebars.Exception;\n\ndescribe('strict', function () {\n describe('strict mode', function () {\n it('"
},
{
"path": "spec/subexpressions.js",
"chars": 5964,
"preview": "describe('subexpressions', function () {\n it('arg-less helper', function () {\n expectTemplate('{{foo (bar)}}!')\n "
},
{
"path": "spec/tokenizer.js",
"chars": 20654,
"preview": "function shouldMatchTokens(result, tokens) {\n for (var index = 0; index < result.length; index++) {\n expect(result[i"
},
{
"path": "spec/umd-runtime.html",
"chars": 1680,
"preview": "<!doctype html>\n<html>\n <head>\n <title>Handlebars Runtime UMD Smoke Test</title>\n <meta http-equiv=\"Content-Type\""
},
{
"path": "spec/umd.html",
"chars": 1965,
"preview": "<!doctype html>\n<html>\n <head>\n <title>Handlebars UMD Module Smoke Test</title>\n <meta http-equiv=\"Content-Type\" "
},
{
"path": "spec/utils.js",
"chars": 3516,
"preview": "describe('utils', function () {\n describe('#SafeString', function () {\n it('constructing a safestring from a string "
},
{
"path": "spec/vendor/require.js",
"chars": 82688,
"preview": "/** vim: et:ts=4:sw=4:sts=4\n * @license RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved"
},
{
"path": "spec/whitespace-control.js",
"chars": 4373,
"preview": "describe('whitespace control', function () {\n it('should strip whitespace around mustache calls', function () {\n var"
},
{
"path": "tasks/publish-to-aws.js",
"chars": 2723,
"preview": "/* eslint-disable no-console */\nconst fs = require('fs');\nconst { S3, PutObjectCommand } = require('@aws-sdk/client-s3')"
},
{
"path": "tasks/tests/README.md",
"chars": 43,
"preview": "Use `mocha tasks/tests` to run these tests\n"
},
{
"path": "tasks/tests/cli.test.js",
"chars": 8940,
"preview": "const fs = require('fs');\nconst { exec } = require('child_process');\nconst { execCommand, FileTestHelper } = require('cl"
},
{
"path": "tasks/tests/git.test.js",
"chars": 4259,
"preview": "const os = require('os');\nconst path = require('path');\nconst fs = require('fs-extra');\nconst { spawnSync } = require('c"
},
{
"path": "tasks/util/async-grunt-task.js",
"chars": 333,
"preview": "module.exports = { createRegisterAsyncTaskFn };\n\nfunction createRegisterAsyncTaskFn(grunt) {\n return function registerA"
},
{
"path": "tasks/util/exec-file.js",
"chars": 469,
"preview": "const childProcess = require('child_process');\n\nmodule.exports = {\n execNodeJsScriptWithInheritedOutput,\n};\n\nasync func"
},
{
"path": "tasks/util/git.js",
"chars": 1785,
"preview": "const childProcess = require('child_process');\n\nmodule.exports = {\n async remotes() {\n return git('remote', '-v');\n "
},
{
"path": "tasks/version.js",
"chars": 1742,
"preview": "const { execSync } = require('child_process');\nconst git = require('./util/git');\nconst semver = require('semver');\ncons"
},
{
"path": "tests/bench/compare.mjs",
"chars": 7142,
"preview": "import {\n mkdirSync,\n readFileSync,\n readdirSync,\n writeFileSync,\n statSync,\n} from 'node:fs';\nimport { basename, d"
},
{
"path": "tests/bench/perf.mjs",
"chars": 8261,
"preview": "import { execSync } from 'node:child_process';\nimport { Bench } from 'tinybench';\nimport Handlebars from '../../lib/inde"
},
{
"path": "tests/bench/report.mjs",
"chars": 3911,
"preview": "import { writeFileSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } "
},
{
"path": "tests/bench/size.mjs",
"chars": 2503,
"preview": "import { readFileSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileU"
},
{
"path": "tests/bench/templates.mjs",
"chars": 7035,
"preview": "export const templates = {\n // --- Small templates ---\n 'static string (no expressions)': {\n template: 'Hello world"
},
{
"path": "tests/browser/README.md",
"chars": 401,
"preview": "# Browser Tests with Playwright\n\nThese tests execute Mocha tests from the `spec`-folder in multiple browsers.\n\n## Using "
},
{
"path": "tests/browser/playwright.config.js",
"chars": 545,
"preview": "const { devices } = require('@playwright/test');\n\n/** @type {import('@playwright/test').PlaywrightTestConfig} */\nconst c"
},
{
"path": "tests/browser/tests/lib.spec.js",
"chars": 827,
"preview": "const { test, expect } = require('@playwright/test');\n\nasync function waitForMochaAndAssertResult(page) {\n await page.w"
},
{
"path": "tests/integration/README.md",
"chars": 556,
"preview": "Add a new integration test by creating a new subfolder\n\nAdd a file \"test.sh\" to that runs the test. \"test.sh\" should exi"
},
{
"path": "tests/integration/multi-nodejs-test/.gitignore",
"chars": 24,
"preview": "target\npackage-lock.json"
},
{
"path": "tests/integration/multi-nodejs-test/package.json",
"chars": 409,
"preview": "{\n \"name\": \"multi-nodejs-test\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"description\": \"Simple integration test with"
},
{
"path": "tests/integration/multi-nodejs-test/precompile-test-template.txt.hbs",
"chars": 18,
"preview": "Author: {{author}}"
},
{
"path": "tests/integration/multi-nodejs-test/run-handlebars.js",
"chars": 426,
"preview": "// This test should run successfully with node 0.10++ as long as Handlebars has been compiled before\nvar assert = requir"
},
{
"path": "tests/integration/multi-nodejs-test/test.sh",
"chars": 981,
"preview": "#!/bin/bash\n\nset -e\n\ncd \"$( dirname \"$( readlink -f \"$0\" )\" )\" || exit 1\n# shellcheck disable=SC1090\n[ -s \"$NVM_DIR/nvm."
},
{
"path": "tests/integration/rollup-test/.gitignore",
"chars": 35,
"preview": "node_modules\ndist\npackage-lock.json"
},
{
"path": "tests/integration/rollup-test/package.json",
"chars": 331,
"preview": "{\n \"name\": \"rollup-test\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"description\": \"Various tests with Handlebars and "
},
{
"path": "tests/integration/rollup-test/rollup.config.js",
"chars": 196,
"preview": "import { nodeResolve } from '@rollup/plugin-node-resolve';\n\nexport default {\n input: 'src/index.js',\n output: {\n fi"
},
{
"path": "tests/integration/rollup-test/src/index.js",
"chars": 232,
"preview": "import Handlebars from 'handlebars/lib/handlebars';\n\nconst template = Handlebars.compile('Author: {{author}}');\nconst re"
},
{
"path": "tests/integration/rollup-test/test.sh",
"chars": 189,
"preview": "#!/bin/bash\n\nset -e\n\n# Cleanup: package-lock and \"npm ci\" is not working with local dependencies\nrm -rf dist package-loc"
},
{
"path": "tests/integration/run-integration-tests.sh",
"chars": 289,
"preview": "#!/bin/bash\n\nset -e\n\ncd \"$( dirname \"$( readlink -f \"$0\" )\" )\"\n\nfor i in */test.sh ; do\n (\n echo \"------------------"
},
{
"path": "tests/integration/webpack-babel-test/.babelrc",
"chars": 143,
"preview": "{\n \"plugins\": [\n \"@roundingwellos/babel-plugin-handlebars-inline-precompile\"\n // \"istanbul\"\n ],\n \"presets\": [[\""
},
{
"path": "tests/integration/webpack-babel-test/.gitignore",
"chars": 35,
"preview": "node_modules\ndist\npackage-lock.json"
},
{
"path": "tests/integration/webpack-babel-test/package.json",
"chars": 527,
"preview": "{\n \"name\": \"webpack-babel-test\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"description\": \"\",\n \"scripts\": {\n \"buil"
},
{
"path": "tests/integration/webpack-babel-test/src/handlebars-inline-precompile-test.js",
"chars": 361,
"preview": "import Handlebars from 'handlebars/runtime';\nimport hbs from 'handlebars-inline-precompile';\nimport { assertEquals } fro"
},
{
"path": "tests/integration/webpack-babel-test/src/lib/assert.js",
"chars": 152,
"preview": "export function assertEquals(actual, expected) {\n if (actual !== expected) {\n throw new Error(`Expected \"${actual}\" "
},
{
"path": "tests/integration/webpack-babel-test/test.sh",
"chars": 323,
"preview": "#!/bin/bash\n\nset -e\n\n# Cleanup: package-lock and \"npm ci\" is not working with local dependencies\nrm -rf dist package-loc"
},
{
"path": "tests/integration/webpack-babel-test/webpack.config.js",
"chars": 634,
"preview": "const fs = require('fs');\n\nconst testFiles = fs.readdirSync('src');\nconst entryPoints = {};\ntestFiles\n .filter((file) ="
},
{
"path": "tests/integration/webpack-test/.gitignore",
"chars": 35,
"preview": "node_modules\ndist\npackage-lock.json"
},
{
"path": "tests/integration/webpack-test/package.json",
"chars": 369,
"preview": "{\n \"name\": \"webpack-test\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"description\": \"Various tests with Handlebars and"
},
{
"path": "tests/integration/webpack-test/src/handlebars-default-import-test.js",
"chars": 206,
"preview": "import Handlebars from 'handlebars';\nimport { assertEquals } from './lib/assert';\n\nconst template = Handlebars.compile('"
},
{
"path": "tests/integration/webpack-test/src/handlebars-esm-import-test.js",
"chars": 221,
"preview": "import Handlebars from 'handlebars/lib/handlebars';\nimport { assertEquals } from './lib/assert';\n\nconst template = Handl"
},
{
"path": "tests/integration/webpack-test/src/handlebars-loader-test.js",
"chars": 332,
"preview": "import { assertEquals } from './lib/assert';\n\nimport testTemplate from './test-template.handlebars';\nassertEquals(testTe"
},
{
"path": "tests/integration/webpack-test/src/handlebars-register-helper-test.js",
"chars": 297,
"preview": "import Handlebars from 'handlebars';\nimport { assertEquals } from './lib/assert';\n\nHandlebars.registerHelper('loud', fun"
},
{
"path": "tests/integration/webpack-test/src/handlebars-runtime-test.js",
"chars": 1291,
"preview": "import Handlebars from 'handlebars/runtime';\nimport { assertEquals } from './lib/assert';\n\nconst template = Handlebars.t"
},
{
"path": "tests/integration/webpack-test/src/lib/assert.js",
"chars": 152,
"preview": "export function assertEquals(actual, expected) {\n if (actual !== expected) {\n throw new Error(`Expected \"${actual}\" "
},
{
"path": "tests/integration/webpack-test/src/test-template.handlebars",
"chars": 20,
"preview": "Author: {{author}}\n\n"
},
{
"path": "tests/integration/webpack-test/test.sh",
"chars": 323,
"preview": "#!/bin/bash\n\nset -e\n\n# Cleanup: package-lock and \"npm ci\" is not working with local dependencies\nrm -rf dist package-loc"
},
{
"path": "tests/integration/webpack-test/webpack.config.js",
"chars": 459,
"preview": "const fs = require('fs');\n\nconst testFiles = fs.readdirSync('src');\nconst entryPoints = {};\ntestFiles\n .filter((file) ="
},
{
"path": "tests/print-script.js",
"chars": 2908,
"preview": "/* eslint-disable no-console, no-var */\n// Util script for debugging source code generation issues\n\nvar script = process"
},
{
"path": "tests/rspack/rspack.test.js",
"chars": 9530,
"preview": "import { describe, it, expect, beforeAll } from 'vitest';\nimport fs from 'fs';\nimport path from 'path';\nimport { execSyn"
},
{
"path": "tstyche.config.json",
"chars": 98,
"preview": "{\n \"testFileMatch\": [\"types/__typetests__/**/*.tst.ts\"],\n \"tsconfig\": \"./types/tsconfig.json\"\n}\n"
},
{
"path": "types/__typetests__/handlebars.tst.ts",
"chars": 18926,
"preview": "import { expect, test, describe } from 'tstyche';\nimport Handlebars from 'handlebars';\nimport {\n HandlebarsTemplatable,"
},
{
"path": "types/index.d.ts",
"chars": 9213,
"preview": "/* These definitions were imported from https://github.com/DefinitelyTyped/DefinitelyTyped\n * and includes previous cont"
},
{
"path": "types/tsconfig.json",
"chars": 300,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"lib\": [\"es6\"],\n \"noImplicitAny\": true,\n \"noImplicitThis\": "
},
{
"path": "vitest.config.js",
"chars": 1744,
"preview": "import { defineConfig } from 'vitest/config';\nimport { playwright } from '@vitest/browser-playwright';\n\nexport default d"
}
]
About this extraction
This page contains the full source code of the handlebars-lang/handlebars.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 166 files (631.6 KB), approximately 162.3k tokens, and a symbol index with 186 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.