Compare commits
267 Commits
karma
...
perf-upstream
| Author | SHA1 | Date | |
|---|---|---|---|
| 95c890ed88 | |||
| b10cec2322 | |||
| 800c3db6b2 | |||
| be945bb7bd | |||
| 824e3789c8 | |||
| f8c6677458 | |||
| f1421eb2ab | |||
| b5ad730473 | |||
| ff02463429 | |||
| a8a1c9dd4f | |||
| 243060b310 | |||
| 60d17f546d | |||
| 61659fbe63 | |||
| c1bc257d39 | |||
| 1b321a8bef | |||
| 92e842f82e | |||
| 6f1de2025e | |||
| d025292e06 | |||
| 71350d90ec | |||
| bf93bafeca | |||
| 70b8f1145d | |||
| 2f68d9425b | |||
| 169ef75066 | |||
| d65683434d | |||
| 1245255892 | |||
| 22e04868c0 | |||
| a3ac164642 | |||
| 864b721fef | |||
| cc8574e631 | |||
| f422bfdf3e | |||
| 25c696b889 | |||
| 1fc4ef09c1 | |||
| ce1f2abf5a | |||
| 2afecb25db | |||
| cb828aa282 | |||
| 0bc9f634ca | |||
| ae83edd5c4 | |||
| 8ce2be4496 | |||
| d3b93571a7 | |||
| 99d162cadc | |||
| 7147755b55 | |||
| 9413ea99ce | |||
| f202c501a3 | |||
| bb55dead2d | |||
| 6e10b1f658 | |||
| 65f24eb8b6 | |||
| 35f001862c | |||
| b3ffb16e5d | |||
| 42adee3f82 | |||
| b4a54ad86e | |||
| e6686963ba | |||
| 262b84b08a | |||
| 6186128ad0 | |||
| 03fe783a69 | |||
| e534a911f3 | |||
| f6ff3bf52b | |||
| da41887377 | |||
| 83baaa469f | |||
| 785a63e0a8 | |||
| 903ca504b5 | |||
| 69dbe65c19 | |||
| 54cbc8c6c6 | |||
| b14ff4a6fc | |||
| e0de1d9a4a | |||
| 9bff03fd3b | |||
| a9fe6d5a03 | |||
| ce8982fe55 | |||
| 80809ef7fc | |||
| 30bd78d457 | |||
| 0896d00c2e | |||
| f73be4ebf3 | |||
| 29ed243268 | |||
| dd6ff05079 | |||
| c18ed16f71 | |||
| 7e5890cf5a | |||
| c7278d176a | |||
| 35ea8e61bf | |||
| a98b01c2ee | |||
| b3b26712d9 | |||
| 37f4ea18b4 | |||
| 48870b41f7 | |||
| 82c613244a | |||
| e2f63da5c0 | |||
| 4da5882660 | |||
| 78e7e28ff9 | |||
| 03d387bf8e | |||
| e0f50b4eec | |||
| 9ed9418488 | |||
| ef0fc290b9 | |||
| edc65b5c19 | |||
| 715f4af179 | |||
| 3bd0fa8b32 | |||
| 476d61667d | |||
| 5ba0c72fe6 | |||
| 49621c15ac | |||
| 1dc2c0bffb | |||
| fe58756da2 | |||
| c295ef085f | |||
| c1ad3c8057 | |||
| af92e32822 | |||
| 2954e7ea66 | |||
| b0f1a62ddb | |||
| 7d9450d3c3 | |||
| 8eefee56ff | |||
| e538c29d92 | |||
| fd93073146 | |||
| 6790c080c6 | |||
| a9a8e40321 | |||
| e66aed5b99 | |||
| 7d4d170ce4 | |||
| eb860c0899 | |||
| 2f509a8fca | |||
| 19bdace85a | |||
| b6d3de7123 | |||
| f058970169 | |||
| 071003b89c | |||
| 1a08e1d0a7 | |||
| 212f9d930b | |||
| a65a55382c | |||
| b1034a6702 | |||
| 8727d2015a | |||
| a39bfa5fe3 | |||
| 75794c5dbc | |||
| 3438891bd2 | |||
| 0ec71dcc5c | |||
| 6f17a8e75c | |||
| f8eade7b41 | |||
| d36adf9343 | |||
| 2fd4a3da7a | |||
| 3800b7dd4c | |||
| 2ea32e8db9 | |||
| fd3ca85d13 | |||
| edfe6b899c | |||
| 0c8230c253 | |||
| d23ccf636a | |||
| 8a062f19d5 | |||
| 44ee971e44 | |||
| 37411901da | |||
| dfe0fcb130 | |||
| 3fb331ef40 | |||
| 2f0991a022 | |||
| 4085e066fc | |||
| dce6f54fdf | |||
| 7b11f55c53 | |||
| b0bb2a42b4 | |||
| e46c239b3d | |||
| 081e1d0ad9 | |||
| 2ef6fbd1fc | |||
| 50e844b563 | |||
| c56995d706 | |||
| 566536b03d | |||
| 6aaac4a779 | |||
| 4ae86f3282 | |||
| 25bae31578 | |||
| 74795a3a7f | |||
| 0419daec52 | |||
| 8df74a0119 | |||
| 35d3fdb1dd | |||
| dce44541db | |||
| a510d82252 | |||
| 77e38330e6 | |||
| 3ce4425056 | |||
| 2f8aa1c839 | |||
| 2f681d256f | |||
| 1b04524a53 | |||
| 3fc7e6feb2 | |||
| 16c654579a | |||
| f349aa43c0 | |||
| 913b845aa9 | |||
| 920bf1c5ad | |||
| d1eca1c5cf | |||
| d7d14ed6e5 | |||
| 379172e236 | |||
| 148b19182d | |||
| e1fa310101 | |||
| 94419deb95 | |||
| 63be1c8130 | |||
| 7f3da649c0 | |||
| f21f900cf5 | |||
| 999220bf2d | |||
| d5caa56d6c | |||
| ce3cd8a685 | |||
| 33773c2cb2 | |||
| 7b74175e0e | |||
| c933387415 | |||
| 1a78717286 | |||
| 64002dc506 | |||
| fb26f69485 | |||
| be107d2ca1 | |||
| 2441ba1335 | |||
| 5c676a32fb | |||
| 228950169d | |||
| 680ec9666a | |||
| 58ec656fbc | |||
| 77674e4f6c | |||
| 714a4c4482 | |||
| d40cbfc70d | |||
| b6e2f76a45 | |||
| 1b78b5b7b2 | |||
| a5e6afb05d | |||
| 8baba76b64 | |||
| 508347ea94 | |||
| ad341bfc1b | |||
| 911e676af4 | |||
| 0fa10b6e87 | |||
| 660a117536 | |||
| c9970200a0 | |||
| 6300e57f11 | |||
| 6c89939af7 | |||
| a6476739f0 | |||
| 9119265204 | |||
| d117b3e0f9 | |||
| 2b7ed7e0bb | |||
| 9a36966b16 | |||
| 6c92579faf | |||
| 7535e48a79 | |||
| 11b99b361d | |||
| 275ab37b2e | |||
| bff3af52f2 | |||
| 9fb7baddcc | |||
| 21696005cd | |||
| 601ce8a69f | |||
| bbe0a94d6e | |||
| f2211ddd65 | |||
| e8722e2551 | |||
| 094a2564fe | |||
| a6a0e50551 | |||
| 5c9d5b5b54 | |||
| f0d4159a5e | |||
| 2ff795cf2f | |||
| e8ceafa652 | |||
| 6b644802d9 | |||
| 768ddbd661 | |||
| 06baeaebfd | |||
| c7be766902 | |||
| b37232fc8d | |||
| 0757843712 | |||
| 27212f5953 | |||
| 7130a11539 | |||
| 4788b03192 | |||
| 0b8fd1d8a8 | |||
| 89c8b3a886 | |||
| b55a120e82 | |||
| dfc75545fa | |||
| 400bada31f | |||
| 834695e241 | |||
| 5302782c08 | |||
| 118836f390 | |||
| ee6fadffb4 | |||
| ec64cf9293 | |||
| 9a94d1771f | |||
| 164f1c13f7 | |||
| 8517352e20 | |||
| 63fdb92472 | |||
| 8a9c79b799 | |||
| eb1ec52b45 | |||
| 21bf95c92a | |||
| cc0b239aaf | |||
| a6121cae79 | |||
| 871c32a6f9 | |||
| 326734b0f3 | |||
| 2ea6119a83 | |||
| 8289c0bf3a | |||
| 25458fdc2a | |||
| 63a08890cc | |||
| 0f054efcdb | |||
| 3f77b82ed0 |
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
.rvmrc
|
|
||||||
.DS_Store
|
|
||||||
/tmp/
|
|
||||||
*.sublime-project
|
|
||||||
*.sublime-workspace
|
|
||||||
npm-debug.log
|
|
||||||
sauce_connect.log*
|
|
||||||
.idea
|
|
||||||
yarn-error.log
|
|
||||||
node_modules
|
|
||||||
/handlebars-release.tgz
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
lib/handlebars/compiler/parser.js
|
|
||||||
/coverage/
|
|
||||||
/dist/
|
|
||||||
/integration-testing/*/dist/
|
|
||||||
|
|
||||||
# Third-party or files that must remain unchanged
|
|
||||||
/spec/expected/
|
|
||||||
/spec/vendor
|
|
||||||
|
|
||||||
# JS-Snippets
|
|
||||||
src/*.js
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
|
|
||||||
globals: {
|
|
||||||
self: false
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
es6: true
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-console': 'warn',
|
|
||||||
|
|
||||||
// temporarily disabled until the violating places are fixed.
|
|
||||||
'no-func-assign': 'off',
|
|
||||||
'no-sparse-arrays': 'off',
|
|
||||||
|
|
||||||
// Best Practices //
|
|
||||||
//----------------//
|
|
||||||
'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-floating-decimal': '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-octal-escape': 'error',
|
|
||||||
'no-process-env': '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',
|
|
||||||
|
|
||||||
// Variables //
|
|
||||||
//-----------//
|
|
||||||
'no-label-var': 'error',
|
|
||||||
'no-undef-init': 'error',
|
|
||||||
'no-use-before-define': ['error', 'nofunc'],
|
|
||||||
|
|
||||||
// ECMAScript 6 //
|
|
||||||
//--------------//
|
|
||||||
'no-var': 'error'
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module',
|
|
||||||
ecmaVersion: 6,
|
|
||||||
ecmaFeatures: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Upgrade to Prettier 2.7
|
||||||
|
3d228334530860a6e3f99dc10777c84bf22292c1
|
||||||
|
# Format markdown files with Prettier
|
||||||
|
dfe2eaaf20f0b679d94e5a799757c4394d80f1cc
|
||||||
|
# migrate to oxlint and oxfmt
|
||||||
|
0c1d00282ca619c3416ad819bdf53c0852b32415
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: '/'
|
||||||
|
open-pull-requests-limit: 0
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
allow:
|
||||||
|
- dependency-type: production
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
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 (Publish)
|
||||||
|
if: matrix.node-version != '20'
|
||||||
|
run: npx vitest run --project publish
|
||||||
|
|
||||||
|
- 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
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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 }}
|
||||||
+2
-3
@@ -4,7 +4,6 @@
|
|||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
sauce_connect.log*
|
|
||||||
.idea
|
.idea
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
@@ -13,8 +12,8 @@ node_modules
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
lib/handlebars/compiler/parser.js
|
|
||||||
/coverage/
|
/coverage/
|
||||||
/dist/
|
/dist/
|
||||||
/integration-testing/*/dist/
|
/tests/bench/results/
|
||||||
|
/tests/integration/*/dist/
|
||||||
/spec/tmp/*
|
/spec/tmp/*
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[submodule "spec/mustache"]
|
[submodule "spec/mustache"]
|
||||||
path = spec/mustache
|
path = spec/mustache
|
||||||
url = git://github.com/mustache/spec.git
|
url = https://github.com/mustache/spec.git
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$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"
|
||||||
|
]
|
||||||
|
}
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"$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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
.rvmrc
|
|
||||||
.DS_Store
|
|
||||||
/tmp/
|
|
||||||
*.sublime-project
|
|
||||||
*.sublime-workspace
|
|
||||||
npm-debug.log
|
|
||||||
sauce_connect.log*
|
|
||||||
.idea
|
|
||||||
yarn-error.log
|
|
||||||
node_modules
|
|
||||||
/handlebars-release.tgz
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
lib/handlebars/compiler/parser.js
|
|
||||||
/coverage/
|
|
||||||
/dist/
|
|
||||||
/integration-testing/*/dist/
|
|
||||||
|
|
||||||
# Third-party or files that must remain unchanged
|
|
||||||
/spec/expected/
|
|
||||||
/spec/vendor
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://swc.rs/schema.json",
|
||||||
|
"module": {
|
||||||
|
"type": "commonjs",
|
||||||
|
"importInterop": "swc"
|
||||||
|
},
|
||||||
|
"sourceMaps": "inline"
|
||||||
|
}
|
||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: test
|
|
||||||
name: check javascript (eslint)
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run lint
|
|
||||||
- stage: test
|
|
||||||
name: check formatting (prettier)
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run check-format
|
|
||||||
- stage: test
|
|
||||||
name: check typescript definitions (dtslint)
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run dtslint
|
|
||||||
- stage: test
|
|
||||||
name: extensive tests and publish to aws
|
|
||||||
script: npm run extensive-tests-and-publish-to-aws
|
|
||||||
env:
|
|
||||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
|
||||||
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
|
|
||||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
|
||||||
- SAUCE_USERNAME=handlebars
|
|
||||||
- secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I=
|
|
||||||
node_js: '10'
|
|
||||||
- stage: test
|
|
||||||
name: test with latest nodejs-lts
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run test
|
|
||||||
- stage: test
|
|
||||||
name: test with active nodejs
|
|
||||||
node_js: node
|
|
||||||
script: npm run test
|
|
||||||
cache: npm
|
|
||||||
email:
|
|
||||||
on_failure: change
|
|
||||||
on_success: never
|
|
||||||
git:
|
|
||||||
depth: 100
|
|
||||||
+29
-44
@@ -1,20 +1,25 @@
|
|||||||
# How to Contribute
|
# 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
|
## Reporting Issues
|
||||||
|
|
||||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
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)
|
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.
|
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 site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
||||||
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
- The branch `master` contains the current development version (v5).
|
||||||
- The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
- The branch `4.x` contains the previous stable version. Only critical bugfixes are backported there.
|
||||||
should merge the branch `4.x` into the master branch regularly.
|
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
@@ -30,26 +35,18 @@ Generally we like to see pull requests that
|
|||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
To build Handlebars.js you'll need a few things installed.
|
To build Handlebars.js you'll need Node.js installed.
|
||||||
|
|
||||||
- Node.js
|
|
||||||
- [Grunt](http://gruntjs.com/getting-started)
|
|
||||||
|
|
||||||
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`.
|
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`.
|
Project dependencies may be installed via `npm install`.
|
||||||
|
|
||||||
To build Handlebars.js from scratch, you'll want to run `grunt`
|
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`.
|
||||||
in the root of the project. That will build Handlebars and output the
|
|
||||||
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
|
|
||||||
You can also run our set of benchmarks with `grunt bench`.
|
|
||||||
|
|
||||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
|
||||||
|
|
||||||
If you notice any problems, please report them to the GitHub issue tracker at
|
If you notice any problems, please report them to the GitHub issue tracker at
|
||||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||||
|
|
||||||
##Running Tests
|
## Running Tests
|
||||||
|
|
||||||
To run tests locally, first install all dependencies.
|
To run tests locally, first install all dependencies.
|
||||||
|
|
||||||
@@ -73,27 +70,22 @@ npm test
|
|||||||
|
|
||||||
## Linting and Formatting
|
## Linting and Formatting
|
||||||
|
|
||||||
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
|
Handlebars uses `oxlint` for linting, `oxfmt` for formatting, and `eslint` (with `eslint-plugin-compat`) for browser API compatibility checks.
|
||||||
We do linting and formatting in two phases:
|
Committed files are linted and formatted in a pre-commit hook.
|
||||||
|
|
||||||
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
You can use the following scripts to make sure that the CI job does not fail:
|
||||||
while warnings are allowed.
|
|
||||||
- The travis-ci job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
|
||||||
are forbidden.
|
|
||||||
|
|
||||||
You can use the following scripts to make sure that the travis-job does not fail:
|
- **npm run lint** will run all linters and fail on warnings
|
||||||
|
- **npm run format** will format all files
|
||||||
- **npm run lint** will run `eslint` and fail on warnings
|
- **npm run check-before-pull-request** will perform all checks that our CI job does, excluding integration tests.
|
||||||
- **npm run format** will run `prettier` on all files
|
- **npm run test:integration** will run integration tests (bundler compatibility with webpack, rollup, etc.)
|
||||||
- **npm run check-before-pull-request** will perform all most checks that travis does in its build-job, excluding the "integration-test".
|
These tests only work on Linux.
|
||||||
- **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
|
||||||
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
|
||||||
|
|
||||||
## Releasing the latest version
|
## Releasing the latest version
|
||||||
|
|
||||||
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||||
|
|
||||||
- Push-access to `wycats/handlebars.js`
|
- Push-access to `handlebars-lang/handlebars.js`
|
||||||
- Publishing rights on npmjs.com for the `handlebars` package
|
- Publishing rights on npmjs.com for the `handlebars` package
|
||||||
- Publishing rights on gemfury for the `handlebars-source` 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 repo for legacy package managers: `components/handlebars`
|
||||||
@@ -101,19 +93,12 @@ Before attempting the release Handlebars, please make sure that you have the fol
|
|||||||
|
|
||||||
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||||
|
|
||||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
|
||||||
|
|
||||||
A full release may be completed with the following:
|
A full release may be completed with the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm ci
|
npm ci
|
||||||
yo release
|
npm run build
|
||||||
npm publish
|
npm publish
|
||||||
yo release:publish components handlebars.js dist/components/
|
|
||||||
|
|
||||||
cd dist/components/
|
|
||||||
gem build handlebars-source.gemspec
|
|
||||||
gem push handlebars-source-*.gem
|
|
||||||
```
|
```
|
||||||
|
|
||||||
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
|
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
|
||||||
@@ -126,13 +111,13 @@ in those places still point to the latest version
|
|||||||
|
|
||||||
When everything is OK, the **handlebars site** needs to be updated.
|
When everything is OK, the **handlebars site** needs to be updated.
|
||||||
|
|
||||||
Go to the master branch of the repo [handlebars-lang/handlebarsjs.com-github-pages](https://github.com/handlebars-lang/handlebarsjs.com-github-pages/tree/master)
|
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
|
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.
|
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
|
(note that the default-branch of this repo is not the master and regular changes are done
|
||||||
in the `handlebars-lang/docs`-repo).
|
in the `handlebars-lang/docs`-repo).
|
||||||
|
|
||||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
|
||||||
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
[jsfiddle]: https://jsfiddle.net/4nbwjaqz/4/
|
||||||
|
|||||||
@@ -1,60 +1,54 @@
|
|||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
|
|
||||||
1. How can I file a bug report:
|
## How can I file a bug report:
|
||||||
|
|
||||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||||
|
|
||||||
1. Why isn't my Mustache template working?
|
## 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/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
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).
|
||||||
|
|
||||||
1. Why is it slower when compiling?
|
## 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.
|
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.
|
||||||
|
|
||||||
1. Why doesn't this work with Content Security Policy restrictions?
|
## 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.
|
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.
|
||||||
|
|
||||||
1. How can I include script tags in my template?
|
## 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:
|
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
|
```html
|
||||||
<script type="text/x-handlebars">
|
<script type="text/x-handlebars">
|
||||||
foo
|
foo
|
||||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
||||||
|
|
||||||
1. Why are my precompiled scripts throwing exceptions?
|
## 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:
|
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
|
```sh
|
||||||
handlebars --version
|
handlebars --version
|
||||||
```
|
```
|
||||||
If using the integrated precompiler and
|
|
||||||
|
|
||||||
```javascript
|
If using the integrated precompiler and
|
||||||
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.
|
```javascript
|
||||||
|
console.log(Handlebars.VERSION);
|
||||||
|
```
|
||||||
|
|
||||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
On the client side.
|
||||||
|
|
||||||
1. Why doesn't IE like the `default` name in the AMD module?
|
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.
|
||||||
|
|
||||||
Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
|
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).
|
||||||
|
|
||||||
1. How do I load the runtime library when using AMD?
|
## How do I load the runtime library when using AMD?
|
||||||
|
|
||||||
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
The `handlebars.runtime.js` file includes a UMD build, which exposes the library as both the module root and the `default` field for compatibility.
|
||||||
|
|
||||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
|
||||||
|
|
||||||
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
|
||||||
|
|||||||
-298
@@ -1,298 +0,0 @@
|
|||||||
/* eslint-disable no-process-env */
|
|
||||||
module.exports = function(grunt) {
|
|
||||||
grunt.initConfig({
|
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
|
||||||
|
|
||||||
clean: [
|
|
||||||
'tmp',
|
|
||||||
'dist',
|
|
||||||
'lib/handlebars/compiler/parser.js',
|
|
||||||
'integration-testing/**/node_modules'
|
|
||||||
],
|
|
||||||
|
|
||||||
copy: {
|
|
||||||
dist: {
|
|
||||||
options: {
|
|
||||||
processContent: function(content) {
|
|
||||||
return (
|
|
||||||
grunt.template.process(
|
|
||||||
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
|
|
||||||
) + content
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
|
|
||||||
},
|
|
||||||
cdnjs: {
|
|
||||||
files: [
|
|
||||||
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
expand: true,
|
|
||||||
cwd: 'components/',
|
|
||||||
src: ['**'],
|
|
||||||
dest: 'dist/components'
|
|
||||||
},
|
|
||||||
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
babel: {
|
|
||||||
options: {
|
|
||||||
sourceMaps: 'inline',
|
|
||||||
loose: ['es6.modules'],
|
|
||||||
auxiliaryCommentBefore: 'istanbul ignore next'
|
|
||||||
},
|
|
||||||
amd: {
|
|
||||||
options: {
|
|
||||||
modules: 'amd'
|
|
||||||
},
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
expand: true,
|
|
||||||
cwd: 'lib/',
|
|
||||||
src: '**/!(index).js',
|
|
||||||
dest: 'dist/amd/'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
cjs: {
|
|
||||||
options: {
|
|
||||||
modules: 'common'
|
|
||||||
},
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
cwd: 'lib/',
|
|
||||||
expand: true,
|
|
||||||
src: '**/!(index).js',
|
|
||||||
dest: 'dist/cjs/'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
webpack: {
|
|
||||||
options: {
|
|
||||||
context: __dirname,
|
|
||||||
module: {
|
|
||||||
loaders: [
|
|
||||||
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
|
|
||||||
{
|
|
||||||
test: /\.jsx?$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader:
|
|
||||||
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: 'dist/',
|
|
||||||
library: 'Handlebars',
|
|
||||||
libraryTarget: 'umd'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handlebars: {
|
|
||||||
entry: './lib/handlebars.js',
|
|
||||||
output: {
|
|
||||||
filename: 'handlebars.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runtime: {
|
|
||||||
entry: './lib/handlebars.runtime.js',
|
|
||||||
output: {
|
|
||||||
filename: 'handlebars.runtime.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
requirejs: {
|
|
||||||
options: {
|
|
||||||
optimize: 'none',
|
|
||||||
baseUrl: 'dist/amd/'
|
|
||||||
},
|
|
||||||
dist: {
|
|
||||||
options: {
|
|
||||||
name: 'handlebars',
|
|
||||||
out: 'dist/handlebars.amd.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runtime: {
|
|
||||||
options: {
|
|
||||||
name: 'handlebars.runtime',
|
|
||||||
out: 'dist/handlebars.runtime.amd.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
uglify: {
|
|
||||||
options: {
|
|
||||||
mangle: true,
|
|
||||||
compress: true,
|
|
||||||
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
|
|
||||||
},
|
|
||||||
dist: {
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
cwd: 'dist/',
|
|
||||||
expand: true,
|
|
||||||
src: ['handlebars*.js', '!*.min.js'],
|
|
||||||
dest: 'dist/',
|
|
||||||
rename: function(dest, src) {
|
|
||||||
return dest + src.replace(/\.js$/, '.min.js');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
concat: {
|
|
||||||
tests: {
|
|
||||||
src: ['spec/!(require).js'],
|
|
||||||
dest: 'tmp/tests.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
connect: {
|
|
||||||
server: {
|
|
||||||
options: {
|
|
||||||
base: '.',
|
|
||||||
hostname: '*',
|
|
||||||
port: 9999
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'saucelabs-mocha': {
|
|
||||||
all: {
|
|
||||||
options: {
|
|
||||||
build: process.env.TRAVIS_JOB_ID,
|
|
||||||
urls: [
|
|
||||||
'http://localhost:9999/spec/?headless=true',
|
|
||||||
'http://localhost:9999/spec/amd.html?headless=true'
|
|
||||||
],
|
|
||||||
detailedError: true,
|
|
||||||
concurrency: 4,
|
|
||||||
browsers: [
|
|
||||||
{ browserName: 'chrome' },
|
|
||||||
{ browserName: 'firefox', platform: 'Linux' },
|
|
||||||
// {browserName: 'safari', version: 9, platform: 'OS X 10.11'},
|
|
||||||
// {browserName: 'safari', version: 8, platform: 'OS X 10.10'},
|
|
||||||
{
|
|
||||||
browserName: 'internet explorer',
|
|
||||||
version: 11,
|
|
||||||
platform: 'Windows 8.1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
browserName: 'internet explorer',
|
|
||||||
version: 10,
|
|
||||||
platform: 'Windows 8'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sanity: {
|
|
||||||
options: {
|
|
||||||
build: process.env.TRAVIS_JOB_ID,
|
|
||||||
urls: [
|
|
||||||
'http://localhost:9999/spec/umd.html?headless=true',
|
|
||||||
'http://localhost:9999/spec/amd-runtime.html?headless=true',
|
|
||||||
'http://localhost:9999/spec/umd-runtime.html?headless=true'
|
|
||||||
],
|
|
||||||
detailedError: true,
|
|
||||||
concurrency: 2,
|
|
||||||
browsers: [{ browserName: 'chrome' }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bgShell: {
|
|
||||||
integrationTests: {
|
|
||||||
cmd: './integration-testing/run-integration-tests.sh',
|
|
||||||
bg: false,
|
|
||||||
fail: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
scripts: {
|
|
||||||
options: {
|
|
||||||
atBegin: true
|
|
||||||
},
|
|
||||||
|
|
||||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
|
||||||
tasks: ['on-file-change']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load tasks from npm
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
|
||||||
grunt.loadNpmTasks('grunt-babel');
|
|
||||||
grunt.loadNpmTasks('grunt-bg-shell');
|
|
||||||
grunt.loadNpmTasks('@knappi/grunt-saucelabs');
|
|
||||||
grunt.loadNpmTasks('grunt-webpack');
|
|
||||||
|
|
||||||
grunt.task.loadTasks('tasks');
|
|
||||||
|
|
||||||
this.registerTask(
|
|
||||||
'build',
|
|
||||||
'Builds a distributable version of the current project',
|
|
||||||
['parser', 'node', 'globals']
|
|
||||||
);
|
|
||||||
|
|
||||||
this.registerTask('node', ['babel:cjs']);
|
|
||||||
this.registerTask('globals', ['webpack']);
|
|
||||||
|
|
||||||
this.registerTask('release', 'Build final packages', [
|
|
||||||
'amd',
|
|
||||||
'uglify',
|
|
||||||
'test:min',
|
|
||||||
'copy:dist',
|
|
||||||
'copy:components',
|
|
||||||
'copy:cdnjs'
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
|
||||||
|
|
||||||
this.registerTask('test', ['test:bin', 'test:cov']);
|
|
||||||
|
|
||||||
grunt.registerTask('bench', ['metrics']);
|
|
||||||
|
|
||||||
if (process.env.SAUCE_ACCESS_KEY) {
|
|
||||||
grunt.registerTask('sauce', ['concat:tests', 'connect', 'saucelabs-mocha']);
|
|
||||||
} else {
|
|
||||||
grunt.registerTask('sauce', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
|
|
||||||
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
|
||||||
'default',
|
|
||||||
'bgShell:integrationTests',
|
|
||||||
'sauce',
|
|
||||||
'metrics',
|
|
||||||
'publish-to-aws'
|
|
||||||
]);
|
|
||||||
grunt.registerTask('on-file-change', [
|
|
||||||
'build',
|
|
||||||
'amd',
|
|
||||||
'concat:tests',
|
|
||||||
'test'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// === Primary tasks ===
|
|
||||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
|
||||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
|
||||||
grunt.registerTask('integration-tests', [
|
|
||||||
'default',
|
|
||||||
'bgShell:integrationTests'
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
-168
@@ -1,168 +0,0 @@
|
|||||||
[](https://travis-ci.org/wycats/handlebars.js)
|
|
||||||
[](https://ci.appveyor.com/project/wycats/handlebars-js)
|
|
||||||
[](https://saucelabs.com/u/handlebars)
|
|
||||||
|
|
||||||
Handlebars.js
|
|
||||||
=============
|
|
||||||
|
|
||||||
Handlebars.js is an extension to the [Mustache templating
|
|
||||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
|
||||||
Handlebars.js and Mustache are both logicless templating languages that
|
|
||||||
keep the view and the code separated like we all know they should be.
|
|
||||||
|
|
||||||
Checkout the official Handlebars docs site at
|
|
||||||
[https://handlebarsjs.com/](https://handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
See our [installation documentation](https://handlebarsjs.com/installation/).
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
In general, the syntax of Handlebars.js templates is a superset
|
|
||||||
of Mustache templates. For basic syntax, check out the [Mustache
|
|
||||||
manpage](http://mustache.github.com/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/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](http://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.
|
|
||||||
- Alternative delimiters are not supported.
|
|
||||||
|
|
||||||
|
|
||||||
Supported Environments
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Handlebars has been designed to work in any ECMAScript 3 environment. This includes
|
|
||||||
|
|
||||||
- Node.js
|
|
||||||
- Chrome
|
|
||||||
- Firefox
|
|
||||||
- Safari 5+
|
|
||||||
- Opera 11+
|
|
||||||
- IE 6+
|
|
||||||
|
|
||||||
Older versions and other runtimes are likely to work but have not been formally
|
|
||||||
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
|
||||||
|
|
||||||
[](https://saucelabs.com/u/handlebars)
|
|
||||||
|
|
||||||
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](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
|
||||||
|
|
||||||
|
|
||||||
Upgrading
|
|
||||||
---------
|
|
||||||
|
|
||||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
|
||||||
|
|
||||||
Known Issues
|
|
||||||
------------
|
|
||||||
|
|
||||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
|
||||||
|
|
||||||
|
|
||||||
Handlebars in the Wild
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
* [Assemble](http://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.
|
|
||||||
* [Cory](https://github.com/leo/cory), by [@leo](https://github.com/leo), is another tiny static site generator
|
|
||||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
|
||||||
* [dashbars](https://github.com/pismute/dashbars) A modern helper library for Handlebars.js.
|
|
||||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
|
||||||
structure your views, also with automatic data binding support.
|
|
||||||
* [Ghost](https://ghost.org/) Just a blogging platform.
|
|
||||||
* [handlebars_assets](http://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 extendible and embeddable layout blocks as seen in other popular templating languages.
|
|
||||||
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
|
|
||||||
from Don Park.
|
|
||||||
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
|
|
||||||
renderer for Handlebars.js.
|
|
||||||
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
|
|
||||||
for anyone who would like to try out Handlebars.js in their browser.
|
|
||||||
* [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use
|
|
||||||
Handlebars.js with [jQuery](http://jquery.com/).
|
|
||||||
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
|
||||||
handlebars projects.
|
|
||||||
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
|
||||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
|
|
||||||
supports Handlebars.js as one of its template plugins.
|
|
||||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
|
||||||
templating engine, extending it with automatic data binding support.
|
|
||||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
|
||||||
* [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!
|
|
||||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime **DEPRECATED**
|
|
||||||
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
|
||||||
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
|
|
||||||
|
|
||||||
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/wycats/handlebars.js/pull/new/master
|
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
[](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
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# 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.
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Test against these versions of Node.js
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- nodejs_version: "10"
|
|
||||||
|
|
||||||
platform:
|
|
||||||
- x64
|
|
||||||
|
|
||||||
# Install scripts (runs after repo cloning)
|
|
||||||
install:
|
|
||||||
# Get the latest stable version of Node.js
|
|
||||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
|
||||||
# Clone submodules (mustache spec)
|
|
||||||
- cmd: git submodule update --init --recursive
|
|
||||||
# Install modules
|
|
||||||
- cmd: npm ci
|
|
||||||
|
|
||||||
|
|
||||||
# Post-install test scripts
|
|
||||||
test_script:
|
|
||||||
# Output useful info for debugging
|
|
||||||
- cmd: node --version
|
|
||||||
- cmd: npm --version
|
|
||||||
# Run tests
|
|
||||||
- cmd: npm run test
|
|
||||||
|
|
||||||
# Don't actually build
|
|
||||||
build: off
|
|
||||||
|
|
||||||
on_failure:
|
|
||||||
- cmd: 7z a coverage.zip coverage
|
|
||||||
- cmd: appveyor PushArtifact coverage.zip
|
|
||||||
|
|
||||||
|
|
||||||
# Set build version format here instead of in the admin panel
|
|
||||||
version: "{build}"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "prettier",
|
|
||||||
"globals": {
|
|
||||||
"require": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
// Disabling for tests, for now.
|
|
||||||
"no-path-concat": 0,
|
|
||||||
|
|
||||||
"no-var": 0,
|
|
||||||
"no-shadow": 0,
|
|
||||||
"handle-callback-err": 0,
|
|
||||||
"no-console": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
var async = require('neo-async'),
|
|
||||||
fs = require('fs'),
|
|
||||||
zlib = require('zlib');
|
|
||||||
|
|
||||||
module.exports = function(grunt, callback) {
|
|
||||||
var distFiles = fs.readdirSync('dist'),
|
|
||||||
distSizes = {};
|
|
||||||
|
|
||||||
async.each(
|
|
||||||
distFiles,
|
|
||||||
function(file, callback) {
|
|
||||||
var content;
|
|
||||||
try {
|
|
||||||
content = fs.readFileSync('dist/' + file);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'EISDIR') {
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file = file.replace(/\.js/, '').replace(/\./g, '_');
|
|
||||||
distSizes[file] = content.length;
|
|
||||||
|
|
||||||
zlib.gzip(content, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
distSizes[file + '_gz'] = data.length;
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
grunt.log.writeln(
|
|
||||||
'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)
|
|
||||||
);
|
|
||||||
callback([distSizes]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
var metrics = fs.readdirSync(__dirname);
|
|
||||||
metrics.forEach(function(metric) {
|
|
||||||
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = RegExp.$1;
|
|
||||||
metric = require('./' + name);
|
|
||||||
if (metric instanceof Function) {
|
|
||||||
module.exports[name] = metric;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
var _ = require('underscore'),
|
|
||||||
templates = require('./templates');
|
|
||||||
|
|
||||||
module.exports = function(grunt, callback) {
|
|
||||||
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
|
|
||||||
var Handlebars = require('../lib');
|
|
||||||
|
|
||||||
var templateSizes = {};
|
|
||||||
_.each(templates, function(info, template) {
|
|
||||||
var src = info.handlebars,
|
|
||||||
compiled = Handlebars.precompile(src, {}),
|
|
||||||
knownHelpers = Handlebars.precompile(src, {
|
|
||||||
knownHelpersOnly: true,
|
|
||||||
knownHelpers: info.helpers
|
|
||||||
});
|
|
||||||
|
|
||||||
templateSizes[template] = compiled.length;
|
|
||||||
templateSizes['knownOnly_' + template] = knownHelpers.length;
|
|
||||||
});
|
|
||||||
grunt.log.writeln(
|
|
||||||
'Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)
|
|
||||||
);
|
|
||||||
callback([templateSizes]);
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
helpers: {
|
|
||||||
foo: function() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
bar: true
|
|
||||||
},
|
|
||||||
|
|
||||||
handlebars:
|
|
||||||
'{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
names: [
|
|
||||||
{ name: 'Moe' },
|
|
||||||
{ name: 'Larry' },
|
|
||||||
{ name: 'Curly' },
|
|
||||||
{ name: 'Shemp' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
handlebars: '{{#each names}}{{name}}{{/each}}',
|
|
||||||
dust: '{#names}{name}{/names}',
|
|
||||||
mustache: '{{#names}}{{name}}{{/names}}',
|
|
||||||
eco: '<% for item in @names: %><%= item.name %><% end %>'
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
names: [
|
|
||||||
{ name: 'Moe' },
|
|
||||||
{ name: 'Larry' },
|
|
||||||
{ name: 'Curly' },
|
|
||||||
{ name: 'Shemp' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
handlebars: '{{#names}}{{name}}{{/names}}'
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<h1>{header}</h1>
|
|
||||||
{?items}
|
|
||||||
<ul>
|
|
||||||
{#items}
|
|
||||||
{#current}
|
|
||||||
<li><strong>{name}</strong></li>
|
|
||||||
{:else}
|
|
||||||
<li><a href="{url}">{name}</a></li>
|
|
||||||
{/current}
|
|
||||||
{/items}
|
|
||||||
</ul>
|
|
||||||
{:else}
|
|
||||||
<p>The list is empty.</p>
|
|
||||||
{/items}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<h1><%= @header() %></h1>
|
|
||||||
<% if @items.length: %>
|
|
||||||
<ul>
|
|
||||||
<% for item in @items: %>
|
|
||||||
<% if item.current: %>
|
|
||||||
<li><strong><%= item.name %></strong></li>
|
|
||||||
<% else: %>
|
|
||||||
<li><a href="<%= item.url %>"><%= item.name %></a></li>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
<% else: %>
|
|
||||||
<p>The list is empty.</p>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<h1>{{header}}</h1>
|
|
||||||
{{#if items}}
|
|
||||||
<ul>
|
|
||||||
{{#each items}}
|
|
||||||
{{#if current}}
|
|
||||||
<li><strong>{{name}}</strong></li>
|
|
||||||
{{^}}
|
|
||||||
<li><a href="{{url}}">{{name}}</a></li>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
{{^}}
|
|
||||||
<p>The list is empty.</p>
|
|
||||||
{{/if}}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
header: function() {
|
|
||||||
return 'Colors';
|
|
||||||
},
|
|
||||||
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
|
|
||||||
items: [
|
|
||||||
{ name: 'red', current: true, url: '#Red' },
|
|
||||||
{ name: 'green', current: false, url: '#Green' },
|
|
||||||
{ name: 'blue', current: false, url: '#Blue' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
handlebars: fs.readFileSync(__dirname + '/complex.handlebars').toString(),
|
|
||||||
dust: fs.readFileSync(__dirname + '/complex.dust').toString(),
|
|
||||||
eco: fs.readFileSync(__dirname + '/complex.eco').toString(),
|
|
||||||
mustache: fs.readFileSync(__dirname + '/complex.mustache').toString()
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<h1>{{header}}</h1>
|
|
||||||
{{#hasItems}}
|
|
||||||
<ul>
|
|
||||||
{{#items}}
|
|
||||||
{{#current}}
|
|
||||||
<li><strong>{{name}}</strong></li>
|
|
||||||
{{/current}}
|
|
||||||
{{^current}}
|
|
||||||
<li><a href="{{url}}">{{name}}</a></li>
|
|
||||||
{{/current}}
|
|
||||||
{{/items}}
|
|
||||||
</ul>
|
|
||||||
{{/hasItems}}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
names: [
|
|
||||||
{ name: 'Moe' },
|
|
||||||
{ name: 'Larry' },
|
|
||||||
{ name: 'Curly' },
|
|
||||||
{ name: 'Shemp' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
names: [
|
|
||||||
{ name: 'Moe' },
|
|
||||||
{ name: 'Larry' },
|
|
||||||
{ name: 'Curly' },
|
|
||||||
{ name: 'Shemp' }
|
|
||||||
],
|
|
||||||
foo: 'bar'
|
|
||||||
},
|
|
||||||
handlebars: '{{#each names}}{{../foo}}{{/each}}',
|
|
||||||
mustache: '{{#names}}{{foo}}{{/names}}',
|
|
||||||
eco: '<% for item in @names: %><%= @foo %><% end %>'
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
names: [
|
|
||||||
{ bat: 'foo', name: ['Moe'] },
|
|
||||||
{ bat: 'foo', name: ['Larry'] },
|
|
||||||
{ bat: 'foo', name: ['Curly'] },
|
|
||||||
{ bat: 'foo', name: ['Shemp'] }
|
|
||||||
],
|
|
||||||
foo: 'bar'
|
|
||||||
},
|
|
||||||
handlebars:
|
|
||||||
'{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
|
|
||||||
mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}',
|
|
||||||
eco:
|
|
||||||
'<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
var templates = fs.readdirSync(__dirname);
|
|
||||||
templates.forEach(function(template) {
|
|
||||||
if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.exports[RegExp.$1] = require('./' + RegExp.$1);
|
|
||||||
});
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { person: { name: 'Larry', age: 45 } },
|
|
||||||
handlebars: '{{#person}}{{name}}{{age}}{{/person}}'
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { person: { name: 'Larry', age: 45 } },
|
|
||||||
handlebars: '{{#with person}}{{name}}{{age}}{{/with}}',
|
|
||||||
dust: '{#person}{name}{age}{/person}',
|
|
||||||
eco: '<%= @person.name %><%= @person.age %>',
|
|
||||||
mustache: '{{#person}}{{name}}{{age}}{{/person}}'
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
name: '1',
|
|
||||||
kids: [{ name: '1.1', kids: [{ name: '1.1.1', kids: [] }] }]
|
|
||||||
},
|
|
||||||
partials: {
|
|
||||||
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
|
|
||||||
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
|
|
||||||
},
|
|
||||||
handlebars: '{{name}}{{#each kids}}{{>recursion}}{{/each}}',
|
|
||||||
dust: '{name}{#kids}{>recursion:./}{/kids}',
|
|
||||||
mustache: '{{name}}{{#kids}}{{>recursion}}{{/kids}}'
|
|
||||||
};
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {
|
|
||||||
peeps: [
|
|
||||||
{ name: 'Moe', count: 15 },
|
|
||||||
{ name: 'Larry', count: 5 },
|
|
||||||
{ name: 'Curly', count: 1 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
partials: {
|
|
||||||
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
|
|
||||||
handlebars: {
|
|
||||||
variables: 'Hello {{name}}! You have {{count}} new messages.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
|
|
||||||
dust: '{#peeps}{>variables/}{/peeps}',
|
|
||||||
mustache: '{{#peeps}}{{>variables}}{{/peeps}}'
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { person: { name: { bar: { baz: 'Larry' } }, age: 45 } },
|
|
||||||
handlebars:
|
|
||||||
'{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
|
|
||||||
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
|
|
||||||
eco:
|
|
||||||
'<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
|
|
||||||
mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}'
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {},
|
|
||||||
handlebars: 'Hello world',
|
|
||||||
dust: 'Hello world',
|
|
||||||
mustache: 'Hello world',
|
|
||||||
eco: 'Hello world'
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
helpers: {
|
|
||||||
echo: function(value) {
|
|
||||||
return 'foo ' + value;
|
|
||||||
},
|
|
||||||
header: function() {
|
|
||||||
return 'Colors';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handlebars: '{{echo (header)}}',
|
|
||||||
eco: '<%= @echo(@header()) %>'
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.context = module.exports.helpers;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { name: 'Mick', count: 30 },
|
|
||||||
handlebars: 'Hello {{name}}! You have {{count}} new messages.',
|
|
||||||
dust: 'Hello {name}! You have {count} new messages.',
|
|
||||||
mustache: 'Hello {{name}}! You have {{count}} new messages.',
|
|
||||||
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
|
|
||||||
};
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
var _ = require('underscore'),
|
|
||||||
runner = require('./util/template-runner'),
|
|
||||||
eco,
|
|
||||||
dust,
|
|
||||||
Handlebars,
|
|
||||||
Mustache;
|
|
||||||
|
|
||||||
try {
|
|
||||||
dust = require('dustjs-linkedin');
|
|
||||||
} catch (err) {
|
|
||||||
/* NOP */
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Mustache = require('mustache');
|
|
||||||
} catch (err) {
|
|
||||||
/* NOP */
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
eco = require('eco');
|
|
||||||
} catch (err) {
|
|
||||||
/* NOP */
|
|
||||||
}
|
|
||||||
|
|
||||||
function error() {
|
|
||||||
throw new Error('EWOT');
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeSuite(bench, name, template, handlebarsOnly) {
|
|
||||||
// Create aliases to minimize any impact from having to walk up the closure tree.
|
|
||||||
var templateName = name,
|
|
||||||
context = template.context,
|
|
||||||
partials = template.partials,
|
|
||||||
handlebarsOut,
|
|
||||||
compatOut,
|
|
||||||
dustOut,
|
|
||||||
ecoOut,
|
|
||||||
mustacheOut;
|
|
||||||
|
|
||||||
var handlebar = Handlebars.compile(template.handlebars, { data: false }),
|
|
||||||
compat = Handlebars.compile(template.handlebars, {
|
|
||||||
data: false,
|
|
||||||
compat: true
|
|
||||||
}),
|
|
||||||
options = { helpers: template.helpers };
|
|
||||||
_.each(template.partials && template.partials.handlebars, function(
|
|
||||||
partial,
|
|
||||||
partialName
|
|
||||||
) {
|
|
||||||
Handlebars.registerPartial(
|
|
||||||
partialName,
|
|
||||||
Handlebars.compile(partial, { data: false })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
handlebarsOut = handlebar(context, options);
|
|
||||||
bench('handlebars', function() {
|
|
||||||
handlebar(context, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
compatOut = compat(context, options);
|
|
||||||
bench('compat', function() {
|
|
||||||
compat(context, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (handlebarsOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dust) {
|
|
||||||
if (template.dust) {
|
|
||||||
dustOut = false;
|
|
||||||
dust.loadSource(dust.compile(template.dust, templateName));
|
|
||||||
|
|
||||||
dust.render(templateName, context, function(err, out) {
|
|
||||||
dustOut = out;
|
|
||||||
});
|
|
||||||
|
|
||||||
bench('dust', function() {
|
|
||||||
dust.render(templateName, context, function() {});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bench('dust', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eco) {
|
|
||||||
if (template.eco) {
|
|
||||||
var ecoTemplate = eco.compile(template.eco);
|
|
||||||
|
|
||||||
ecoOut = ecoTemplate(context);
|
|
||||||
|
|
||||||
bench('eco', function() {
|
|
||||||
ecoTemplate(context);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bench('eco', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Mustache) {
|
|
||||||
var mustacheSource = template.mustache,
|
|
||||||
mustachePartials = partials && partials.mustache;
|
|
||||||
|
|
||||||
if (mustacheSource) {
|
|
||||||
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
|
|
||||||
|
|
||||||
bench('mustache', function() {
|
|
||||||
Mustache.to_html(mustacheSource, context, mustachePartials);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bench('mustache', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hack around whitespace until we have whitespace control
|
|
||||||
handlebarsOut = handlebarsOut.replace(/\s/g, '');
|
|
||||||
function compare(b, lang) {
|
|
||||||
if (b == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
b = b.replace(/\s/g, '');
|
|
||||||
|
|
||||||
if (handlebarsOut !== b) {
|
|
||||||
throw new Error(
|
|
||||||
'Template output mismatch: ' +
|
|
||||||
name +
|
|
||||||
'\n\nHandlebars: ' +
|
|
||||||
handlebarsOut +
|
|
||||||
'\n\n' +
|
|
||||||
lang +
|
|
||||||
': ' +
|
|
||||||
b
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compare(compatOut, 'compat');
|
|
||||||
compare(dustOut, 'dust');
|
|
||||||
compare(ecoOut, 'eco');
|
|
||||||
compare(mustacheOut, 'mustache');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function(grunt, callback) {
|
|
||||||
// Deferring load incase we are being run inline with the grunt build
|
|
||||||
Handlebars = require('../lib');
|
|
||||||
|
|
||||||
console.log('Execution Throughput');
|
|
||||||
runner(grunt, makeSuite, function(times, scaled) {
|
|
||||||
callback(scaled);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
var _ = require('underscore'),
|
|
||||||
Benchmark = require('benchmark');
|
|
||||||
|
|
||||||
function BenchWarmer() {
|
|
||||||
this.benchmarks = [];
|
|
||||||
this.currentBenches = [];
|
|
||||||
this.names = [];
|
|
||||||
this.times = {};
|
|
||||||
this.minimum = Infinity;
|
|
||||||
this.maximum = -Infinity;
|
|
||||||
this.errors = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var print = require('util').print;
|
|
||||||
|
|
||||||
BenchWarmer.prototype = {
|
|
||||||
winners: function(benches) {
|
|
||||||
return Benchmark.filter(benches, 'fastest');
|
|
||||||
},
|
|
||||||
suite: function(suite, fn) {
|
|
||||||
this.suiteName = suite;
|
|
||||||
this.times[suite] = {};
|
|
||||||
this.first = true;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
fn(function(name, benchFn) {
|
|
||||||
self.push(name, benchFn);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
push: function(name, fn) {
|
|
||||||
if (this.names.indexOf(name) === -1) {
|
|
||||||
this.names.push(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
var first = this.first,
|
|
||||||
suiteName = this.suiteName,
|
|
||||||
self = this;
|
|
||||||
this.first = false;
|
|
||||||
|
|
||||||
var bench = new Benchmark(fn, {
|
|
||||||
name: this.suiteName + ': ' + name,
|
|
||||||
onComplete: function() {
|
|
||||||
if (first) {
|
|
||||||
self.startLine(suiteName);
|
|
||||||
}
|
|
||||||
self.writeBench(bench);
|
|
||||||
self.currentBenches.push(bench);
|
|
||||||
},
|
|
||||||
onError: function() {
|
|
||||||
self.errors[this.name] = this;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
bench.suiteName = this.suiteName;
|
|
||||||
bench.benchName = name;
|
|
||||||
|
|
||||||
this.benchmarks.push(bench);
|
|
||||||
},
|
|
||||||
|
|
||||||
bench: function(callback) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.printHeader('ops/msec', true);
|
|
||||||
|
|
||||||
Benchmark.invoke(this.benchmarks, {
|
|
||||||
name: 'run',
|
|
||||||
onComplete: function() {
|
|
||||||
self.scaleTimes();
|
|
||||||
|
|
||||||
self.startLine('');
|
|
||||||
|
|
||||||
print('\n');
|
|
||||||
self.printHeader('scaled');
|
|
||||||
_.each(self.scaled, function(value, name) {
|
|
||||||
self.startLine(name);
|
|
||||||
|
|
||||||
_.each(self.names, function(lang) {
|
|
||||||
self.writeValue(value[lang] || '');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
print('\n');
|
|
||||||
|
|
||||||
var errors = false,
|
|
||||||
prop,
|
|
||||||
bench;
|
|
||||||
for (prop in self.errors) {
|
|
||||||
if (
|
|
||||||
Object.prototype.hasOwnProperty.call(self, prop) &&
|
|
||||||
self.errors[prop].error.message !== 'EWOT'
|
|
||||||
) {
|
|
||||||
errors = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors) {
|
|
||||||
print('\n\nErrors:\n');
|
|
||||||
Object.keys(self.errors).forEach(function(prop) {
|
|
||||||
if (self.errors[prop].error.message !== 'EWOT') {
|
|
||||||
bench = self.errors[prop];
|
|
||||||
print('\n' + bench.name + ':\n');
|
|
||||||
print(bench.error.message);
|
|
||||||
if (bench.error.stack) {
|
|
||||||
print(bench.error.stack.join('\n'));
|
|
||||||
}
|
|
||||||
print('\n');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
print('\n');
|
|
||||||
},
|
|
||||||
|
|
||||||
scaleTimes: function() {
|
|
||||||
var scaled = (this.scaled = {});
|
|
||||||
_.each(
|
|
||||||
this.times,
|
|
||||||
function(times, name) {
|
|
||||||
var output = (scaled[name] = {});
|
|
||||||
|
|
||||||
_.each(
|
|
||||||
times,
|
|
||||||
function(time, lang) {
|
|
||||||
output[lang] = (
|
|
||||||
((time - this.minimum) / (this.maximum - this.minimum)) *
|
|
||||||
100
|
|
||||||
).toFixed(2);
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
printHeader: function(title, winners) {
|
|
||||||
var benchSize = 0,
|
|
||||||
names = this.names,
|
|
||||||
i,
|
|
||||||
l;
|
|
||||||
|
|
||||||
for (i = 0, l = names.length; i < l; i++) {
|
|
||||||
var name = names[i];
|
|
||||||
|
|
||||||
if (benchSize < name.length) {
|
|
||||||
benchSize = name.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nameSize = benchSize + 2;
|
|
||||||
this.benchSize = 20;
|
|
||||||
var horSize = 0;
|
|
||||||
|
|
||||||
this.startLine(title);
|
|
||||||
horSize = horSize + this.benchSize;
|
|
||||||
for (i = 0, l = names.length; i < l; i++) {
|
|
||||||
this.writeValue(names[i]);
|
|
||||||
horSize = horSize + this.benchSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (winners) {
|
|
||||||
print('WINNER(S)');
|
|
||||||
horSize = horSize + 'WINNER(S)'.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
print('\n' + new Array(horSize + 1).join('-'));
|
|
||||||
},
|
|
||||||
|
|
||||||
startLine: function(name) {
|
|
||||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(
|
|
||||||
bench
|
|
||||||
) {
|
|
||||||
return bench.name.split(': ')[1];
|
|
||||||
});
|
|
||||||
|
|
||||||
this.currentBenches = [];
|
|
||||||
|
|
||||||
print(winners.join(', '));
|
|
||||||
print('\n');
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
this.writeValue(name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
writeBench: function(bench) {
|
|
||||||
var out;
|
|
||||||
|
|
||||||
if (!bench.error) {
|
|
||||||
var count = bench.hz,
|
|
||||||
moe = (count * bench.stats.rme) / 100,
|
|
||||||
minimum,
|
|
||||||
maximum;
|
|
||||||
|
|
||||||
count = Math.round(count / 1000);
|
|
||||||
moe = Math.round(moe / 1000);
|
|
||||||
minimum = count - moe;
|
|
||||||
maximum = count + moe;
|
|
||||||
|
|
||||||
out = count + ' ±' + moe + ' (' + bench.cycles + ')';
|
|
||||||
|
|
||||||
this.times[bench.suiteName][bench.benchName] = count;
|
|
||||||
this.minimum = Math.min(this.minimum, minimum);
|
|
||||||
this.maximum = Math.max(this.maximum, maximum);
|
|
||||||
} else if (bench.error.message === 'EWOT') {
|
|
||||||
out = 'NA';
|
|
||||||
} else {
|
|
||||||
out = 'E';
|
|
||||||
}
|
|
||||||
this.writeValue(out);
|
|
||||||
},
|
|
||||||
|
|
||||||
writeValue: function(out) {
|
|
||||||
var padding = this.benchSize - out.length + 1;
|
|
||||||
out = out + new Array(padding).join(' ');
|
|
||||||
print(out);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = BenchWarmer;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
var _ = require('underscore'),
|
|
||||||
BenchWarmer = require('./benchwarmer'),
|
|
||||||
templates = require('../templates');
|
|
||||||
|
|
||||||
module.exports = function(grunt, makeSuite, callback) {
|
|
||||||
var warmer = new BenchWarmer();
|
|
||||||
|
|
||||||
var handlebarsOnly = grunt.option('handlebars-only'),
|
|
||||||
grep = grunt.option('grep');
|
|
||||||
if (grep) {
|
|
||||||
grep = new RegExp(grep);
|
|
||||||
}
|
|
||||||
|
|
||||||
_.each(templates, function(template, name) {
|
|
||||||
if (!template.handlebars || (grep && !grep.test(name))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
warmer.suite(name, function(bench) {
|
|
||||||
makeSuite(bench, name, template, handlebarsOnly);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
warmer.bench(function() {
|
|
||||||
if (callback) {
|
|
||||||
callback(warmer.times, warmer.scaled);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
rules: {
|
|
||||||
'no-console': 0,
|
|
||||||
'no-var': 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
-176
@@ -1,176 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
var argv = parseArgs({
|
|
||||||
'f': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Output File',
|
|
||||||
'alias': 'output'
|
|
||||||
},
|
|
||||||
'map': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Source Map File'
|
|
||||||
},
|
|
||||||
'a': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Exports amd style (require.js)',
|
|
||||||
'alias': 'amd'
|
|
||||||
},
|
|
||||||
'c': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
|
||||||
'alias': 'commonjs',
|
|
||||||
'default': null
|
|
||||||
},
|
|
||||||
'h': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
|
||||||
'alias': 'handlebarPath',
|
|
||||||
'default': ''
|
|
||||||
},
|
|
||||||
'k': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Known helpers',
|
|
||||||
'alias': 'known'
|
|
||||||
},
|
|
||||||
'o': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Known helpers only',
|
|
||||||
'alias': 'knownOnly'
|
|
||||||
},
|
|
||||||
'm': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Minimize output',
|
|
||||||
'alias': 'min'
|
|
||||||
},
|
|
||||||
'n': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Template namespace',
|
|
||||||
'alias': 'namespace',
|
|
||||||
'default': 'Handlebars.templates'
|
|
||||||
},
|
|
||||||
's': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Output template function only.',
|
|
||||||
'alias': 'simple'
|
|
||||||
},
|
|
||||||
'N': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
|
||||||
'alias': 'name'
|
|
||||||
},
|
|
||||||
'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'
|
|
||||||
},
|
|
||||||
'r': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Template root. Base value that will be stripped from template names.',
|
|
||||||
'alias': 'root'
|
|
||||||
},
|
|
||||||
'p': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Compiling a partial template',
|
|
||||||
'alias': 'partial'
|
|
||||||
},
|
|
||||||
'd': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Include data when compiling',
|
|
||||||
'alias': 'data'
|
|
||||||
},
|
|
||||||
'e': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'Template extension.',
|
|
||||||
'alias': 'extension',
|
|
||||||
'default': 'handlebars'
|
|
||||||
},
|
|
||||||
'b': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
|
||||||
'alias': 'bom'
|
|
||||||
},
|
|
||||||
'v': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Prints the current compiler version',
|
|
||||||
'alias': 'version'
|
|
||||||
},
|
|
||||||
'help': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Outputs this message'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
argv.files = argv._;
|
|
||||||
delete argv._;
|
|
||||||
|
|
||||||
var Precompiler = require('../dist/cjs/precompiler');
|
|
||||||
Precompiler.loadTemplates(argv, function(err, opts) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.help || (!opts.templates.length && !opts.version)) {
|
|
||||||
printUsage(argv._spec, 120);
|
|
||||||
} else {
|
|
||||||
Precompiler.cli(opts);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function pad(n) {
|
|
||||||
var str = '';
|
|
||||||
while (str.length < n) {
|
|
||||||
str += ' ';
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseArgs(spec) {
|
|
||||||
var opts = { alias: {}, boolean: [], default: {}, string: [] };
|
|
||||||
|
|
||||||
Object.keys(spec).forEach(function (arg) {
|
|
||||||
var opt = spec[arg];
|
|
||||||
opts[opt.type].push(arg);
|
|
||||||
if ('alias' in opt) opts.alias[arg] = opt.alias;
|
|
||||||
if ('default' in opt) opts.default[arg] = opt.default;
|
|
||||||
});
|
|
||||||
|
|
||||||
var argv = require('minimist')(process.argv.slice(2), opts);
|
|
||||||
argv._spec = spec;
|
|
||||||
return argv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function printUsage(spec, wrap) {
|
|
||||||
var wordwrap = require('wordwrap');
|
|
||||||
|
|
||||||
console.log('Precompile handlebar templates.');
|
|
||||||
console.log('Usage: handlebars [template|directory]...');
|
|
||||||
|
|
||||||
var opts = [];
|
|
||||||
var width = 0;
|
|
||||||
Object.keys(spec).forEach(function (arg) {
|
|
||||||
var opt = spec[arg];
|
|
||||||
|
|
||||||
var name = (arg.length === 1 ? '-' : '--') + arg;
|
|
||||||
if ('alias' in opt) name += ', --' + opt.alias;
|
|
||||||
|
|
||||||
var meta = '[' + opt.type + ']';
|
|
||||||
if ('default' in opt) meta += ' [default: ' + JSON.stringify(opt.default) + ']';
|
|
||||||
|
|
||||||
opts.push({ name: name, desc: opt.description, meta: meta });
|
|
||||||
if (name.length > width) width = name.length;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Options:');
|
|
||||||
opts.forEach(function (opt) {
|
|
||||||
var desc = wordwrap(width + 4, wrap + 1)(opt.desc);
|
|
||||||
|
|
||||||
console.log(' %s%s%s%s%s',
|
|
||||||
opt.name,
|
|
||||||
pad(width - opt.name.length + 2),
|
|
||||||
desc.slice(width + 4),
|
|
||||||
pad(wrap - opt.meta.length - desc.split(/\n/).pop().length),
|
|
||||||
opt.meta
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#!/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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.7.6",
|
"version": "5.0.0-alpha.1",
|
||||||
"main": "handlebars.js",
|
"main": "handlebars.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
|
|||||||
@@ -3,7 +3,5 @@
|
|||||||
"repo": "components/handlebars.js",
|
"repo": "components/handlebars.js",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "handlebars.js",
|
"main": "handlebars.js",
|
||||||
"scripts": [
|
"scripts": ["handlebars.js"]
|
||||||
"handlebars.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-32
@@ -1,35 +1,35 @@
|
|||||||
{
|
{
|
||||||
"name": "components/handlebars.js",
|
"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.",
|
"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",
|
"homepage": "http://handlebarsjs.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "component",
|
"type": "component",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"mustache",
|
"mustache",
|
||||||
"html"
|
"html"
|
||||||
],
|
],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Chris Wanstrath",
|
"name": "Chris Wanstrath",
|
||||||
"homepage": "http://chriswanstrath.com"
|
"homepage": "http://chriswanstrath.com"
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"robloach/component-installer": "*"
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"component": {
|
|
||||||
"name": "handlebars",
|
|
||||||
"scripts": [
|
|
||||||
"handlebars.js"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"handlebars.runtime.js"
|
|
||||||
],
|
|
||||||
"shim": {
|
|
||||||
"exports": "Handlebars"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"robloach/component-installer": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"component": {
|
||||||
|
"name": "handlebars",
|
||||||
|
"scripts": [
|
||||||
|
"handlebars.js"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"handlebars.runtime.js"
|
||||||
|
],
|
||||||
|
"shim": {
|
||||||
|
"exports": "Handlebars"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
|||||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||||
gem.summary = %q{Handlebars.js source code wrapper}
|
gem.summary = %q{Handlebars.js source code wrapper}
|
||||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
|
||||||
gem.version = package["version"].sub "-", "."
|
gem.version = package["version"].sub "-", "."
|
||||||
gem.license = "MIT"
|
gem.license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>handlebars.js</id>
|
<id>handlebars.js</id>
|
||||||
<version>4.7.6</version>
|
<version>5.0.0-alpha.1</version>
|
||||||
<authors>handlebars.js Authors</authors>
|
<authors>handlebars.js Authors</authors>
|
||||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Extension of the Mustache logicless template language</description>
|
<description>Extension of the Mustache logicless template language</description>
|
||||||
<releaseNotes></releaseNotes>
|
<releaseNotes></releaseNotes>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.7.6",
|
"version": "5.0.0-alpha.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"jspm": {
|
"jspm": {
|
||||||
"main": "handlebars",
|
"main": "handlebars",
|
||||||
|
|||||||
+32
-33
@@ -34,8 +34,8 @@ let ast = Handlebars.parseWithoutProcessing(myTemplate);
|
|||||||
|
|
||||||
`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:
|
`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")
|
- 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 `''`)
|
- 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`.
|
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
|
||||||
|
|
||||||
@@ -132,7 +132,6 @@ interface PartialBlockStatement <: Statement {
|
|||||||
|
|
||||||
`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.
|
`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
|
```java
|
||||||
interface ContentStatement <: Statement {
|
interface ContentStatement <: Statement {
|
||||||
type: "ContentStatement";
|
type: "ContentStatement";
|
||||||
@@ -148,7 +147,6 @@ interface CommentStatement <: Statement {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
interface Decorator <: Statement {
|
interface Decorator <: Statement {
|
||||||
type: "Decorator";
|
type: "Decorator";
|
||||||
@@ -209,7 +207,6 @@ interface PathExpression <: Expression {
|
|||||||
- `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.
|
- `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.
|
- `original` is the path as entered by the user. Separator and scope references are left untouched.
|
||||||
|
|
||||||
|
|
||||||
##### Literals
|
##### Literals
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@@ -242,7 +239,6 @@ interface NullLiteral <: Literal {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Miscellaneous
|
### Miscellaneous
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@@ -279,8 +275,8 @@ function ImportScanner() {
|
|||||||
}
|
}
|
||||||
ImportScanner.prototype = new Visitor();
|
ImportScanner.prototype = new Visitor();
|
||||||
|
|
||||||
ImportScanner.prototype.PartialStatement = function(partial) {
|
ImportScanner.prototype.PartialStatement = function (partial) {
|
||||||
this.partials.push({request: partial.name.original});
|
this.partials.push({ request: partial.name.original });
|
||||||
|
|
||||||
Visitor.prototype.PartialStatement.call(this, partial);
|
Visitor.prototype.PartialStatement.call(this, partial);
|
||||||
};
|
};
|
||||||
@@ -291,7 +287,7 @@ scanner.accept(ast);
|
|||||||
|
|
||||||
The current node's ancestors will be maintained in the `parents` array, with the most recent parent listed first.
|
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 `mutation` 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 in tact. This return structure only apply to mutation mode and non-mutation mode visitors are free to return whatever values they wish.
|
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.
|
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.
|
||||||
|
|
||||||
@@ -300,8 +296,7 @@ Implementors that may need to support mutation mode are encouraged to utilize th
|
|||||||
The `Handlebars.JavaScriptCompiler` object has a number of methods that may be customized to alter the output of the 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)`
|
- `nameLookup(parent, name, type)`
|
||||||
Used to generate the code to resolve a give path component.
|
Used to generate the code to resolve a given path component.
|
||||||
|
|
||||||
- `parent` is the existing code in the path resolution
|
- `parent` is the existing code in the path resolution
|
||||||
- `name` is the current path component
|
- `name` is the current path component
|
||||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
|
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
|
||||||
@@ -315,14 +310,13 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
|||||||
Allows for custom compiler flags used in the runtime version checking logic.
|
Allows for custom compiler flags used in the runtime version checking logic.
|
||||||
|
|
||||||
- `appendToBuffer(source, location, explicit)`
|
- `appendToBuffer(source, location, explicit)`
|
||||||
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||||
|
- `source` is the source code whose result is to be appending
|
||||||
- `source` is the source code whose result is to be appending
|
- `location` is the location of the source in the source map.
|
||||||
- `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.
|
||||||
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
|
||||||
|
|
||||||
- `initializeBuffer()`
|
- `initializeBuffer()`
|
||||||
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
|
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.
|
### Example for the compiler api.
|
||||||
|
|
||||||
@@ -330,7 +324,6 @@ This example changes all lookups of properties are performed by a helper (`looku
|
|||||||
|
|
||||||
There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it.
|
There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it.
|
||||||
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function MyCompiler() {
|
function MyCompiler() {
|
||||||
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
||||||
@@ -338,29 +331,35 @@ function MyCompiler() {
|
|||||||
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
|
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
|
||||||
|
|
||||||
// Use this compile to compile BlockStatment-Blocks
|
// Use this compile to compile BlockStatment-Blocks
|
||||||
MyCompiler.prototype.compiler = MyCompiler
|
MyCompiler.prototype.compiler = MyCompiler;
|
||||||
|
|
||||||
MyCompiler.prototype.nameLookup = function(parent, name, type) {
|
MyCompiler.prototype.nameLookup = function (parent, name, type) {
|
||||||
if (type === 'context') {
|
if (type === 'context') {
|
||||||
return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)])
|
return this.source.functionCall('helpers.lookupLowerCase', '', [
|
||||||
|
parent,
|
||||||
|
JSON.stringify(name),
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
|
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(
|
||||||
|
this,
|
||||||
|
parent,
|
||||||
|
name,
|
||||||
|
type
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
var env = Handlebars.create();
|
var env = Handlebars.create();
|
||||||
env.registerHelper('lookupLowerCase', function(parent, name) {
|
env.registerHelper('lookupLowerCase', function (parent, name) {
|
||||||
return parent[name.toLowerCase()]
|
return parent[name.toLowerCase()];
|
||||||
})
|
});
|
||||||
|
|
||||||
env.JavaScriptCompiler = MyCompiler;
|
env.JavaScriptCompiler = MyCompiler;
|
||||||
|
|
||||||
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
|
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
|
||||||
console.log(template({
|
console.log(
|
||||||
test: [
|
template({
|
||||||
{value: 'a'},
|
test: [{ value: 'a' }, { value: 'b' }, { value: 'c' }],
|
||||||
{value: 'b'},
|
})
|
||||||
{value: 'c'}
|
);
|
||||||
]
|
|
||||||
}));
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Decorators
|
# Decorators
|
||||||
|
|
||||||
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 setup a particular state in the system prior to running the block.
|
**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 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.
|
||||||
|
|
||||||
@@ -16,4 +18,4 @@ Decorators are executed when the block program is instantiated and are passed `(
|
|||||||
|
|
||||||
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.
|
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/wycats/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
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.
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['eslint:recommended', 'plugin:es5/no-es2015', 'prettier'],
|
|
||||||
globals: {
|
|
||||||
self: false
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
node: true
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-console': 'off',
|
|
||||||
'no-var': 'off'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "webpack-babel-test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/core": "^7.5.5",
|
|
||||||
"@babel/preset-env": "^7.5.5",
|
|
||||||
"@roundingwellos/babel-plugin-handlebars-inline-precompile": "^3.0.1",
|
|
||||||
"babel-loader": "^8.0.6",
|
|
||||||
"babel-plugin-istanbul": "^5.2.0",
|
|
||||||
"handlebars": "file:../..",
|
|
||||||
"handlebars-loader": "^1.7.1",
|
|
||||||
"nyc": "^14.1.1",
|
|
||||||
"webpack": "^4.39.3",
|
|
||||||
"webpack-cli": "^3.3.7"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --config webpack.config.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "webpack-test",
|
|
||||||
"description": "Various tests with Handlebars and Webpack",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --config webpack.config.js",
|
|
||||||
"test": "node dist/main.js"
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
|
||||||
"handlebars": "file:../..",
|
|
||||||
"handlebars-loader": "^1.7.1",
|
|
||||||
"webpack": "^4.39.3",
|
|
||||||
"webpack-cli": "^3.3.7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import Handlebars from 'handlebars/dist/handlebars';
|
|
||||||
|
|
||||||
import { assertEquals } from './lib/assert';
|
|
||||||
|
|
||||||
const template = Handlebars.compile('Author: {{author}}');
|
|
||||||
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import * as HandlebarsViaImport from 'handlebars';
|
|
||||||
const HandlebarsViaRequire = require('handlebars');
|
|
||||||
import { assertEquals } from './lib/assert';
|
|
||||||
|
|
||||||
HandlebarsViaImport.registerHelper('loud', function(text) {
|
|
||||||
return text.toUpperCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
const template = HandlebarsViaRequire.compile('Author: {{loud author}}');
|
|
||||||
assertEquals(template({ author: 'Yehuda' }), 'Author: YEHUDA');
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import * as Handlebars from 'handlebars/dist/handlebars';
|
|
||||||
|
|
||||||
import { assertEquals } from './lib/assert';
|
|
||||||
|
|
||||||
const template = Handlebars.compile('Author: {{author}}');
|
|
||||||
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
|
|
||||||
+9
-8
@@ -1,15 +1,16 @@
|
|||||||
|
import {
|
||||||
|
parser as Parser,
|
||||||
|
parse,
|
||||||
|
parseWithoutProcessing,
|
||||||
|
Visitor,
|
||||||
|
} from '@handlebars/parser';
|
||||||
|
|
||||||
import runtime from './handlebars.runtime';
|
import runtime from './handlebars.runtime';
|
||||||
|
|
||||||
// Compiler imports
|
// Compiler imports
|
||||||
import AST from './handlebars/compiler/ast';
|
import AST from './handlebars/compiler/ast';
|
||||||
import {
|
|
||||||
parser as Parser,
|
|
||||||
parse,
|
|
||||||
parseWithoutProcessing
|
|
||||||
} from './handlebars/compiler/base';
|
|
||||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||||
import Visitor from './handlebars/compiler/visitor';
|
|
||||||
|
|
||||||
import noConflict from './handlebars/no-conflict';
|
import noConflict from './handlebars/no-conflict';
|
||||||
|
|
||||||
@@ -17,10 +18,10 @@ let _create = runtime.create;
|
|||||||
function create() {
|
function create() {
|
||||||
let hb = _create();
|
let hb = _create();
|
||||||
|
|
||||||
hb.compile = function(input, options) {
|
hb.compile = function (input, options) {
|
||||||
return compile(input, options, hb);
|
return compile(input, options, hb);
|
||||||
};
|
};
|
||||||
hb.precompile = function(input, options) {
|
hb.precompile = function (input, options) {
|
||||||
return precompile(input, options, hb);
|
return precompile(input, options, hb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { Exception } from '@handlebars/parser';
|
||||||
import * as base from './handlebars/base';
|
import * as base from './handlebars/base';
|
||||||
|
|
||||||
// Each of these augment the Handlebars object. No need to setup here.
|
// Each of these augment the Handlebars object. No need to setup here.
|
||||||
// (This is done to easily share code between commonjs and browse envs)
|
// (This is done to easily share code between commonjs and browse envs)
|
||||||
import SafeString from './handlebars/safe-string';
|
import SafeString from './handlebars/safe-string';
|
||||||
import Exception from './handlebars/exception';
|
|
||||||
import * as Utils from './handlebars/utils';
|
import * as Utils from './handlebars/utils';
|
||||||
import * as runtime from './handlebars/runtime';
|
import * as runtime from './handlebars/runtime';
|
||||||
|
|
||||||
@@ -19,8 +19,11 @@ function create() {
|
|||||||
hb.Utils = Utils;
|
hb.Utils = Utils;
|
||||||
hb.escapeExpression = Utils.escapeExpression;
|
hb.escapeExpression = Utils.escapeExpression;
|
||||||
|
|
||||||
hb.VM = runtime;
|
// Spread into a plain object so that runtime functions (e.g. checkRevision)
|
||||||
hb.template = function(spec) {
|
// 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 runtime.template(spec, hb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -1,11 +1,11 @@
|
|||||||
|
import { Exception } from '@handlebars/parser';
|
||||||
import { createFrame, extend, toString } from './utils';
|
import { createFrame, extend, toString } from './utils';
|
||||||
import Exception from './exception';
|
|
||||||
import { registerDefaultHelpers } from './helpers';
|
import { registerDefaultHelpers } from './helpers';
|
||||||
import { registerDefaultDecorators } from './decorators';
|
import { registerDefaultDecorators } from './decorators';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import { resetLoggedProperties } from './internal/proto-access';
|
import { resetLoggedProperties } from './internal/proto-access';
|
||||||
|
|
||||||
export const VERSION = '4.7.6';
|
export const VERSION = '4.7.7';
|
||||||
export const COMPILER_REVISION = 8;
|
export const COMPILER_REVISION = 8;
|
||||||
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export const REVISION_CHANGES = {
|
|||||||
5: '== 2.0.0-alpha.x',
|
5: '== 2.0.0-alpha.x',
|
||||||
6: '>= 2.0.0-beta.1',
|
6: '>= 2.0.0-beta.1',
|
||||||
7: '>= 4.0.0 <4.3.0',
|
7: '>= 4.0.0 <4.3.0',
|
||||||
8: '>= 4.3.0'
|
8: '>= 4.3.0',
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectType = '[object Object]';
|
const objectType = '[object Object]';
|
||||||
@@ -37,7 +37,7 @@ HandlebarsEnvironment.prototype = {
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
log: logger.log,
|
log: logger.log,
|
||||||
|
|
||||||
registerHelper: function(name, fn) {
|
registerHelper: function (name, fn) {
|
||||||
if (toString.call(name) === objectType) {
|
if (toString.call(name) === objectType) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
throw new Exception('Arg not supported with multiple helpers');
|
throw new Exception('Arg not supported with multiple helpers');
|
||||||
@@ -47,11 +47,11 @@ HandlebarsEnvironment.prototype = {
|
|||||||
this.helpers[name] = fn;
|
this.helpers[name] = fn;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unregisterHelper: function(name) {
|
unregisterHelper: function (name) {
|
||||||
delete this.helpers[name];
|
delete this.helpers[name];
|
||||||
},
|
},
|
||||||
|
|
||||||
registerPartial: function(name, partial) {
|
registerPartial: function (name, partial) {
|
||||||
if (toString.call(name) === objectType) {
|
if (toString.call(name) === objectType) {
|
||||||
extend(this.partials, name);
|
extend(this.partials, name);
|
||||||
} else {
|
} else {
|
||||||
@@ -63,11 +63,11 @@ HandlebarsEnvironment.prototype = {
|
|||||||
this.partials[name] = partial;
|
this.partials[name] = partial;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unregisterPartial: function(name) {
|
unregisterPartial: function (name) {
|
||||||
delete this.partials[name];
|
delete this.partials[name];
|
||||||
},
|
},
|
||||||
|
|
||||||
registerDecorator: function(name, fn) {
|
registerDecorator: function (name, fn) {
|
||||||
if (toString.call(name) === objectType) {
|
if (toString.call(name) === objectType) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
throw new Exception('Arg not supported with multiple decorators');
|
throw new Exception('Arg not supported with multiple decorators');
|
||||||
@@ -77,7 +77,7 @@ HandlebarsEnvironment.prototype = {
|
|||||||
this.decorators[name] = fn;
|
this.decorators[name] = fn;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unregisterDecorator: function(name) {
|
unregisterDecorator: function (name) {
|
||||||
delete this.decorators[name];
|
delete this.decorators[name];
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +86,7 @@ HandlebarsEnvironment.prototype = {
|
|||||||
*/
|
*/
|
||||||
resetLoggedPropertyAccesses() {
|
resetLoggedPropertyAccesses() {
|
||||||
resetLoggedProperties();
|
resetLoggedProperties();
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export let log = logger.log;
|
export let log = logger.log;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ let AST = {
|
|||||||
// a mustache is definitely a helper if:
|
// a mustache is definitely a helper if:
|
||||||
// * it is an eligible helper, and
|
// * it is an eligible helper, and
|
||||||
// * it has at least one parameter or hash segment
|
// * it has at least one parameter or hash segment
|
||||||
helperExpression: function(node) {
|
helperExpression: function (node) {
|
||||||
return (
|
return (
|
||||||
node.type === 'SubExpression' ||
|
node.type === 'SubExpression' ||
|
||||||
((node.type === 'MustacheStatement' ||
|
((node.type === 'MustacheStatement' ||
|
||||||
@@ -13,18 +13,18 @@ let AST = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
scopedId: function(path) {
|
scopedId: function (path) {
|
||||||
return /^\.|this\b/.test(path.original);
|
return /^\.|this\b/.test(path.original);
|
||||||
},
|
},
|
||||||
|
|
||||||
// an ID is simple if it only has one part, and that part is not
|
// an ID is simple if it only has one part, and that part is not
|
||||||
// `..` or `this`.
|
// `..` or `this`.
|
||||||
simpleId: function(path) {
|
simpleId: function (path) {
|
||||||
return (
|
return (
|
||||||
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
|
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 be exported as an object rather than the root of the module as the jison lexer
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import parser from './parser';
|
|
||||||
import WhitespaceControl from './whitespace-control';
|
|
||||||
import * as Helpers from './helpers';
|
|
||||||
import { extend } from '../utils';
|
|
||||||
|
|
||||||
export { parser };
|
|
||||||
|
|
||||||
let yy = {};
|
|
||||||
extend(yy, Helpers);
|
|
||||||
|
|
||||||
export function parseWithoutProcessing(input, options) {
|
|
||||||
// Just return if an already-compiled AST was passed in.
|
|
||||||
if (input.type === 'Program') {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.yy = yy;
|
|
||||||
|
|
||||||
// Altering the shared object here, but this is ok as parser is a sync operation
|
|
||||||
yy.locInfo = function(locInfo) {
|
|
||||||
return new yy.SourceLocation(options && options.srcName, locInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
let ast = parser.parse(input);
|
|
||||||
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse(input, options) {
|
|
||||||
let ast = parseWithoutProcessing(input, options);
|
|
||||||
let strip = new WhitespaceControl(options);
|
|
||||||
|
|
||||||
return strip.accept(ast);
|
|
||||||
}
|
|
||||||
@@ -4,45 +4,51 @@ import { isArray } from '../utils';
|
|||||||
let SourceNode;
|
let SourceNode;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* istanbul ignore next */
|
/* v8 ignore next */
|
||||||
if (typeof define !== 'function' || !define.amd) {
|
if (typeof define !== 'function' || !define.amd) {
|
||||||
// We don't support this in AMD environments. For these environments, we asusme that
|
// 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.
|
// they are running on the browser and thus have no need for the source-map library.
|
||||||
let SourceMap = require('source-map');
|
// 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;
|
SourceNode = SourceMap.SourceNode;
|
||||||
}
|
}
|
||||||
|
// oxlint-disable-next-line no-unused-vars -- Babel 5 requires named catch param
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* NOP */
|
/* NOP */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* istanbul ignore if: tested but not covered in istanbul due to dist build */
|
/* v8 ignore next -- tested but not covered due to dist build */
|
||||||
if (!SourceNode) {
|
if (!SourceNode) {
|
||||||
SourceNode = function(line, column, srcFile, chunks) {
|
SourceNode = function (line, column, srcFile, chunks) {
|
||||||
this.src = '';
|
this.src = '';
|
||||||
if (chunks) {
|
if (chunks) {
|
||||||
this.add(chunks);
|
this.add(chunks);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/* istanbul ignore next */
|
/* v8 ignore next */
|
||||||
SourceNode.prototype = {
|
SourceNode.prototype = {
|
||||||
add: function(chunks) {
|
add: function (chunks) {
|
||||||
if (isArray(chunks)) {
|
if (isArray(chunks)) {
|
||||||
chunks = chunks.join('');
|
chunks = chunks.join('');
|
||||||
}
|
}
|
||||||
this.src += chunks;
|
this.src += chunks;
|
||||||
},
|
},
|
||||||
prepend: function(chunks) {
|
prepend: function (chunks) {
|
||||||
if (isArray(chunks)) {
|
if (isArray(chunks)) {
|
||||||
chunks = chunks.join('');
|
chunks = chunks.join('');
|
||||||
}
|
}
|
||||||
this.src = chunks + this.src;
|
this.src = chunks + this.src;
|
||||||
},
|
},
|
||||||
toStringWithSourceMap: function() {
|
toStringWithSourceMap: function () {
|
||||||
return { code: this.toString() };
|
return { code: this.toString() };
|
||||||
},
|
},
|
||||||
toString: function() {
|
toString: function () {
|
||||||
return this.src;
|
return this.src;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,32 +76,32 @@ CodeGen.prototype = {
|
|||||||
isEmpty() {
|
isEmpty() {
|
||||||
return !this.source.length;
|
return !this.source.length;
|
||||||
},
|
},
|
||||||
prepend: function(source, loc) {
|
prepend: function (source, loc) {
|
||||||
this.source.unshift(this.wrap(source, loc));
|
this.source.unshift(this.wrap(source, loc));
|
||||||
},
|
},
|
||||||
push: function(source, loc) {
|
push: function (source, loc) {
|
||||||
this.source.push(this.wrap(source, loc));
|
this.source.push(this.wrap(source, loc));
|
||||||
},
|
},
|
||||||
|
|
||||||
merge: function() {
|
merge: function () {
|
||||||
let source = this.empty();
|
let source = this.empty();
|
||||||
this.each(function(line) {
|
this.each(function (line) {
|
||||||
source.add([' ', line, '\n']);
|
source.add([' ', line, '\n']);
|
||||||
});
|
});
|
||||||
return source;
|
return source;
|
||||||
},
|
},
|
||||||
|
|
||||||
each: function(iter) {
|
each: function (iter) {
|
||||||
for (let i = 0, len = this.source.length; i < len; i++) {
|
for (let i = 0, len = this.source.length; i < len; i++) {
|
||||||
iter(this.source[i]);
|
iter(this.source[i]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
empty: function() {
|
empty: function () {
|
||||||
let loc = this.currentLocation || { start: {} };
|
let loc = this.currentLocation || { start: {} };
|
||||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||||
},
|
},
|
||||||
wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
|
wrap: function (chunk, loc = this.currentLocation || { start: {} }) {
|
||||||
if (chunk instanceof SourceNode) {
|
if (chunk instanceof SourceNode) {
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
@@ -110,12 +116,12 @@ CodeGen.prototype = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
functionCall: function(fn, type, params) {
|
functionCall: function (fn, type, params) {
|
||||||
params = this.generateList(params);
|
params = this.generateList(params);
|
||||||
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
|
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
|
||||||
},
|
},
|
||||||
|
|
||||||
quotedString: function(str) {
|
quotedString: function (str) {
|
||||||
return (
|
return (
|
||||||
'"' +
|
'"' +
|
||||||
(str + '')
|
(str + '')
|
||||||
@@ -129,10 +135,10 @@ CodeGen.prototype = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
objectLiteral: function(obj) {
|
objectLiteral: function (obj) {
|
||||||
let pairs = [];
|
let pairs = [];
|
||||||
|
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach((key) => {
|
||||||
let value = castChunk(obj[key], this);
|
let value = castChunk(obj[key], this);
|
||||||
if (value !== 'undefined') {
|
if (value !== 'undefined') {
|
||||||
pairs.push([this.quotedString(key), ':', value]);
|
pairs.push([this.quotedString(key), ':', value]);
|
||||||
@@ -145,7 +151,7 @@ CodeGen.prototype = {
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
generateList: function(entries) {
|
generateList: function (entries) {
|
||||||
let ret = this.empty();
|
let ret = this.empty();
|
||||||
|
|
||||||
for (let i = 0, len = entries.length; i < len; i++) {
|
for (let i = 0, len = entries.length; i < len; i++) {
|
||||||
@@ -159,13 +165,13 @@ CodeGen.prototype = {
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
generateArray: function(entries) {
|
generateArray: function (entries) {
|
||||||
let ret = this.generateList(entries);
|
let ret = this.generateList(entries);
|
||||||
ret.prepend('[');
|
ret.prepend('[');
|
||||||
ret.add(']');
|
ret.add(']');
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CodeGen;
|
export default CodeGen;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/* eslint-disable new-cap */
|
import { Exception } from '@handlebars/parser';
|
||||||
|
|
||||||
import Exception from '../exception';
|
|
||||||
import { isArray, indexOf, extend } from '../utils';
|
import { isArray, indexOf, extend } from '../utils';
|
||||||
import AST from './ast';
|
import AST from './ast';
|
||||||
|
|
||||||
@@ -16,7 +14,7 @@ export function Compiler() {}
|
|||||||
Compiler.prototype = {
|
Compiler.prototype = {
|
||||||
compiler: Compiler,
|
compiler: Compiler,
|
||||||
|
|
||||||
equals: function(other) {
|
equals: function (other) {
|
||||||
let len = this.opcodes.length;
|
let len = this.opcodes.length;
|
||||||
if (other.opcodes.length !== len) {
|
if (other.opcodes.length !== len) {
|
||||||
return false;
|
return false;
|
||||||
@@ -47,13 +45,11 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
guid: 0,
|
guid: 0,
|
||||||
|
|
||||||
compile: function(program, options) {
|
compile: function (program, options) {
|
||||||
this.sourceNode = [];
|
this.sourceNode = [];
|
||||||
this.opcodes = [];
|
this.opcodes = [];
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.stringParams = options.stringParams;
|
|
||||||
this.trackIds = options.trackIds;
|
|
||||||
|
|
||||||
options.blockParams = options.blockParams || [];
|
options.blockParams = options.blockParams || [];
|
||||||
|
|
||||||
@@ -67,7 +63,7 @@ Compiler.prototype = {
|
|||||||
unless: true,
|
unless: true,
|
||||||
with: true,
|
with: true,
|
||||||
log: true,
|
log: true,
|
||||||
lookup: true
|
lookup: true,
|
||||||
},
|
},
|
||||||
options.knownHelpers
|
options.knownHelpers
|
||||||
);
|
);
|
||||||
@@ -75,8 +71,8 @@ Compiler.prototype = {
|
|||||||
return this.accept(program);
|
return this.accept(program);
|
||||||
},
|
},
|
||||||
|
|
||||||
compileProgram: function(program) {
|
compileProgram: function (program) {
|
||||||
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
let childCompiler = new this.compiler(),
|
||||||
result = childCompiler.compile(program, this.options),
|
result = childCompiler.compile(program, this.options),
|
||||||
guid = this.guid++;
|
guid = this.guid++;
|
||||||
|
|
||||||
@@ -88,8 +84,8 @@ Compiler.prototype = {
|
|||||||
return guid;
|
return guid;
|
||||||
},
|
},
|
||||||
|
|
||||||
accept: function(node) {
|
accept: function (node) {
|
||||||
/* istanbul ignore next: Sanity code */
|
/* v8 ignore next -- Sanity code */
|
||||||
if (!this[node.type]) {
|
if (!this[node.type]) {
|
||||||
throw new Exception('Unknown type: ' + node.type, node);
|
throw new Exception('Unknown type: ' + node.type, node);
|
||||||
}
|
}
|
||||||
@@ -100,7 +96,7 @@ Compiler.prototype = {
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
Program: function(program) {
|
Program: function (program) {
|
||||||
this.options.blockParams.unshift(program.blockParams);
|
this.options.blockParams.unshift(program.blockParams);
|
||||||
|
|
||||||
let body = program.body,
|
let body = program.body,
|
||||||
@@ -117,7 +113,7 @@ Compiler.prototype = {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
BlockStatement: function(block) {
|
BlockStatement: function (block) {
|
||||||
transformLiteralToPath(block);
|
transformLiteralToPath(block);
|
||||||
|
|
||||||
let program = block.program,
|
let program = block.program,
|
||||||
@@ -162,7 +158,7 @@ Compiler.prototype = {
|
|||||||
this.opcode('registerDecorator', params.length, path.original);
|
this.opcode('registerDecorator', params.length, path.original);
|
||||||
},
|
},
|
||||||
|
|
||||||
PartialStatement: function(partial) {
|
PartialStatement: function (partial) {
|
||||||
this.usePartial = true;
|
this.usePartial = true;
|
||||||
|
|
||||||
let program = partial.program;
|
let program = partial.program;
|
||||||
@@ -201,11 +197,11 @@ Compiler.prototype = {
|
|||||||
this.opcode('invokePartial', isDynamic, partialName, indent);
|
this.opcode('invokePartial', isDynamic, partialName, indent);
|
||||||
this.opcode('append');
|
this.opcode('append');
|
||||||
},
|
},
|
||||||
PartialBlockStatement: function(partialBlock) {
|
PartialBlockStatement: function (partialBlock) {
|
||||||
this.PartialStatement(partialBlock);
|
this.PartialStatement(partialBlock);
|
||||||
},
|
},
|
||||||
|
|
||||||
MustacheStatement: function(mustache) {
|
MustacheStatement: function (mustache) {
|
||||||
this.SubExpression(mustache);
|
this.SubExpression(mustache);
|
||||||
|
|
||||||
if (mustache.escaped && !this.options.noEscape) {
|
if (mustache.escaped && !this.options.noEscape) {
|
||||||
@@ -218,15 +214,15 @@ Compiler.prototype = {
|
|||||||
this.DecoratorBlock(decorator);
|
this.DecoratorBlock(decorator);
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentStatement: function(content) {
|
ContentStatement: function (content) {
|
||||||
if (content.value) {
|
if (content.value) {
|
||||||
this.opcode('appendContent', content.value);
|
this.opcode('appendContent', content.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
CommentStatement: function() {},
|
CommentStatement: function () {},
|
||||||
|
|
||||||
SubExpression: function(sexpr) {
|
SubExpression: function (sexpr) {
|
||||||
transformLiteralToPath(sexpr);
|
transformLiteralToPath(sexpr);
|
||||||
let type = this.classifySexpr(sexpr);
|
let type = this.classifySexpr(sexpr);
|
||||||
|
|
||||||
@@ -238,7 +234,7 @@ Compiler.prototype = {
|
|||||||
this.ambiguousSexpr(sexpr);
|
this.ambiguousSexpr(sexpr);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
ambiguousSexpr: function (sexpr, program, inverse) {
|
||||||
let path = sexpr.path,
|
let path = sexpr.path,
|
||||||
name = path.parts[0],
|
name = path.parts[0],
|
||||||
isBlock = program != null || inverse != null;
|
isBlock = program != null || inverse != null;
|
||||||
@@ -254,14 +250,14 @@ Compiler.prototype = {
|
|||||||
this.opcode('invokeAmbiguous', name, isBlock);
|
this.opcode('invokeAmbiguous', name, isBlock);
|
||||||
},
|
},
|
||||||
|
|
||||||
simpleSexpr: function(sexpr) {
|
simpleSexpr: function (sexpr) {
|
||||||
let path = sexpr.path;
|
let path = sexpr.path;
|
||||||
path.strict = true;
|
path.strict = true;
|
||||||
this.accept(path);
|
this.accept(path);
|
||||||
this.opcode('resolvePossibleLambda');
|
this.opcode('resolvePossibleLambda');
|
||||||
},
|
},
|
||||||
|
|
||||||
helperSexpr: function(sexpr, program, inverse) {
|
helperSexpr: function (sexpr, program, inverse) {
|
||||||
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||||
path = sexpr.path,
|
path = sexpr.path,
|
||||||
name = path.parts[0];
|
name = path.parts[0];
|
||||||
@@ -287,7 +283,7 @@ Compiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
PathExpression: function(path) {
|
PathExpression: function (path) {
|
||||||
this.addDepth(path.depth);
|
this.addDepth(path.depth);
|
||||||
this.opcode('getContext', path.depth);
|
this.opcode('getContext', path.depth);
|
||||||
|
|
||||||
@@ -314,27 +310,27 @@ Compiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
StringLiteral: function(string) {
|
StringLiteral: function (string) {
|
||||||
this.opcode('pushString', string.value);
|
this.opcode('pushString', string.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
NumberLiteral: function(number) {
|
NumberLiteral: function (number) {
|
||||||
this.opcode('pushLiteral', number.value);
|
this.opcode('pushLiteral', number.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
BooleanLiteral: function(bool) {
|
BooleanLiteral: function (bool) {
|
||||||
this.opcode('pushLiteral', bool.value);
|
this.opcode('pushLiteral', bool.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
UndefinedLiteral: function() {
|
UndefinedLiteral: function () {
|
||||||
this.opcode('pushLiteral', 'undefined');
|
this.opcode('pushLiteral', 'undefined');
|
||||||
},
|
},
|
||||||
|
|
||||||
NullLiteral: function() {
|
NullLiteral: function () {
|
||||||
this.opcode('pushLiteral', 'null');
|
this.opcode('pushLiteral', 'null');
|
||||||
},
|
},
|
||||||
|
|
||||||
Hash: function(hash) {
|
Hash: function (hash) {
|
||||||
let pairs = hash.pairs,
|
let pairs = hash.pairs,
|
||||||
i = 0,
|
i = 0,
|
||||||
l = pairs.length;
|
l = pairs.length;
|
||||||
@@ -351,15 +347,15 @@ Compiler.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
opcode: function(name) {
|
opcode: function (name) {
|
||||||
this.opcodes.push({
|
this.opcodes.push({
|
||||||
opcode: name,
|
opcode: name,
|
||||||
args: slice.call(arguments, 1),
|
args: slice.call(arguments, 1),
|
||||||
loc: this.sourceNode[0].loc
|
loc: this.sourceNode[0].loc,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addDepth: function(depth) {
|
addDepth: function (depth) {
|
||||||
if (!depth) {
|
if (!depth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -367,7 +363,7 @@ Compiler.prototype = {
|
|||||||
this.useDepths = true;
|
this.useDepths = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
classifySexpr: function(sexpr) {
|
classifySexpr: function (sexpr) {
|
||||||
let isSimple = AST.helpers.simpleId(sexpr.path);
|
let isSimple = AST.helpers.simpleId(sexpr.path);
|
||||||
|
|
||||||
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
|
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
|
||||||
@@ -402,57 +398,17 @@ Compiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
pushParams: function(params) {
|
pushParams: function (params) {
|
||||||
for (let i = 0, l = params.length; i < l; i++) {
|
for (let i = 0, l = params.length; i < l; i++) {
|
||||||
this.pushParam(params[i]);
|
this.pushParam(params[i]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
pushParam: function(val) {
|
pushParam: function (val) {
|
||||||
let value = val.value != null ? val.value : val.original || '';
|
this.accept(val);
|
||||||
|
|
||||||
if (this.stringParams) {
|
|
||||||
if (value.replace) {
|
|
||||||
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val.depth) {
|
|
||||||
this.addDepth(val.depth);
|
|
||||||
}
|
|
||||||
this.opcode('getContext', val.depth || 0);
|
|
||||||
this.opcode('pushStringParam', value, val.type);
|
|
||||||
|
|
||||||
if (val.type === 'SubExpression') {
|
|
||||||
// SubExpressions get evaluated and passed in
|
|
||||||
// in string params mode.
|
|
||||||
this.accept(val);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.trackIds) {
|
|
||||||
let blockParamIndex;
|
|
||||||
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
|
|
||||||
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
|
||||||
}
|
|
||||||
if (blockParamIndex) {
|
|
||||||
let blockParamChild = val.parts.slice(1).join('.');
|
|
||||||
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
|
|
||||||
} else {
|
|
||||||
value = val.original || value;
|
|
||||||
if (value.replace) {
|
|
||||||
value = value
|
|
||||||
.replace(/^this(?:\.|$)/, '')
|
|
||||||
.replace(/^\.\//, '')
|
|
||||||
.replace(/^\.$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.opcode('pushId', val.type, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.accept(val);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
|
setupFullMustacheParams: function (sexpr, program, inverse, omitEmpty) {
|
||||||
let params = sexpr.params;
|
let params = sexpr.params;
|
||||||
this.pushParams(params);
|
this.pushParams(params);
|
||||||
|
|
||||||
@@ -468,7 +424,7 @@ Compiler.prototype = {
|
|||||||
return params;
|
return params;
|
||||||
},
|
},
|
||||||
|
|
||||||
blockParamIndex: function(name) {
|
blockParamIndex: function (name) {
|
||||||
for (
|
for (
|
||||||
let depth = 0, len = this.options.blockParams.length;
|
let depth = 0, len = this.options.blockParams.length;
|
||||||
depth < len;
|
depth < len;
|
||||||
@@ -480,57 +436,25 @@ Compiler.prototype = {
|
|||||||
return [depth, param];
|
return [depth, param];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function precompile(input, options, env) {
|
export function precompile(input, options = {}, env) {
|
||||||
if (
|
validateInput(input, options);
|
||||||
input == null ||
|
|
||||||
(typeof input !== 'string' && input.type !== 'Program')
|
|
||||||
) {
|
|
||||||
throw new Exception(
|
|
||||||
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
|
|
||||||
input
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
options = options || {};
|
let environment = compileEnvironment(input, options, env);
|
||||||
if (!('data' in options)) {
|
|
||||||
options.data = true;
|
|
||||||
}
|
|
||||||
if (options.compat) {
|
|
||||||
options.useDepths = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ast = env.parse(input, options),
|
|
||||||
environment = new env.Compiler().compile(ast, options);
|
|
||||||
return new env.JavaScriptCompiler().compile(environment, options);
|
return new env.JavaScriptCompiler().compile(environment, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile(input, options = {}, env) {
|
export function compile(input, options = {}, env) {
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
options = extend({}, options);
|
options = extend({}, options);
|
||||||
if (!('data' in options)) {
|
|
||||||
options.data = true;
|
validateInput(input, options);
|
||||||
}
|
|
||||||
if (options.compat) {
|
|
||||||
options.useDepths = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let compiled;
|
let compiled;
|
||||||
|
|
||||||
function compileInput() {
|
function compileInput() {
|
||||||
let ast = env.parse(input, options),
|
let environment = compileEnvironment(input, options, env),
|
||||||
environment = new env.Compiler().compile(ast, options),
|
|
||||||
templateSpec = new env.JavaScriptCompiler().compile(
|
templateSpec = new env.JavaScriptCompiler().compile(
|
||||||
environment,
|
environment,
|
||||||
options,
|
options,
|
||||||
@@ -541,25 +465,41 @@ export function compile(input, options = {}, env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Template is only compiled on first use and cached after that point.
|
// Template is only compiled on first use and cached after that point.
|
||||||
function ret(context, execOptions) {
|
return function (context, execOptions) {
|
||||||
if (!compiled) {
|
if (!compiled) {
|
||||||
compiled = compileInput();
|
compiled = compileInput();
|
||||||
}
|
}
|
||||||
return compiled.call(this, context, execOptions);
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ret._setup = function(setupOptions) {
|
|
||||||
if (!compiled) {
|
if (options.trackIds || options.stringParams) {
|
||||||
compiled = compileInput();
|
throw new Exception(
|
||||||
}
|
'TrackIds and stringParams are no longer supported. See Github #1145'
|
||||||
return compiled._setup(setupOptions);
|
);
|
||||||
};
|
}
|
||||||
ret._child = function(i, data, blockParams, depths) {
|
|
||||||
if (!compiled) {
|
if (!('data' in options)) {
|
||||||
compiled = compileInput();
|
options.data = true;
|
||||||
}
|
}
|
||||||
return compiled._child(i, data, blockParams, depths);
|
if (options.compat) {
|
||||||
};
|
options.useDepths = true;
|
||||||
return ret;
|
}
|
||||||
|
}
|
||||||
|
function compileEnvironment(input, options, env) {
|
||||||
|
let ast = env.parse(input, options);
|
||||||
|
return new env.Compiler().compile(ast, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function argEquals(a, b) {
|
function argEquals(a, b) {
|
||||||
@@ -588,7 +528,7 @@ function transformLiteralToPath(sexpr) {
|
|||||||
depth: 0,
|
depth: 0,
|
||||||
parts: [literal.original + ''],
|
parts: [literal.original + ''],
|
||||||
original: literal.original + '',
|
original: literal.original + '',
|
||||||
loc: literal.loc
|
loc: literal.loc,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
import Exception from '../exception';
|
|
||||||
|
|
||||||
function validateClose(open, close) {
|
|
||||||
close = close.path ? close.path.original : close;
|
|
||||||
|
|
||||||
if (open.path.original !== close) {
|
|
||||||
let errorNode = { loc: open.path.loc };
|
|
||||||
|
|
||||||
throw new Exception(
|
|
||||||
open.path.original + " doesn't match " + close,
|
|
||||||
errorNode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SourceLocation(source, locInfo) {
|
|
||||||
this.source = source;
|
|
||||||
this.start = {
|
|
||||||
line: locInfo.first_line,
|
|
||||||
column: locInfo.first_column
|
|
||||||
};
|
|
||||||
this.end = {
|
|
||||||
line: locInfo.last_line,
|
|
||||||
column: locInfo.last_column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function id(token) {
|
|
||||||
if (/^\[.*\]$/.test(token)) {
|
|
||||||
return token.substring(1, token.length - 1);
|
|
||||||
} else {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stripFlags(open, close) {
|
|
||||||
return {
|
|
||||||
open: open.charAt(2) === '~',
|
|
||||||
close: close.charAt(close.length - 3) === '~'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stripComment(comment) {
|
|
||||||
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function preparePath(data, parts, loc) {
|
|
||||||
loc = this.locInfo(loc);
|
|
||||||
|
|
||||||
let original = data ? '@' : '',
|
|
||||||
dig = [],
|
|
||||||
depth = 0;
|
|
||||||
|
|
||||||
for (let i = 0, l = parts.length; i < l; i++) {
|
|
||||||
let part = parts[i].part,
|
|
||||||
// If we have [] syntax then we do not treat path references as operators,
|
|
||||||
// i.e. foo.[this] resolves to approximately context.foo['this']
|
|
||||||
isLiteral = parts[i].original !== part;
|
|
||||||
original += (parts[i].separator || '') + part;
|
|
||||||
|
|
||||||
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
|
||||||
if (dig.length > 0) {
|
|
||||||
throw new Exception('Invalid path: ' + original, { loc });
|
|
||||||
} else if (part === '..') {
|
|
||||||
depth++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dig.push(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'PathExpression',
|
|
||||||
data,
|
|
||||||
depth,
|
|
||||||
parts: dig,
|
|
||||||
original,
|
|
||||||
loc
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
|
||||||
// Must use charAt to support IE pre-10
|
|
||||||
let escapeFlag = open.charAt(3) || open.charAt(2),
|
|
||||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
|
||||||
|
|
||||||
let decorator = /\*/.test(open);
|
|
||||||
return {
|
|
||||||
type: decorator ? 'Decorator' : 'MustacheStatement',
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
hash,
|
|
||||||
escaped,
|
|
||||||
strip,
|
|
||||||
loc: this.locInfo(locInfo)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
|
|
||||||
validateClose(openRawBlock, close);
|
|
||||||
|
|
||||||
locInfo = this.locInfo(locInfo);
|
|
||||||
let program = {
|
|
||||||
type: 'Program',
|
|
||||||
body: contents,
|
|
||||||
strip: {},
|
|
||||||
loc: locInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'BlockStatement',
|
|
||||||
path: openRawBlock.path,
|
|
||||||
params: openRawBlock.params,
|
|
||||||
hash: openRawBlock.hash,
|
|
||||||
program,
|
|
||||||
openStrip: {},
|
|
||||||
inverseStrip: {},
|
|
||||||
closeStrip: {},
|
|
||||||
loc: locInfo
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prepareBlock(
|
|
||||||
openBlock,
|
|
||||||
program,
|
|
||||||
inverseAndProgram,
|
|
||||||
close,
|
|
||||||
inverted,
|
|
||||||
locInfo
|
|
||||||
) {
|
|
||||||
if (close && close.path) {
|
|
||||||
validateClose(openBlock, close);
|
|
||||||
}
|
|
||||||
|
|
||||||
let decorator = /\*/.test(openBlock.open);
|
|
||||||
|
|
||||||
program.blockParams = openBlock.blockParams;
|
|
||||||
|
|
||||||
let inverse, inverseStrip;
|
|
||||||
|
|
||||||
if (inverseAndProgram) {
|
|
||||||
if (decorator) {
|
|
||||||
throw new Exception(
|
|
||||||
'Unexpected inverse block on decorator',
|
|
||||||
inverseAndProgram
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inverseAndProgram.chain) {
|
|
||||||
inverseAndProgram.program.body[0].closeStrip = close.strip;
|
|
||||||
}
|
|
||||||
|
|
||||||
inverseStrip = inverseAndProgram.strip;
|
|
||||||
inverse = inverseAndProgram.program;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inverted) {
|
|
||||||
inverted = inverse;
|
|
||||||
inverse = program;
|
|
||||||
program = inverted;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: decorator ? 'DecoratorBlock' : 'BlockStatement',
|
|
||||||
path: openBlock.path,
|
|
||||||
params: openBlock.params,
|
|
||||||
hash: openBlock.hash,
|
|
||||||
program,
|
|
||||||
inverse,
|
|
||||||
openStrip: openBlock.strip,
|
|
||||||
inverseStrip,
|
|
||||||
closeStrip: close && close.strip,
|
|
||||||
loc: this.locInfo(locInfo)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prepareProgram(statements, loc) {
|
|
||||||
if (!loc && statements.length) {
|
|
||||||
const firstLoc = statements[0].loc,
|
|
||||||
lastLoc = statements[statements.length - 1].loc;
|
|
||||||
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (firstLoc && lastLoc) {
|
|
||||||
loc = {
|
|
||||||
source: firstLoc.source,
|
|
||||||
start: {
|
|
||||||
line: firstLoc.start.line,
|
|
||||||
column: firstLoc.start.column
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
line: lastLoc.end.line,
|
|
||||||
column: lastLoc.end.column
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'Program',
|
|
||||||
body: statements,
|
|
||||||
strip: {},
|
|
||||||
loc: loc
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function preparePartialBlock(open, program, close, locInfo) {
|
|
||||||
validateClose(open, close);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'PartialBlockStatement',
|
|
||||||
name: open.path,
|
|
||||||
params: open.params,
|
|
||||||
hash: open.hash,
|
|
||||||
program,
|
|
||||||
openStrip: open.strip,
|
|
||||||
closeStrip: close && close.strip,
|
|
||||||
loc: this.locInfo(locInfo)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Exception } from '@handlebars/parser';
|
||||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
||||||
import Exception from '../exception';
|
|
||||||
import { isArray } from '../utils';
|
import { isArray } from '../utils';
|
||||||
import CodeGen from './code-gen';
|
import CodeGen from './code-gen';
|
||||||
|
|
||||||
@@ -12,20 +12,25 @@ function JavaScriptCompiler() {}
|
|||||||
JavaScriptCompiler.prototype = {
|
JavaScriptCompiler.prototype = {
|
||||||
// PUBLIC API: You can override these methods in a subclass to provide
|
// PUBLIC API: You can override these methods in a subclass to provide
|
||||||
// alternative compiled forms for name lookup and buffering semantics
|
// alternative compiled forms for name lookup and buffering semantics
|
||||||
nameLookup: function(parent, name /*, type */) {
|
nameLookup: function (parent, name /*, type */) {
|
||||||
return this.internalNameLookup(parent, name);
|
return this.internalNameLookup(parent, name);
|
||||||
},
|
},
|
||||||
depthedLookup: function(name) {
|
depthedLookup: function (name) {
|
||||||
return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
|
return [
|
||||||
|
this.aliasable('container.lookup'),
|
||||||
|
'(depths, ',
|
||||||
|
JSON.stringify(name),
|
||||||
|
')',
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
compilerInfo: function() {
|
compilerInfo: function () {
|
||||||
const revision = COMPILER_REVISION,
|
const revision = COMPILER_REVISION,
|
||||||
versions = REVISION_CHANGES[revision];
|
versions = REVISION_CHANGES[revision];
|
||||||
return [revision, versions];
|
return [revision, versions];
|
||||||
},
|
},
|
||||||
|
|
||||||
appendToBuffer: function(source, location, explicit) {
|
appendToBuffer: function (source, location, explicit) {
|
||||||
// Force a source as this simplifies the merge logic.
|
// Force a source as this simplifies the merge logic.
|
||||||
if (!isArray(source)) {
|
if (!isArray(source)) {
|
||||||
source = [source];
|
source = [source];
|
||||||
@@ -45,22 +50,20 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
initializeBuffer: function() {
|
initializeBuffer: function () {
|
||||||
return this.quotedString('');
|
return this.quotedString('');
|
||||||
},
|
},
|
||||||
// END PUBLIC API
|
// END PUBLIC API
|
||||||
internalNameLookup: function(parent, name) {
|
internalNameLookup: function (parent, name) {
|
||||||
this.lookupPropertyFunctionIsUsed = true;
|
this.lookupPropertyFunctionIsUsed = true;
|
||||||
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
|
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
|
||||||
},
|
},
|
||||||
|
|
||||||
lookupPropertyFunctionIsUsed: false,
|
lookupPropertyFunctionIsUsed: false,
|
||||||
|
|
||||||
compile: function(environment, options, context, asObject) {
|
compile: function (environment, options, context, asObject) {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.stringParams = this.options.stringParams;
|
|
||||||
this.trackIds = this.options.trackIds;
|
|
||||||
this.precompile = !asObject;
|
this.precompile = !asObject;
|
||||||
|
|
||||||
this.name = this.environment.name;
|
this.name = this.environment.name;
|
||||||
@@ -68,7 +71,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.context = context || {
|
this.context = context || {
|
||||||
decorators: [],
|
decorators: [],
|
||||||
programs: [],
|
programs: [],
|
||||||
environments: []
|
environments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.preamble();
|
this.preamble();
|
||||||
@@ -109,7 +112,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.source.currentLocation = firstLoc;
|
this.source.currentLocation = firstLoc;
|
||||||
this.pushSource('');
|
this.pushSource('');
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* v8 ignore next */
|
||||||
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
|
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
|
||||||
throw new Exception('Compile completed with content left on stack');
|
throw new Exception('Compile completed with content left on stack');
|
||||||
}
|
}
|
||||||
@@ -120,11 +123,12 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.decorators.prepend([
|
this.decorators.prepend([
|
||||||
'var decorators = container.decorators, ',
|
'var decorators = container.decorators, ',
|
||||||
this.lookupPropertyFunctionVarDeclaration(),
|
this.lookupPropertyFunctionVarDeclaration(),
|
||||||
';\n'
|
';\n',
|
||||||
]);
|
]);
|
||||||
this.decorators.push('return fn;');
|
this.decorators.push('return fn;');
|
||||||
|
|
||||||
if (asObject) {
|
if (asObject) {
|
||||||
|
// eslint-disable-next-line no-new-func
|
||||||
this.decorators = Function.apply(this, [
|
this.decorators = Function.apply(this, [
|
||||||
'fn',
|
'fn',
|
||||||
'props',
|
'props',
|
||||||
@@ -133,7 +137,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
'data',
|
'data',
|
||||||
'blockParams',
|
'blockParams',
|
||||||
'depths',
|
'depths',
|
||||||
this.decorators.merge()
|
this.decorators.merge(),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
this.decorators.prepend(
|
this.decorators.prepend(
|
||||||
@@ -150,25 +154,26 @@ JavaScriptCompiler.prototype = {
|
|||||||
if (!this.isChild) {
|
if (!this.isChild) {
|
||||||
let ret = {
|
let ret = {
|
||||||
compiler: this.compilerInfo(),
|
compiler: this.compilerInfo(),
|
||||||
main: fn
|
main: fn,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.decorators) {
|
if (this.decorators) {
|
||||||
ret.main_d = this.decorators; // eslint-disable-line camelcase
|
ret.main_d = this.decorators;
|
||||||
ret.useDecorators = true;
|
ret.useDecorators = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { programs, decorators } = this.context;
|
let { programs, decorators } = this.context;
|
||||||
for (i = 0, l = programs.length; i < l; i++) {
|
for (i = 0, l = programs.length; i < l; i++) {
|
||||||
if (programs[i]) {
|
ret[i] = programs[i];
|
||||||
ret[i] = programs[i];
|
if (decorators[i]) {
|
||||||
if (decorators[i]) {
|
ret[i + '_d'] = decorators[i];
|
||||||
ret[i + '_d'] = decorators[i];
|
ret.useDecorators = true;
|
||||||
ret.useDecorators = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Release AST/compiler references only needed during compilation for dedup
|
||||||
|
this.context.environments.length = 0;
|
||||||
|
|
||||||
if (this.environment.usePartial) {
|
if (this.environment.usePartial) {
|
||||||
ret.usePartial = true;
|
ret.usePartial = true;
|
||||||
}
|
}
|
||||||
@@ -207,7 +212,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
preamble: function() {
|
preamble: function () {
|
||||||
// track the last context pushed into place to allow skipping the
|
// track the last context pushed into place to allow skipping the
|
||||||
// getContext opcode when it would be a noop
|
// getContext opcode when it would be a noop
|
||||||
this.lastContext = 0;
|
this.lastContext = 0;
|
||||||
@@ -215,7 +220,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.decorators = new CodeGen(this.options.srcName);
|
this.decorators = new CodeGen(this.options.srcName);
|
||||||
},
|
},
|
||||||
|
|
||||||
createFunctionContext: function(asObject) {
|
createFunctionContext: function (asObject) {
|
||||||
let varDeclarations = '';
|
let varDeclarations = '';
|
||||||
|
|
||||||
let locals = this.stackVars.concat(this.registers.list);
|
let locals = this.stackVars.concat(this.registers.list);
|
||||||
@@ -230,7 +235,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// aliases will not be used, but this case is already being run on the client and
|
// 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.
|
// we aren't concern about minimizing the template size.
|
||||||
let aliasCount = 0;
|
let aliasCount = 0;
|
||||||
Object.keys(this.aliases).forEach(alias => {
|
Object.keys(this.aliases).forEach((alias) => {
|
||||||
let node = this.aliases[alias];
|
let node = this.aliases[alias];
|
||||||
if (node.children && node.referenceCount > 1) {
|
if (node.children && node.referenceCount > 1) {
|
||||||
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
|
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
|
||||||
@@ -257,25 +262,25 @@ JavaScriptCompiler.prototype = {
|
|||||||
if (asObject) {
|
if (asObject) {
|
||||||
params.push(source);
|
params.push(source);
|
||||||
|
|
||||||
return Function.apply(this, params);
|
return Function.apply(this, params); // eslint-disable-line no-new-func
|
||||||
} else {
|
} else {
|
||||||
return this.source.wrap([
|
return this.source.wrap([
|
||||||
'function(',
|
'function(',
|
||||||
params.join(','),
|
params.join(','),
|
||||||
') {\n ',
|
') {\n ',
|
||||||
source,
|
source,
|
||||||
'}'
|
'}',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mergeSource: function(varDeclarations) {
|
mergeSource: function (varDeclarations) {
|
||||||
let isSimple = this.environment.isSimple,
|
let isSimple = this.environment.isSimple,
|
||||||
appendOnly = !this.forceBuffer,
|
appendOnly = !this.forceBuffer,
|
||||||
appendFirst,
|
appendFirst,
|
||||||
sourceSeen,
|
sourceSeen,
|
||||||
bufferStart,
|
bufferStart,
|
||||||
bufferEnd;
|
bufferEnd;
|
||||||
this.source.each(line => {
|
this.source.each((line) => {
|
||||||
if (line.appendToBuffer) {
|
if (line.appendToBuffer) {
|
||||||
if (bufferStart) {
|
if (bufferStart) {
|
||||||
line.prepend(' + ');
|
line.prepend(' + ');
|
||||||
@@ -329,7 +334,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
return this.source.merge();
|
return this.source.merge();
|
||||||
},
|
},
|
||||||
|
|
||||||
lookupPropertyFunctionVarDeclaration: function() {
|
lookupPropertyFunctionVarDeclaration: function () {
|
||||||
return `
|
return `
|
||||||
lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
@@ -349,7 +354,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
|
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
|
||||||
// replace it on the stack with the result of properly
|
// replace it on the stack with the result of properly
|
||||||
// invoking blockHelperMissing.
|
// invoking blockHelperMissing.
|
||||||
blockValue: function(name) {
|
blockValue: function (name) {
|
||||||
let blockHelperMissing = this.aliasable(
|
let blockHelperMissing = this.aliasable(
|
||||||
'container.hooks.blockHelperMissing'
|
'container.hooks.blockHelperMissing'
|
||||||
),
|
),
|
||||||
@@ -368,7 +373,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// Compiler value, before: lastHelper=value of last found helper, if any
|
// Compiler value, before: lastHelper=value of last found helper, if any
|
||||||
// On stack, after, if no lastHelper: same as [blockValue]
|
// On stack, after, if no lastHelper: same as [blockValue]
|
||||||
// On stack, after, if lastHelper: value
|
// On stack, after, if lastHelper: value
|
||||||
ambiguousBlockValue: function() {
|
ambiguousBlockValue: function () {
|
||||||
// We're being a bit cheeky and reusing the options value from the prior exec
|
// We're being a bit cheeky and reusing the options value from the prior exec
|
||||||
let blockHelperMissing = this.aliasable(
|
let blockHelperMissing = this.aliasable(
|
||||||
'container.hooks.blockHelperMissing'
|
'container.hooks.blockHelperMissing'
|
||||||
@@ -388,7 +393,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
current,
|
current,
|
||||||
' = ',
|
' = ',
|
||||||
this.source.functionCall(blockHelperMissing, 'call', params),
|
this.source.functionCall(blockHelperMissing, 'call', params),
|
||||||
'}'
|
'}',
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -398,7 +403,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// On stack, after: ...
|
// On stack, after: ...
|
||||||
//
|
//
|
||||||
// Appends the string value of `content` to the current buffer
|
// Appends the string value of `content` to the current buffer
|
||||||
appendContent: function(content) {
|
appendContent: function (content) {
|
||||||
if (this.pendingContent) {
|
if (this.pendingContent) {
|
||||||
content = this.pendingContent + content;
|
content = this.pendingContent + content;
|
||||||
} else {
|
} else {
|
||||||
@@ -417,9 +422,9 @@ JavaScriptCompiler.prototype = {
|
|||||||
//
|
//
|
||||||
// If `value` is truthy, or 0, it is coerced into a string and appended
|
// If `value` is truthy, or 0, it is coerced into a string and appended
|
||||||
// Otherwise, the empty string is appended
|
// Otherwise, the empty string is appended
|
||||||
append: function() {
|
append: function () {
|
||||||
if (this.isInline()) {
|
if (this.isInline()) {
|
||||||
this.replaceStack(current => [' != null ? ', current, ' : ""']);
|
this.replaceStack((current) => [' != null ? ', current, ' : ""']);
|
||||||
|
|
||||||
this.pushSource(this.appendToBuffer(this.popStack()));
|
this.pushSource(this.appendToBuffer(this.popStack()));
|
||||||
} else {
|
} else {
|
||||||
@@ -429,13 +434,13 @@ JavaScriptCompiler.prototype = {
|
|||||||
local,
|
local,
|
||||||
' != null) { ',
|
' != null) { ',
|
||||||
this.appendToBuffer(local, undefined, true),
|
this.appendToBuffer(local, undefined, true),
|
||||||
' }'
|
' }',
|
||||||
]);
|
]);
|
||||||
if (this.environment.isSimple) {
|
if (this.environment.isSimple) {
|
||||||
this.pushSource([
|
this.pushSource([
|
||||||
'else { ',
|
'else { ',
|
||||||
this.appendToBuffer("''", undefined, true),
|
this.appendToBuffer("''", undefined, true),
|
||||||
' }'
|
' }',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -447,13 +452,13 @@ JavaScriptCompiler.prototype = {
|
|||||||
// On stack, after: ...
|
// On stack, after: ...
|
||||||
//
|
//
|
||||||
// Escape `value` and append it to the buffer
|
// Escape `value` and append it to the buffer
|
||||||
appendEscaped: function() {
|
appendEscaped: function () {
|
||||||
this.pushSource(
|
this.pushSource(
|
||||||
this.appendToBuffer([
|
this.appendToBuffer([
|
||||||
this.aliasable('container.escapeExpression'),
|
this.aliasable('container.escapeExpression'),
|
||||||
'(',
|
'(',
|
||||||
this.popStack(),
|
this.popStack(),
|
||||||
')'
|
')',
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -465,7 +470,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// Compiler value, after: lastContext=depth
|
// Compiler value, after: lastContext=depth
|
||||||
//
|
//
|
||||||
// Set the value of the `lastContext` compiler value to the depth
|
// Set the value of the `lastContext` compiler value to the depth
|
||||||
getContext: function(depth) {
|
getContext: function (depth) {
|
||||||
this.lastContext = depth;
|
this.lastContext = depth;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -475,7 +480,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// On stack, after: currentContext, ...
|
// On stack, after: currentContext, ...
|
||||||
//
|
//
|
||||||
// Pushes the value of the current context onto the stack.
|
// Pushes the value of the current context onto the stack.
|
||||||
pushContext: function() {
|
pushContext: function () {
|
||||||
this.pushStackLiteral(this.contextName(this.lastContext));
|
this.pushStackLiteral(this.contextName(this.lastContext));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -486,7 +491,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
//
|
//
|
||||||
// Looks up the value of `name` on the current context and pushes
|
// Looks up the value of `name` on the current context and pushes
|
||||||
// it onto the stack.
|
// it onto the stack.
|
||||||
lookupOnContext: function(parts, falsy, strict, scoped) {
|
lookupOnContext: function (parts, falsy, strict, scoped) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
if (!scoped && this.options.compat && !this.lastContext) {
|
if (!scoped && this.options.compat && !this.lastContext) {
|
||||||
@@ -507,7 +512,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
//
|
//
|
||||||
// Looks up the value of `parts` on the given block param and pushes
|
// Looks up the value of `parts` on the given block param and pushes
|
||||||
// it onto the stack.
|
// it onto the stack.
|
||||||
lookupBlockParam: function(blockParamId, parts) {
|
lookupBlockParam: function (blockParamId, parts) {
|
||||||
this.useBlockParams = true;
|
this.useBlockParams = true;
|
||||||
|
|
||||||
this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
|
this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
|
||||||
@@ -520,7 +525,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// On stack, after: data, ...
|
// On stack, after: data, ...
|
||||||
//
|
//
|
||||||
// Push the data lookup operator
|
// Push the data lookup operator
|
||||||
lookupData: function(depth, parts, strict) {
|
lookupData: function (depth, parts, strict) {
|
||||||
if (!depth) {
|
if (!depth) {
|
||||||
this.pushStackLiteral('data');
|
this.pushStackLiteral('data');
|
||||||
} else {
|
} else {
|
||||||
@@ -530,16 +535,24 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.resolvePath('data', parts, 0, true, strict);
|
this.resolvePath('data', parts, 0, true, strict);
|
||||||
},
|
},
|
||||||
|
|
||||||
resolvePath: function(type, parts, i, falsy, strict) {
|
resolvePath: function (type, parts, startPartIndex, falsy, strict) {
|
||||||
if (this.options.strict || this.options.assumeObjects) {
|
if (this.options.strict || this.options.assumeObjects) {
|
||||||
this.push(strictLookup(this.options.strict && strict, this, parts, type));
|
this.push(
|
||||||
|
strictLookup(
|
||||||
|
this.options.strict && strict,
|
||||||
|
this,
|
||||||
|
parts,
|
||||||
|
startPartIndex,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = parts.length;
|
let len = parts.length;
|
||||||
for (; i < len; i++) {
|
for (let i = startPartIndex; i < len; i++) {
|
||||||
/* eslint-disable no-loop-func */
|
/* eslint-disable no-loop-func */
|
||||||
this.replaceStack(current => {
|
this.replaceStack((current) => {
|
||||||
let lookup = this.nameLookup(current, parts[i], type);
|
let lookup = this.nameLookup(current, parts[i], type);
|
||||||
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
// 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.
|
// needs to have the special handling for these values.
|
||||||
@@ -561,68 +574,30 @@ JavaScriptCompiler.prototype = {
|
|||||||
//
|
//
|
||||||
// If the `value` is a lambda, replace it on the stack by
|
// If the `value` is a lambda, replace it on the stack by
|
||||||
// the return value of the lambda
|
// the return value of the lambda
|
||||||
resolvePossibleLambda: function() {
|
resolvePossibleLambda: function () {
|
||||||
this.push([
|
this.push([
|
||||||
this.aliasable('container.lambda'),
|
this.aliasable('container.lambda'),
|
||||||
'(',
|
'(',
|
||||||
this.popStack(),
|
this.popStack(),
|
||||||
', ',
|
', ',
|
||||||
this.contextName(0),
|
this.contextName(0),
|
||||||
')'
|
')',
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
// [pushStringParam]
|
emptyHash: function (omitEmpty) {
|
||||||
//
|
|
||||||
// On stack, before: ...
|
|
||||||
// On stack, after: string, currentContext, ...
|
|
||||||
//
|
|
||||||
// This opcode is designed for use in string mode, which
|
|
||||||
// provides the string value of a parameter along with its
|
|
||||||
// depth rather than resolving it immediately.
|
|
||||||
pushStringParam: function(string, type) {
|
|
||||||
this.pushContext();
|
|
||||||
this.pushString(type);
|
|
||||||
|
|
||||||
// If it's a subexpression, the string result
|
|
||||||
// will be pushed after this opcode.
|
|
||||||
if (type !== 'SubExpression') {
|
|
||||||
if (typeof string === 'string') {
|
|
||||||
this.pushString(string);
|
|
||||||
} else {
|
|
||||||
this.pushStackLiteral(string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emptyHash: function(omitEmpty) {
|
|
||||||
if (this.trackIds) {
|
|
||||||
this.push('{}'); // hashIds
|
|
||||||
}
|
|
||||||
if (this.stringParams) {
|
|
||||||
this.push('{}'); // hashContexts
|
|
||||||
this.push('{}'); // hashTypes
|
|
||||||
}
|
|
||||||
this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
|
this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
|
||||||
},
|
},
|
||||||
pushHash: function() {
|
pushHash: function () {
|
||||||
if (this.hash) {
|
if (this.hash) {
|
||||||
this.hashes.push(this.hash);
|
this.hashes.push(this.hash);
|
||||||
}
|
}
|
||||||
this.hash = { values: {}, types: [], contexts: [], ids: [] };
|
this.hash = { values: {} };
|
||||||
},
|
},
|
||||||
popHash: function() {
|
popHash: function () {
|
||||||
let hash = this.hash;
|
let hash = this.hash;
|
||||||
this.hash = this.hashes.pop();
|
this.hash = this.hashes.pop();
|
||||||
|
|
||||||
if (this.trackIds) {
|
|
||||||
this.push(this.objectLiteral(hash.ids));
|
|
||||||
}
|
|
||||||
if (this.stringParams) {
|
|
||||||
this.push(this.objectLiteral(hash.contexts));
|
|
||||||
this.push(this.objectLiteral(hash.types));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.push(this.objectLiteral(hash.values));
|
this.push(this.objectLiteral(hash.values));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -632,7 +607,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// On stack, after: quotedString(string), ...
|
// On stack, after: quotedString(string), ...
|
||||||
//
|
//
|
||||||
// Push a quoted version of `string` onto the stack
|
// Push a quoted version of `string` onto the stack
|
||||||
pushString: function(string) {
|
pushString: function (string) {
|
||||||
this.pushStackLiteral(this.quotedString(string));
|
this.pushStackLiteral(this.quotedString(string));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -644,7 +619,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// Pushes a value onto the stack. This operation prevents
|
// Pushes a value onto the stack. This operation prevents
|
||||||
// the compiler from creating a temporary variable to hold
|
// the compiler from creating a temporary variable to hold
|
||||||
// it.
|
// it.
|
||||||
pushLiteral: function(value) {
|
pushLiteral: function (value) {
|
||||||
this.pushStackLiteral(value);
|
this.pushStackLiteral(value);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -656,7 +631,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// Push a program expression onto the stack. This takes
|
// Push a program expression onto the stack. This takes
|
||||||
// a compile-time guid and converts it into a runtime-accessible
|
// a compile-time guid and converts it into a runtime-accessible
|
||||||
// expression.
|
// expression.
|
||||||
pushProgram: function(guid) {
|
pushProgram: function (guid) {
|
||||||
if (guid != null) {
|
if (guid != null) {
|
||||||
this.pushStackLiteral(this.programExpression(guid));
|
this.pushStackLiteral(this.programExpression(guid));
|
||||||
} else {
|
} else {
|
||||||
@@ -681,9 +656,9 @@ JavaScriptCompiler.prototype = {
|
|||||||
'fn',
|
'fn',
|
||||||
'props',
|
'props',
|
||||||
'container',
|
'container',
|
||||||
options
|
options,
|
||||||
]),
|
]),
|
||||||
' || fn;'
|
' || fn;',
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -696,7 +671,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// and pushes the helper's return value onto the stack.
|
// and pushes the helper's return value onto the stack.
|
||||||
//
|
//
|
||||||
// If the helper is not found, `helperMissing` is called.
|
// If the helper is not found, `helperMissing` is called.
|
||||||
invokeHelper: function(paramSize, name, isSimple) {
|
invokeHelper: function (paramSize, name, isSimple) {
|
||||||
let nonHelper = this.popStack(),
|
let nonHelper = this.popStack(),
|
||||||
helper = this.setupHelper(paramSize, name);
|
helper = this.setupHelper(paramSize, name);
|
||||||
|
|
||||||
@@ -717,7 +692,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
let functionLookupCode = [
|
let functionLookupCode = [
|
||||||
'(',
|
'(',
|
||||||
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
|
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
|
||||||
')'
|
')',
|
||||||
];
|
];
|
||||||
let functionCall = this.source.functionCall(
|
let functionCall = this.source.functionCall(
|
||||||
functionLookupCode,
|
functionLookupCode,
|
||||||
@@ -727,7 +702,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.push(functionCall);
|
this.push(functionCall);
|
||||||
},
|
},
|
||||||
|
|
||||||
itemsSeparatedBy: function(items, separator) {
|
itemsSeparatedBy: function (items, separator) {
|
||||||
let result = [];
|
let result = [];
|
||||||
result.push(items[0]);
|
result.push(items[0]);
|
||||||
for (let i = 1; i < items.length; i++) {
|
for (let i = 1; i < items.length; i++) {
|
||||||
@@ -742,7 +717,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
//
|
//
|
||||||
// This operation is used when the helper is known to exist,
|
// This operation is used when the helper is known to exist,
|
||||||
// so a `helperMissing` fallback is not required.
|
// so a `helperMissing` fallback is not required.
|
||||||
invokeKnownHelper: function(paramSize, name) {
|
invokeKnownHelper: function (paramSize, name) {
|
||||||
let helper = this.setupHelper(paramSize, name);
|
let helper = this.setupHelper(paramSize, name);
|
||||||
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
|
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
|
||||||
},
|
},
|
||||||
@@ -759,7 +734,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// This operation emits more code than the other options,
|
// This operation emits more code than the other options,
|
||||||
// and can be avoided by passing the `knownHelpers` and
|
// and can be avoided by passing the `knownHelpers` and
|
||||||
// `knownHelpersOnly` flags at compile-time.
|
// `knownHelpersOnly` flags at compile-time.
|
||||||
invokeAmbiguous: function(name, helperCall) {
|
invokeAmbiguous: function (name, helperCall) {
|
||||||
this.useRegister('helper');
|
this.useRegister('helper');
|
||||||
|
|
||||||
let nonHelper = this.popStack();
|
let nonHelper = this.popStack();
|
||||||
@@ -791,7 +766,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.aliasable('"function"'),
|
this.aliasable('"function"'),
|
||||||
' ? ',
|
' ? ',
|
||||||
this.source.functionCall('helper', 'call', helper.callParams),
|
this.source.functionCall('helper', 'call', helper.callParams),
|
||||||
' : helper))'
|
' : helper))',
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -802,7 +777,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
//
|
//
|
||||||
// This operation pops off a context, invokes a partial with that context,
|
// This operation pops off a context, invokes a partial with that context,
|
||||||
// and pushes the result of the invocation back.
|
// and pushes the result of the invocation back.
|
||||||
invokePartial: function(isDynamic, name, indent) {
|
invokePartial: function (isDynamic, name, indent) {
|
||||||
let params = [],
|
let params = [],
|
||||||
options = this.setupParams(name, 1, params);
|
options = this.setupParams(name, 1, params);
|
||||||
|
|
||||||
@@ -839,70 +814,28 @@ JavaScriptCompiler.prototype = {
|
|||||||
// On stack, after: ..., hash, ...
|
// On stack, after: ..., hash, ...
|
||||||
//
|
//
|
||||||
// Pops a value off the stack and assigns it to the current hash
|
// Pops a value off the stack and assigns it to the current hash
|
||||||
assignToHash: function(key) {
|
assignToHash: function (key) {
|
||||||
let value = this.popStack(),
|
this.hash.values[key] = this.popStack();
|
||||||
context,
|
|
||||||
type,
|
|
||||||
id;
|
|
||||||
|
|
||||||
if (this.trackIds) {
|
|
||||||
id = this.popStack();
|
|
||||||
}
|
|
||||||
if (this.stringParams) {
|
|
||||||
type = this.popStack();
|
|
||||||
context = this.popStack();
|
|
||||||
}
|
|
||||||
|
|
||||||
let hash = this.hash;
|
|
||||||
if (context) {
|
|
||||||
hash.contexts[key] = context;
|
|
||||||
}
|
|
||||||
if (type) {
|
|
||||||
hash.types[key] = type;
|
|
||||||
}
|
|
||||||
if (id) {
|
|
||||||
hash.ids[key] = id;
|
|
||||||
}
|
|
||||||
hash.values[key] = value;
|
|
||||||
},
|
|
||||||
|
|
||||||
pushId: function(type, name, child) {
|
|
||||||
if (type === 'BlockParam') {
|
|
||||||
this.pushStackLiteral(
|
|
||||||
'blockParams[' +
|
|
||||||
name[0] +
|
|
||||||
'].path[' +
|
|
||||||
name[1] +
|
|
||||||
']' +
|
|
||||||
(child ? ' + ' + JSON.stringify('.' + child) : '')
|
|
||||||
);
|
|
||||||
} else if (type === 'PathExpression') {
|
|
||||||
this.pushString(name);
|
|
||||||
} else if (type === 'SubExpression') {
|
|
||||||
this.pushStackLiteral('true');
|
|
||||||
} else {
|
|
||||||
this.pushStackLiteral('null');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
|
|
||||||
compiler: JavaScriptCompiler,
|
compiler: JavaScriptCompiler,
|
||||||
|
|
||||||
compileChildren: function(environment, options) {
|
compileChildren: function (environment, options) {
|
||||||
let children = environment.children,
|
let children = environment.children,
|
||||||
child,
|
child,
|
||||||
compiler;
|
compiler;
|
||||||
|
|
||||||
for (let i = 0, l = children.length; i < l; i++) {
|
for (let i = 0, l = children.length; i < l; i++) {
|
||||||
child = children[i];
|
child = children[i];
|
||||||
compiler = new this.compiler(); // eslint-disable-line new-cap
|
compiler = new this.compiler();
|
||||||
|
|
||||||
let existing = this.matchExistingProgram(child);
|
let existing = this.matchExistingProgram(child);
|
||||||
|
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
// Placeholder to prevent name conflicts for nested children
|
||||||
let index = this.context.programs.length;
|
let index = this.context.programs.push('') - 1;
|
||||||
child.index = index;
|
child.index = index;
|
||||||
child.name = 'program' + index;
|
child.name = 'program' + index;
|
||||||
this.context.programs[index] = compiler.compile(
|
this.context.programs[index] = compiler.compile(
|
||||||
@@ -927,7 +860,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
matchExistingProgram: function(child) {
|
matchExistingProgram: function (child) {
|
||||||
for (let i = 0, len = this.context.environments.length; i < len; i++) {
|
for (let i = 0, len = this.context.environments.length; i < len; i++) {
|
||||||
let environment = this.context.environments[i];
|
let environment = this.context.environments[i];
|
||||||
if (environment && environment.equals(child)) {
|
if (environment && environment.equals(child)) {
|
||||||
@@ -936,7 +869,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
programExpression: function(guid) {
|
programExpression: function (guid) {
|
||||||
let child = this.environment.children[guid],
|
let child = this.environment.children[guid],
|
||||||
programParams = [child.index, 'data', child.blockParams];
|
programParams = [child.index, 'data', child.blockParams];
|
||||||
|
|
||||||
@@ -950,14 +883,14 @@ JavaScriptCompiler.prototype = {
|
|||||||
return 'container.program(' + programParams.join(', ') + ')';
|
return 'container.program(' + programParams.join(', ') + ')';
|
||||||
},
|
},
|
||||||
|
|
||||||
useRegister: function(name) {
|
useRegister: function (name) {
|
||||||
if (!this.registers[name]) {
|
if (!this.registers[name]) {
|
||||||
this.registers[name] = true;
|
this.registers[name] = true;
|
||||||
this.registers.list.push(name);
|
this.registers.list.push(name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
push: function(expr) {
|
push: function (expr) {
|
||||||
if (!(expr instanceof Literal)) {
|
if (!(expr instanceof Literal)) {
|
||||||
expr = this.source.wrap(expr);
|
expr = this.source.wrap(expr);
|
||||||
}
|
}
|
||||||
@@ -966,11 +899,11 @@ JavaScriptCompiler.prototype = {
|
|||||||
return expr;
|
return expr;
|
||||||
},
|
},
|
||||||
|
|
||||||
pushStackLiteral: function(item) {
|
pushStackLiteral: function (item) {
|
||||||
this.push(new Literal(item));
|
this.push(new Literal(item));
|
||||||
},
|
},
|
||||||
|
|
||||||
pushSource: function(source) {
|
pushSource: function (source) {
|
||||||
if (this.pendingContent) {
|
if (this.pendingContent) {
|
||||||
this.source.push(
|
this.source.push(
|
||||||
this.appendToBuffer(
|
this.appendToBuffer(
|
||||||
@@ -986,13 +919,13 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
replaceStack: function(callback) {
|
replaceStack: function (callback) {
|
||||||
let prefix = ['('],
|
let prefix = ['('],
|
||||||
stack,
|
stack,
|
||||||
createdStack,
|
createdStack,
|
||||||
usedLiteral;
|
usedLiteral;
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* v8 ignore next */
|
||||||
if (!this.isInline()) {
|
if (!this.isInline()) {
|
||||||
throw new Exception('replaceStack on non-inline');
|
throw new Exception('replaceStack on non-inline');
|
||||||
}
|
}
|
||||||
@@ -1025,22 +958,22 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.push(prefix.concat(item, ')'));
|
this.push(prefix.concat(item, ')'));
|
||||||
},
|
},
|
||||||
|
|
||||||
incrStack: function() {
|
incrStack: function () {
|
||||||
this.stackSlot++;
|
this.stackSlot++;
|
||||||
if (this.stackSlot > this.stackVars.length) {
|
if (this.stackSlot > this.stackVars.length) {
|
||||||
this.stackVars.push('stack' + this.stackSlot);
|
this.stackVars.push('stack' + this.stackSlot);
|
||||||
}
|
}
|
||||||
return this.topStackName();
|
return this.topStackName();
|
||||||
},
|
},
|
||||||
topStackName: function() {
|
topStackName: function () {
|
||||||
return 'stack' + this.stackSlot;
|
return 'stack' + this.stackSlot;
|
||||||
},
|
},
|
||||||
flushInline: function() {
|
flushInline: function () {
|
||||||
let inlineStack = this.inlineStack;
|
let inlineStack = this.inlineStack;
|
||||||
this.inlineStack = [];
|
this.inlineStack = [];
|
||||||
for (let i = 0, len = inlineStack.length; i < len; i++) {
|
for (let i = 0, len = inlineStack.length; i < len; i++) {
|
||||||
let entry = inlineStack[i];
|
let entry = inlineStack[i];
|
||||||
/* istanbul ignore if */
|
/* v8 ignore next */
|
||||||
if (entry instanceof Literal) {
|
if (entry instanceof Literal) {
|
||||||
this.compileStack.push(entry);
|
this.compileStack.push(entry);
|
||||||
} else {
|
} else {
|
||||||
@@ -1050,11 +983,11 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isInline: function() {
|
isInline: function () {
|
||||||
return this.inlineStack.length;
|
return this.inlineStack.length;
|
||||||
},
|
},
|
||||||
|
|
||||||
popStack: function(wrapped) {
|
popStack: function (wrapped) {
|
||||||
let inline = this.isInline(),
|
let inline = this.isInline(),
|
||||||
item = (inline ? this.inlineStack : this.compileStack).pop();
|
item = (inline ? this.inlineStack : this.compileStack).pop();
|
||||||
|
|
||||||
@@ -1062,7 +995,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
return item.value;
|
return item.value;
|
||||||
} else {
|
} else {
|
||||||
if (!inline) {
|
if (!inline) {
|
||||||
/* istanbul ignore next */
|
/* v8 ignore next */
|
||||||
if (!this.stackSlot) {
|
if (!this.stackSlot) {
|
||||||
throw new Exception('Invalid stack pop');
|
throw new Exception('Invalid stack pop');
|
||||||
}
|
}
|
||||||
@@ -1072,11 +1005,11 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
topStack: function() {
|
topStack: function () {
|
||||||
let stack = this.isInline() ? this.inlineStack : this.compileStack,
|
let stack = this.isInline() ? this.inlineStack : this.compileStack,
|
||||||
item = stack[stack.length - 1];
|
item = stack[stack.length - 1];
|
||||||
|
|
||||||
/* istanbul ignore if */
|
/* v8 ignore next */
|
||||||
if (item instanceof Literal) {
|
if (item instanceof Literal) {
|
||||||
return item.value;
|
return item.value;
|
||||||
} else {
|
} else {
|
||||||
@@ -1084,7 +1017,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
contextName: function(context) {
|
contextName: function (context) {
|
||||||
if (this.useDepths && context) {
|
if (this.useDepths && context) {
|
||||||
return 'depths[' + context + ']';
|
return 'depths[' + context + ']';
|
||||||
} else {
|
} else {
|
||||||
@@ -1092,15 +1025,15 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
quotedString: function(str) {
|
quotedString: function (str) {
|
||||||
return this.source.quotedString(str);
|
return this.source.quotedString(str);
|
||||||
},
|
},
|
||||||
|
|
||||||
objectLiteral: function(obj) {
|
objectLiteral: function (obj) {
|
||||||
return this.source.objectLiteral(obj);
|
return this.source.objectLiteral(obj);
|
||||||
},
|
},
|
||||||
|
|
||||||
aliasable: function(name) {
|
aliasable: function (name) {
|
||||||
let ret = this.aliases[name];
|
let ret = this.aliases[name];
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ret.referenceCount++;
|
ret.referenceCount++;
|
||||||
@@ -1114,7 +1047,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
setupHelper: function(paramSize, name, blockHelper) {
|
setupHelper: function (paramSize, name, blockHelper) {
|
||||||
let params = [],
|
let params = [],
|
||||||
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
||||||
let foundHelper = this.nameLookup('helpers', name, 'helper'),
|
let foundHelper = this.nameLookup('helpers', name, 'helper'),
|
||||||
@@ -1128,15 +1061,12 @@ JavaScriptCompiler.prototype = {
|
|||||||
params: params,
|
params: params,
|
||||||
paramsInit: paramsInit,
|
paramsInit: paramsInit,
|
||||||
name: foundHelper,
|
name: foundHelper,
|
||||||
callParams: [callContext].concat(params)
|
callParams: [callContext].concat(params),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
setupParams: function(helper, paramSize, params) {
|
setupParams: function (helper, paramSize, params) {
|
||||||
let options = {},
|
let options = {},
|
||||||
contexts = [],
|
|
||||||
types = [],
|
|
||||||
ids = [],
|
|
||||||
objectArgs = !params,
|
objectArgs = !params,
|
||||||
param;
|
param;
|
||||||
|
|
||||||
@@ -1147,14 +1077,6 @@ JavaScriptCompiler.prototype = {
|
|||||||
options.name = this.quotedString(helper);
|
options.name = this.quotedString(helper);
|
||||||
options.hash = this.popStack();
|
options.hash = this.popStack();
|
||||||
|
|
||||||
if (this.trackIds) {
|
|
||||||
options.hashIds = this.popStack();
|
|
||||||
}
|
|
||||||
if (this.stringParams) {
|
|
||||||
options.hashTypes = this.popStack();
|
|
||||||
options.hashContexts = this.popStack();
|
|
||||||
}
|
|
||||||
|
|
||||||
let inverse = this.popStack(),
|
let inverse = this.popStack(),
|
||||||
program = this.popStack();
|
program = this.popStack();
|
||||||
|
|
||||||
@@ -1171,28 +1093,12 @@ JavaScriptCompiler.prototype = {
|
|||||||
while (i--) {
|
while (i--) {
|
||||||
param = this.popStack();
|
param = this.popStack();
|
||||||
params[i] = param;
|
params[i] = param;
|
||||||
|
|
||||||
if (this.trackIds) {
|
|
||||||
ids[i] = this.popStack();
|
|
||||||
}
|
|
||||||
if (this.stringParams) {
|
|
||||||
types[i] = this.popStack();
|
|
||||||
contexts[i] = this.popStack();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectArgs) {
|
if (objectArgs) {
|
||||||
options.args = this.source.generateArray(params);
|
options.args = this.source.generateArray(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.trackIds) {
|
|
||||||
options.ids = this.source.generateArray(ids);
|
|
||||||
}
|
|
||||||
if (this.stringParams) {
|
|
||||||
options.types = this.source.generateArray(types);
|
|
||||||
options.contexts = this.source.generateArray(contexts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.data) {
|
if (this.options.data) {
|
||||||
options.data = 'data';
|
options.data = 'data';
|
||||||
}
|
}
|
||||||
@@ -1202,7 +1108,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
|
|
||||||
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
setupHelperArgs: function (helper, paramSize, params, useRegister) {
|
||||||
let options = this.setupParams(helper, paramSize, params);
|
let options = this.setupParams(helper, paramSize, params);
|
||||||
options.loc = JSON.stringify(this.source.currentLocation);
|
options.loc = JSON.stringify(this.source.currentLocation);
|
||||||
options = this.objectLiteral(options);
|
options = this.objectLiteral(options);
|
||||||
@@ -1216,10 +1122,10 @@ JavaScriptCompiler.prototype = {
|
|||||||
} else {
|
} else {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
(function() {
|
(function () {
|
||||||
const reservedWords = (
|
const reservedWords = (
|
||||||
'break else new var' +
|
'break else new var' +
|
||||||
' case finally return void' +
|
' case finally return void' +
|
||||||
@@ -1249,22 +1155,21 @@ JavaScriptCompiler.prototype = {
|
|||||||
/**
|
/**
|
||||||
* @deprecated May be removed in the next major version
|
* @deprecated May be removed in the next major version
|
||||||
*/
|
*/
|
||||||
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
JavaScriptCompiler.isValidJavaScriptVariableName = function (name) {
|
||||||
return (
|
return (
|
||||||
!JavaScriptCompiler.RESERVED_WORDS[name] &&
|
!JavaScriptCompiler.RESERVED_WORDS[name] &&
|
||||||
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
|
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function strictLookup(requireTerminal, compiler, parts, type) {
|
function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
|
||||||
let stack = compiler.popStack(),
|
let stack = compiler.popStack(),
|
||||||
i = 0,
|
|
||||||
len = parts.length;
|
len = parts.length;
|
||||||
if (requireTerminal) {
|
if (requireTerminal) {
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < len; i++) {
|
for (let i = startPartIndex; i < len; i++) {
|
||||||
stack = compiler.nameLookup(stack, parts[i], type);
|
stack = compiler.nameLookup(stack, parts[i], type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1274,10 +1179,10 @@ function strictLookup(requireTerminal, compiler, parts, type) {
|
|||||||
'(',
|
'(',
|
||||||
stack,
|
stack,
|
||||||
', ',
|
', ',
|
||||||
compiler.quotedString(parts[i]),
|
compiler.quotedString(parts[len]),
|
||||||
', ',
|
', ',
|
||||||
JSON.stringify(compiler.source.currentLocation),
|
JSON.stringify(compiler.source.currentLocation),
|
||||||
' )'
|
' )',
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return stack;
|
return stack;
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
/* eslint-disable new-cap */
|
|
||||||
import Visitor from './visitor';
|
|
||||||
|
|
||||||
export function print(ast) {
|
|
||||||
return new PrintVisitor().accept(ast);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PrintVisitor() {
|
|
||||||
this.padding = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintVisitor.prototype = new Visitor();
|
|
||||||
|
|
||||||
PrintVisitor.prototype.pad = function(string) {
|
|
||||||
let out = '';
|
|
||||||
|
|
||||||
for (let i = 0, l = this.padding; i < l; i++) {
|
|
||||||
out += ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
out += string + '\n';
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.Program = function(program) {
|
|
||||||
let out = '',
|
|
||||||
body = program.body,
|
|
||||||
i,
|
|
||||||
l;
|
|
||||||
|
|
||||||
if (program.blockParams) {
|
|
||||||
let blockParams = 'BLOCK PARAMS: [';
|
|
||||||
for (i = 0, l = program.blockParams.length; i < l; i++) {
|
|
||||||
blockParams += ' ' + program.blockParams[i];
|
|
||||||
}
|
|
||||||
blockParams += ' ]';
|
|
||||||
out += this.pad(blockParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0, l = body.length; i < l; i++) {
|
|
||||||
out += this.accept(body[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.padding--;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.MustacheStatement = function(mustache) {
|
|
||||||
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
|
|
||||||
};
|
|
||||||
PrintVisitor.prototype.Decorator = function(mustache) {
|
|
||||||
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
|
|
||||||
block
|
|
||||||
) {
|
|
||||||
let out = '';
|
|
||||||
|
|
||||||
out += this.pad(
|
|
||||||
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
|
|
||||||
);
|
|
||||||
this.padding++;
|
|
||||||
out += this.pad(this.SubExpression(block));
|
|
||||||
if (block.program) {
|
|
||||||
out += this.pad('PROGRAM:');
|
|
||||||
this.padding++;
|
|
||||||
out += this.accept(block.program);
|
|
||||||
this.padding--;
|
|
||||||
}
|
|
||||||
if (block.inverse) {
|
|
||||||
if (block.program) {
|
|
||||||
this.padding++;
|
|
||||||
}
|
|
||||||
out += this.pad('{{^}}');
|
|
||||||
this.padding++;
|
|
||||||
out += this.accept(block.inverse);
|
|
||||||
this.padding--;
|
|
||||||
if (block.program) {
|
|
||||||
this.padding--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.padding--;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.PartialStatement = function(partial) {
|
|
||||||
let content = 'PARTIAL:' + partial.name.original;
|
|
||||||
if (partial.params[0]) {
|
|
||||||
content += ' ' + this.accept(partial.params[0]);
|
|
||||||
}
|
|
||||||
if (partial.hash) {
|
|
||||||
content += ' ' + this.accept(partial.hash);
|
|
||||||
}
|
|
||||||
return this.pad('{{> ' + content + ' }}');
|
|
||||||
};
|
|
||||||
PrintVisitor.prototype.PartialBlockStatement = function(partial) {
|
|
||||||
let content = 'PARTIAL BLOCK:' + partial.name.original;
|
|
||||||
if (partial.params[0]) {
|
|
||||||
content += ' ' + this.accept(partial.params[0]);
|
|
||||||
}
|
|
||||||
if (partial.hash) {
|
|
||||||
content += ' ' + this.accept(partial.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
content += ' ' + this.pad('PROGRAM:');
|
|
||||||
this.padding++;
|
|
||||||
content += this.accept(partial.program);
|
|
||||||
this.padding--;
|
|
||||||
|
|
||||||
return this.pad('{{> ' + content + ' }}');
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.ContentStatement = function(content) {
|
|
||||||
return this.pad("CONTENT[ '" + content.value + "' ]");
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.CommentStatement = function(comment) {
|
|
||||||
return this.pad("{{! '" + comment.value + "' }}");
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.SubExpression = function(sexpr) {
|
|
||||||
let params = sexpr.params,
|
|
||||||
paramStrings = [],
|
|
||||||
hash;
|
|
||||||
|
|
||||||
for (let i = 0, l = params.length; i < l; i++) {
|
|
||||||
paramStrings.push(this.accept(params[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
params = '[' + paramStrings.join(', ') + ']';
|
|
||||||
|
|
||||||
hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
|
|
||||||
|
|
||||||
return this.accept(sexpr.path) + ' ' + params + hash;
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.PathExpression = function(id) {
|
|
||||||
let path = id.parts.join('/');
|
|
||||||
return (id.data ? '@' : '') + 'PATH:' + path;
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.StringLiteral = function(string) {
|
|
||||||
return '"' + string.value + '"';
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.NumberLiteral = function(number) {
|
|
||||||
return 'NUMBER{' + number.value + '}';
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.BooleanLiteral = function(bool) {
|
|
||||||
return 'BOOLEAN{' + bool.value + '}';
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.UndefinedLiteral = function() {
|
|
||||||
return 'UNDEFINED';
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.NullLiteral = function() {
|
|
||||||
return 'NULL';
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.Hash = function(hash) {
|
|
||||||
let pairs = hash.pairs,
|
|
||||||
joinedPairs = [];
|
|
||||||
|
|
||||||
for (let i = 0, l = pairs.length; i < l; i++) {
|
|
||||||
joinedPairs.push(this.accept(pairs[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'HASH{' + joinedPairs.join(', ') + '}';
|
|
||||||
};
|
|
||||||
PrintVisitor.prototype.HashPair = function(pair) {
|
|
||||||
return pair.key + '=' + this.accept(pair.value);
|
|
||||||
};
|
|
||||||
/* eslint-enable new-cap */
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import Exception from '../exception';
|
|
||||||
|
|
||||||
function Visitor() {
|
|
||||||
this.parents = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
Visitor.prototype = {
|
|
||||||
constructor: Visitor,
|
|
||||||
mutating: false,
|
|
||||||
|
|
||||||
// Visits a given value. If mutating, will replace the value if necessary.
|
|
||||||
acceptKey: function(node, name) {
|
|
||||||
let value = this.accept(node[name]);
|
|
||||||
if (this.mutating) {
|
|
||||||
// Hacky sanity check: This may have a few false positives for type for the helper
|
|
||||||
// methods but will generally do the right thing without a lot of overhead.
|
|
||||||
if (value && !Visitor.prototype[value.type]) {
|
|
||||||
throw new Exception(
|
|
||||||
'Unexpected node type "' +
|
|
||||||
value.type +
|
|
||||||
'" found when accepting ' +
|
|
||||||
name +
|
|
||||||
' on ' +
|
|
||||||
node.type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
node[name] = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Performs an accept operation with added sanity check to ensure
|
|
||||||
// required keys are not removed.
|
|
||||||
acceptRequired: function(node, name) {
|
|
||||||
this.acceptKey(node, name);
|
|
||||||
|
|
||||||
if (!node[name]) {
|
|
||||||
throw new Exception(node.type + ' requires ' + name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Traverses a given array. If mutating, empty respnses will be removed
|
|
||||||
// for child elements.
|
|
||||||
acceptArray: function(array) {
|
|
||||||
for (let i = 0, l = array.length; i < l; i++) {
|
|
||||||
this.acceptKey(array, i);
|
|
||||||
|
|
||||||
if (!array[i]) {
|
|
||||||
array.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
l--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
accept: function(object) {
|
|
||||||
if (!object) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* istanbul ignore next: Sanity code */
|
|
||||||
if (!this[object.type]) {
|
|
||||||
throw new Exception('Unknown type: ' + object.type, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.current) {
|
|
||||||
this.parents.unshift(this.current);
|
|
||||||
}
|
|
||||||
this.current = object;
|
|
||||||
|
|
||||||
let ret = this[object.type](object);
|
|
||||||
|
|
||||||
this.current = this.parents.shift();
|
|
||||||
|
|
||||||
if (!this.mutating || ret) {
|
|
||||||
return ret;
|
|
||||||
} else if (ret !== false) {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Program: function(program) {
|
|
||||||
this.acceptArray(program.body);
|
|
||||||
},
|
|
||||||
|
|
||||||
MustacheStatement: visitSubExpression,
|
|
||||||
Decorator: visitSubExpression,
|
|
||||||
|
|
||||||
BlockStatement: visitBlock,
|
|
||||||
DecoratorBlock: visitBlock,
|
|
||||||
|
|
||||||
PartialStatement: visitPartial,
|
|
||||||
PartialBlockStatement: function(partial) {
|
|
||||||
visitPartial.call(this, partial);
|
|
||||||
|
|
||||||
this.acceptKey(partial, 'program');
|
|
||||||
},
|
|
||||||
|
|
||||||
ContentStatement: function(/* content */) {},
|
|
||||||
CommentStatement: function(/* comment */) {},
|
|
||||||
|
|
||||||
SubExpression: visitSubExpression,
|
|
||||||
|
|
||||||
PathExpression: function(/* path */) {},
|
|
||||||
|
|
||||||
StringLiteral: function(/* string */) {},
|
|
||||||
NumberLiteral: function(/* number */) {},
|
|
||||||
BooleanLiteral: function(/* bool */) {},
|
|
||||||
UndefinedLiteral: function(/* literal */) {},
|
|
||||||
NullLiteral: function(/* literal */) {},
|
|
||||||
|
|
||||||
Hash: function(hash) {
|
|
||||||
this.acceptArray(hash.pairs);
|
|
||||||
},
|
|
||||||
HashPair: function(pair) {
|
|
||||||
this.acceptRequired(pair, 'value');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function visitSubExpression(mustache) {
|
|
||||||
this.acceptRequired(mustache, 'path');
|
|
||||||
this.acceptArray(mustache.params);
|
|
||||||
this.acceptKey(mustache, 'hash');
|
|
||||||
}
|
|
||||||
function visitBlock(block) {
|
|
||||||
visitSubExpression.call(this, block);
|
|
||||||
|
|
||||||
this.acceptKey(block, 'program');
|
|
||||||
this.acceptKey(block, 'inverse');
|
|
||||||
}
|
|
||||||
function visitPartial(partial) {
|
|
||||||
this.acceptRequired(partial, 'name');
|
|
||||||
this.acceptArray(partial.params);
|
|
||||||
this.acceptKey(partial, 'hash');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Visitor;
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
import Visitor from './visitor';
|
|
||||||
|
|
||||||
function WhitespaceControl(options = {}) {
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
WhitespaceControl.prototype = new Visitor();
|
|
||||||
|
|
||||||
WhitespaceControl.prototype.Program = function(program) {
|
|
||||||
const doStandalone = !this.options.ignoreStandalone;
|
|
||||||
|
|
||||||
let isRoot = !this.isRootSeen;
|
|
||||||
this.isRootSeen = true;
|
|
||||||
|
|
||||||
let body = program.body;
|
|
||||||
for (let i = 0, l = body.length; i < l; i++) {
|
|
||||||
let current = body[i],
|
|
||||||
strip = this.accept(current);
|
|
||||||
|
|
||||||
if (!strip) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
|
|
||||||
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
|
||||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
|
||||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
|
||||||
inlineStandalone =
|
|
||||||
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
|
||||||
|
|
||||||
if (strip.close) {
|
|
||||||
omitRight(body, i, true);
|
|
||||||
}
|
|
||||||
if (strip.open) {
|
|
||||||
omitLeft(body, i, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doStandalone && inlineStandalone) {
|
|
||||||
omitRight(body, i);
|
|
||||||
|
|
||||||
if (omitLeft(body, i)) {
|
|
||||||
// If we are on a standalone node, save the indent info for partials
|
|
||||||
if (current.type === 'PartialStatement') {
|
|
||||||
// Pull out the whitespace from the final line
|
|
||||||
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (doStandalone && openStandalone) {
|
|
||||||
omitRight((current.program || current.inverse).body);
|
|
||||||
|
|
||||||
// Strip out the previous content node if it's whitespace only
|
|
||||||
omitLeft(body, i);
|
|
||||||
}
|
|
||||||
if (doStandalone && closeStandalone) {
|
|
||||||
// Always strip the next node
|
|
||||||
omitRight(body, i);
|
|
||||||
|
|
||||||
omitLeft((current.inverse || current.program).body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return program;
|
|
||||||
};
|
|
||||||
|
|
||||||
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
|
|
||||||
block
|
|
||||||
) {
|
|
||||||
this.accept(block.program);
|
|
||||||
this.accept(block.inverse);
|
|
||||||
|
|
||||||
// Find the inverse program that is involed with whitespace stripping.
|
|
||||||
let program = block.program || block.inverse,
|
|
||||||
inverse = block.program && block.inverse,
|
|
||||||
firstInverse = inverse,
|
|
||||||
lastInverse = inverse;
|
|
||||||
|
|
||||||
if (inverse && inverse.chained) {
|
|
||||||
firstInverse = inverse.body[0].program;
|
|
||||||
|
|
||||||
// Walk the inverse chain to find the last inverse that is actually in the chain.
|
|
||||||
while (lastInverse.chained) {
|
|
||||||
lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let strip = {
|
|
||||||
open: block.openStrip.open,
|
|
||||||
close: block.closeStrip.close,
|
|
||||||
|
|
||||||
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
|
|
||||||
// so our parent can determine if we actually are standalone
|
|
||||||
openStandalone: isNextWhitespace(program.body),
|
|
||||||
closeStandalone: isPrevWhitespace((firstInverse || program).body)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (block.openStrip.close) {
|
|
||||||
omitRight(program.body, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inverse) {
|
|
||||||
let inverseStrip = block.inverseStrip;
|
|
||||||
|
|
||||||
if (inverseStrip.open) {
|
|
||||||
omitLeft(program.body, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inverseStrip.close) {
|
|
||||||
omitRight(firstInverse.body, null, true);
|
|
||||||
}
|
|
||||||
if (block.closeStrip.open) {
|
|
||||||
omitLeft(lastInverse.body, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find standalone else statments
|
|
||||||
if (
|
|
||||||
!this.options.ignoreStandalone &&
|
|
||||||
isPrevWhitespace(program.body) &&
|
|
||||||
isNextWhitespace(firstInverse.body)
|
|
||||||
) {
|
|
||||||
omitLeft(program.body);
|
|
||||||
omitRight(firstInverse.body);
|
|
||||||
}
|
|
||||||
} else if (block.closeStrip.open) {
|
|
||||||
omitLeft(program.body, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return strip;
|
|
||||||
};
|
|
||||||
|
|
||||||
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
|
|
||||||
mustache
|
|
||||||
) {
|
|
||||||
return mustache.strip;
|
|
||||||
};
|
|
||||||
|
|
||||||
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
|
|
||||||
node
|
|
||||||
) {
|
|
||||||
/* istanbul ignore next */
|
|
||||||
let strip = node.strip || {};
|
|
||||||
return {
|
|
||||||
inlineStandalone: true,
|
|
||||||
open: strip.open,
|
|
||||||
close: strip.close
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function isPrevWhitespace(body, i, isRoot) {
|
|
||||||
if (i === undefined) {
|
|
||||||
i = body.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes that end with newlines are considered whitespace (but are special
|
|
||||||
// cased for strip operations)
|
|
||||||
let prev = body[i - 1],
|
|
||||||
sibling = body[i - 2];
|
|
||||||
if (!prev) {
|
|
||||||
return isRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev.type === 'ContentStatement') {
|
|
||||||
return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(
|
|
||||||
prev.original
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function isNextWhitespace(body, i, isRoot) {
|
|
||||||
if (i === undefined) {
|
|
||||||
i = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let next = body[i + 1],
|
|
||||||
sibling = body[i + 2];
|
|
||||||
if (!next) {
|
|
||||||
return isRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next.type === 'ContentStatement') {
|
|
||||||
return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(
|
|
||||||
next.original
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marks the node to the right of the position as omitted.
|
|
||||||
// I.e. {{foo}}' ' will mark the ' ' node as omitted.
|
|
||||||
//
|
|
||||||
// If i is undefined, then the first child will be marked as such.
|
|
||||||
//
|
|
||||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
|
||||||
// content is met.
|
|
||||||
function omitRight(body, i, multiple) {
|
|
||||||
let current = body[i == null ? 0 : i + 1];
|
|
||||||
if (
|
|
||||||
!current ||
|
|
||||||
current.type !== 'ContentStatement' ||
|
|
||||||
(!multiple && current.rightStripped)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let original = current.value;
|
|
||||||
current.value = current.value.replace(
|
|
||||||
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
|
|
||||||
''
|
|
||||||
);
|
|
||||||
current.rightStripped = current.value !== original;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marks the node to the left of the position as omitted.
|
|
||||||
// I.e. ' '{{foo}} will mark the ' ' node as omitted.
|
|
||||||
//
|
|
||||||
// If i is undefined then the last child will be marked as such.
|
|
||||||
//
|
|
||||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
|
||||||
// content is met.
|
|
||||||
function omitLeft(body, i, multiple) {
|
|
||||||
let current = body[i == null ? body.length - 1 : i - 1];
|
|
||||||
if (
|
|
||||||
!current ||
|
|
||||||
current.type !== 'ContentStatement' ||
|
|
||||||
(!multiple && current.leftStripped)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We omit the last node if it's whitespace only and not preceded by a non-content node.
|
|
||||||
let original = current.value;
|
|
||||||
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
|
|
||||||
current.leftStripped = current.value !== original;
|
|
||||||
return current.leftStripped;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WhitespaceControl;
|
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
import { extend } from '../utils';
|
import { extend } from '../utils';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerDecorator('inline', function(fn, props, container, options) {
|
instance.registerDecorator(
|
||||||
let ret = fn;
|
'inline',
|
||||||
if (!props.partials) {
|
function (fn, props, container, options) {
|
||||||
props.partials = {};
|
let ret = fn;
|
||||||
ret = function(context, options) {
|
if (!props.partials) {
|
||||||
// Create a new partials stack frame prior to exec.
|
props.partials = {};
|
||||||
let original = container.partials;
|
ret = function (context, options) {
|
||||||
container.partials = extend({}, original, props.partials);
|
// Create a new partials stack frame prior to exec.
|
||||||
let ret = fn(context, options);
|
let original = container.partials;
|
||||||
container.partials = original;
|
container.partials = extend({}, original, props.partials);
|
||||||
return ret;
|
let ret = fn(context, options);
|
||||||
};
|
container.partials = original;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
props.partials[options.args[0]] = options.fn;
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
props.partials[options.args[0]] = options.fn;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
const errorProps = [
|
|
||||||
'description',
|
|
||||||
'fileName',
|
|
||||||
'lineNumber',
|
|
||||||
'endLineNumber',
|
|
||||||
'message',
|
|
||||||
'name',
|
|
||||||
'number',
|
|
||||||
'stack'
|
|
||||||
];
|
|
||||||
|
|
||||||
function Exception(message, node) {
|
|
||||||
let loc = node && node.loc,
|
|
||||||
line,
|
|
||||||
endLineNumber,
|
|
||||||
column,
|
|
||||||
endColumn;
|
|
||||||
|
|
||||||
if (loc) {
|
|
||||||
line = loc.start.line;
|
|
||||||
endLineNumber = loc.end.line;
|
|
||||||
column = loc.start.column;
|
|
||||||
endColumn = loc.end.column;
|
|
||||||
|
|
||||||
message += ' - ' + line + ':' + column;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tmp = Error.prototype.constructor.call(this, message);
|
|
||||||
|
|
||||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
|
||||||
for (let idx = 0; idx < errorProps.length; idx++) {
|
|
||||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (Error.captureStackTrace) {
|
|
||||||
Error.captureStackTrace(this, Exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (loc) {
|
|
||||||
this.lineNumber = line;
|
|
||||||
this.endLineNumber = endLineNumber;
|
|
||||||
|
|
||||||
// Work around issue under safari where we can't directly set the column value
|
|
||||||
/* istanbul ignore next */
|
|
||||||
if (Object.defineProperty) {
|
|
||||||
Object.defineProperty(this, 'column', {
|
|
||||||
value: column,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, 'endColumn', {
|
|
||||||
value: endColumn,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.column = column;
|
|
||||||
this.endColumn = endColumn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (nop) {
|
|
||||||
/* Ignore if the browser is very particular */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception.prototype = new Error();
|
|
||||||
|
|
||||||
export default Exception;
|
|
||||||
@@ -20,7 +20,8 @@ export function moveHelperToHooks(instance, helperName, keepHelper) {
|
|||||||
if (instance.helpers[helperName]) {
|
if (instance.helpers[helperName]) {
|
||||||
instance.hooks[helperName] = instance.helpers[helperName];
|
instance.hooks[helperName] = instance.helpers[helperName];
|
||||||
if (!keepHelper) {
|
if (!keepHelper) {
|
||||||
delete instance.helpers[helperName];
|
// Using delete is slow
|
||||||
|
instance.helpers[helperName] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { appendContextPath, createFrame, isArray } from '../utils';
|
import { isArray } from '../utils';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
instance.registerHelper('blockHelperMissing', function (context, options) {
|
||||||
let inverse = options.inverse,
|
let inverse = options.inverse,
|
||||||
fn = options.fn;
|
fn = options.fn;
|
||||||
|
|
||||||
@@ -11,24 +11,11 @@ export default function(instance) {
|
|||||||
return inverse(this);
|
return inverse(this);
|
||||||
} else if (isArray(context)) {
|
} else if (isArray(context)) {
|
||||||
if (context.length > 0) {
|
if (context.length > 0) {
|
||||||
if (options.ids) {
|
|
||||||
options.ids = [options.name];
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance.helpers.each(context, options);
|
return instance.helpers.each(context, options);
|
||||||
} else {
|
} else {
|
||||||
return inverse(this);
|
return inverse(this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.data && options.ids) {
|
|
||||||
let data = createFrame(options.data);
|
|
||||||
data.contextPath = appendContextPath(
|
|
||||||
options.data.contextPath,
|
|
||||||
options.name
|
|
||||||
);
|
|
||||||
options = { data: data };
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(context, options);
|
return fn(context, options);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import {
|
import { Exception } from '@handlebars/parser';
|
||||||
appendContextPath,
|
import { createFrame, isArray, isFunction, isMap, isSet } from '../utils';
|
||||||
blockParams,
|
|
||||||
createFrame,
|
|
||||||
isArray,
|
|
||||||
isFunction
|
|
||||||
} from '../utils';
|
|
||||||
import Exception from '../exception';
|
|
||||||
|
|
||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerHelper('each', function(context, options) {
|
instance.registerHelper('each', function (context, options) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
throw new Exception('Must pass iterator to #each');
|
throw new Exception('Must pass iterator to #each');
|
||||||
}
|
}
|
||||||
@@ -17,13 +11,7 @@ export default function(instance) {
|
|||||||
inverse = options.inverse,
|
inverse = options.inverse,
|
||||||
i = 0,
|
i = 0,
|
||||||
ret = '',
|
ret = '',
|
||||||
data,
|
data;
|
||||||
contextPath;
|
|
||||||
|
|
||||||
if (options.data && options.ids) {
|
|
||||||
contextPath =
|
|
||||||
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFunction(context)) {
|
if (isFunction(context)) {
|
||||||
context = context.call(this);
|
context = context.call(this);
|
||||||
@@ -33,26 +21,19 @@ export default function(instance) {
|
|||||||
data = createFrame(options.data);
|
data = createFrame(options.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function execIteration(field, index, last) {
|
function execIteration(field, value, index, last) {
|
||||||
if (data) {
|
if (data) {
|
||||||
data.key = field;
|
data.key = field;
|
||||||
data.index = index;
|
data.index = index;
|
||||||
data.first = index === 0;
|
data.first = index === 0;
|
||||||
data.last = !!last;
|
data.last = !!last;
|
||||||
|
|
||||||
if (contextPath) {
|
|
||||||
data.contextPath = contextPath + field;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret =
|
ret =
|
||||||
ret +
|
ret +
|
||||||
fn(context[field], {
|
fn(value, {
|
||||||
data: data,
|
data: data,
|
||||||
blockParams: blockParams(
|
blockParams: [context[field], field],
|
||||||
[context[field], field],
|
|
||||||
[contextPath + field, null]
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,34 +41,44 @@ export default function(instance) {
|
|||||||
if (isArray(context)) {
|
if (isArray(context)) {
|
||||||
for (let j = context.length; i < j; i++) {
|
for (let j = context.length; i < j; i++) {
|
||||||
if (i in context) {
|
if (i in context) {
|
||||||
execIteration(i, i, i === context.length - 1);
|
execIteration(i, context[i], i, i === context.length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (global.Symbol && context[global.Symbol.iterator]) {
|
} 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 newContext = [];
|
||||||
const iterator = context[global.Symbol.iterator]();
|
const iterator = context[Symbol.iterator]();
|
||||||
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||||
newContext.push(it.value);
|
newContext.push(it.value);
|
||||||
}
|
}
|
||||||
context = newContext;
|
context = newContext;
|
||||||
for (let j = context.length; i < j; i++) {
|
for (let j = context.length; i < j; i++) {
|
||||||
execIteration(i, i, i === context.length - 1);
|
execIteration(i, context[i], i, i === context.length - 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let priorKey;
|
let priorKey;
|
||||||
|
|
||||||
Object.keys(context).forEach(key => {
|
Object.keys(context).forEach((key) => {
|
||||||
// We're running the iterations one step out of sync so we can detect
|
// 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
|
// the last iteration without have to scan the object twice and create
|
||||||
// an itermediate keys array.
|
// an intermediate keys array.
|
||||||
if (priorKey !== undefined) {
|
if (priorKey !== undefined) {
|
||||||
execIteration(priorKey, i - 1);
|
execIteration(priorKey, context[priorKey], i - 1);
|
||||||
}
|
}
|
||||||
priorKey = key;
|
priorKey = key;
|
||||||
i++;
|
i++;
|
||||||
});
|
});
|
||||||
if (priorKey !== undefined) {
|
if (priorKey !== undefined) {
|
||||||
execIteration(priorKey, i - 1, true);
|
execIteration(priorKey, context[priorKey], i - 1, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Exception from '../exception';
|
import { Exception } from '@handlebars/parser';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
instance.registerHelper('helperMissing', function (/* [args, ]options */) {
|
||||||
if (arguments.length === 1) {
|
if (arguments.length === 1) {
|
||||||
// A missing field in a {{foo}} construct.
|
// A missing field in a {{foo}} construct.
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { Exception } from '@handlebars/parser';
|
||||||
import { isEmpty, isFunction } from '../utils';
|
import { isEmpty, isFunction } from '../utils';
|
||||||
import Exception from '../exception';
|
|
||||||
|
|
||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerHelper('if', function(conditional, options) {
|
instance.registerHelper('if', function (conditional, options) {
|
||||||
if (arguments.length != 2) {
|
if (arguments.length != 2) {
|
||||||
throw new Exception('#if requires exactly one argument');
|
throw new Exception('#if requires exactly one argument');
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ export default function(instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
// 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 condtional as purely not empty based on the
|
// 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.
|
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||||
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
|
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
|
||||||
return options.inverse(this);
|
return options.inverse(this);
|
||||||
@@ -20,14 +20,14 @@ export default function(instance) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.registerHelper('unless', function(conditional, options) {
|
instance.registerHelper('unless', function (conditional, options) {
|
||||||
if (arguments.length != 2) {
|
if (arguments.length != 2) {
|
||||||
throw new Exception('#unless requires exactly one argument');
|
throw new Exception('#unless requires exactly one argument');
|
||||||
}
|
}
|
||||||
return instance.helpers['if'].call(this, conditional, {
|
return instance.helpers['if'].call(this, conditional, {
|
||||||
fn: options.inverse,
|
fn: options.inverse,
|
||||||
inverse: options.fn,
|
inverse: options.fn,
|
||||||
hash: options.hash
|
hash: options.hash,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerHelper('log', function(/* message, options */) {
|
instance.registerHelper('log', function (/* message, options */) {
|
||||||
let args = [undefined],
|
let args = [undefined],
|
||||||
options = arguments[arguments.length - 1];
|
options = arguments[arguments.length - 1];
|
||||||
for (let i = 0; i < arguments.length - 1; i++) {
|
for (let i = 0; i < arguments.length - 1; i++) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerHelper('lookup', function(obj, field, options) {
|
instance.registerHelper('lookup', function (obj, field, options) {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
// Note for 5.0: Change to "obj == null" in 5.0
|
// Note for 5.0: Change to "obj == null" in 5.0
|
||||||
return obj;
|
return obj;
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import {
|
import { Exception } from '@handlebars/parser';
|
||||||
appendContextPath,
|
import { isEmpty, isFunction } from '../utils';
|
||||||
blockParams,
|
|
||||||
createFrame,
|
|
||||||
isEmpty,
|
|
||||||
isFunction
|
|
||||||
} from '../utils';
|
|
||||||
import Exception from '../exception';
|
|
||||||
|
|
||||||
export default function(instance) {
|
export default function (instance) {
|
||||||
instance.registerHelper('with', function(context, options) {
|
instance.registerHelper('with', function (context, options) {
|
||||||
if (arguments.length != 2) {
|
if (arguments.length != 2) {
|
||||||
throw new Exception('#with requires exactly one argument');
|
throw new Exception('#with requires exactly one argument');
|
||||||
}
|
}
|
||||||
@@ -20,17 +14,10 @@ export default function(instance) {
|
|||||||
|
|
||||||
if (!isEmpty(context)) {
|
if (!isEmpty(context)) {
|
||||||
let data = options.data;
|
let data = options.data;
|
||||||
if (options.data && options.ids) {
|
|
||||||
data = createFrame(options.data);
|
|
||||||
data.contextPath = appendContextPath(
|
|
||||||
options.data.contextPath,
|
|
||||||
options.ids[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(context, {
|
return fn(context, {
|
||||||
data: data,
|
data: data,
|
||||||
blockParams: blockParams([context], [data && data.contextPath])
|
blockParams: [context],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return options.inverse(this);
|
return options.inverse(this);
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { extend } from '../utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
|
|
||||||
* The resulting object can be used with "object[property]" to check if a property exists
|
|
||||||
* @param {...object} sources a varargs parameter of source objects that will be merged
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function createNewLookupObject(...sources) {
|
|
||||||
return extend(Object.create(null), ...sources);
|
|
||||||
}
|
|
||||||
@@ -1,34 +1,32 @@
|
|||||||
import { createNewLookupObject } from './create-new-lookup-object';
|
import { extend } from '../utils';
|
||||||
import * as logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
const loggedProperties = Object.create(null);
|
const loggedProperties = Object.create(null);
|
||||||
|
|
||||||
export function createProtoAccessControl(runtimeOptions) {
|
export function createProtoAccessControl(runtimeOptions) {
|
||||||
let defaultMethodWhiteList = Object.create(null);
|
// Create an object with "null"-prototype to avoid truthy results on
|
||||||
defaultMethodWhiteList['constructor'] = false;
|
// prototype properties.
|
||||||
defaultMethodWhiteList['__defineGetter__'] = false;
|
const propertyWhiteList = Object.create(null);
|
||||||
defaultMethodWhiteList['__defineSetter__'] = false;
|
|
||||||
defaultMethodWhiteList['__lookupGetter__'] = false;
|
|
||||||
|
|
||||||
let defaultPropertyWhiteList = Object.create(null);
|
|
||||||
// eslint-disable-next-line no-proto
|
// eslint-disable-next-line no-proto
|
||||||
defaultPropertyWhiteList['__proto__'] = false;
|
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 {
|
return {
|
||||||
properties: {
|
properties: {
|
||||||
whitelist: createNewLookupObject(
|
whitelist: propertyWhiteList,
|
||||||
defaultPropertyWhiteList,
|
defaultValue: runtimeOptions.allowProtoPropertiesByDefault,
|
||||||
runtimeOptions.allowedProtoProperties
|
|
||||||
),
|
|
||||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
whitelist: createNewLookupObject(
|
whitelist: methodWhiteList,
|
||||||
defaultMethodWhiteList,
|
defaultValue: runtimeOptions.allowProtoMethodsByDefault,
|
||||||
runtimeOptions.allowedProtoMethods
|
},
|
||||||
),
|
|
||||||
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +45,11 @@ function checkWhiteList(protoAccessControlForType, propertyName) {
|
|||||||
if (protoAccessControlForType.defaultValue !== undefined) {
|
if (protoAccessControlForType.defaultValue !== undefined) {
|
||||||
return protoAccessControlForType.defaultValue;
|
return protoAccessControlForType.defaultValue;
|
||||||
}
|
}
|
||||||
logUnexpecedPropertyAccessOnce(propertyName);
|
logUnexpectedPropertyAccessOnce(propertyName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function logUnexpecedPropertyAccessOnce(propertyName) {
|
function logUnexpectedPropertyAccessOnce(propertyName) {
|
||||||
if (loggedProperties[propertyName] !== true) {
|
if (loggedProperties[propertyName] !== true) {
|
||||||
loggedProperties[propertyName] = true;
|
loggedProperties[propertyName] = true;
|
||||||
logger.log(
|
logger.log(
|
||||||
@@ -64,7 +62,7 @@ function logUnexpecedPropertyAccessOnce(propertyName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function resetLoggedProperties() {
|
export function resetLoggedProperties() {
|
||||||
Object.keys(loggedProperties).forEach(propertyName => {
|
Object.keys(loggedProperties).forEach((propertyName) => {
|
||||||
delete loggedProperties[propertyName];
|
delete loggedProperties[propertyName];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
export function wrapHelper(helper, transformOptionsFn) {
|
export function wrapHelper(helper, transformOptionsFn) {
|
||||||
if (typeof helper !== 'function') {
|
if (typeof helper !== 'function') {
|
||||||
// This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639
|
// 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.
|
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
|
||||||
return helper;
|
return helper;
|
||||||
}
|
}
|
||||||
let wrapper = function(/* dynamic arguments */) {
|
let wrapper = function (/* dynamic arguments */) {
|
||||||
const options = arguments[arguments.length - 1];
|
const options = arguments[arguments.length - 1];
|
||||||
arguments[arguments.length - 1] = transformOptionsFn(options);
|
arguments[arguments.length - 1] = transformOptionsFn(options);
|
||||||
return helper.apply(this, arguments);
|
return helper.apply(this, arguments);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ let logger = {
|
|||||||
level: 'info',
|
level: 'info',
|
||||||
|
|
||||||
// Maps a given level value to the `methodMap` indexes above.
|
// Maps a given level value to the `methodMap` indexes above.
|
||||||
lookupLevel: function(level) {
|
lookupLevel: function (level) {
|
||||||
if (typeof level === 'string') {
|
if (typeof level === 'string') {
|
||||||
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
|
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
|
||||||
if (levelMap >= 0) {
|
if (levelMap >= 0) {
|
||||||
@@ -19,7 +19,7 @@ let logger = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Can be overridden in the host environment
|
// Can be overridden in the host environment
|
||||||
log: function(level, ...message) {
|
log: function (level, ...message) {
|
||||||
level = logger.lookupLevel(level);
|
level = logger.lookupLevel(level);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -33,7 +33,7 @@ let logger = {
|
|||||||
}
|
}
|
||||||
console[method](...message); // eslint-disable-line no-console
|
console[method](...message); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
export default function(Handlebars) {
|
export default function (Handlebars) {
|
||||||
/* istanbul ignore next */
|
let $Handlebars = globalThis.Handlebars;
|
||||||
let root = typeof global !== 'undefined' ? global : window,
|
|
||||||
$Handlebars = root.Handlebars;
|
/* v8 ignore next */
|
||||||
/* istanbul ignore next */
|
Handlebars.noConflict = function () {
|
||||||
Handlebars.noConflict = function() {
|
if (globalThis.Handlebars === Handlebars) {
|
||||||
if (root.Handlebars === Handlebars) {
|
globalThis.Handlebars = $Handlebars;
|
||||||
root.Handlebars = $Handlebars;
|
|
||||||
}
|
}
|
||||||
return Handlebars;
|
return Handlebars;
|
||||||
};
|
};
|
||||||
|
|||||||
+39
-61
@@ -1,16 +1,16 @@
|
|||||||
|
import { Exception } from '@handlebars/parser';
|
||||||
import * as Utils from './utils';
|
import * as Utils from './utils';
|
||||||
import Exception from './exception';
|
|
||||||
import {
|
import {
|
||||||
COMPILER_REVISION,
|
COMPILER_REVISION,
|
||||||
createFrame,
|
createFrame,
|
||||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||||
REVISION_CHANGES
|
REVISION_CHANGES,
|
||||||
} from './base';
|
} from './base';
|
||||||
import { moveHelperToHooks } from './helpers';
|
import { moveHelperToHooks } from './helpers';
|
||||||
import { wrapHelper } from './internal/wrapHelper';
|
import { wrapHelper } from './internal/wrapHelper';
|
||||||
import {
|
import {
|
||||||
createProtoAccessControl,
|
createProtoAccessControl,
|
||||||
resultIsAllowed
|
resultIsAllowed,
|
||||||
} from './internal/proto-access';
|
} from './internal/proto-access';
|
||||||
|
|
||||||
export function checkRevision(compilerInfo) {
|
export function checkRevision(compilerInfo) {
|
||||||
@@ -47,7 +47,7 @@ export function checkRevision(compilerInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function template(templateSpec, env) {
|
export function template(templateSpec, env) {
|
||||||
/* istanbul ignore next */
|
/* v8 ignore next */
|
||||||
if (!env) {
|
if (!env) {
|
||||||
throw new Exception('No environment passed to template');
|
throw new Exception('No environment passed to template');
|
||||||
}
|
}
|
||||||
@@ -68,23 +68,13 @@ export function template(templateSpec, env) {
|
|||||||
function invokePartialWrapper(partial, context, options) {
|
function invokePartialWrapper(partial, context, options) {
|
||||||
if (options.hash) {
|
if (options.hash) {
|
||||||
context = Utils.extend({}, context, options.hash);
|
context = Utils.extend({}, context, options.hash);
|
||||||
if (options.ids) {
|
|
||||||
options.ids[0] = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||||
|
|
||||||
let extendedOptions = Utils.extend({}, options, {
|
options.hooks = this.hooks;
|
||||||
hooks: this.hooks,
|
options.protoAccessControl = this.protoAccessControl;
|
||||||
protoAccessControl: this.protoAccessControl
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = env.VM.invokePartial.call(
|
let result = env.VM.invokePartial.call(this, partial, context, options);
|
||||||
this,
|
|
||||||
partial,
|
|
||||||
context,
|
|
||||||
extendedOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == null && env.compile) {
|
if (result == null && env.compile) {
|
||||||
options.partials[options.name] = env.compile(
|
options.partials[options.name] = env.compile(
|
||||||
@@ -92,7 +82,7 @@ export function template(templateSpec, env) {
|
|||||||
templateSpec.compilerOptions,
|
templateSpec.compilerOptions,
|
||||||
env
|
env
|
||||||
);
|
);
|
||||||
result = options.partials[options.name](context, extendedOptions);
|
result = options.partials[options.name](context, options);
|
||||||
}
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (options.indent) {
|
if (options.indent) {
|
||||||
@@ -118,15 +108,19 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
// Just add water
|
// Just add water
|
||||||
let container = {
|
let container = {
|
||||||
strict: function(obj, name, loc) {
|
strict: function (obj, name, loc) {
|
||||||
if (!obj || !(name in obj)) {
|
if (!obj || !(name in obj)) {
|
||||||
throw new Exception('"' + name + '" not defined in ' + obj, {
|
throw new Exception('"' + name + '" not defined in ' + obj, {
|
||||||
loc: loc
|
loc: loc,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return obj[name];
|
return container.lookupProperty(obj, name);
|
||||||
},
|
},
|
||||||
lookupProperty: function(parent, propertyName) {
|
lookupProperty: function (parent, propertyName) {
|
||||||
|
if (Utils.isMap(parent)) {
|
||||||
|
return parent.get(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
let result = parent[propertyName];
|
let result = parent[propertyName];
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return result;
|
return result;
|
||||||
@@ -140,7 +134,7 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
lookup: function(depths, name) {
|
lookup: function (depths, name) {
|
||||||
const len = depths.length;
|
const len = depths.length;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
let result = depths[i] && container.lookupProperty(depths[i], name);
|
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||||
@@ -149,21 +143,21 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lambda: function(current, context) {
|
lambda: function (current, context) {
|
||||||
return typeof current === 'function' ? current.call(context) : current;
|
return typeof current === 'function' ? current.call(context) : current;
|
||||||
},
|
},
|
||||||
|
|
||||||
escapeExpression: Utils.escapeExpression,
|
escapeExpression: Utils.escapeExpression,
|
||||||
invokePartial: invokePartialWrapper,
|
invokePartial: invokePartialWrapper,
|
||||||
|
|
||||||
fn: function(i) {
|
fn: function (i) {
|
||||||
let ret = templateSpec[i];
|
let ret = templateSpec[i];
|
||||||
ret.decorator = templateSpec[i + '_d'];
|
ret.decorator = templateSpec[i + '_d'];
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
programs: [],
|
programs: [],
|
||||||
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
program: function (i, data, declaredBlockParams, blockParams, depths) {
|
||||||
let programWrapper = this.programs[i],
|
let programWrapper = this.programs[i],
|
||||||
fn = this.fn(i);
|
fn = this.fn(i);
|
||||||
if (data || depths || blockParams || declaredBlockParams) {
|
if (data || depths || blockParams || declaredBlockParams) {
|
||||||
@@ -182,13 +176,13 @@ export function template(templateSpec, env) {
|
|||||||
return programWrapper;
|
return programWrapper;
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function(value, depth) {
|
data: function (value, depth) {
|
||||||
while (value && depth--) {
|
while (value && depth--) {
|
||||||
value = value._parent;
|
value = value._parent;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
mergeIfNeeded: function(param, common) {
|
mergeIfNeeded: function (param, common) {
|
||||||
let obj = param || common;
|
let obj = param || common;
|
||||||
|
|
||||||
if (param && common && param !== common) {
|
if (param && common && param !== common) {
|
||||||
@@ -201,13 +195,13 @@ export function template(templateSpec, env) {
|
|||||||
nullContext: Object.seal({}),
|
nullContext: Object.seal({}),
|
||||||
|
|
||||||
noop: env.VM.noop,
|
noop: env.VM.noop,
|
||||||
compilerInfo: templateSpec.compiler
|
compilerInfo: templateSpec.compiler,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ret(context, options = {}) {
|
function ret(context, options = {}) {
|
||||||
let data = options.data;
|
let data = options.data;
|
||||||
|
|
||||||
ret._setup(options);
|
_setup(options);
|
||||||
if (!options.partial && templateSpec.useData) {
|
if (!options.partial && templateSpec.useData) {
|
||||||
data = initData(context, data);
|
data = initData(context, data);
|
||||||
}
|
}
|
||||||
@@ -252,10 +246,11 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
ret.isTop = true;
|
ret.isTop = true;
|
||||||
|
|
||||||
ret._setup = function(options) {
|
function _setup(options) {
|
||||||
if (!options.partial) {
|
if (!options.partial) {
|
||||||
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
|
let mergedHelpers = {};
|
||||||
wrapHelpersToPassLookupProperty(mergedHelpers, container);
|
addHelpers(mergedHelpers, env.helpers, container);
|
||||||
|
addHelpers(mergedHelpers, options.helpers, container);
|
||||||
container.helpers = mergedHelpers;
|
container.helpers = mergedHelpers;
|
||||||
|
|
||||||
if (templateSpec.usePartial) {
|
if (templateSpec.usePartial) {
|
||||||
@@ -288,26 +283,8 @@ export function template(templateSpec, env) {
|
|||||||
container.decorators = options.decorators;
|
container.decorators = options.decorators;
|
||||||
container.hooks = options.hooks;
|
container.hooks = options.hooks;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
ret._child = function(i, data, blockParams, depths) {
|
|
||||||
if (templateSpec.useBlockParams && !blockParams) {
|
|
||||||
throw new Exception('must pass block params');
|
|
||||||
}
|
|
||||||
if (templateSpec.useDepths && !depths) {
|
|
||||||
throw new Exception('must pass parent depths');
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapProgram(
|
|
||||||
container,
|
|
||||||
i,
|
|
||||||
templateSpec[i],
|
|
||||||
data,
|
|
||||||
0,
|
|
||||||
blockParams,
|
|
||||||
depths
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,9 +348,6 @@ export function invokePartial(partial, context, options) {
|
|||||||
// Use the current closure context to save the partial-block if this partial
|
// Use the current closure context to save the partial-block if this partial
|
||||||
const currentPartialBlock = options.data && options.data['partial-block'];
|
const currentPartialBlock = options.data && options.data['partial-block'];
|
||||||
options.partial = true;
|
options.partial = true;
|
||||||
if (options.ids) {
|
|
||||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
let partialBlock;
|
let partialBlock;
|
||||||
if (options.fn && options.fn !== noop) {
|
if (options.fn && options.fn !== noop) {
|
||||||
@@ -400,7 +374,9 @@ export function invokePartial(partial, context, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (partial === undefined) {
|
if (partial === undefined) {
|
||||||
throw new Exception('The partial ' + options.name + ' could not be found');
|
throw new Exception(
|
||||||
|
'The partial "' + options.name + '" could not be found'
|
||||||
|
);
|
||||||
} else if (partial instanceof Function) {
|
} else if (partial instanceof Function) {
|
||||||
return partial(context, options);
|
return partial(context, options);
|
||||||
}
|
}
|
||||||
@@ -435,16 +411,18 @@ function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
|||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
function addHelpers(mergedHelpers, helpers, container) {
|
||||||
Object.keys(mergedHelpers).forEach(helperName => {
|
if (!helpers) return;
|
||||||
let helper = mergedHelpers[helperName];
|
Object.keys(helpers).forEach((helperName) => {
|
||||||
|
let helper = helpers[helperName];
|
||||||
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function passLookupPropertyOption(helper, container) {
|
function passLookupPropertyOption(helper, container) {
|
||||||
const lookupProperty = container.lookupProperty;
|
const lookupProperty = container.lookupProperty;
|
||||||
return wrapHelper(helper, options => {
|
return wrapHelper(helper, (options) => {
|
||||||
return Utils.extend({ lookupProperty }, options);
|
options.lookupProperty = lookupProperty;
|
||||||
|
return options;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ function SafeString(string) {
|
|||||||
this.string = string;
|
this.string = string;
|
||||||
}
|
}
|
||||||
|
|
||||||
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
|
SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
|
||||||
return '' + this.string;
|
return '' + this.string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+12
-30
@@ -5,7 +5,7 @@ const escape = {
|
|||||||
'"': '"',
|
'"': '"',
|
||||||
"'": ''',
|
"'": ''',
|
||||||
'`': '`',
|
'`': '`',
|
||||||
'=': '='
|
'=': '=',
|
||||||
};
|
};
|
||||||
|
|
||||||
const badChars = /[&<>"'`=]/g,
|
const badChars = /[&<>"'`=]/g,
|
||||||
@@ -30,32 +30,23 @@ export function extend(obj /* , ...source */) {
|
|||||||
export let toString = Object.prototype.toString;
|
export let toString = Object.prototype.toString;
|
||||||
|
|
||||||
// Sourced from lodash
|
// Sourced from lodash
|
||||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
// https://github.com/lodash/lodash/blob/4.17.21/LICENSE
|
||||||
/* eslint-disable func-style */
|
export function isFunction(value) {
|
||||||
let isFunction = function(value) {
|
|
||||||
return typeof value === 'function';
|
return typeof value === 'function';
|
||||||
};
|
|
||||||
// fallback for older versions of Chrome and Safari
|
|
||||||
/* istanbul ignore next */
|
|
||||||
if (isFunction(/x/)) {
|
|
||||||
isFunction = function(value) {
|
|
||||||
return (
|
|
||||||
typeof value === 'function' &&
|
|
||||||
toString.call(value) === '[object Function]'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export { isFunction };
|
|
||||||
/* eslint-enable func-style */
|
|
||||||
|
|
||||||
/* istanbul ignore next */
|
function testTag(name) {
|
||||||
export const isArray =
|
const tag = '[object ' + name + ']';
|
||||||
Array.isArray ||
|
return function (value) {
|
||||||
function(value) {
|
|
||||||
return value && typeof value === 'object'
|
return value && typeof value === 'object'
|
||||||
? toString.call(value) === '[object Array]'
|
? toString.call(value) === tag
|
||||||
: false;
|
: 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.
|
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
|
||||||
export function indexOf(array, value) {
|
export function indexOf(array, value) {
|
||||||
@@ -105,12 +96,3 @@ export function createFrame(object) {
|
|||||||
frame._parent = object;
|
frame._parent = object;
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function blockParams(params, ids) {
|
|
||||||
params.path = ids;
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function appendContextPath(contextPath, id) {
|
|
||||||
return (contextPath ? contextPath + '.' : '') + id;
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-4
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
var handlebars = require('../dist/cjs/handlebars')['default'];
|
var handlebars = require('../dist/cjs/handlebars')['default'];
|
||||||
|
|
||||||
var printer = require('../dist/cjs/handlebars/compiler/printer');
|
var parser = require('@handlebars/parser');
|
||||||
handlebars.PrintVisitor = printer.PrintVisitor;
|
handlebars.PrintVisitor = parser.PrintVisitor;
|
||||||
handlebars.print = printer.print;
|
handlebars.print = parser.print;
|
||||||
|
|
||||||
module.exports = handlebars;
|
module.exports = handlebars;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ function extension(module, filename) {
|
|||||||
var templateString = fs.readFileSync(filename, 'utf8');
|
var templateString = fs.readFileSync(filename, 'utf8');
|
||||||
module.exports = handlebars.compile(templateString);
|
module.exports = handlebars.compile(templateString);
|
||||||
}
|
}
|
||||||
/* istanbul ignore else */
|
/* v8 ignore next 4 */
|
||||||
if (typeof require !== 'undefined' && require.extensions) {
|
if (typeof require !== 'undefined' && require.extensions) {
|
||||||
require.extensions['.handlebars'] = extension;
|
require.extensions['.handlebars'] = extension;
|
||||||
require.extensions['.hbs'] = extension;
|
require.extensions['.hbs'] = extension;
|
||||||
|
|||||||
+32
-28
@@ -1,16 +1,16 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import Async from 'neo-async';
|
import Async from 'neo-async';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as Handlebars from './handlebars';
|
import Handlebars from './handlebars';
|
||||||
import { basename } from 'path';
|
import { basename } from 'path';
|
||||||
import { SourceMapConsumer, SourceNode } from 'source-map';
|
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||||
|
|
||||||
module.exports.loadTemplates = function(opts, callback) {
|
module.exports.loadTemplates = function (opts, callback) {
|
||||||
loadStrings(opts, function(err, strings) {
|
loadStrings(opts, function (err, strings) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
loadFiles(opts, function(err, files) {
|
loadFiles(opts, function (err, files) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -36,7 +36,7 @@ function loadStrings(opts, callback) {
|
|||||||
|
|
||||||
Async.map(
|
Async.map(
|
||||||
strings,
|
strings,
|
||||||
function(string, callback) {
|
function (string, callback) {
|
||||||
if (string !== '-') {
|
if (string !== '-') {
|
||||||
callback(undefined, string);
|
callback(undefined, string);
|
||||||
} else {
|
} else {
|
||||||
@@ -44,19 +44,19 @@ function loadStrings(opts, callback) {
|
|||||||
let buffer = '';
|
let buffer = '';
|
||||||
process.stdin.setEncoding('utf8');
|
process.stdin.setEncoding('utf8');
|
||||||
|
|
||||||
process.stdin.on('data', function(chunk) {
|
process.stdin.on('data', function (chunk) {
|
||||||
buffer += chunk;
|
buffer += chunk;
|
||||||
});
|
});
|
||||||
process.stdin.on('end', function() {
|
process.stdin.on('end', function () {
|
||||||
callback(undefined, buffer);
|
callback(undefined, buffer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(err, strings) {
|
function (err, strings) {
|
||||||
strings = strings.map((string, index) => ({
|
strings = strings.map((string, index) => ({
|
||||||
name: names[index],
|
name: names[index],
|
||||||
path: names[index],
|
path: names[index],
|
||||||
source: string
|
source: string,
|
||||||
}));
|
}));
|
||||||
callback(err, strings);
|
callback(err, strings);
|
||||||
}
|
}
|
||||||
@@ -67,20 +67,23 @@ function loadFiles(opts, callback) {
|
|||||||
// Build file extension pattern
|
// Build file extension pattern
|
||||||
let extension = (opts.extension || 'handlebars').replace(
|
let extension = (opts.extension || 'handlebars').replace(
|
||||||
/[\\^$*+?.():=!|{}\-[\]]/g,
|
/[\\^$*+?.():=!|{}\-[\]]/g,
|
||||||
function(arg) {
|
function (arg) {
|
||||||
return '\\' + arg;
|
return '\\' + arg;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
extension = new RegExp('\\.' + extension + '$');
|
extension = new RegExp('\\.' + extension + '$');
|
||||||
|
|
||||||
let ret = [],
|
let ret = [],
|
||||||
queue = (opts.files || []).map(template => ({ template, root: opts.root }));
|
queue = (opts.files || []).map((template) => ({
|
||||||
|
template,
|
||||||
|
root: opts.root,
|
||||||
|
}));
|
||||||
Async.whilst(
|
Async.whilst(
|
||||||
() => queue.length,
|
() => queue.length,
|
||||||
function(callback) {
|
function (callback) {
|
||||||
let { template: path, root } = queue.shift();
|
let { template: path, root } = queue.shift();
|
||||||
|
|
||||||
fs.stat(path, function(err, stat) {
|
fs.stat(path, function (err, stat) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(
|
return callback(
|
||||||
new Handlebars.Exception(`Unable to open template file "${path}"`)
|
new Handlebars.Exception(`Unable to open template file "${path}"`)
|
||||||
@@ -90,12 +93,12 @@ function loadFiles(opts, callback) {
|
|||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
opts.hasDirectory = true;
|
opts.hasDirectory = true;
|
||||||
|
|
||||||
fs.readdir(path, function(err, children) {
|
fs.readdir(path, function (err, children) {
|
||||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
/* v8 ignore next -- Race condition that being too lazy to test */
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
children.forEach(function(file) {
|
children.forEach(function (file) {
|
||||||
let childPath = path + '/' + file;
|
let childPath = path + '/' + file;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -109,8 +112,8 @@ function loadFiles(opts, callback) {
|
|||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
fs.readFile(path, 'utf8', function(err, data) {
|
fs.readFile(path, 'utf8', function (err, data) {
|
||||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
/* v8 ignore next -- Race condition that being too lazy to test */
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
@@ -131,7 +134,7 @@ function loadFiles(opts, callback) {
|
|||||||
ret.push({
|
ret.push({
|
||||||
path: path,
|
path: path,
|
||||||
name: name,
|
name: name,
|
||||||
source: data
|
source: data,
|
||||||
});
|
});
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
@@ -139,7 +142,7 @@ function loadFiles(opts, callback) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function(err) {
|
function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -149,7 +152,7 @@ function loadFiles(opts, callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.cli = function(opts) {
|
module.exports.cli = async function (opts) {
|
||||||
if (opts.version) {
|
if (opts.version) {
|
||||||
console.log(Handlebars.VERSION);
|
console.log(Handlebars.VERSION);
|
||||||
return;
|
return;
|
||||||
@@ -218,10 +221,10 @@ module.exports.cli = function(opts) {
|
|||||||
output.add('{};\n');
|
output.add('{};\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.templates.forEach(function(template) {
|
for (const template of opts.templates) {
|
||||||
let options = {
|
let options = {
|
||||||
knownHelpers: known,
|
knownHelpers: known,
|
||||||
knownHelpersOnly: opts.o
|
knownHelpersOnly: opts.o,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (opts.map) {
|
if (opts.map) {
|
||||||
@@ -235,11 +238,12 @@ module.exports.cli = function(opts) {
|
|||||||
|
|
||||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||||
if (opts.map) {
|
if (opts.map) {
|
||||||
let consumer = new SourceMapConsumer(precompiled.map);
|
let consumer = await new SourceMapConsumer(precompiled.map);
|
||||||
precompiled = SourceNode.fromStringWithSourceMap(
|
precompiled = SourceNode.fromStringWithSourceMap(
|
||||||
precompiled.code,
|
precompiled.code,
|
||||||
consumer
|
consumer
|
||||||
);
|
);
|
||||||
|
consumer.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.simple) {
|
if (opts.simple) {
|
||||||
@@ -258,10 +262,10 @@ module.exports.cli = function(opts) {
|
|||||||
template.name,
|
template.name,
|
||||||
"'] = template(",
|
"'] = template(",
|
||||||
precompiled,
|
precompiled,
|
||||||
');\n'
|
');\n',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// Output the content
|
// Output the content
|
||||||
if (!opts.simple) {
|
if (!opts.simple) {
|
||||||
@@ -334,7 +338,7 @@ function minify(output, sourceMapFile) {
|
|||||||
return require('uglify-js').minify(output.code, {
|
return require('uglify-js').minify(output.code, {
|
||||||
sourceMap: {
|
sourceMap: {
|
||||||
content: output.map,
|
content: output.map,
|
||||||
url: sourceMapFile
|
url: sourceMapFile,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user