Compare commits
416 Commits
| 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 | |||
| 77825f8d35 | |||
| a65a55382c | |||
| b1034a6702 | |||
| 8727d2015a | |||
| a39bfa5fe3 | |||
| 3789a30955 | |||
| 75794c5dbc | |||
| 3438891bd2 | |||
| 0ec71dcc5c | |||
| e6ad93ea01 | |||
| 2bf4fc6fd3 | |||
| b64202bc91 | |||
| c2f1e62031 | |||
| 08e9a11a34 | |||
| 6f17a8e75c | |||
| 1fd2edee2a | |||
| 3c9c2f5cf2 | |||
| 16487a088e | |||
| 309d2b49a1 | |||
| 645ac73844 | |||
| b454b02147 | |||
| f8eade7b41 | |||
| d36adf9343 | |||
| 7adc19ab40 | |||
| 9dd8d10e12 | |||
| 4671c4b383 | |||
| e46baa1fe2 | |||
| c491b4ea25 | |||
| 738391a064 | |||
| 2fd4a3da7a | |||
| 80c4516fda | |||
| d79212a662 | |||
| b440c38886 | |||
| 2dba7eee3f | |||
| 3800b7dd4c | |||
| 2ea32e8db9 | |||
| c9789691af | |||
| 9278f217e0 | |||
| d78cc73d3c | |||
| 4de51fe26b | |||
| a32d05f2fc | |||
| ad63f5189f | |||
| fd3ca85d13 | |||
| edfe6b899c | |||
| 586e672c8b | |||
| f0c6c4cc1f | |||
| a4fd391ba1 | |||
| 9d5aa363cf | |||
| 14ba3d0c43 | |||
| 4cddfe7017 | |||
| f152dfc892 | |||
| 3c1e252169 | |||
| 0d5c807017 | |||
| 1f0834b1a2 | |||
| 575d8772e2 | |||
| 7af1c12db6 | |||
| 0c8230c253 | |||
| 91a1b5d2f4 | |||
| 770d746e60 | |||
| d7f0dcf2bb | |||
| 187d611e8c | |||
| d337f40d0e | |||
| d03b6ecfc4 | |||
| d23ccf636a | |||
| 164b7ff0de | |||
| ac4655ee95 | |||
| 14b621caf5 | |||
| 1ec1737d24 | |||
| 1ebce2b53c | |||
| 3a5b65e02b | |||
| dde108e283 | |||
| dc5495216d | |||
| 8a062f19d5 | |||
| d1fb07b32b | |||
| 04b19848cc | |||
| 44ee971e44 | |||
| c40d9f33ad | |||
| 37411901da | |||
| 8901c28e9a | |||
| dfe0fcb130 | |||
| 3fb331ef40 | |||
| e97685e989 | |||
| 2f0991a022 | |||
| 4085e066fc | |||
| e913dc5f12 | |||
| 1f61f21250 | |||
| 587e7a3e63 | |||
| edcc84f292 | |||
| 23d58e79bb | |||
| dce6f54fdf | |||
| c819c8b533 | |||
| 827c9d0747 | |||
| f7f05d7558 | |||
| 1988878087 | |||
| 886ba86c2f | |||
| 0817dad7e7 | |||
| 93516a0b07 | |||
| 93e284ed9b | |||
| c02b05fa81 | |||
| 7b11f55c53 | |||
| 8de121d21c | |||
| 6914090086 | |||
| d54137810a | |||
| c2ac79c970 | |||
| b0bb2a42b4 | |||
| 7ef86173ab | |||
| b75e3e1f40 | |||
| 5e9d17f8fa | |||
| b24797da01 | |||
| a243067883 | |||
| 088e61812a | |||
| 7052e88068 | |||
| b8913fcc65 | |||
| 62ed3c25c7 | |||
| e46c239b3d | |||
| 7fcf9d24f8 | |||
| 081e1d0ad9 | |||
| 2ef6fbd1fc | |||
| c76ded8f0f | |||
| 50e844b563 | |||
| a15d01d383 | |||
| 8e1cce7918 | |||
| def79fd5d6 | |||
| fd2e5c983d | |||
| 8d5530ee2c | |||
| 2ab261eab7 | |||
| 143ac806da | |||
| f1752fe66f | |||
| aaab6099f2 | |||
| c56995d706 | |||
| feb60f85c9 | |||
| 566536b03d | |||
| 0b593bfe5d | |||
| 2e53fba68f | |||
| ba570c42cf | |||
| 6aaac4a779 | |||
| 0440af2147 | |||
| 4ae86f3282 | |||
| b793350fec | |||
| 26d0f7a80d | |||
| b7eada0149 | |||
| b8e769fcb6 | |||
| 9cb31653a4 | |||
| c53f340374 | |||
| 059b330579 | |||
| 8f6047cdfd | |||
| cf7545ef5a | |||
| c958cc8955 | |||
| b250b2d53a | |||
| ff4d827c09 | |||
| e4738491b3 | |||
| 54f7e11b28 | |||
| 8742bde701 | |||
| 2357140c68 | |||
| c5cbeac039 | |||
| 213c0bbe3c | |||
| 050cca0866 | |||
| 2d5579078b | |||
| e3639e2407 | |||
| c2a17c7b30 | |||
| 1266838829 | |||
| 64ecb9ea84 | |||
| 93444c58db | |||
| 25bae31578 | |||
| d148d7cb1f | |||
| 74795a3a7f | |||
| a89081d440 | |||
| bba65605b1 | |||
| 06b7224ed9 | |||
| 2078c727c6 | |||
| fff3e40402 | |||
| 35bcd4405f | |||
| 0419daec52 | |||
| 8df74a0119 | |||
| 00b4f2ff98 | |||
| e47b5ec192 | |||
| c55a7bed78 | |||
| 35d3fdb1dd | |||
| dce44541db | |||
| 164c7ceea4 | |||
| 6ab48d8def | |||
| 8ac20285f6 | |||
| 888750ec27 | |||
| 133b96a2ff | |||
| f119497312 | |||
| 62b64ecc3d | |||
| a57b6824e0 | |||
| a26633f204 | |||
| f9cce4dd02 | |||
| 16572cd848 | |||
| 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
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
module.exports = {
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"self": false
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"ecmaFeatures": {
|
||||
// Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time.
|
||||
"arrowFunctions": true,
|
||||
"blockBindings": true,
|
||||
"defaultParams": true,
|
||||
"destructuring": true,
|
||||
"modules": true,
|
||||
"objectLiteralComputedProperties": true,
|
||||
"objectLiteralDuplicateProperties": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"restParams": true,
|
||||
"spread": true,
|
||||
"templateStrings": true
|
||||
},
|
||||
"rules": {
|
||||
// overrides eslint:recommended defaults
|
||||
"no-sparse-arrays": "off",
|
||||
"no-func-assign": "off",
|
||||
"no-console": "warn",
|
||||
"no-debugger": "warn",
|
||||
"no-unreachable": "warn",
|
||||
|
||||
// Possible Errors //
|
||||
//-----------------//
|
||||
"no-unsafe-negation": "error",
|
||||
|
||||
|
||||
// Best Practices //
|
||||
//----------------//
|
||||
"curly": "error",
|
||||
"default-case": "warn",
|
||||
"dot-notation": ["error", { "allowKeywords": false }],
|
||||
"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-spaces": "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",
|
||||
"wrap-iife": "error",
|
||||
|
||||
|
||||
// Variables //
|
||||
//-----------//
|
||||
"no-catch-shadow": "error",
|
||||
"no-label-var": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-use-before-define": ["error", "nofunc"],
|
||||
|
||||
|
||||
// Stylistic Issues //
|
||||
//------------------//
|
||||
"comma-dangle": ["error", "never"],
|
||||
"quote-props": ["error", "as-needed", { "keywords": true, "unnecessary": false }],
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"camelcase": "error",
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"comma-style": ["error", "last"],
|
||||
"consistent-this": ["warn", "self"],
|
||||
"eol-last": "error",
|
||||
"func-style": ["error", "declaration"],
|
||||
"key-spacing": ["error", {
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}],
|
||||
"new-cap": "error",
|
||||
"new-parens": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"no-nested-ternary": "warn",
|
||||
"no-new-object": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-extra-parens": ["error", "functions"],
|
||||
"quotes": ["error", "single", "avoid-escape"],
|
||||
"semi": "error",
|
||||
"semi-spacing": ["error", { "before": false, "after": true }],
|
||||
"keyword-spacing": "error",
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"space-before-function-paren": ["error", { "anonymous": "never", "named": "never" }],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": ["error", "always", { "markers": [","] }],
|
||||
"wrap-regex": "warn",
|
||||
|
||||
// ECMAScript 6 //
|
||||
//--------------//
|
||||
"no-var": "warn"
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
||||
@@ -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 }}
|
||||
+12
-7
@@ -1,14 +1,19 @@
|
||||
vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
lib/handlebars/compiler/parser.js
|
||||
/dist/
|
||||
/tmp/
|
||||
/coverage/
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
sauce_connect.log*
|
||||
.idea
|
||||
yarn-error.log
|
||||
/yarn-error.log
|
||||
/yarn.lock
|
||||
node_modules
|
||||
/handlebars-release.tgz
|
||||
.nyc_output
|
||||
|
||||
# Generated files
|
||||
/coverage/
|
||||
/dist/
|
||||
/tests/bench/results/
|
||||
/tests/integration/*/dist/
|
||||
/spec/tmp/*
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "spec/mustache"]
|
||||
path = spec/mustache
|
||||
url = git://github.com/mustache/spec.git
|
||||
url = https://github.com/mustache/spec.git
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
instrumentation:
|
||||
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://swc.rs/schema.json",
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"importInterop": "swc"
|
||||
},
|
||||
"sourceMaps": "inline"
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
language: node_js
|
||||
before_install:
|
||||
- npm install -g grunt-cli
|
||||
script:
|
||||
- grunt --stack travis
|
||||
- multi-nodejs-test/run-tests.sh 0.10 0.12 4 5 6 7 8 9 10 11
|
||||
email:
|
||||
on_failure: change
|
||||
on_success: never
|
||||
env:
|
||||
global:
|
||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
||||
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
|
||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
||||
matrix:
|
||||
include:
|
||||
- node_js: '10'
|
||||
env:
|
||||
- PUBLISH=true
|
||||
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
|
||||
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
git:
|
||||
depth: 100
|
||||
+52
-44
@@ -1,26 +1,32 @@
|
||||
# How to Contribute
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
Please refer to our [Security Policy](https://github.com/handlebars-lang/handlebars.js/blob/master/SECURITY.md).
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please 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.
|
||||
|
||||
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
|
||||
|
||||
* The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
||||
* The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
||||
should merge the branch `4.x` into the master branch regularly.
|
||||
- The branch `master` contains the current development version (v5).
|
||||
- The branch `4.x` contains the previous stable version. Only critical bugfixes are backported there.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We also accept [pull requests][pull-request]!
|
||||
|
||||
Generally we like to see pull requests that
|
||||
|
||||
- Maintain the existing code style
|
||||
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
||||
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
@@ -29,33 +35,27 @@ Generally we like to see pull requests that
|
||||
|
||||
## Building
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* [Grunt](http://gruntjs.com/getting-started)
|
||||
To build Handlebars.js you'll need Node.js installed.
|
||||
|
||||
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
|
||||
|
||||
Project dependencies may be installed via `npm install`.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `grunt`
|
||||
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/`.
|
||||
To build Handlebars.js from scratch, run `npm run build` in the root of the project. That will compile CJS modules via SWC and bundle UMD distributions via rspack, outputting results to the dist/ folder. To run tests, use `npm test`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/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.
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
Clone the mustache specs into the spec/mustache folder.
|
||||
|
||||
```sh
|
||||
cd spec
|
||||
rm -r mustache
|
||||
@@ -63,53 +63,61 @@ git clone https://github.com/mustache/spec.git mustache
|
||||
```
|
||||
|
||||
From the root directory, run the tests.
|
||||
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
## Ember testing
|
||||
## Linting and Formatting
|
||||
|
||||
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
|
||||
Handlebars uses `oxlint` for linting, `oxfmt` for formatting, and `eslint` (with `eslint-plugin-compat`) for browser API compatibility checks.
|
||||
Committed files are linted and formatted in a pre-commit hook.
|
||||
|
||||
```sh
|
||||
npm link
|
||||
grunt build release
|
||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
||||
You can use the following scripts to make sure that the CI job does not fail:
|
||||
|
||||
cd $emberRepoDir
|
||||
npm link handlebars
|
||||
npm test
|
||||
```
|
||||
- **npm run lint** will run all linters and fail on warnings
|
||||
- **npm run format** will format all files
|
||||
- **npm run check-before-pull-request** will perform all checks that our CI job does, excluding integration tests.
|
||||
- **npm run test:integration** will run integration tests (bundler compatibility with webpack, rollup, etc.)
|
||||
These tests only work on Linux.
|
||||
|
||||
## Releasing the latest version
|
||||
|
||||
*When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch.*
|
||||
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||
|
||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
||||
- Push-access to `handlebars-lang/handlebars.js`
|
||||
- Publishing rights on npmjs.com for the `handlebars` package
|
||||
- Publishing rights on gemfury for the `handlebars-source` package
|
||||
- Push-access to the repo for legacy package managers: `components/handlebars`
|
||||
- Push-access to the production-repo of the handlebars site: `handlebars-lang/handlebarsjs.com-github-pages`
|
||||
|
||||
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||
|
||||
A full release may be completed with the following:
|
||||
|
||||
```
|
||||
yo release
|
||||
npm ci
|
||||
npm run build
|
||||
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
|
||||
in those places still point to the latest version
|
||||
|
||||
* [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
|
||||
* [The bower package](https://github.com/components/handlebars.js) (check the package.json)
|
||||
* [The AWS S3 Bucket](http://builds.handlebarsjs.com.s3.amazonaws.com/) (check latest-tag)
|
||||
* [RubyGems](https://rubygems.org/gems/handlebars-source)
|
||||
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
|
||||
- [The bower package](https://github.com/components/handlebars.js) (check the package.json)
|
||||
- [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
|
||||
- [RubyGems](https://rubygems.org/gems/handlebars-source)
|
||||
|
||||
When everything is OK, the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
|
||||
When everything is OK, the **handlebars site** needs to be updated.
|
||||
|
||||
Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
|
||||
and make a minimal change to the README. This will invoke a github-action that redeploys
|
||||
the site, fetching the latest version-number from the npm-registry.
|
||||
(note that the default-branch of this repo is not the master and regular changes are done
|
||||
in the `handlebars-lang/docs`-repo).
|
||||
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
||||
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
|
||||
[jsfiddle]: https://jsfiddle.net/4nbwjaqz/4/
|
||||
|
||||
@@ -1,60 +1,54 @@
|
||||
# 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
|
||||
<script type="text/x-handlebars">
|
||||
foo
|
||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||
</script>
|
||||
```
|
||||
```html
|
||||
<script type="text/x-handlebars">
|
||||
foo
|
||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||
</script>
|
||||
```
|
||||
|
||||
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
||||
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
|
||||
handlebars --version
|
||||
```
|
||||
If using the integrated precompiler and
|
||||
```sh
|
||||
handlebars --version
|
||||
```
|
||||
|
||||
```javascript
|
||||
console.log(Handlebars.VERSION);
|
||||
```
|
||||
On the client side.
|
||||
If using the integrated precompiler and
|
||||
|
||||
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 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.
|
||||
The `handlebars.runtime.js` file includes a UMD build, which exposes the library as both the module root and the `default` field for compatibility.
|
||||
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
/* eslint-disable no-process-env */
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
eslint: {
|
||||
options: {
|
||||
},
|
||||
files: [
|
||||
'*.js',
|
||||
'bench/**/*.js',
|
||||
'tasks/**/*.js',
|
||||
'lib/**/!(*.min|parser).js',
|
||||
'spec/**/!(*.amd|json2|require).js',
|
||||
'multi-nodejs-test/*.js'
|
||||
]
|
||||
},
|
||||
|
||||
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
|
||||
|
||||
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: {
|
||||
checkTypes: {
|
||||
cmd: 'npm run checkTypes',
|
||||
bg: false,
|
||||
fail: true
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
scripts: {
|
||||
options: {
|
||||
atBegin: true
|
||||
},
|
||||
|
||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||
tasks: ['build', 'amd', 'tests', 'test']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Build a new version of the library
|
||||
this.registerTask('build', 'Builds a distributable version of the current project', [
|
||||
'eslint',
|
||||
'bgShell:checkTypes',
|
||||
'parser',
|
||||
'node',
|
||||
'globals']);
|
||||
|
||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
||||
this.registerTask('node', ['babel:cjs']);
|
||||
this.registerTask('globals', ['webpack']);
|
||||
this.registerTask('tests', ['concat:tests']);
|
||||
|
||||
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
|
||||
// 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('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-saucelabs');
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
grunt.registerTask('bench', ['metrics']);
|
||||
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
|
||||
|
||||
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
|
||||
|
||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (C) 2011-2017 by Yehuda Katz
|
||||
Copyright (C) 2011-2019 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
-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
|
||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
See our [installation documentation](http://handlebarsjs.com/installation.html).
|
||||
|
||||
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](http://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](http://handlebarsjs.com/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](http://handlebarsjs.com/#paths)
|
||||
- [Helpers](http://handlebarsjs.com/#helpers)
|
||||
- [Block Expressions](http://handlebarsjs.com/#block-expressions)
|
||||
- [Literal Values](http://handlebarsjs.com/#literals)
|
||||
- [Delimited Comments](http://handlebarsjs.com/#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,37 +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 install
|
||||
- cmd: npm install -g grunt-cli
|
||||
|
||||
|
||||
# Post-install test scripts
|
||||
test_script:
|
||||
# Output useful info for debugging
|
||||
- cmd: node --version
|
||||
- cmd: npm --version
|
||||
# Run tests
|
||||
- cmd: grunt --stack travis
|
||||
|
||||
# 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,14 +0,0 @@
|
||||
{
|
||||
"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,38 +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,19 +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,12 +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,7 +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,4 +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,4 +0,0 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
||||
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
|
||||
};
|
||||
@@ -1,6 +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,6 +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,10 +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,11 +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,7 +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,8 +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,130 +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,197 +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 (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
errors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors) {
|
||||
print('\n\nErrors:\n');
|
||||
for (prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var optimist = require('optimist')
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
|
||||
'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'
|
||||
}
|
||||
})
|
||||
|
||||
.wrap(120)
|
||||
.check(function(argv) {
|
||||
if (argv.version) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var argv = optimist.argv;
|
||||
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)) {
|
||||
optimist.showHelp();
|
||||
} else {
|
||||
Precompiler.cli(opts);
|
||||
}
|
||||
});
|
||||
@@ -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",
|
||||
"version": "4.1.2-0",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"main": "handlebars.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {}
|
||||
|
||||
@@ -3,7 +3,5 @@
|
||||
"repo": "components/handlebars.js",
|
||||
"version": "1.0.0",
|
||||
"main": "handlebars.js",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
]
|
||||
"scripts": ["handlebars.js"]
|
||||
}
|
||||
|
||||
+32
-32
@@ -1,35 +1,35 @@
|
||||
{
|
||||
"name": "components/handlebars.js",
|
||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||
"homepage": "http://handlebarsjs.com",
|
||||
"license": "MIT",
|
||||
"type": "component",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"html"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wanstrath",
|
||||
"homepage": "http://chriswanstrath.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"robloach/component-installer": "*"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"name": "handlebars",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
],
|
||||
"files": [
|
||||
"handlebars.runtime.js"
|
||||
],
|
||||
"shim": {
|
||||
"exports": "Handlebars"
|
||||
}
|
||||
}
|
||||
"name": "components/handlebars.js",
|
||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||
"homepage": "http://handlebarsjs.com",
|
||||
"license": "MIT",
|
||||
"type": "component",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"html"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wanstrath",
|
||||
"homepage": "http://chriswanstrath.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"robloach/component-installer": "*"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"name": "handlebars",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
],
|
||||
"files": [
|
||||
"handlebars.runtime.js"
|
||||
],
|
||||
"shim": {
|
||||
"exports": "Handlebars"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||
gem.summary = %q{Handlebars.js source code wrapper}
|
||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
||||
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
|
||||
gem.version = package["version"].sub "-", "."
|
||||
gem.license = "MIT"
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>4.1.2-0</version>
|
||||
<version>5.0.0-alpha.1</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Extension of the Mustache logicless template language</description>
|
||||
<releaseNotes></releaseNotes>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "4.1.2-0",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"license": "MIT",
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
|
||||
+60
-33
@@ -16,6 +16,34 @@ var ast = Handlebars.parse(myTemplate);
|
||||
Handlebars.precompile(ast);
|
||||
```
|
||||
|
||||
### Parsing
|
||||
|
||||
There are two primary APIs that are used to parse an existing template into the AST:
|
||||
|
||||
#### parseWithoutProcessing
|
||||
|
||||
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
let ast = Handlebars.parseWithoutProcessing(myTemplate);
|
||||
```
|
||||
|
||||
#### parse
|
||||
|
||||
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
|
||||
|
||||
- Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
|
||||
- Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
|
||||
|
||||
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
let ast = Handlebars.parse(myTemplate);
|
||||
```
|
||||
|
||||
### Basic
|
||||
|
||||
@@ -43,7 +71,7 @@ interface Position {
|
||||
interface Program <: Node {
|
||||
type: "Program";
|
||||
body: [ Statement ];
|
||||
|
||||
|
||||
blockParams: [ string ];
|
||||
}
|
||||
```
|
||||
@@ -104,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.
|
||||
|
||||
|
||||
```java
|
||||
interface ContentStatement <: Statement {
|
||||
type: "ContentStatement";
|
||||
@@ -120,7 +147,6 @@ interface CommentStatement <: Statement {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```java
|
||||
interface Decorator <: Statement {
|
||||
type: "Decorator";
|
||||
@@ -181,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.
|
||||
- `original` is the path as entered by the user. Separator and scope references are left untouched.
|
||||
|
||||
|
||||
##### Literals
|
||||
|
||||
```java
|
||||
@@ -214,7 +239,6 @@ interface NullLiteral <: Literal {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
```java
|
||||
@@ -251,8 +275,8 @@ function ImportScanner() {
|
||||
}
|
||||
ImportScanner.prototype = new Visitor();
|
||||
|
||||
ImportScanner.prototype.PartialStatement = function(partial) {
|
||||
this.partials.push({request: partial.name.original});
|
||||
ImportScanner.prototype.PartialStatement = function (partial) {
|
||||
this.partials.push({ request: partial.name.original });
|
||||
|
||||
Visitor.prototype.PartialStatement.call(this, partial);
|
||||
};
|
||||
@@ -263,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 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.
|
||||
|
||||
@@ -272,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:
|
||||
|
||||
- `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
|
||||
- `name` is the current path component
|
||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
|
||||
@@ -281,20 +304,19 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
||||
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
|
||||
|
||||
- `depthedLookup(name)`
|
||||
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
|
||||
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
|
||||
|
||||
- `compilerInfo()`
|
||||
Allows for custom compiler flags used in the runtime version checking logic.
|
||||
|
||||
- `appendToBuffer(source, location, explicit)`
|
||||
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||
|
||||
- `source` is the source code whose result is to be appending
|
||||
- `location` is the location of the source in the source map.
|
||||
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
||||
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||
- `source` is the source code whose result is to be appending
|
||||
- `location` is the location of the source in the source map.
|
||||
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
||||
|
||||
- `initializeBuffer()`
|
||||
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
|
||||
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.
|
||||
|
||||
@@ -302,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.
|
||||
|
||||
|
||||
```javascript
|
||||
function MyCompiler() {
|
||||
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
||||
@@ -310,29 +331,35 @@ function MyCompiler() {
|
||||
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
|
||||
|
||||
// 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') {
|
||||
return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)])
|
||||
return this.source.functionCall('helpers.lookupLowerCase', '', [
|
||||
parent,
|
||||
JSON.stringify(name),
|
||||
]);
|
||||
} 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();
|
||||
env.registerHelper('lookupLowerCase', function(parent, name) {
|
||||
return parent[name.toLowerCase()]
|
||||
})
|
||||
env.registerHelper('lookupLowerCase', function (parent, name) {
|
||||
return parent[name.toLowerCase()];
|
||||
});
|
||||
|
||||
env.JavaScriptCompiler = MyCompiler;
|
||||
|
||||
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
|
||||
console.log(template({
|
||||
test: [
|
||||
{value: 'a'},
|
||||
{value: 'b'},
|
||||
{value: 'c'}
|
||||
]
|
||||
}));
|
||||
console.log(
|
||||
template({
|
||||
test: [{ value: 'a' }, { value: 'b' }, { value: 'c' }],
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
];
|
||||
+10
-4
@@ -1,11 +1,16 @@
|
||||
import {
|
||||
parser as Parser,
|
||||
parse,
|
||||
parseWithoutProcessing,
|
||||
Visitor,
|
||||
} from '@handlebars/parser';
|
||||
|
||||
import runtime from './handlebars.runtime';
|
||||
|
||||
// Compiler imports
|
||||
import AST from './handlebars/compiler/ast';
|
||||
import { parser as Parser, parse } from './handlebars/compiler/base';
|
||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||
import Visitor from './handlebars/compiler/visitor';
|
||||
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
|
||||
@@ -13,10 +18,10 @@ let _create = runtime.create;
|
||||
function create() {
|
||||
let hb = _create();
|
||||
|
||||
hb.compile = function(input, options) {
|
||||
hb.compile = function (input, options) {
|
||||
return compile(input, options, hb);
|
||||
};
|
||||
hb.precompile = function(input, options) {
|
||||
hb.precompile = function (input, options) {
|
||||
return precompile(input, options, hb);
|
||||
};
|
||||
|
||||
@@ -25,6 +30,7 @@ function create() {
|
||||
hb.JavaScriptCompiler = JavaScriptCompiler;
|
||||
hb.Parser = Parser;
|
||||
hb.parse = parse;
|
||||
hb.parseWithoutProcessing = parseWithoutProcessing;
|
||||
|
||||
return hb;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as base from './handlebars/base';
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
import SafeString from './handlebars/safe-string';
|
||||
import Exception from './handlebars/exception';
|
||||
import * as Utils from './handlebars/utils';
|
||||
import * as runtime from './handlebars/runtime';
|
||||
|
||||
@@ -19,8 +19,11 @@ function create() {
|
||||
hb.Utils = Utils;
|
||||
hb.escapeExpression = Utils.escapeExpression;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
// Spread into a plain object so that runtime functions (e.g. checkRevision)
|
||||
// can be overridden by consumers. ES module namespace objects are sealed with
|
||||
// getter-only properties per spec, which would prevent monkey-patching.
|
||||
hb.VM = { ...runtime };
|
||||
hb.template = function (spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
|
||||
|
||||
+34
-18
@@ -1,11 +1,13 @@
|
||||
import {createFrame, extend, toString} from './utils';
|
||||
import Exception from './exception';
|
||||
import {registerDefaultHelpers} from './helpers';
|
||||
import {registerDefaultDecorators} from './decorators';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, extend, toString } from './utils';
|
||||
import { registerDefaultHelpers } from './helpers';
|
||||
import { registerDefaultDecorators } from './decorators';
|
||||
import logger from './logger';
|
||||
import { resetLoggedProperties } from './internal/proto-access';
|
||||
|
||||
export const VERSION = '4.1.2-0';
|
||||
export const COMPILER_REVISION = 7;
|
||||
export const VERSION = '4.7.7';
|
||||
export const COMPILER_REVISION = 8;
|
||||
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||
|
||||
export const REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
@@ -14,7 +16,8 @@ export const REVISION_CHANGES = {
|
||||
4: '== 1.x.x',
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1',
|
||||
7: '>= 4.0.0'
|
||||
7: '>= 4.0.0 <4.3.0',
|
||||
8: '>= 4.3.0',
|
||||
};
|
||||
|
||||
const objectType = '[object Object]';
|
||||
@@ -34,45 +37,58 @@ HandlebarsEnvironment.prototype = {
|
||||
logger: logger,
|
||||
log: logger.log,
|
||||
|
||||
registerHelper: function(name, fn) {
|
||||
registerHelper: function (name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
if (fn) {
|
||||
throw new Exception('Arg not supported with multiple helpers');
|
||||
}
|
||||
extend(this.helpers, name);
|
||||
} else {
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
},
|
||||
unregisterHelper: function(name) {
|
||||
unregisterHelper: function (name) {
|
||||
delete this.helpers[name];
|
||||
},
|
||||
|
||||
registerPartial: function(name, partial) {
|
||||
registerPartial: function (name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
extend(this.partials, name);
|
||||
} else {
|
||||
if (typeof partial === 'undefined') {
|
||||
throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
|
||||
throw new Exception(
|
||||
`Attempting to register a partial called "${name}" as undefined`
|
||||
);
|
||||
}
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(name) {
|
||||
unregisterPartial: function (name) {
|
||||
delete this.partials[name];
|
||||
},
|
||||
|
||||
registerDecorator: function(name, fn) {
|
||||
registerDecorator: function (name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
|
||||
if (fn) {
|
||||
throw new Exception('Arg not supported with multiple decorators');
|
||||
}
|
||||
extend(this.decorators, name);
|
||||
} else {
|
||||
this.decorators[name] = fn;
|
||||
}
|
||||
},
|
||||
unregisterDecorator: function(name) {
|
||||
unregisterDecorator: function (name) {
|
||||
delete this.decorators[name];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Reset the memory of illegal property accesses that have already been logged.
|
||||
* @deprecated should only be used in handlebars test-cases
|
||||
*/
|
||||
resetLoggedPropertyAccesses() {
|
||||
resetLoggedProperties();
|
||||
},
|
||||
};
|
||||
|
||||
export let log = logger.log;
|
||||
|
||||
export {createFrame, logger};
|
||||
export { createFrame, logger };
|
||||
|
||||
@@ -4,25 +4,29 @@ let AST = {
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
helperExpression: function(node) {
|
||||
return (node.type === 'SubExpression')
|
||||
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
|
||||
&& !!((node.params && node.params.length) || node.hash));
|
||||
helperExpression: function (node) {
|
||||
return (
|
||||
node.type === 'SubExpression' ||
|
||||
((node.type === 'MustacheStatement' ||
|
||||
node.type === 'BlockStatement') &&
|
||||
!!((node.params && node.params.length) || node.hash))
|
||||
);
|
||||
},
|
||||
|
||||
scopedId: function(path) {
|
||||
return (/^\.|this\b/).test(path.original);
|
||||
scopedId: function (path) {
|
||||
return /^\.|this\b/.test(path.original);
|
||||
},
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
simpleId: function(path) {
|
||||
return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
|
||||
}
|
||||
}
|
||||
simpleId: function (path) {
|
||||
return (
|
||||
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// Must be exported as an object rather than the root of the module as the jison lexer
|
||||
// must modify the object to operate properly.
|
||||
export default AST;
|
||||
|
||||
@@ -1,24 +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 parse(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 strip = new WhitespaceControl(options);
|
||||
return strip.accept(parser.parse(input));
|
||||
}
|
||||
@@ -1,52 +1,57 @@
|
||||
/* global define */
|
||||
import {isArray} from '../utils';
|
||||
import { isArray } from '../utils';
|
||||
|
||||
let SourceNode;
|
||||
|
||||
try {
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
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.
|
||||
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;
|
||||
}
|
||||
// oxlint-disable-next-line no-unused-vars -- Babel 5 requires named catch param
|
||||
} catch (err) {
|
||||
/* 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) {
|
||||
SourceNode = function(line, column, srcFile, chunks) {
|
||||
SourceNode = function (line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
}
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
SourceNode.prototype = {
|
||||
add: function(chunks) {
|
||||
add: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend: function(chunks) {
|
||||
prepend: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap: function() {
|
||||
return {code: this.toString()};
|
||||
toStringWithSourceMap: function () {
|
||||
return { code: this.toString() };
|
||||
},
|
||||
toString: function() {
|
||||
toString: function () {
|
||||
return this.src;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function castChunk(chunk, codeGen, loc) {
|
||||
if (isArray(chunk)) {
|
||||
let ret = [];
|
||||
@@ -62,7 +67,6 @@ function castChunk(chunk, codeGen, loc) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
||||
function CodeGen(srcFile) {
|
||||
this.srcFile = srcFile;
|
||||
this.source = [];
|
||||
@@ -72,67 +76,74 @@ CodeGen.prototype = {
|
||||
isEmpty() {
|
||||
return !this.source.length;
|
||||
},
|
||||
prepend: function(source, loc) {
|
||||
prepend: function (source, loc) {
|
||||
this.source.unshift(this.wrap(source, loc));
|
||||
},
|
||||
push: function(source, loc) {
|
||||
push: function (source, loc) {
|
||||
this.source.push(this.wrap(source, loc));
|
||||
},
|
||||
|
||||
merge: function() {
|
||||
merge: function () {
|
||||
let source = this.empty();
|
||||
this.each(function(line) {
|
||||
this.each(function (line) {
|
||||
source.add([' ', line, '\n']);
|
||||
});
|
||||
return source;
|
||||
},
|
||||
|
||||
each: function(iter) {
|
||||
each: function (iter) {
|
||||
for (let i = 0, len = this.source.length; i < len; i++) {
|
||||
iter(this.source[i]);
|
||||
}
|
||||
},
|
||||
|
||||
empty: function() {
|
||||
let loc = this.currentLocation || {start: {}};
|
||||
empty: function () {
|
||||
let loc = this.currentLocation || { start: {} };
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||
},
|
||||
wrap: function(chunk, loc = this.currentLocation || {start: {}}) {
|
||||
wrap: function (chunk, loc = this.currentLocation || { start: {} }) {
|
||||
if (chunk instanceof SourceNode) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
chunk = castChunk(chunk, this, loc);
|
||||
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
|
||||
return new SourceNode(
|
||||
loc.start.line,
|
||||
loc.start.column,
|
||||
this.srcFile,
|
||||
chunk
|
||||
);
|
||||
},
|
||||
|
||||
functionCall: function(fn, type, params) {
|
||||
functionCall: function (fn, type, params) {
|
||||
params = this.generateList(params);
|
||||
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + (str + '')
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') + '"';
|
||||
quotedString: function (str) {
|
||||
return (
|
||||
'"' +
|
||||
(str + '')
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') +
|
||||
'"'
|
||||
);
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
objectLiteral: function (obj) {
|
||||
let pairs = [];
|
||||
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
let value = castChunk(obj[key], this);
|
||||
if (value !== 'undefined') {
|
||||
pairs.push([this.quotedString(key), ':', value]);
|
||||
}
|
||||
Object.keys(obj).forEach((key) => {
|
||||
let value = castChunk(obj[key], this);
|
||||
if (value !== 'undefined') {
|
||||
pairs.push([this.quotedString(key), ':', value]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let ret = this.generateList(pairs);
|
||||
ret.prepend('{');
|
||||
@@ -140,8 +151,7 @@ CodeGen.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
|
||||
generateList: function(entries) {
|
||||
generateList: function (entries) {
|
||||
let ret = this.empty();
|
||||
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
@@ -155,14 +165,13 @@ CodeGen.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateArray: function(entries) {
|
||||
generateArray: function (entries) {
|
||||
let ret = this.generateList(entries);
|
||||
ret.prepend('[');
|
||||
ret.add(']');
|
||||
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default CodeGen;
|
||||
|
||||
|
||||
+151
-176
@@ -1,7 +1,5 @@
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
import Exception from '../exception';
|
||||
import {isArray, indexOf, extend} from '../utils';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isArray, indexOf, extend } from '../utils';
|
||||
import AST from './ast';
|
||||
|
||||
const slice = [].slice;
|
||||
@@ -16,7 +14,7 @@ export function Compiler() {}
|
||||
Compiler.prototype = {
|
||||
compiler: Compiler,
|
||||
|
||||
equals: function(other) {
|
||||
equals: function (other) {
|
||||
let len = this.opcodes.length;
|
||||
if (other.opcodes.length !== len) {
|
||||
return false;
|
||||
@@ -24,8 +22,11 @@ Compiler.prototype = {
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
let opcode = this.opcodes[i],
|
||||
otherOpcode = other.opcodes[i];
|
||||
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
|
||||
otherOpcode = other.opcodes[i];
|
||||
if (
|
||||
opcode.opcode !== otherOpcode.opcode ||
|
||||
!argEquals(opcode.args, otherOpcode.args)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -44,44 +45,36 @@ Compiler.prototype = {
|
||||
|
||||
guid: 0,
|
||||
|
||||
compile: function(program, options) {
|
||||
compile: function (program, options) {
|
||||
this.sourceNode = [];
|
||||
this.opcodes = [];
|
||||
this.children = [];
|
||||
this.options = options;
|
||||
this.stringParams = options.stringParams;
|
||||
this.trackIds = options.trackIds;
|
||||
|
||||
options.blockParams = options.blockParams || [];
|
||||
|
||||
// These changes will propagate to the other compiler components
|
||||
let knownHelpers = options.knownHelpers;
|
||||
options.knownHelpers = {
|
||||
'helperMissing': true,
|
||||
'blockHelperMissing': true,
|
||||
'each': true,
|
||||
'if': true,
|
||||
'unless': true,
|
||||
'with': true,
|
||||
'log': true,
|
||||
'lookup': true
|
||||
};
|
||||
if (knownHelpers) {
|
||||
// the next line should use "Object.keys", but the code has been like this a long time and changing it, might
|
||||
// cause backwards-compatibility issues... It's an old library...
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (let name in knownHelpers) {
|
||||
this.options.knownHelpers[name] = knownHelpers[name];
|
||||
}
|
||||
}
|
||||
options.knownHelpers = extend(
|
||||
Object.create(null),
|
||||
{
|
||||
helperMissing: true,
|
||||
blockHelperMissing: true,
|
||||
each: true,
|
||||
if: true,
|
||||
unless: true,
|
||||
with: true,
|
||||
log: true,
|
||||
lookup: true,
|
||||
},
|
||||
options.knownHelpers
|
||||
);
|
||||
|
||||
return this.accept(program);
|
||||
},
|
||||
|
||||
compileProgram: function(program) {
|
||||
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
||||
result = childCompiler.compile(program, this.options),
|
||||
guid = this.guid++;
|
||||
compileProgram: function (program) {
|
||||
let childCompiler = new this.compiler(),
|
||||
result = childCompiler.compile(program, this.options),
|
||||
guid = this.guid++;
|
||||
|
||||
this.usePartial = this.usePartial || result.usePartial;
|
||||
|
||||
@@ -91,8 +84,8 @@ Compiler.prototype = {
|
||||
return guid;
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
/* istanbul ignore next: Sanity code */
|
||||
accept: function (node) {
|
||||
/* v8 ignore next -- Sanity code */
|
||||
if (!this[node.type]) {
|
||||
throw new Exception('Unknown type: ' + node.type, node);
|
||||
}
|
||||
@@ -103,11 +96,11 @@ Compiler.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
Program: function(program) {
|
||||
Program: function (program) {
|
||||
this.options.blockParams.unshift(program.blockParams);
|
||||
|
||||
let body = program.body,
|
||||
bodyLength = body.length;
|
||||
bodyLength = body.length;
|
||||
for (let i = 0; i < bodyLength; i++) {
|
||||
this.accept(body[i]);
|
||||
}
|
||||
@@ -120,11 +113,11 @@ Compiler.prototype = {
|
||||
return this;
|
||||
},
|
||||
|
||||
BlockStatement: function(block) {
|
||||
BlockStatement: function (block) {
|
||||
transformLiteralToPath(block);
|
||||
|
||||
let program = block.program,
|
||||
inverse = block.inverse;
|
||||
inverse = block.inverse;
|
||||
|
||||
program = program && this.compileProgram(program);
|
||||
inverse = inverse && this.compileProgram(inverse);
|
||||
@@ -159,13 +152,13 @@ Compiler.prototype = {
|
||||
DecoratorBlock(decorator) {
|
||||
let program = decorator.program && this.compileProgram(decorator.program);
|
||||
let params = this.setupFullMustacheParams(decorator, program, undefined),
|
||||
path = decorator.path;
|
||||
path = decorator.path;
|
||||
|
||||
this.useDecorators = true;
|
||||
this.opcode('registerDecorator', params.length, path.original);
|
||||
},
|
||||
|
||||
PartialStatement: function(partial) {
|
||||
PartialStatement: function (partial) {
|
||||
this.usePartial = true;
|
||||
|
||||
let program = partial.program;
|
||||
@@ -175,17 +168,20 @@ Compiler.prototype = {
|
||||
|
||||
let params = partial.params;
|
||||
if (params.length > 1) {
|
||||
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
|
||||
throw new Exception(
|
||||
'Unsupported number of partial arguments: ' + params.length,
|
||||
partial
|
||||
);
|
||||
} else if (!params.length) {
|
||||
if (this.options.explicitPartialContext) {
|
||||
this.opcode('pushLiteral', 'undefined');
|
||||
} else {
|
||||
params.push({type: 'PathExpression', parts: [], depth: 0});
|
||||
params.push({ type: 'PathExpression', parts: [], depth: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
let partialName = partial.name.original,
|
||||
isDynamic = partial.name.type === 'SubExpression';
|
||||
isDynamic = partial.name.type === 'SubExpression';
|
||||
if (isDynamic) {
|
||||
this.accept(partial.name);
|
||||
}
|
||||
@@ -201,11 +197,11 @@ Compiler.prototype = {
|
||||
this.opcode('invokePartial', isDynamic, partialName, indent);
|
||||
this.opcode('append');
|
||||
},
|
||||
PartialBlockStatement: function(partialBlock) {
|
||||
PartialBlockStatement: function (partialBlock) {
|
||||
this.PartialStatement(partialBlock);
|
||||
},
|
||||
|
||||
MustacheStatement: function(mustache) {
|
||||
MustacheStatement: function (mustache) {
|
||||
this.SubExpression(mustache);
|
||||
|
||||
if (mustache.escaped && !this.options.noEscape) {
|
||||
@@ -218,16 +214,15 @@ Compiler.prototype = {
|
||||
this.DecoratorBlock(decorator);
|
||||
},
|
||||
|
||||
|
||||
ContentStatement: function(content) {
|
||||
ContentStatement: function (content) {
|
||||
if (content.value) {
|
||||
this.opcode('appendContent', content.value);
|
||||
}
|
||||
},
|
||||
|
||||
CommentStatement: function() {},
|
||||
CommentStatement: function () {},
|
||||
|
||||
SubExpression: function(sexpr) {
|
||||
SubExpression: function (sexpr) {
|
||||
transformLiteralToPath(sexpr);
|
||||
let type = this.classifySexpr(sexpr);
|
||||
|
||||
@@ -239,10 +234,10 @@ Compiler.prototype = {
|
||||
this.ambiguousSexpr(sexpr);
|
||||
}
|
||||
},
|
||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||
ambiguousSexpr: function (sexpr, program, inverse) {
|
||||
let path = sexpr.path,
|
||||
name = path.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
name = path.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
|
||||
this.opcode('getContext', path.depth);
|
||||
|
||||
@@ -255,38 +250,46 @@ Compiler.prototype = {
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleSexpr: function(sexpr) {
|
||||
simpleSexpr: function (sexpr) {
|
||||
let path = sexpr.path;
|
||||
path.strict = true;
|
||||
this.accept(path);
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
helperSexpr: function(sexpr, program, inverse) {
|
||||
helperSexpr: function (sexpr, program, inverse) {
|
||||
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||
path = sexpr.path,
|
||||
name = path.parts[0];
|
||||
path = sexpr.path,
|
||||
name = path.parts[0];
|
||||
|
||||
if (this.options.knownHelpers[name]) {
|
||||
this.opcode('invokeKnownHelper', params.length, name);
|
||||
} else if (this.options.knownHelpersOnly) {
|
||||
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
|
||||
throw new Exception(
|
||||
'You specified knownHelpersOnly, but used the unknown helper ' + name,
|
||||
sexpr
|
||||
);
|
||||
} else {
|
||||
path.strict = true;
|
||||
path.falsy = true;
|
||||
|
||||
this.accept(path);
|
||||
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
|
||||
this.opcode(
|
||||
'invokeHelper',
|
||||
params.length,
|
||||
path.original,
|
||||
AST.helpers.simpleId(path)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
PathExpression: function(path) {
|
||||
PathExpression: function (path) {
|
||||
this.addDepth(path.depth);
|
||||
this.opcode('getContext', path.depth);
|
||||
|
||||
let name = path.parts[0],
|
||||
scoped = AST.helpers.scopedId(path),
|
||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
||||
scoped = AST.helpers.scopedId(path),
|
||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
||||
|
||||
if (blockParamId) {
|
||||
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
||||
@@ -297,34 +300,40 @@ Compiler.prototype = {
|
||||
this.options.data = true;
|
||||
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
||||
} else {
|
||||
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
|
||||
this.opcode(
|
||||
'lookupOnContext',
|
||||
path.parts,
|
||||
path.falsy,
|
||||
path.strict,
|
||||
scoped
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
StringLiteral: function(string) {
|
||||
StringLiteral: function (string) {
|
||||
this.opcode('pushString', string.value);
|
||||
},
|
||||
|
||||
NumberLiteral: function(number) {
|
||||
NumberLiteral: function (number) {
|
||||
this.opcode('pushLiteral', number.value);
|
||||
},
|
||||
|
||||
BooleanLiteral: function(bool) {
|
||||
BooleanLiteral: function (bool) {
|
||||
this.opcode('pushLiteral', bool.value);
|
||||
},
|
||||
|
||||
UndefinedLiteral: function() {
|
||||
UndefinedLiteral: function () {
|
||||
this.opcode('pushLiteral', 'undefined');
|
||||
},
|
||||
|
||||
NullLiteral: function() {
|
||||
NullLiteral: function () {
|
||||
this.opcode('pushLiteral', 'null');
|
||||
},
|
||||
|
||||
Hash: function(hash) {
|
||||
Hash: function (hash) {
|
||||
let pairs = hash.pairs,
|
||||
i = 0,
|
||||
l = pairs.length;
|
||||
i = 0,
|
||||
l = pairs.length;
|
||||
|
||||
this.opcode('pushHash');
|
||||
|
||||
@@ -338,11 +347,15 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
opcode: function(name) {
|
||||
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
|
||||
opcode: function (name) {
|
||||
this.opcodes.push({
|
||||
opcode: name,
|
||||
args: slice.call(arguments, 1),
|
||||
loc: this.sourceNode[0].loc,
|
||||
});
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
addDepth: function (depth) {
|
||||
if (!depth) {
|
||||
return;
|
||||
}
|
||||
@@ -350,7 +363,7 @@ Compiler.prototype = {
|
||||
this.useDepths = true;
|
||||
},
|
||||
|
||||
classifySexpr: function(sexpr) {
|
||||
classifySexpr: function (sexpr) {
|
||||
let isSimple = AST.helpers.simpleId(sexpr.path);
|
||||
|
||||
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
|
||||
@@ -368,8 +381,7 @@ Compiler.prototype = {
|
||||
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
|
||||
if (isEligible && !isHelper) {
|
||||
let name = sexpr.path.parts[0],
|
||||
options = this.options;
|
||||
|
||||
options = this.options;
|
||||
if (options.knownHelpers[name]) {
|
||||
isHelper = true;
|
||||
} else if (options.knownHelpersOnly) {
|
||||
@@ -386,59 +398,17 @@ Compiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
pushParams: function(params) {
|
||||
pushParams: function (params) {
|
||||
for (let i = 0, l = params.length; i < l; i++) {
|
||||
this.pushParam(params[i]);
|
||||
}
|
||||
},
|
||||
|
||||
pushParam: function(val) {
|
||||
let value = val.value != null ? val.value : val.original || '';
|
||||
|
||||
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);
|
||||
}
|
||||
pushParam: function (val) {
|
||||
this.accept(val);
|
||||
},
|
||||
|
||||
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
|
||||
setupFullMustacheParams: function (sexpr, program, inverse, omitEmpty) {
|
||||
let params = sexpr.params;
|
||||
this.pushParams(params);
|
||||
|
||||
@@ -454,77 +424,82 @@ Compiler.prototype = {
|
||||
return params;
|
||||
},
|
||||
|
||||
blockParamIndex: function(name) {
|
||||
for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
|
||||
blockParamIndex: function (name) {
|
||||
for (
|
||||
let depth = 0, len = this.options.blockParams.length;
|
||||
depth < len;
|
||||
depth++
|
||||
) {
|
||||
let blockParams = this.options.blockParams[depth],
|
||||
param = blockParams && indexOf(blockParams, name);
|
||||
param = blockParams && indexOf(blockParams, name);
|
||||
if (blockParams && param >= 0) {
|
||||
return [depth, param];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export function precompile(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.precompile. You passed ' + input);
|
||||
}
|
||||
export function precompile(input, options = {}, env) {
|
||||
validateInput(input, options);
|
||||
|
||||
options = options || {};
|
||||
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);
|
||||
let environment = compileEnvironment(input, options, env);
|
||||
return new env.JavaScriptCompiler().compile(environment, options);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
validateInput(input, options);
|
||||
|
||||
let compiled;
|
||||
|
||||
function compileInput() {
|
||||
let environment = compileEnvironment(input, options, env),
|
||||
templateSpec = new env.JavaScriptCompiler().compile(
|
||||
environment,
|
||||
options,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
return env.template(templateSpec);
|
||||
}
|
||||
|
||||
// Template is only compiled on first use and cached after that point.
|
||||
return function (context, execOptions) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled.call(this, context, execOptions);
|
||||
};
|
||||
}
|
||||
|
||||
function validateInput(input, options) {
|
||||
if (
|
||||
input == null ||
|
||||
(typeof input !== 'string' && input.type !== 'Program')
|
||||
) {
|
||||
throw new Exception(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
|
||||
input
|
||||
);
|
||||
}
|
||||
|
||||
if (options.trackIds || options.stringParams) {
|
||||
throw new Exception(
|
||||
'TrackIds and stringParams are no longer supported. See Github #1145'
|
||||
);
|
||||
}
|
||||
|
||||
options = extend({}, options);
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
if (options.compat) {
|
||||
options.useDepths = true;
|
||||
}
|
||||
|
||||
let compiled;
|
||||
|
||||
function compileInput() {
|
||||
let ast = env.parse(input, options),
|
||||
environment = new env.Compiler().compile(ast, options),
|
||||
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
|
||||
return env.template(templateSpec);
|
||||
}
|
||||
|
||||
// Template is only compiled on first use and cached after that point.
|
||||
function ret(context, execOptions) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled.call(this, context, execOptions);
|
||||
}
|
||||
ret._setup = function(setupOptions) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled._setup(setupOptions);
|
||||
};
|
||||
ret._child = function(i, data, blockParams, depths) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled._child(i, data, blockParams, depths);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
function compileEnvironment(input, options, env) {
|
||||
let ast = env.parse(input, options);
|
||||
return new env.Compiler().compile(ast, options);
|
||||
}
|
||||
|
||||
function argEquals(a, b) {
|
||||
@@ -553,7 +528,7 @@ function transformLiteralToPath(sexpr) {
|
||||
depth: 0,
|
||||
parts: [literal.original + ''],
|
||||
original: literal.original + '',
|
||||
loc: literal.loc
|
||||
loc: literal.loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,210 +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)
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,171 +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,129 +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,216 +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 preceeded 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;
|
||||
@@ -3,4 +3,3 @@ import registerInline from './decorators/inline';
|
||||
export function registerDefaultDecorators(instance) {
|
||||
registerInline(instance);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import {extend} from '../utils';
|
||||
import { extend } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerDecorator('inline', function(fn, props, container, options) {
|
||||
let ret = fn;
|
||||
if (!props.partials) {
|
||||
props.partials = {};
|
||||
ret = function(context, options) {
|
||||
// Create a new partials stack frame prior to exec.
|
||||
let original = container.partials;
|
||||
container.partials = extend({}, original, props.partials);
|
||||
let ret = fn(context, options);
|
||||
container.partials = original;
|
||||
return ret;
|
||||
};
|
||||
export default function (instance) {
|
||||
instance.registerDecorator(
|
||||
'inline',
|
||||
function (fn, props, container, options) {
|
||||
let ret = fn;
|
||||
if (!props.partials) {
|
||||
props.partials = {};
|
||||
ret = function (context, options) {
|
||||
// Create a new partials stack frame prior to exec.
|
||||
let original = container.partials;
|
||||
container.partials = extend({}, original, props.partials);
|
||||
let ret = fn(context, options);
|
||||
container.partials = original;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
props.partials[options.args[0]] = options.fn;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
props.partials[options.args[0]] = options.fn;
|
||||
|
||||
return ret;
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
|
||||
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(message, node) {
|
||||
let loc = node && node.loc,
|
||||
line,
|
||||
column;
|
||||
if (loc) {
|
||||
line = loc.start.line;
|
||||
column = loc.start.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;
|
||||
|
||||
// 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
|
||||
});
|
||||
} else {
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
} catch (nop) {
|
||||
/* Ignore if the browser is very particular */
|
||||
}
|
||||
}
|
||||
|
||||
Exception.prototype = new Error();
|
||||
|
||||
export default Exception;
|
||||
@@ -15,3 +15,13 @@ export function registerDefaultHelpers(instance) {
|
||||
registerLookup(instance);
|
||||
registerWith(instance);
|
||||
}
|
||||
|
||||
export function moveHelperToHooks(instance, helperName, keepHelper) {
|
||||
if (instance.helpers[helperName]) {
|
||||
instance.hooks[helperName] = instance.helpers[helperName];
|
||||
if (!keepHelper) {
|
||||
// Using delete is slow
|
||||
instance.helpers[helperName] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {appendContextPath, createFrame, isArray} from '../utils';
|
||||
import { isArray } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('blockHelperMissing', function (context, options) {
|
||||
let inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
fn = options.fn;
|
||||
|
||||
if (context === true) {
|
||||
return fn(this);
|
||||
@@ -11,21 +11,11 @@ export default function(instance) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if (context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,71 +1,84 @@
|
||||
import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils';
|
||||
import Exception from '../exception';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, isArray, isFunction, isMap, isSet } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('each', function (context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
let fn = options.fn,
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data,
|
||||
contextPath;
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data;
|
||||
|
||||
if (options.data && options.ids) {
|
||||
contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
if (isFunction(context)) {
|
||||
context = context.call(this);
|
||||
}
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
function execIteration(field, index, last) {
|
||||
function execIteration(field, value, index, last) {
|
||||
if (data) {
|
||||
data.key = field;
|
||||
data.index = index;
|
||||
data.first = index === 0;
|
||||
data.last = !!last;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + field;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ret + fn(context[field], {
|
||||
data: data,
|
||||
blockParams: blockParams([context[field], field], [contextPath + field, null])
|
||||
});
|
||||
ret =
|
||||
ret +
|
||||
fn(value, {
|
||||
data: data,
|
||||
blockParams: [context[field], field],
|
||||
});
|
||||
}
|
||||
|
||||
if (context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for (let j = context.length; i < j; i++) {
|
||||
if (i in context) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
execIteration(i, context[i], i, i === context.length - 1);
|
||||
}
|
||||
}
|
||||
} else if (isMap(context)) {
|
||||
const j = context.size;
|
||||
for (const [key, value] of context) {
|
||||
execIteration(key, value, i++, i === j);
|
||||
}
|
||||
} else if (isSet(context)) {
|
||||
const j = context.size;
|
||||
for (const value of context) {
|
||||
execIteration(i, value, i++, i === j);
|
||||
}
|
||||
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
|
||||
const newContext = [];
|
||||
const iterator = context[Symbol.iterator]();
|
||||
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||
newContext.push(it.value);
|
||||
}
|
||||
context = newContext;
|
||||
for (let j = context.length; i < j; i++) {
|
||||
execIteration(i, context[i], i, i === context.length - 1);
|
||||
}
|
||||
} else {
|
||||
let priorKey;
|
||||
|
||||
for (let key in context) {
|
||||
if (context.hasOwnProperty(key)) {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an itermediate keys array.
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1);
|
||||
}
|
||||
priorKey = key;
|
||||
i++;
|
||||
Object.keys(context).forEach((key) => {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an intermediate keys array.
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, context[priorKey], i - 1);
|
||||
}
|
||||
}
|
||||
priorKey = key;
|
||||
i++;
|
||||
});
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1, true);
|
||||
execIteration(priorKey, context[priorKey], i - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import Exception from '../exception';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('helperMissing', function (/* [args, ]options */) {
|
||||
if (arguments.length === 1) {
|
||||
// A missing field in a {{foo}} construct.
|
||||
return undefined;
|
||||
} else {
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
|
||||
throw new Exception(
|
||||
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import {isEmpty, isFunction} from '../utils';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||
export default function (instance) {
|
||||
instance.registerHelper('if', function (conditional, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#if requires exactly one argument');
|
||||
}
|
||||
if (isFunction(conditional)) {
|
||||
conditional = conditional.call(this);
|
||||
}
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the 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.
|
||||
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
@@ -14,7 +20,14 @@ export default function(instance) {
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||
instance.registerHelper('unless', function (conditional, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#unless requires exactly one argument');
|
||||
}
|
||||
return instance.helpers['if'].call(this, conditional, {
|
||||
fn: options.inverse,
|
||||
inverse: options.fn,
|
||||
hash: options.hash,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('log', function(/* message, options */) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('log', function (/* message, options */) {
|
||||
let args = [undefined],
|
||||
options = arguments[arguments.length - 1];
|
||||
options = arguments[arguments.length - 1];
|
||||
for (let i = 0; i < arguments.length - 1; i++) {
|
||||
args.push(arguments[i]);
|
||||
}
|
||||
@@ -14,6 +14,6 @@ export default function(instance) {
|
||||
}
|
||||
args[0] = level;
|
||||
|
||||
instance.log(... args);
|
||||
instance.log(...args);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('lookup', function (obj, field, options) {
|
||||
if (!obj) {
|
||||
// Note for 5.0: Change to "obj == null" in 5.0
|
||||
return obj;
|
||||
}
|
||||
if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
|
||||
return undefined;
|
||||
}
|
||||
return obj[field];
|
||||
return options.lookupProperty(obj, field);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import {appendContextPath, blockParams, createFrame, isEmpty, isFunction} from '../utils';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
export default function (instance) {
|
||||
instance.registerHelper('with', function (context, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#with requires exactly one argument');
|
||||
}
|
||||
if (isFunction(context)) {
|
||||
context = context.call(this);
|
||||
}
|
||||
|
||||
let fn = options.fn;
|
||||
|
||||
if (!isEmpty(context)) {
|
||||
let data = options.data;
|
||||
if (options.data && options.ids) {
|
||||
data = createFrame(options.data);
|
||||
data.contextPath = appendContextPath(options.data.contextPath, options.ids[0]);
|
||||
}
|
||||
|
||||
return fn(context, {
|
||||
data: data,
|
||||
blockParams: blockParams([context], [data && data.contextPath])
|
||||
blockParams: [context],
|
||||
});
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { extend } from '../utils';
|
||||
import logger from '../logger';
|
||||
|
||||
const loggedProperties = Object.create(null);
|
||||
|
||||
export function createProtoAccessControl(runtimeOptions) {
|
||||
// Create an object with "null"-prototype to avoid truthy results on
|
||||
// prototype properties.
|
||||
const propertyWhiteList = Object.create(null);
|
||||
// eslint-disable-next-line no-proto
|
||||
propertyWhiteList['__proto__'] = false;
|
||||
extend(propertyWhiteList, runtimeOptions.allowedProtoProperties);
|
||||
|
||||
const methodWhiteList = Object.create(null);
|
||||
methodWhiteList['constructor'] = false;
|
||||
methodWhiteList['__defineGetter__'] = false;
|
||||
methodWhiteList['__defineSetter__'] = false;
|
||||
methodWhiteList['__lookupGetter__'] = false;
|
||||
extend(methodWhiteList, runtimeOptions.allowedProtoMethods);
|
||||
|
||||
return {
|
||||
properties: {
|
||||
whitelist: propertyWhiteList,
|
||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault,
|
||||
},
|
||||
methods: {
|
||||
whitelist: methodWhiteList,
|
||||
defaultValue: runtimeOptions.allowProtoMethodsByDefault,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function resultIsAllowed(result, protoAccessControl, propertyName) {
|
||||
if (typeof result === 'function') {
|
||||
return checkWhiteList(protoAccessControl.methods, propertyName);
|
||||
} else {
|
||||
return checkWhiteList(protoAccessControl.properties, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
function checkWhiteList(protoAccessControlForType, propertyName) {
|
||||
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
|
||||
return protoAccessControlForType.whitelist[propertyName] === true;
|
||||
}
|
||||
if (protoAccessControlForType.defaultValue !== undefined) {
|
||||
return protoAccessControlForType.defaultValue;
|
||||
}
|
||||
logUnexpectedPropertyAccessOnce(propertyName);
|
||||
return false;
|
||||
}
|
||||
|
||||
function logUnexpectedPropertyAccessOnce(propertyName) {
|
||||
if (loggedProperties[propertyName] !== true) {
|
||||
loggedProperties[propertyName] = true;
|
||||
logger.log(
|
||||
'error',
|
||||
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
|
||||
`You can add a runtime option to disable the check or this warning:\n` +
|
||||
`See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function resetLoggedProperties() {
|
||||
Object.keys(loggedProperties).forEach((propertyName) => {
|
||||
delete loggedProperties[propertyName];
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function wrapHelper(helper, transformOptionsFn) {
|
||||
if (typeof helper !== 'function') {
|
||||
// This should not happen, but apparently it does in https://github.com/handlebars-lang/handlebars.js/issues/1639
|
||||
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
|
||||
return helper;
|
||||
}
|
||||
let wrapper = function (/* dynamic arguments */) {
|
||||
const options = arguments[arguments.length - 1];
|
||||
arguments[arguments.length - 1] = transformOptionsFn(options);
|
||||
return helper.apply(this, arguments);
|
||||
};
|
||||
return wrapper;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import {indexOf} from './utils';
|
||||
import { indexOf } from './utils';
|
||||
|
||||
let logger = {
|
||||
methodMap: ['debug', 'info', 'warn', 'error'],
|
||||
level: 'info',
|
||||
|
||||
// Maps a given level value to the `methodMap` indexes above.
|
||||
lookupLevel: function(level) {
|
||||
lookupLevel: function (level) {
|
||||
if (typeof level === 'string') {
|
||||
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
|
||||
if (levelMap >= 0) {
|
||||
@@ -19,17 +19,21 @@ let logger = {
|
||||
},
|
||||
|
||||
// Can be overridden in the host environment
|
||||
log: function(level, ...message) {
|
||||
log: function (level, ...message) {
|
||||
level = logger.lookupLevel(level);
|
||||
|
||||
if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
|
||||
if (
|
||||
typeof console !== 'undefined' &&
|
||||
logger.lookupLevel(logger.level) <= level
|
||||
) {
|
||||
let method = logger.methodMap[level];
|
||||
if (!console[method]) { // eslint-disable-line no-console
|
||||
// eslint-disable-next-line no-console
|
||||
if (!console[method]) {
|
||||
method = 'log';
|
||||
}
|
||||
console[method](...message); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default logger;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/* global window */
|
||||
export default function(Handlebars) {
|
||||
/* istanbul ignore next */
|
||||
let root = typeof global !== 'undefined' ? global : window,
|
||||
$Handlebars = root.Handlebars;
|
||||
/* istanbul ignore next */
|
||||
Handlebars.noConflict = function() {
|
||||
if (root.Handlebars === Handlebars) {
|
||||
root.Handlebars = $Handlebars;
|
||||
export default function (Handlebars) {
|
||||
let $Handlebars = globalThis.Handlebars;
|
||||
|
||||
/* v8 ignore next */
|
||||
Handlebars.noConflict = function () {
|
||||
if (globalThis.Handlebars === Handlebars) {
|
||||
globalThis.Handlebars = $Handlebars;
|
||||
}
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
+222
-75
@@ -1,27 +1,53 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as Utils from './utils';
|
||||
import Exception from './exception';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
|
||||
import {
|
||||
COMPILER_REVISION,
|
||||
createFrame,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES,
|
||||
} from './base';
|
||||
import { moveHelperToHooks } from './helpers';
|
||||
import { wrapHelper } from './internal/wrapHelper';
|
||||
import {
|
||||
createProtoAccessControl,
|
||||
resultIsAllowed,
|
||||
} from './internal/proto-access';
|
||||
|
||||
export function checkRevision(compilerInfo) {
|
||||
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||
'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||
'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
|
||||
}
|
||||
if (
|
||||
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
|
||||
compilerRevision <= COMPILER_REVISION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
|
||||
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception(
|
||||
'Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||
'Please update your precompiler to a newer version (' +
|
||||
runtimeVersions +
|
||||
') or downgrade your runtime to an older version (' +
|
||||
compilerVersions +
|
||||
').'
|
||||
);
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception(
|
||||
'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||
'Please update your runtime to a newer version (' +
|
||||
compilerInfo[1] +
|
||||
').'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function template(templateSpec, env) {
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
if (!env) {
|
||||
throw new Exception('No environment passed to template');
|
||||
}
|
||||
@@ -32,22 +58,30 @@ export function template(templateSpec, env) {
|
||||
templateSpec.main.decorator = templateSpec.main_d;
|
||||
|
||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||
// for external users to override these as psuedo-supported APIs.
|
||||
// for external users to override these as pseudo-supported APIs.
|
||||
env.VM.checkRevision(templateSpec.compiler);
|
||||
|
||||
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
|
||||
const templateWasPrecompiledWithCompilerV7 =
|
||||
templateSpec.compiler && templateSpec.compiler[0] === 7;
|
||||
|
||||
function invokePartialWrapper(partial, context, options) {
|
||||
if (options.hash) {
|
||||
context = Utils.extend({}, context, options.hash);
|
||||
if (options.ids) {
|
||||
options.ids[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||
|
||||
options.hooks = this.hooks;
|
||||
options.protoAccessControl = this.protoAccessControl;
|
||||
|
||||
let result = env.VM.invokePartial.call(this, partial, context, options);
|
||||
|
||||
if (result == null && env.compile) {
|
||||
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
|
||||
options.partials[options.name] = env.compile(
|
||||
partial,
|
||||
templateSpec.compilerOptions,
|
||||
env
|
||||
);
|
||||
result = options.partials[options.name](context, options);
|
||||
}
|
||||
if (result != null) {
|
||||
@@ -64,61 +98,94 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
|
||||
throw new Exception(
|
||||
'The partial ' +
|
||||
options.name +
|
||||
' could not be compiled when running in runtime-only mode'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Just add water
|
||||
let container = {
|
||||
strict: function(obj, name) {
|
||||
if (!(name in obj)) {
|
||||
throw new Exception('"' + name + '" not defined in ' + obj);
|
||||
strict: function (obj, name, loc) {
|
||||
if (!obj || !(name in obj)) {
|
||||
throw new Exception('"' + name + '" not defined in ' + obj, {
|
||||
loc: loc,
|
||||
});
|
||||
}
|
||||
return obj[name];
|
||||
return container.lookupProperty(obj, name);
|
||||
},
|
||||
lookup: function(depths, name) {
|
||||
lookupProperty: function (parent, propertyName) {
|
||||
if (Utils.isMap(parent)) {
|
||||
return parent.get(propertyName);
|
||||
}
|
||||
|
||||
let result = parent[propertyName];
|
||||
if (result == null) {
|
||||
return result;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
|
||||
return result;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
lookup: function (depths, name) {
|
||||
const len = depths.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (depths[i] && depths[i][name] != null) {
|
||||
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||
if (result != null) {
|
||||
return depths[i][name];
|
||||
}
|
||||
}
|
||||
},
|
||||
lambda: function(current, context) {
|
||||
lambda: function (current, context) {
|
||||
return typeof current === 'function' ? current.call(context) : current;
|
||||
},
|
||||
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
fn: function (i) {
|
||||
let ret = templateSpec[i];
|
||||
ret.decorator = templateSpec[i + '_d'];
|
||||
return ret;
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
||||
program: function (i, data, declaredBlockParams, blockParams, depths) {
|
||||
let programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
fn = this.fn(i);
|
||||
if (data || depths || blockParams || declaredBlockParams) {
|
||||
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
|
||||
programWrapper = wrapProgram(
|
||||
this,
|
||||
i,
|
||||
fn,
|
||||
data,
|
||||
declaredBlockParams,
|
||||
blockParams,
|
||||
depths
|
||||
);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
|
||||
data: function(value, depth) {
|
||||
data: function (value, depth) {
|
||||
while (value && depth--) {
|
||||
value = value._parent;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
mergeIfNeeded: function (param, common) {
|
||||
let obj = param || common;
|
||||
|
||||
if (param && common && (param !== common)) {
|
||||
if (param && common && param !== common) {
|
||||
obj = Utils.extend({}, common, param);
|
||||
}
|
||||
|
||||
@@ -128,77 +195,127 @@ export function template(templateSpec, env) {
|
||||
nullContext: Object.seal({}),
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
compilerInfo: templateSpec.compiler,
|
||||
};
|
||||
|
||||
function ret(context, options = {}) {
|
||||
let data = options.data;
|
||||
|
||||
ret._setup(options);
|
||||
_setup(options);
|
||||
if (!options.partial && templateSpec.useData) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
let depths,
|
||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||
if (templateSpec.useDepths) {
|
||||
if (options.depths) {
|
||||
depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
|
||||
depths =
|
||||
context != options.depths[0]
|
||||
? [context].concat(options.depths)
|
||||
: options.depths;
|
||||
} else {
|
||||
depths = [context];
|
||||
}
|
||||
}
|
||||
|
||||
function main(context/*, options*/) {
|
||||
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
|
||||
function main(context /*, options*/) {
|
||||
return (
|
||||
'' +
|
||||
templateSpec.main(
|
||||
container,
|
||||
context,
|
||||
container.helpers,
|
||||
container.partials,
|
||||
data,
|
||||
blockParams,
|
||||
depths
|
||||
)
|
||||
);
|
||||
}
|
||||
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
|
||||
|
||||
main = executeDecorators(
|
||||
templateSpec.main,
|
||||
main,
|
||||
container,
|
||||
options.depths || [],
|
||||
data,
|
||||
blockParams
|
||||
);
|
||||
return main(context, options);
|
||||
}
|
||||
|
||||
ret.isTop = true;
|
||||
|
||||
ret._setup = function(options) {
|
||||
function _setup(options) {
|
||||
if (!options.partial) {
|
||||
container.helpers = container.merge(options.helpers, env.helpers);
|
||||
let mergedHelpers = {};
|
||||
addHelpers(mergedHelpers, env.helpers, container);
|
||||
addHelpers(mergedHelpers, options.helpers, container);
|
||||
container.helpers = mergedHelpers;
|
||||
|
||||
if (templateSpec.usePartial) {
|
||||
container.partials = container.merge(options.partials, env.partials);
|
||||
// Use mergeIfNeeded here to prevent compiling global partials multiple times
|
||||
container.partials = container.mergeIfNeeded(
|
||||
options.partials,
|
||||
env.partials
|
||||
);
|
||||
}
|
||||
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
||||
container.decorators = container.merge(options.decorators, env.decorators);
|
||||
container.decorators = Utils.extend(
|
||||
{},
|
||||
env.decorators,
|
||||
options.decorators
|
||||
);
|
||||
}
|
||||
|
||||
container.hooks = {};
|
||||
container.protoAccessControl = createProtoAccessControl(options);
|
||||
|
||||
let keepHelperInHelpers =
|
||||
options.allowCallsToHelperMissing ||
|
||||
templateWasPrecompiledWithCompilerV7;
|
||||
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
|
||||
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
|
||||
} else {
|
||||
container.protoAccessControl = options.protoAccessControl; // internal option
|
||||
container.helpers = options.helpers;
|
||||
container.partials = options.partials;
|
||||
container.decorators = options.decorators;
|
||||
container.hooks = options.hooks;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
|
||||
export function wrapProgram(
|
||||
container,
|
||||
i,
|
||||
fn,
|
||||
data,
|
||||
declaredBlockParams,
|
||||
blockParams,
|
||||
depths
|
||||
) {
|
||||
function prog(context, options = {}) {
|
||||
let currentDepths = depths;
|
||||
if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
|
||||
if (
|
||||
depths &&
|
||||
context != depths[0] &&
|
||||
!(context === container.nullContext && depths[0] === null)
|
||||
) {
|
||||
currentDepths = [context].concat(depths);
|
||||
}
|
||||
|
||||
return fn(container,
|
||||
context,
|
||||
container.helpers, container.partials,
|
||||
options.data || data,
|
||||
blockParams && [options.blockParams].concat(blockParams),
|
||||
currentDepths);
|
||||
return fn(
|
||||
container,
|
||||
context,
|
||||
container.helpers,
|
||||
container.partials,
|
||||
options.data || data,
|
||||
blockParams && [options.blockParams].concat(blockParams),
|
||||
currentDepths
|
||||
);
|
||||
}
|
||||
|
||||
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
|
||||
@@ -209,6 +326,9 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
|
||||
return prog;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is currently part of the official API, therefore implementation details should not be changed.
|
||||
*/
|
||||
export function resolvePartial(partial, context, options) {
|
||||
if (!partial) {
|
||||
if (options.name === '@partial-block') {
|
||||
@@ -228,17 +348,16 @@ export function invokePartial(partial, context, options) {
|
||||
// Use the current closure context to save the partial-block if this partial
|
||||
const currentPartialBlock = options.data && options.data['partial-block'];
|
||||
options.partial = true;
|
||||
if (options.ids) {
|
||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||
}
|
||||
|
||||
let partialBlock;
|
||||
if (options.fn && options.fn !== noop) {
|
||||
options.data = createFrame(options.data);
|
||||
// Wrapper function to get access to currentPartialBlock from the closure
|
||||
let fn = options.fn;
|
||||
partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options = {}) {
|
||||
|
||||
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
|
||||
context,
|
||||
options = {}
|
||||
) {
|
||||
// Restore the partial-block from the closure for the execution of the block
|
||||
// i.e. the part inside the block of the partial call.
|
||||
options.data = createFrame(options.data);
|
||||
@@ -255,13 +374,17 @@ export function invokePartial(partial, context, options) {
|
||||
}
|
||||
|
||||
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) {
|
||||
return partial(context, options);
|
||||
}
|
||||
}
|
||||
|
||||
export function noop() { return ''; }
|
||||
export function noop() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function initData(context, data) {
|
||||
if (!data || !('root' in data)) {
|
||||
@@ -274,8 +397,32 @@ function initData(context, data) {
|
||||
function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
||||
if (fn.decorator) {
|
||||
let props = {};
|
||||
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
|
||||
prog = fn.decorator(
|
||||
prog,
|
||||
props,
|
||||
container,
|
||||
depths && depths[0],
|
||||
data,
|
||||
blockParams,
|
||||
depths
|
||||
);
|
||||
Utils.extend(prog, props);
|
||||
}
|
||||
return prog;
|
||||
}
|
||||
|
||||
function addHelpers(mergedHelpers, helpers, container) {
|
||||
if (!helpers) return;
|
||||
Object.keys(helpers).forEach((helperName) => {
|
||||
let helper = helpers[helperName];
|
||||
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||
});
|
||||
}
|
||||
|
||||
function passLookupPropertyOption(helper, container) {
|
||||
const lookupProperty = container.lookupProperty;
|
||||
return wrapHelper(helper, (options) => {
|
||||
options.lookupProperty = lookupProperty;
|
||||
return options;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ function SafeString(string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
|
||||
SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
|
||||
return '' + this.string;
|
||||
};
|
||||
|
||||
|
||||
+19
-29
@@ -5,17 +5,17 @@ const escape = {
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
'=': '=',
|
||||
};
|
||||
|
||||
const badChars = /[&<>"'`=]/g,
|
||||
possible = /[&<>"'`=]/;
|
||||
possible = /[&<>"'`=]/;
|
||||
|
||||
function escapeChar(chr) {
|
||||
return escape[chr];
|
||||
}
|
||||
|
||||
export function extend(obj/* , ...source */) {
|
||||
export function extend(obj /* , ...source */) {
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
for (let key in arguments[i]) {
|
||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||
@@ -30,25 +30,23 @@ export function extend(obj/* , ...source */) {
|
||||
export let toString = Object.prototype.toString;
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
/* eslint-disable func-style */
|
||||
let isFunction = function(value) {
|
||||
// https://github.com/lodash/lodash/blob/4.17.21/LICENSE
|
||||
export function isFunction(value) {
|
||||
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]';
|
||||
}
|
||||
|
||||
function testTag(name) {
|
||||
const tag = '[object ' + name + ']';
|
||||
return function (value) {
|
||||
return value && typeof value === 'object'
|
||||
? toString.call(value) === tag
|
||||
: false;
|
||||
};
|
||||
}
|
||||
export {isFunction};
|
||||
/* eslint-enable func-style */
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isArray = Array.isArray || function(value) {
|
||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
||||
};
|
||||
export const isArray = Array.isArray;
|
||||
export const isMap = testTag('Map');
|
||||
export const isSet = testTag('Set');
|
||||
|
||||
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
|
||||
export function indexOf(array, value) {
|
||||
@@ -60,7 +58,6 @@ export function indexOf(array, value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
export function escapeExpression(string) {
|
||||
if (typeof string !== 'string') {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
@@ -78,7 +75,9 @@ export function escapeExpression(string) {
|
||||
string = '' + string;
|
||||
}
|
||||
|
||||
if (!possible.test(string)) { return string; }
|
||||
if (!possible.test(string)) {
|
||||
return string;
|
||||
}
|
||||
return string.replace(badChars, escapeChar);
|
||||
}
|
||||
|
||||
@@ -97,12 +96,3 @@ export function createFrame(object) {
|
||||
frame._parent = object;
|
||||
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 printer = require('../dist/cjs/handlebars/compiler/printer');
|
||||
handlebars.PrintVisitor = printer.PrintVisitor;
|
||||
handlebars.print = printer.print;
|
||||
var parser = require('@handlebars/parser');
|
||||
handlebars.PrintVisitor = parser.PrintVisitor;
|
||||
handlebars.print = parser.print;
|
||||
|
||||
module.exports = handlebars;
|
||||
|
||||
@@ -18,7 +18,7 @@ function extension(module, filename) {
|
||||
var templateString = fs.readFileSync(filename, 'utf8');
|
||||
module.exports = handlebars.compile(templateString);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
/* v8 ignore next 4 */
|
||||
if (typeof require !== 'undefined' && require.extensions) {
|
||||
require.extensions['.handlebars'] = extension;
|
||||
require.extensions['.hbs'] = extension;
|
||||
|
||||
+136
-90
@@ -1,17 +1,16 @@
|
||||
/* eslint-disable no-console */
|
||||
import Async from 'neo-async';
|
||||
import fs from 'fs';
|
||||
import * as Handlebars from './handlebars';
|
||||
import {basename} from 'path';
|
||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
||||
import Handlebars from './handlebars';
|
||||
import { basename } from 'path';
|
||||
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||
|
||||
|
||||
module.exports.loadTemplates = function(opts, callback) {
|
||||
loadStrings(opts, function(err, strings) {
|
||||
module.exports.loadTemplates = function (opts, callback) {
|
||||
loadStrings(opts, function (err, strings) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
loadFiles(opts, function(err, files) {
|
||||
loadFiles(opts, function (err, files) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
@@ -25,14 +24,19 @@ module.exports.loadTemplates = function(opts, callback) {
|
||||
|
||||
function loadStrings(opts, callback) {
|
||||
let strings = arrayCast(opts.string),
|
||||
names = arrayCast(opts.name);
|
||||
names = arrayCast(opts.name);
|
||||
|
||||
if (names.length !== strings.length
|
||||
&& strings.length > 1) {
|
||||
return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
|
||||
if (names.length !== strings.length && strings.length > 1) {
|
||||
return callback(
|
||||
new Handlebars.Exception(
|
||||
'Number of names did not match the number of string inputs'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Async.map(strings, function(string, callback) {
|
||||
Async.map(
|
||||
strings,
|
||||
function (string, callback) {
|
||||
if (string !== '-') {
|
||||
callback(undefined, string);
|
||||
} else {
|
||||
@@ -40,105 +44,124 @@ function loadStrings(opts, callback) {
|
||||
let buffer = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('data', function(chunk) {
|
||||
process.stdin.on('data', function (chunk) {
|
||||
buffer += chunk;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
process.stdin.on('end', function () {
|
||||
callback(undefined, buffer);
|
||||
});
|
||||
}
|
||||
},
|
||||
function(err, strings) {
|
||||
function (err, strings) {
|
||||
strings = strings.map((string, index) => ({
|
||||
name: names[index],
|
||||
path: names[index],
|
||||
source: string
|
||||
source: string,
|
||||
}));
|
||||
callback(err, strings);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function loadFiles(opts, callback) {
|
||||
// Build file extension pattern
|
||||
let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-[\]]/g, function(arg) { return '\\' + arg; });
|
||||
let extension = (opts.extension || 'handlebars').replace(
|
||||
/[\\^$*+?.():=!|{}\-[\]]/g,
|
||||
function (arg) {
|
||||
return '\\' + arg;
|
||||
}
|
||||
);
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let ret = [],
|
||||
queue = (opts.files || []).map((template) => ({template, root: opts.root}));
|
||||
Async.whilst(() => queue.length, function(callback) {
|
||||
let {template: path, root} = queue.shift();
|
||||
queue = (opts.files || []).map((template) => ({
|
||||
template,
|
||||
root: opts.root,
|
||||
}));
|
||||
Async.whilst(
|
||||
() => queue.length,
|
||||
function (callback) {
|
||||
let { template: path, root } = queue.shift();
|
||||
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (err) {
|
||||
return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
|
||||
}
|
||||
fs.stat(path, function (err, stat) {
|
||||
if (err) {
|
||||
return callback(
|
||||
new Handlebars.Exception(`Unable to open template file "${path}"`)
|
||||
);
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
opts.hasDirectory = true;
|
||||
if (stat.isDirectory()) {
|
||||
opts.hasDirectory = true;
|
||||
|
||||
fs.readdir(path, function(err, children) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
children.forEach(function(file) {
|
||||
let childPath = path + '/' + file;
|
||||
|
||||
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
||||
queue.push({template: childPath, root: root || path});
|
||||
fs.readdir(path, function (err, children) {
|
||||
/* v8 ignore next -- Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
children.forEach(function (file) {
|
||||
let childPath = path + '/' + file;
|
||||
|
||||
callback();
|
||||
});
|
||||
if (
|
||||
extension.test(childPath) ||
|
||||
fs.statSync(childPath).isDirectory()
|
||||
) {
|
||||
queue.push({ template: childPath, root: root || path });
|
||||
}
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path, 'utf8', function (err, data) {
|
||||
/* v8 ignore next -- Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
let name = path;
|
||||
if (!root) {
|
||||
name = basename(name);
|
||||
} else if (name.indexOf(root) === 0) {
|
||||
name = name.substring(root.length + 1);
|
||||
}
|
||||
name = name.replace(extension, '');
|
||||
|
||||
ret.push({
|
||||
path: path,
|
||||
name: name,
|
||||
source: data,
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
fs.readFile(path, 'utf8', function(err, data) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
let name = path;
|
||||
if (!root) {
|
||||
name = basename(name);
|
||||
} else if (name.indexOf(root) === 0) {
|
||||
name = name.substring(root.length + 1);
|
||||
}
|
||||
name = name.replace(extension, '');
|
||||
|
||||
ret.push({
|
||||
path: path,
|
||||
name: name,
|
||||
source: data
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
callback(undefined, ret);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(undefined, ret);
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
module.exports.cli = async function (opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.templates.length && !opts.hasDirectory) {
|
||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
||||
throw new Handlebars.Exception(
|
||||
'Must define at least one template or directory.'
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.simple && opts.min) {
|
||||
@@ -147,12 +170,18 @@ module.exports.cli = function(opts) {
|
||||
|
||||
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
|
||||
if (opts.simple && multiple) {
|
||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
||||
throw new Handlebars.Exception(
|
||||
'Unable to output multiple templates in simple mode'
|
||||
);
|
||||
}
|
||||
|
||||
// Force simple mode if we have only one template and it's unnamed.
|
||||
if (!opts.amd && !opts.commonjs && opts.templates.length === 1
|
||||
&& !opts.templates[0].name) {
|
||||
if (
|
||||
!opts.amd &&
|
||||
!opts.commonjs &&
|
||||
opts.templates.length === 1 &&
|
||||
!opts.templates[0].name
|
||||
) {
|
||||
opts.simple = true;
|
||||
}
|
||||
|
||||
@@ -172,7 +201,11 @@ module.exports.cli = function(opts) {
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
output.add(
|
||||
"define(['" +
|
||||
opts.handlebarPath +
|
||||
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||
);
|
||||
} else if (opts.commonjs) {
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else {
|
||||
@@ -188,10 +221,10 @@ module.exports.cli = function(opts) {
|
||||
output.add('{};\n');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
for (const template of opts.templates) {
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
knownHelpersOnly: opts.o,
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
@@ -205,8 +238,12 @@ module.exports.cli = function(opts) {
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
let consumer = await new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(
|
||||
precompiled.code,
|
||||
consumer
|
||||
);
|
||||
consumer.destroy();
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
@@ -219,9 +256,16 @@ module.exports.cli = function(opts) {
|
||||
if (opts.amd && !multiple) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
|
||||
output.add([
|
||||
objectName,
|
||||
"['",
|
||||
template.name,
|
||||
"'] = template(",
|
||||
precompiled,
|
||||
');\n',
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
@@ -286,13 +330,15 @@ function minify(output, sourceMapFile) {
|
||||
throw e;
|
||||
}
|
||||
// it does not exist!
|
||||
console.error('Code minimization is disabled due to missing uglify-js dependency');
|
||||
console.error(
|
||||
'Code minimization is disabled due to missing uglify-js dependency'
|
||||
);
|
||||
return output;
|
||||
}
|
||||
return require('uglify-js').minify(output.code, {
|
||||
sourceMap: {
|
||||
content: output.map,
|
||||
url: sourceMapFile
|
||||
}
|
||||
url: sourceMapFile,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
module.exports = {
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"self": false
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
// overrides eslint:recommended defaults
|
||||
"no-sparse-arrays": "off",
|
||||
"no-func-assign": "off",
|
||||
"no-console": "off",
|
||||
"no-debugger": "warn",
|
||||
"no-unreachable": "warn",
|
||||
|
||||
// Possible Errors //
|
||||
//-----------------//
|
||||
"no-unsafe-negation": "error",
|
||||
|
||||
|
||||
// Best Practices //
|
||||
//----------------//
|
||||
"curly": "error",
|
||||
"default-case": "warn",
|
||||
"dot-notation": ["error", { "allowKeywords": false }],
|
||||
"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-spaces": "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",
|
||||
"wrap-iife": "error",
|
||||
|
||||
|
||||
// Variables //
|
||||
//-----------//
|
||||
"no-catch-shadow": "error",
|
||||
"no-label-var": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-use-before-define": ["error", "nofunc"],
|
||||
|
||||
|
||||
// Stylistic Issues //
|
||||
//------------------//
|
||||
"comma-dangle": ["error", "never"],
|
||||
"quote-props": ["error", "as-needed", { "keywords": true, "unnecessary": false }],
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"camelcase": "error",
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"comma-style": ["error", "last"],
|
||||
"consistent-this": ["warn", "self"],
|
||||
"eol-last": "error",
|
||||
"func-style": ["error", "declaration"],
|
||||
"key-spacing": ["error", {
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}],
|
||||
"new-cap": "error",
|
||||
"new-parens": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"no-nested-ternary": "warn",
|
||||
"no-new-object": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-extra-parens": ["error", "functions"],
|
||||
"quotes": ["error", "single", "avoid-escape"],
|
||||
"semi": "error",
|
||||
"semi-spacing": ["error", { "before": false, "after": true }],
|
||||
"keyword-spacing": "error",
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"space-before-function-paren": ["error", { "anonymous": "never", "named": "never" }],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": ["error", "always", { "markers": [","] }],
|
||||
"wrap-regex": "warn",
|
||||
|
||||
// ECMAScript 6 //
|
||||
//--------------//
|
||||
"no-var": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Author: Yehuda
|
||||
@@ -1,13 +0,0 @@
|
||||
// This test should run with node 0.10 as long as Handlebars has been compiled before
|
||||
var Handlebars = require('../');
|
||||
var fs = require('fs');
|
||||
|
||||
console.log('Testing build Handlebars with Node version ' + process.version);
|
||||
var template = fs.readFileSync(require.resolve('./template.txt.hbs'), 'utf-8');
|
||||
var compiledOutput = Handlebars.compile(template)({author: 'Yehuda'}).trim();
|
||||
var expectedOutput = fs.readFileSync(require.resolve('./expected.txt'), 'utf-8').trim();
|
||||
|
||||
if (compiledOutput !== expectedOutput) {
|
||||
throw new Error('Compiled output (' + compiledOutput + ') did not match expected output (' + expectedOutput + ')');
|
||||
}
|
||||
console.log('Success');
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$( dirname "$( readlink -f "$0" )" )"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
|
||||
# This script tests with precompiler and the built distribution with multiple NodeJS version.
|
||||
# The rest of the travis-build will only work with newer NodeJS versions, because the build
|
||||
# tools don't support older versions.
|
||||
# However, the built distribution should work with older NodeJS versions as well.
|
||||
# This test is simple by design. It merely ensures, that calling Handlebars does not fail with old versions.
|
||||
# It does (almost) not test for correctness, because that is already done in the mocha-tests.
|
||||
# And it does not use any NodeJS based testing framwork to make this part independent of the Node version.
|
||||
|
||||
# A list of NodeJS versions is expected as cli-args
|
||||
echo "Handlebars should be able to run in various versions of NodeJS"
|
||||
for i in "$@" ; do
|
||||
nvm install "$i"
|
||||
nvm exec "$i" node ./run-handlebars.js >/dev/null || exit 1
|
||||
nvm exec "$i" node ../bin/handlebars template.txt.hbs >/dev/null || exit 1
|
||||
echo Success
|
||||
done
|
||||
@@ -1 +0,0 @@
|
||||
Author: {{author}}
|
||||
Generated
+21836
-7173
File diff suppressed because it is too large
Load Diff
+109
-77
@@ -1,97 +1,129 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"barename": "handlebars",
|
||||
"version": "4.1.2-0",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"html",
|
||||
"mustache",
|
||||
"template",
|
||||
"html"
|
||||
"template"
|
||||
],
|
||||
"homepage": "https://handlebarsjs.com/",
|
||||
"license": "MIT",
|
||||
"author": "Yehuda Katz",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"author": "Yehuda Katz",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"neo-async": "^2.6.0",
|
||||
"optimist": "^0.6.1",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-sdk": "^2.1.49",
|
||||
"babel-loader": "^5.0.0",
|
||||
"babel-runtime": "^5.1.10",
|
||||
"benchmark": "~1.0",
|
||||
"dtslint": "^0.5.5",
|
||||
"dustjs-linkedin": "^2.0.2",
|
||||
"eco": "~1.1.0-rc-3",
|
||||
"grunt": "^1.0.3",
|
||||
"grunt-babel": "^5.0.0",
|
||||
"grunt-bg-shell": "^2.3.3",
|
||||
"grunt-cli": "^1",
|
||||
"grunt-contrib-clean": "^1",
|
||||
"grunt-contrib-concat": "^1",
|
||||
"grunt-contrib-connect": "^1",
|
||||
"grunt-contrib-copy": "^1",
|
||||
"grunt-contrib-requirejs": "^1",
|
||||
"grunt-contrib-uglify": "^1",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-eslint": "^20.1.0",
|
||||
"grunt-saucelabs": "9.x",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"istanbul": "^0.3.0",
|
||||
"jison": "~0.3.0",
|
||||
"mocha": "^5",
|
||||
"mock-stdin": "^0.3.0",
|
||||
"mustache": "^2.1.3",
|
||||
"semver": "^5.0.1",
|
||||
"typescript": "^3.4.3",
|
||||
"underscore": "^1.5.1",
|
||||
"webpack": "^1.12.6",
|
||||
"webpack-dev-server": "^1.12.1"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"browser": {
|
||||
".": "./dist/handlebars.min.js",
|
||||
"./runtime": "./dist/handlebars.runtime.min.js"
|
||||
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"scripts": {
|
||||
"checkTypes": "dtslint types",
|
||||
"test": "grunt"
|
||||
},
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
"directories": {
|
||||
"lib": "dist/amd"
|
||||
},
|
||||
"buildConfig": {
|
||||
"minify": true
|
||||
}
|
||||
"handlebars": "bin/handlebars.mjs"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist/*.js",
|
||||
"dist/amd/**/*.js",
|
||||
"dist/cjs/**/*.js",
|
||||
"lib",
|
||||
"print-script",
|
||||
"release-notes.md",
|
||||
"runtime.js",
|
||||
"types/*.d.ts"
|
||||
]
|
||||
"types/*.d.ts",
|
||||
"runtime.d.ts"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"browser": "./dist/cjs/handlebars.js",
|
||||
"types": "types/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('tmp',{recursive:true,force:true})\"",
|
||||
"build:cjs": "swc lib --out-dir dist/cjs --strip-leading-paths --ignore \"**/index.js\"",
|
||||
"build:bundles": "rspack build",
|
||||
"build": "npm run clean && npm run build:cjs && npm run build:bundles",
|
||||
"release": "npm run build",
|
||||
"publish:aws": "npm run build && npm run test:tasks && node tasks/publish-to-aws.js",
|
||||
"format": "oxfmt --write . && oxlint --fix .",
|
||||
"lint": "npm run lint:oxlint && npm run lint:format && npm run lint:types && npm run lint:compat",
|
||||
"lint:oxlint": "oxlint --max-warnings 0 .",
|
||||
"lint:format": "oxfmt --check .",
|
||||
"lint:types": "tstyche",
|
||||
"lint:compat": "eslint",
|
||||
"test": "npm run build && vitest run --project node --project tasks --project rspack --coverage",
|
||||
"test:browser": "vitest run --project browser",
|
||||
"test:unit": "vitest run --project node",
|
||||
"test:tasks": "vitest run --project tasks",
|
||||
"test:publish": "npm run build && vitest run --project publish",
|
||||
"test:browser-smoke": "playwright test --config tests/browser/playwright.config.js",
|
||||
"test:serve": "npx serve -l 9999 .",
|
||||
"test:integration": "npm run build && ./tests/integration/run-integration-tests.sh",
|
||||
"bench": "node tests/bench/perf.mjs",
|
||||
"bench:compare": "node tests/bench/compare.mjs",
|
||||
"bench:size": "node tests/bench/size.mjs",
|
||||
"--- combined tasks ---": "",
|
||||
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@handlebars/parser": "^2.1.0",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.7.6",
|
||||
"yargs": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1011.0",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@rspack/cli": "^1.7.8",
|
||||
"@rspack/core": "^1.7.8",
|
||||
"@swc/cli": "^0.8.0",
|
||||
"@swc/core": "^1.15.18",
|
||||
"@vitest/browser": "^4.0.18",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"cli-testlab": "^6.0.0",
|
||||
"concurrently": "^5.0.0",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint-plugin-compat": "^7.0.1",
|
||||
"fs-extra": "^8.1.0",
|
||||
"husky": "^3.1.0",
|
||||
"lint-staged": "^16.3.2",
|
||||
"mock-stdin": "^0.3.0",
|
||||
"oxfmt": "^0.36.0",
|
||||
"oxlint": "^1.51.0",
|
||||
"semver": "^5.0.1",
|
||||
"tinybench": "^6.0.0",
|
||||
"tstyche": "^6.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"uglify-js": "^3.19.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"uglify-js": "^3.19.3"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,css,json,md}": [
|
||||
"oxfmt --write"
|
||||
],
|
||||
"*.js": [
|
||||
"oxlint --fix"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not dead",
|
||||
"not IE 11",
|
||||
"maintained node versions"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
"directories": {
|
||||
"lib": "dist/cjs"
|
||||
},
|
||||
"buildConfig": {
|
||||
"minify": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user