Compare commits

..

1 Commits

Author SHA1 Message Date
Nils Knappmeier ad85b940e7 fix: "hash" is optional in AST 2019-11-01 22:50:58 +01:00
235 changed files with 13048 additions and 34798 deletions
-11
View File
@@ -1,11 +0,0 @@
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
-24
View File
@@ -1,24 +0,0 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/tests/integration/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
# JS-Snippets
src/*.js
+102 -51
View File
@@ -1,66 +1,117 @@
module.exports = { module.exports = {
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'], "extends": ["eslint:recommended","plugin:compat/recommended"],
globals: { "globals": {
self: false "self": false
}, },
env: { "env": {
node: true, "node": true,
es6: true "es6": true
}, },
rules: { "rules": {
'no-console': 'warn', // 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",
// temporarily disabled until the violating places are fixed.
'no-func-assign': 'off',
'no-sparse-arrays': 'off',
// Best Practices // // Best Practices //
//----------------// //----------------//
'default-case': 'warn', "curly": "error",
'guard-for-in': 'warn', "default-case": "warn",
'no-alert': 'error', "dot-notation": ["error", { "allowKeywords": false }],
'no-caller': 'error', "guard-for-in": "warn",
'no-div-regex': 'warn', "no-alert": "error",
'no-eval': 'error', "no-caller": "error",
'no-extend-native': 'error', "no-div-regex": "warn",
'no-extra-bind': 'error', "no-eval": "error",
'no-floating-decimal': 'error', "no-extend-native": "error",
'no-implied-eval': 'error', "no-extra-bind": "error",
'no-iterator': 'error', "no-floating-decimal": "error",
'no-labels': 'error', "no-implied-eval": "error",
'no-lone-blocks': 'error', "no-iterator": "error",
'no-loop-func': 'error', "no-labels": "error",
'no-multi-str': 'warn', "no-lone-blocks": "error",
'no-global-assign': 'error', "no-loop-func": "error",
'no-new': 'error', "no-multi-spaces": "error",
'no-new-func': 'error', "no-multi-str": "warn",
'no-new-wrappers': 'error', "no-global-assign": "error",
'no-octal-escape': 'error', "no-new": "error",
'no-process-env': 'error', "no-new-func": "error",
'no-proto': 'error', "no-new-wrappers": "error",
'no-return-assign': 'error', "no-octal-escape": "error",
'no-script-url': 'error', "no-process-env": "error",
'no-self-compare': 'error', "no-proto": "error",
'no-sequences': 'error', "no-return-assign": "error",
'no-throw-literal': 'error', "no-script-url": "error",
'no-unused-expressions': 'error', "no-self-compare": "error",
'no-warning-comments': 'warn', "no-sequences": "error",
'no-with': 'error', "no-throw-literal": "error",
radix: 'error', "no-unused-expressions": "error",
"no-warning-comments": "warn",
"no-with": "error",
"radix": "error",
"wrap-iife": "error",
// Variables // // Variables //
//-----------// //-----------//
'no-label-var': 'error', "no-catch-shadow": "error",
'no-undef-init': 'error', "no-label-var": "error",
'no-use-before-define': ['error', 'nofunc'], "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 // // ECMAScript 6 //
//--------------// //--------------//
'no-var': 'error' "no-var": "warn"
}, },
parserOptions: { "parserOptions": {
sourceType: 'module', "sourceType": "module",
ecmaVersion: 6, "ecmaVersion": 6,
ecmaFeatures: {} "ecmaFeatures": {}
} }
}; }
-9
View File
@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
open-pull-requests-limit: 0
schedule:
interval: weekly
allow:
- dependency-type: production
-89
View File
@@ -1,89 +0,0 @@
name: CI
on:
push:
branches:
- master
pull_request: {}
jobs:
lint:
name: Lint
runs-on: 'ubuntu-latest'
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- 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: ['10', '12', '14', '16', '18', '20']
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Test
run: npm run test
- name: Test (Integration)
# https://github.com/webpack/webpack/issues/14532
if: ${{ matrix.node-version != '18' && matrix.node-version != '20' }}
run: |
cd ./tests/integration/rollup-test && ./test.sh && cd -
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
cd ./tests/integration/webpack-test && ./test.sh && cd -
browser:
name: Test (Browser)
runs-on: 'ubuntu-20.04'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: |
npx playwright install-deps
npx playwright install
- name: Build
run: npx grunt prepare
- name: Test
run: npm run test:browser
-38
View File
@@ -1,38 +0,0 @@
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@v2
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- 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_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
+7 -11
View File
@@ -1,19 +1,15 @@
vendor
.rvmrc .rvmrc
.DS_Store .DS_Store
lib/handlebars/compiler/parser.js
/dist/
/tmp/ /tmp/
/coverage/
node_modules
*.sublime-project *.sublime-project
*.sublime-workspace *.sublime-workspace
npm-debug.log npm-debug.log
sauce_connect.log*
.idea .idea
/yarn-error.log yarn-error.log
/yarn.lock
node_modules
/handlebars-release.tgz /handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/tests/integration/*/dist/
/spec/tmp/*
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "spec/mustache"] [submodule "spec/mustache"]
path = spec/mustache path = spec/mustache
url = https://github.com/mustache/spec.git url = git://github.com/mustache/spec.git
+2
View File
@@ -0,0 +1,2 @@
instrumentation:
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
-22
View File
@@ -1,22 +0,0 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/tests/integration/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
+26
View File
@@ -0,0 +1,26 @@
language: node_js
before_install:
- npm install -g grunt-cli
script:
- grunt --stack travis
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
+37 -123
View File
@@ -2,18 +2,18 @@
## Reporting Issues ## Reporting Issues
Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into. Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here) Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed. Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs). Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
## Branches ## Branches
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch. * 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 * 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. should merge the branch `4.x` into the master branch regularly.
## Pull Requests ## Pull Requests
@@ -21,7 +21,6 @@ Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site sh
We also accept [pull requests][pull-request]! We also accept [pull requests][pull-request]!
Generally we like to see pull requests that Generally we like to see pull requests that
- Maintain the existing code style - 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) - Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) - Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
@@ -32,8 +31,8 @@ Generally we like to see pull requests that
To build Handlebars.js you'll need a few things installed. To build Handlebars.js you'll need a few things installed.
- Node.js * Node.js
- [Grunt](http://gruntjs.com/getting-started) * [Grunt](http://gruntjs.com/getting-started)
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`. Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
@@ -47,18 +46,16 @@ 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/`. The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
If you notice any problems, please report them to the GitHub issue tracker at If you notice any problems, please report them to the GitHub issue tracker at
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues). [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
## Running Tests ##Running Tests
To run tests locally, first install all dependencies. To run tests locally, first install all dependencies.
```sh ```sh
npm install npm install
``` ```
Clone the mustache specs into the spec/mustache folder. Clone the mustache specs into the spec/mustache folder.
```sh ```sh
cd spec cd spec
rm -r mustache rm -r mustache
@@ -66,137 +63,54 @@ git clone https://github.com/mustache/spec.git mustache
``` ```
From the root directory, run the tests. From the root directory, run the tests.
```sh ```sh
npm test npm test
``` ```
## Linting and Formatting ## Ember testing
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files. 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.
We do linting and formatting in two phases:
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden, ```sh
while warnings are allowed. npm link
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings grunt build release
are forbidden. 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 run lint** will run `eslint` and fail on warnings npm test
- **npm run format** will run `prettier` on all files ```
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
## Releasing the latest version ## Releasing the latest version
Before attempting the release Handlebars, please make sure that you have the following authorizations: *When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch.*
- Push-access to [handlebars-lang/handlebars.js](https://github.com/handlebars-lang/handlebars.js/) Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
- Publishing rights on npmjs.com for the [handlebars](https://www.npmjs.com/package/handlebars) package
- Publishing rights on rubygems for the [handlebars-source](https://rubygems.org/gems/handlebars-source) package
- Push-access to the repo for legacy package managers: [components/handlebars.js](https://github.com/components/handlebars.js)
- Push-access to the production-repo of the handlebars site: [handlebars-lang/docs](https://github.com/handlebars-lang/docs)
_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:
A full release via Docker may be completed with the following: ```
npm ci
yo release
npm publish
yo release:publish components handlebars.js dist/components/
1. Create a `Dockerfile` in this folder for releasing cd dist/components/
```Dockerfile gem build handlebars-source.gemspec
FROM node:10-slim gem push handlebars-source-*.gem
```
ENV EDITOR=vim
# Update stretch repositories
RUN sed -i -e 's/deb.debian.org/archive.debian.org/g' \
-e 's|security.debian.org|archive.debian.org/|g' \
-e '/stretch-updates/d' /etc/apt/sources.list
# Install release dependencies
RUN apt-get update
RUN apt-get install -y git vim
# Work around deprecated npm dependency install via unauthenticated git-protocol:
# https://github.com/kpdecker/generator-release/blob/87aab9b84c9f083635c3fcc822f18acce1f48736/package.json#L31
RUN git config --system url."https://github.com/".insteadOf git://github.com/
# Configure git
RUN git config --system user.email "release@handlebarsjs.com"
RUN git config --system user.name "handlebars-lang"
RUN mkdir /home/node/.config
RUN mkdir /home/node/.ssh
RUN mkdir /home/node/tmp
# Generate config for yo generator-release:
# https://github.com/kpdecker/generator-release#example
# You have to add a valid GitHub OAuth token!
RUN echo "module.exports = {\n auth: 'oauth',\n token: 'GitHub OAuth token'\n};" > /home/node/.config/generator-release
RUN chown -R node:node /home/node/.config
# Add the generated key to GitHub: https://github.com/settings/keys
RUN ssh-keygen -q -t ed25519 -N '' -f /home/node/.ssh/id_ed25519 -C "release@handlebarsjs.com"
RUN chmod 0600 /home/node/.ssh/id_ed25519*
RUN chown node:node /home/node/.ssh/id_ed25519*
```
2. Build and run the Docker image
```bash
docker build --tag handlebars:release .
docker run --rm --interactive --tty \
--volume $PWD:/app \
--workdir /app \
--user $(id -u):$(id -g) \
--env NPM_CONFIG_PREFIX=/home/node/.npm-global \
handlebars:release bash -c 'export PATH=$PATH:/home/node/.npm-global/bin; bash'
```
* Add SSH key to GitHub: `cat /home/node/.ssh/id_ed25519.pub` (https://github.com/settings/keys)
* Add GitHub API token: `vi /home/node/.config/generator-release`
* Execute the following steps:
```bash
npm ci
npm install -g yo@1 grunt@1 generator-release
npm run release
yo release
npm login
npm publish
yo release:publish components handlebars.js dist/components/
```
6. Publish Ruby `handlebars-source` gem:
```bash
docker run --rm --interactive --tty \
--volume $PWD:/app \
--workdir /app \
--user $(id -u):$(id -g) \
ruby:3.2-slim bash
```
* Execute the following steps:
```bash
cd dist/components/
gem build handlebars-source.gemspec
gem push handlebars-source-*.gem
```
### After the release
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
in those places still point to the latest version in those places still point to the latest version
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag) * [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 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) * [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
- [RubyGems](https://rubygems.org/gems/handlebars-source) * [RubyGems](https://rubygems.org/gems/handlebars-source)
When everything is OK, the **handlebars site** needs to be updated. 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.
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 [generator-release]: https://github.com/walmartlabs/generator-release
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new [issue]: https://github.com/wycats/handlebars.js/issues/new
[jsfiddle]: https://jsfiddle.net/9D88g/180/ [jsfiddle]: https://jsfiddle.net/9D88g/180/
+5 -7
View File
@@ -2,11 +2,11 @@
1. How can I file a bug report: 1. How can I file a bug report:
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues). See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
1. Why isn't my Mustache template working? 1. Why isn't my Mustache template working?
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache). 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).
1. Why is it slower when compiling? 1. Why is it slower when compiling?
@@ -36,18 +36,16 @@
```sh ```sh
handlebars --version handlebars --version
``` ```
If using the integrated precompiler and If using the integrated precompiler and
```javascript ```javascript
console.log(Handlebars.VERSION); console.log(Handlebars.VERSION);
``` ```
On the client side. On the client side.
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler. We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues). 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).
1. Why doesn't IE like the `default` name in the AMD module? 1. Why doesn't IE like the `default` name in the AMD module?
@@ -55,8 +53,8 @@
1. How do I load the runtime library when using AMD? 1. 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/handlebars-lang/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field. 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. 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. If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
+108 -90
View File
@@ -1,42 +1,45 @@
/* eslint-disable no-process-env */ /* eslint-disable no-process-env */
module.exports = function(grunt) { module.exports = function(grunt) {
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
clean: [ eslint: {
'tmp', files: [
'dist', '*.js',
'lib/handlebars/compiler/parser.js', 'bench/**/*.js',
'/tests/integration/**/node_modules' 'tasks/**/*.js',
], 'lib/**/!(*.min|parser).js',
'spec/**/!(*.amd|json2|require).js',
'integration-testing/multi-nodejs-test/*.js',
'integration-testing/webpack-test/*.js',
'integration-testing/webpack-test/src/*.js'
]
},
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js', 'integration-testing/**/node_modules'],
copy: { copy: {
dist: { dist: {
options: { options: {
processContent: function(content) { processContent: function(content) {
return ( return grunt.template.process('/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n')
grunt.template.process( + content;
'/**!\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/' }] files: [
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
]
}, },
cdnjs: { cdnjs: {
files: [ files: [
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' } {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
] ]
}, },
components: { components: {
files: [ files: [
{ {expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
expand: true, {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
cwd: 'components/',
src: ['**'],
dest: 'dist/components'
},
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
] ]
} }
}, },
@@ -51,28 +54,24 @@ module.exports = function(grunt) {
options: { options: {
modules: 'amd' modules: 'amd'
}, },
files: [ files: [{
{ expand: true,
expand: true, cwd: 'lib/',
cwd: 'lib/', src: '**/!(index).js',
src: '**/!(index).js', dest: 'dist/amd/'
dest: 'dist/amd/' }]
}
]
}, },
cjs: { cjs: {
options: { options: {
modules: 'common' modules: 'common'
}, },
files: [ files: [{
{ cwd: 'lib/',
cwd: 'lib/', expand: true,
expand: true, src: '**/!(index).js',
src: '**/!(index).js', dest: 'dist/cjs/'
dest: 'dist/cjs/' }]
}
]
} }
}, },
webpack: { webpack: {
@@ -81,12 +80,7 @@ module.exports = function(grunt) {
module: { module: {
loaders: [ loaders: [
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it. // 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' }
test: /\.jsx?$/,
exclude: /node_modules/,
loader:
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
}
] ]
}, },
output: { output: {
@@ -135,17 +129,15 @@ module.exports = function(grunt) {
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/ preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
}, },
dist: { dist: {
files: [ files: [{
{ cwd: 'dist/',
cwd: 'dist/', expand: true,
expand: true, src: ['handlebars*.js', '!*.min.js'],
src: ['handlebars*.js', '!*.min.js'], dest: 'dist/',
dest: 'dist/', rename: function(dest, src) {
rename: function(dest, src) { return dest + src.replace(/\.js$/, '.min.js');
return dest + src.replace(/\.js$/, '.min.js');
}
} }
] }]
} }
}, },
@@ -165,13 +157,50 @@ module.exports = function(grunt) {
} }
} }
}, },
'saucelabs-mocha': {
shell: { all: {
integrationTests: { options: {
command: './tests/integration/run-integration-tests.sh' 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
},
integrationTests: {
cmd: './integration-testing/run-integration-tests.sh',
bg: false,
fail: true
}
},
watch: { watch: {
scripts: { scripts: {
options: { options: {
@@ -179,11 +208,26 @@ module.exports = function(grunt) {
}, },
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'], files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
tasks: ['on-file-change'] 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 // Load tasks from npm
grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-concat');
@@ -193,45 +237,19 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-babel'); grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-shell'); grunt.loadNpmTasks('grunt-bg-shell');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('@knappi/grunt-saucelabs');
grunt.loadNpmTasks('grunt-webpack'); grunt.loadNpmTasks('grunt-webpack');
grunt.task.loadTasks('tasks'); grunt.task.loadTasks('tasks');
grunt.registerTask('node', ['babel:cjs']); grunt.registerTask('bench', ['metrics']);
grunt.registerTask('amd', ['babel:amd', 'requirejs']); grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
grunt.registerTask('globals', ['webpack']);
grunt.registerTask('release', 'Build final packages', [
'uglify',
'test:min',
'copy:dist',
'copy:components',
'copy:cdnjs'
]);
// Requires secret properties from .travis.yaml grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'bgShell:integrationTests', 'sauce', 'metrics', 'publish:latest'] : ['default']);
grunt.registerTask('extensive-tests-and-publish-to-aws', [
'default',
'shell:integrationTests',
'metrics',
'publish-to-aws'
]);
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
// === Primary tasks ===
grunt.registerTask('dev', ['clean', 'connect', 'watch']); grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']); grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
grunt.registerTask('test', ['test:bin', 'test:cov']); grunt.registerTask('integration-tests', ['default', 'bgShell:integrationTests']);
grunt.registerTask('bench', ['metrics']);
grunt.registerTask('prepare', ['build', 'concat:tests']);
grunt.registerTask(
'build',
'Builds a distributable version of the current project',
['parser', 'node', 'amd', 'globals']
);
grunt.registerTask('integration-tests', [
'default',
'shell:integrationTests'
]);
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (C) 2011-2019 by Yehuda Katz Copyright (C) 2011-2017 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+24 -23
View File
@@ -1,29 +1,28 @@
[![CI Build Status](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml/badge.svg)](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml) [![Travis Build Status](https://img.shields.io/travis/wycats/handlebars.js/master.svg)](https://travis-ci.org/wycats/handlebars.js)
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/handlebars/badge?style=rounded)](https://www.jsdelivr.com/package/npm/handlebars) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/wycats/handlebars.js?branch=master&svg=true)](https://ci.appveyor.com/project/wycats/handlebars-js)
[![npm downloads](https://badgen.net/npm/dm/handlebars)](https://www.npmjs.com/package/handlebars) [![Selenium Test Status](https://saucelabs.com/buildstatus/handlebars)](https://saucelabs.com/u/handlebars)
[![npm version](https://badgen.net/npm/v/handlebars)](https://www.npmjs.com/package/handlebars)
[![Bundle size](https://badgen.net/bundlephobia/minzip/handlebars?label=minified%20%2B%20gzipped)](https://bundlephobia.com/package/handlebars)
[![Install size](https://packagephobia.com/badge?p=handlebars)](https://packagephobia.com/result?p=handlebars)
Handlebars.js Handlebars.js
============= =============
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration. Handlebars.js is an extension to the [Mustache templating
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. 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 Checkout the official Handlebars docs site at
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html). [http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
Installing Installing
---------- ----------
See our [installation documentation](https://handlebarsjs.com/installation/). See our [installation documentation](http://handlebarsjs.com/installation.html).
Usage Usage
----- -----
In general, the syntax of Handlebars.js templates is a superset In general, the syntax of Handlebars.js templates is a superset
of Mustache templates. For basic syntax, check out the [Mustache of Mustache templates. For basic syntax, check out the [Mustache
manpage](https://mustache.github.io/mustache.5.html). manpage](http://mustache.github.com/mustache.5.html).
Once you have a template, use the `Handlebars.compile` method to compile Once you have a template, use the `Handlebars.compile` method to compile
the template into a function. The generated function takes a context the template into a function. The generated function takes a context
@@ -47,25 +46,25 @@ var result = template(data);
// </ul> // </ul>
``` ```
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/). Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
Precompiling Templates Precompiling Templates
---------------------- ----------------------
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html). 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 Differences Between Handlebars.js and Mustache
---------------------------------------------- ----------------------------------------------
Handlebars.js adds a couple of additional features to make writing Handlebars.js adds a couple of additional features to make writing
templates easier and also changes a tiny detail of how partials work. templates easier and also changes a tiny detail of how partials work.
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions) - [Nested Paths](http://handlebarsjs.com/#paths)
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers) - [Helpers](http://handlebarsjs.com/#helpers)
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks) - [Block Expressions](http://handlebarsjs.com/#block-expressions)
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments) - [Literal Values](http://handlebarsjs.com/#literals)
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments) - [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](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority. 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 ### Compatibility
@@ -90,6 +89,8 @@ Handlebars has been designed to work in any ECMAScript 3 environment. This inclu
Older versions and other runtimes are likely to work but have not been formally 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. tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
[![Selenium Test Status](https://saucelabs.com/browser-matrix/handlebars.svg)](https://saucelabs.com/u/handlebars)
Performance Performance
----------- -----------
@@ -101,18 +102,18 @@ does have some big performance advantages. Justin Marney, a.k.a.
[gotascii](http://github.com/gotascii), confirmed that with an [gotascii](http://github.com/gotascii), confirmed that with an
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
rewritten Handlebars (current version) is faster than the old version, rewritten Handlebars (current version) is faster than the old version,
with many performance tests being 5 to 7 times faster than the Mustache equivalent. 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 Upgrading
--------- ---------
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes. See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
Known Issues Known Issues
------------ ------------
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls. See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
Handlebars in the Wild Handlebars in the Wild
@@ -164,4 +165,4 @@ License
------- -------
Handlebars.js is released under the MIT license. Handlebars.js is released under the MIT license.
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
+37
View File
@@ -0,0 +1,37 @@
# 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}"
+14
View File
@@ -0,0 +1,14 @@
{
"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,14 +1,12 @@
var async = require('neo-async'), var async = require('neo-async'),
fs = require('fs'), fs = require('fs'),
zlib = require('zlib'); zlib = require('zlib');
module.exports = function(grunt, callback) { module.exports = function(grunt, callback) {
var distFiles = fs.readdirSync('dist'), var distFiles = fs.readdirSync('dist'),
distSizes = {}; distSizes = {};
async.each( async.each(distFiles, function(file, callback) {
distFiles,
function(file, callback) {
var content; var content;
try { try {
content = fs.readFileSync('dist/' + file); content = fs.readFileSync('dist/' + file);
@@ -34,10 +32,7 @@ module.exports = function(grunt, callback) {
}); });
}, },
function() { function() {
grunt.log.writeln( grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)
);
callback([distSizes]); callback([distSizes]);
} });
);
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var metrics = fs.readdirSync(__dirname); var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) { metrics.forEach(function(metric) {
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) { if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) {
return; return;
} }
@@ -1,24 +1,19 @@
var _ = require('underscore'), var _ = require('underscore'),
templates = require('./templates'); templates = require('./templates');
module.exports = function(grunt, callback) { module.exports = function(grunt, callback) {
// Deferring to here in case we have a build for parser, etc as part of this grunt exec // Deferring to here in case we have a build for parser, etc as part of this grunt exec
var Handlebars = require('../../lib'); var Handlebars = require('../lib');
var templateSizes = {}; var templateSizes = {};
_.each(templates, function(info, template) { _.each(templates, function(info, template) {
var src = info.handlebars, var src = info.handlebars,
compiled = Handlebars.precompile(src, {}), compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, { knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
knownHelpersOnly: true,
knownHelpers: info.helpers
});
templateSizes[template] = compiled.length; templateSizes[template] = compiled.length;
templateSizes['knownOnly_' + template] = knownHelpers.length; templateSizes['knownOnly_' + template] = knownHelpers.length;
}); });
grunt.log.writeln( grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2));
'Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)
);
callback([templateSizes]); callback([templateSizes]);
}; };
@@ -8,6 +8,5 @@ module.exports = {
bar: true bar: true
}, },
handlebars: handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
'{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
}; };
@@ -1,12 +1,5 @@
module.exports = { module.exports = {
context: { context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{name}}{{/each}}', handlebars: '{{#each names}}{{name}}{{/each}}',
dust: '{#names}{name}{/names}', dust: '{#names}{name}{/names}',
mustache: '{{#names}}{{name}}{{/names}}', mustache: '{{#names}}{{name}}{{/names}}',
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
handlebars: '{{#names}}{{name}}{{/names}}'
};
@@ -7,9 +7,9 @@ module.exports = {
}, },
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
items: [ items: [
{ name: 'red', current: true, url: '#Red' }, {name: 'red', current: true, url: '#Red'},
{ name: 'green', current: false, url: '#Green' }, {name: 'green', current: false, url: '#Green'},
{ name: 'blue', current: false, url: '#Blue' } {name: 'blue', current: false, url: '#Blue'}
] ]
}, },
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
};
@@ -1,13 +1,5 @@
module.exports = { module.exports = {
context: { context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}], foo: 'bar' },
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
],
foo: 'bar'
},
handlebars: '{{#each names}}{{../foo}}{{/each}}', handlebars: '{{#each names}}{{../foo}}{{/each}}',
mustache: '{{#names}}{{foo}}{{/names}}', mustache: '{{#names}}{{foo}}{{/names}}',
eco: '<% for item in @names: %><%= @foo %><% end %>' eco: '<% for item in @names: %><%= @foo %><% end %>'
+6
View File
@@ -0,0 +1,6 @@
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 %>'
};
@@ -2,7 +2,7 @@ var fs = require('fs');
var templates = fs.readdirSync(__dirname); var templates = fs.readdirSync(__dirname);
templates.forEach(function(template) { templates.forEach(function(template) {
if (template === 'index.js' || !/(.*)\.js$/.test(template)) { if (template === 'index.js' || !(/(.*)\.js$/.test(template))) {
return; return;
} }
module.exports[RegExp.$1] = require('./' + RegExp.$1); module.exports[RegExp.$1] = require('./' + RegExp.$1);
@@ -1,8 +1,5 @@
module.exports = { module.exports = {
context: { context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
name: '1',
kids: [{ name: '1.1', kids: [{ name: '1.1.1', kids: [] }] }]
},
partials: { partials: {
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' }, mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' } handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
@@ -1,16 +1,8 @@
module.exports = { module.exports = {
context: { context: { peeps: [{name: 'Moe', count: 15}, {name: 'Larry', count: 5}, {name: 'Curly', count: 1}] },
peeps: [
{ name: 'Moe', count: 15 },
{ name: 'Larry', count: 5 },
{ name: 'Curly', count: 1 }
]
},
partials: { partials: {
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' }, mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
handlebars: { handlebars: { variables: 'Hello {{name}}! You have {{count}} new messages.' }
variables: 'Hello {{name}}! You have {{count}} new messages.'
}
}, },
handlebars: '{{#each peeps}}{{>variables}}{{/each}}', handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
+7
View File
@@ -0,0 +1,7 @@
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 +1,8 @@
module.exports = { module.exports = {
context: { name: 'Mick', count: 30 }, context: {name: 'Mick', count: 30},
handlebars: 'Hello {{name}}! You have {{count}} new messages.', handlebars: 'Hello {{name}}! You have {{count}} new messages.',
dust: 'Hello {name}! You have {count} new messages.', dust: 'Hello {name}! You have {count} new messages.',
mustache: 'Hello {{name}}! You have {{count}} new messages.', mustache: 'Hello {{name}}! You have {{count}} new messages.',
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.' eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
}; };
@@ -1,27 +1,19 @@
var _ = require('underscore'), var _ = require('underscore'),
runner = require('./util/template-runner'), runner = require('./util/template-runner'),
eco,
dust, eco, dust, Handlebars, Mustache;
Handlebars,
Mustache;
try { try {
dust = require('dustjs-linkedin'); dust = require('dustjs-linkedin');
} catch (err) { } catch (err) { /* NOP */ }
/* NOP */
}
try { try {
Mustache = require('mustache'); Mustache = require('mustache');
} catch (err) { } catch (err) { /* NOP */ }
/* NOP */
}
try { try {
eco = require('eco'); eco = require('eco');
} catch (err) { } catch (err) { /* NOP */ }
/* NOP */
}
function error() { function error() {
throw new Error('EWOT'); throw new Error('EWOT');
@@ -30,28 +22,21 @@ function error() {
function makeSuite(bench, name, template, handlebarsOnly) { function makeSuite(bench, name, template, handlebarsOnly) {
// Create aliases to minimize any impact from having to walk up the closure tree. // Create aliases to minimize any impact from having to walk up the closure tree.
var templateName = name, var templateName = name,
context = template.context,
partials = template.partials,
handlebarsOut,
compatOut,
dustOut,
ecoOut,
mustacheOut;
var handlebar = Handlebars.compile(template.handlebars, { data: false }), context = template.context,
compat = Handlebars.compile(template.handlebars, { partials = template.partials,
data: false,
compat: true handlebarsOut,
}), compatOut,
options = { helpers: template.helpers }; dustOut,
_.each(template.partials && template.partials.handlebars, function( ecoOut,
partial, mustacheOut;
partialName
) { var handlebar = Handlebars.compile(template.handlebars, {data: false}),
Handlebars.registerPartial( compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
partialName, options = {helpers: template.helpers};
Handlebars.compile(partial, { data: false }) _.each(template.partials && template.partials.handlebars, function(partial, partialName) {
); Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false}));
}); });
handlebarsOut = handlebar(context, options); handlebarsOut = handlebar(context, options);
@@ -73,9 +58,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
dustOut = false; dustOut = false;
dust.loadSource(dust.compile(template.dust, templateName)); dust.loadSource(dust.compile(template.dust, templateName));
dust.render(templateName, context, function(err, out) { dust.render(templateName, context, function(err, out) { dustOut = out; });
dustOut = out;
});
bench('dust', function() { bench('dust', function() {
dust.render(templateName, context, function() {}); dust.render(templateName, context, function() {});
@@ -101,7 +84,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
if (Mustache) { if (Mustache) {
var mustacheSource = template.mustache, var mustacheSource = template.mustache,
mustachePartials = partials && partials.mustache; mustachePartials = partials && partials.mustache;
if (mustacheSource) { if (mustacheSource) {
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials); mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
@@ -124,16 +107,9 @@ function makeSuite(bench, name, template, handlebarsOnly) {
b = b.replace(/\s/g, ''); b = b.replace(/\s/g, '');
if (handlebarsOut !== b) { if (handlebarsOut !== b) {
throw new Error( throw new Error('Template output mismatch: ' + name
'Template output mismatch: ' + + '\n\nHandlebars: ' + handlebarsOut
name + + '\n\n' + lang + ': ' + b);
'\n\nHandlebars: ' +
handlebarsOut +
'\n\n' +
lang +
': ' +
b
);
} }
} }
@@ -145,7 +121,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
module.exports = function(grunt, callback) { module.exports = function(grunt, callback) {
// Deferring load incase we are being run inline with the grunt build // Deferring load incase we are being run inline with the grunt build
Handlebars = require('../../lib'); Handlebars = require('../lib');
console.log('Execution Throughput'); console.log('Execution Throughput');
runner(grunt, makeSuite, function(times, scaled) { runner(grunt, makeSuite, function(times, scaled) {
@@ -1,5 +1,5 @@
var _ = require('underscore'), var _ = require('underscore'),
Benchmark = require('benchmark'); Benchmark = require('benchmark');
function BenchWarmer() { function BenchWarmer() {
this.benchmarks = []; this.benchmarks = [];
@@ -11,6 +11,8 @@ function BenchWarmer() {
this.errors = {}; this.errors = {};
} }
var print = require('util').print;
BenchWarmer.prototype = { BenchWarmer.prototype = {
winners: function(benches) { winners: function(benches) {
return Benchmark.filter(benches, 'fastest'); return Benchmark.filter(benches, 'fastest');
@@ -27,25 +29,20 @@ BenchWarmer.prototype = {
}); });
}, },
push: function(name, fn) { push: function(name, fn) {
if (this.names.indexOf(name) === -1) { if (this.names.indexOf(name) == -1) {
this.names.push(name); this.names.push(name);
} }
var first = this.first, var first = this.first, suiteName = this.suiteName, self = this;
suiteName = this.suiteName,
self = this;
this.first = false; this.first = false;
var bench = new Benchmark(fn, { var bench = new Benchmark(fn, {
name: this.suiteName + ': ' + name, name: this.suiteName + ': ' + name,
onComplete: function() { onComplete: function() {
if (first) { if (first) { self.startLine(suiteName); }
self.startLine(suiteName);
}
self.writeBench(bench); self.writeBench(bench);
self.currentBenches.push(bench); self.currentBenches.push(bench);
}, }, onError: function() {
onError: function() {
self.errors[this.name] = this; self.errors[this.name] = this;
} }
}); });
@@ -67,7 +64,7 @@ BenchWarmer.prototype = {
self.startLine(''); self.startLine('');
console.log('\n'); print('\n');
self.printHeader('scaled'); self.printHeader('scaled');
_.each(self.scaled, function(value, name) { _.each(self.scaled, function(value, name) {
self.startLine(name); self.startLine(name);
@@ -76,77 +73,58 @@ BenchWarmer.prototype = {
self.writeValue(value[lang] || ''); self.writeValue(value[lang] || '');
}); });
}); });
console.log('\n'); print('\n');
var errors = false, var errors = false, prop, bench;
prop,
bench;
for (prop in self.errors) { for (prop in self.errors) {
if ( if (self.errors.hasOwnProperty(prop)
Object.prototype.hasOwnProperty.call(self, prop) && && self.errors[prop].error.message !== 'EWOT') {
self.errors[prop].error.message !== 'EWOT'
) {
errors = true; errors = true;
break; break;
} }
} }
if (errors) { if (errors) {
console.log('\n\nErrors:\n'); print('\n\nErrors:\n');
Object.keys(self.errors).forEach(function(prop) { for (prop in self.errors) {
if (self.errors[prop].error.message !== 'EWOT') { if (self.errors.hasOwnProperty(prop)
&& self.errors[prop].error.message !== 'EWOT') {
bench = self.errors[prop]; bench = self.errors[prop];
console.log('\n' + bench.name + ':\n'); print('\n' + bench.name + ':\n');
console.log(bench.error.message); print(bench.error.message);
if (bench.error.stack) { if (bench.error.stack) {
console.log(bench.error.stack.join('\n')); print(bench.error.stack.join('\n'));
} }
console.log('\n'); print('\n');
} }
}); }
} }
callback(); callback();
} }
}); });
console.log('\n'); print('\n');
}, },
scaleTimes: function() { scaleTimes: function() {
var scaled = (this.scaled = {}); var scaled = this.scaled = {};
_.each( _.each(this.times, function(times, name) {
this.times, var output = scaled[name] = {};
function(times, name) {
var output = (scaled[name] = {});
_.each( _.each(times, function(time, lang) {
times, output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
function(time, lang) { }, this);
output[lang] = ( }, this);
((time - this.minimum) / (this.maximum - this.minimum)) *
100
).toFixed(2);
},
this
);
},
this
);
}, },
printHeader: function(title, winners) { printHeader: function(title, winners) {
var benchSize = 0, var benchSize = 0, names = this.names, i, l;
names = this.names,
i,
l;
for (i = 0, l = names.length; i < l; i++) { for (i = 0, l = names.length; i < l; i++) {
var name = names[i]; var name = names[i];
if (benchSize < name.length) { if (benchSize < name.length) { benchSize = name.length; }
benchSize = name.length;
}
} }
this.nameSize = benchSize + 2; this.nameSize = benchSize + 2;
@@ -161,24 +139,22 @@ BenchWarmer.prototype = {
} }
if (winners) { if (winners) {
console.log('WINNER(S)'); print('WINNER(S)');
horSize = horSize + 'WINNER(S)'.length; horSize = horSize + 'WINNER(S)'.length;
} }
console.log('\n' + new Array(horSize + 1).join('-')); print('\n' + new Array(horSize + 1).join('-'));
}, },
startLine: function(name) { startLine: function(name) {
var winners = Benchmark.map(this.winners(this.currentBenches), function( var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
bench
) {
return bench.name.split(': ')[1]; return bench.name.split(': ')[1];
}); });
this.currentBenches = []; this.currentBenches = [];
console.log(winners.join(', ')); print(winners.join(', '));
console.log('\n'); print('\n');
if (name) { if (name) {
this.writeValue(name); this.writeValue(name);
@@ -189,9 +165,9 @@ BenchWarmer.prototype = {
if (!bench.error) { if (!bench.error) {
var count = bench.hz, var count = bench.hz,
moe = (count * bench.stats.rme) / 100, moe = count * bench.stats.rme / 100,
minimum, minimum,
maximum; maximum;
count = Math.round(count / 1000); count = Math.round(count / 1000);
moe = Math.round(moe / 1000); moe = Math.round(moe / 1000);
@@ -214,7 +190,7 @@ BenchWarmer.prototype = {
writeValue: function(out) { writeValue: function(out) {
var padding = this.benchSize - out.length + 1; var padding = this.benchSize - out.length + 1;
out = out + new Array(padding).join(' '); out = out + new Array(padding).join(' ');
console.log(out); print(out);
} }
}; };
@@ -1,12 +1,12 @@
var _ = require('underscore'), var _ = require('underscore'),
BenchWarmer = require('./benchwarmer'), BenchWarmer = require('./benchwarmer'),
templates = require('../templates'); templates = require('../templates');
module.exports = function(grunt, makeSuite, callback) { module.exports = function(grunt, makeSuite, callback) {
var warmer = new BenchWarmer(); var warmer = new BenchWarmer();
var handlebarsOnly = grunt.option('handlebars-only'), var handlebarsOnly = grunt.option('handlebars-only'),
grep = grunt.option('grep'); grep = grunt.option('grep');
if (grep) { if (grep) {
grep = new RegExp(grep); grep = new RegExp(grep);
} }
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
rules: {
'no-console': 0,
'no-var': 0
}
};
+111 -159
View File
@@ -1,176 +1,128 @@
#!/usr/bin/env node #!/usr/bin/env node
var argv = parseArgs({ var optimist = require('optimist')
'f': { .usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
'type': 'string', 'f': {
'description': 'Output File', 'type': 'string',
'alias': 'output' 'description': 'Output File',
}, 'alias': 'output'
'map': { },
'type': 'string', 'map': {
'description': 'Source Map File' 'type': 'string',
}, 'description': 'Source Map File'
'a': { },
'type': 'boolean', 'a': {
'description': 'Exports amd style (require.js)', 'type': 'boolean',
'alias': 'amd' 'description': 'Exports amd style (require.js)',
}, 'alias': 'amd'
'c': { },
'type': 'string', 'c': {
'description': 'Exports CommonJS style, path to Handlebars module', 'type': 'string',
'alias': 'commonjs', 'description': 'Exports CommonJS style, path to Handlebars module',
'default': null 'alias': 'commonjs',
}, 'default': null
'h': { },
'type': 'string', 'h': {
'description': 'Path to handlebar.js (only valid for amd-style)', 'type': 'string',
'alias': 'handlebarPath', 'description': 'Path to handlebar.js (only valid for amd-style)',
'default': '' 'alias': 'handlebarPath',
}, 'default': ''
'k': { },
'type': 'string', 'k': {
'description': 'Known helpers', 'type': 'string',
'alias': 'known' 'description': 'Known helpers',
}, 'alias': 'known'
'o': { },
'type': 'boolean', 'o': {
'description': 'Known helpers only', 'type': 'boolean',
'alias': 'knownOnly' 'description': 'Known helpers only',
}, 'alias': 'knownOnly'
'm': { },
'type': 'boolean', 'm': {
'description': 'Minimize output', 'type': 'boolean',
'alias': 'min' 'description': 'Minimize output',
}, 'alias': 'min'
'n': { },
'type': 'string', 'n': {
'description': 'Template namespace', 'type': 'string',
'alias': 'namespace', 'description': 'Template namespace',
'default': 'Handlebars.templates' 'alias': 'namespace',
}, 'default': 'Handlebars.templates'
's': { },
'type': 'boolean', 's': {
'description': 'Output template function only.', 'type': 'boolean',
'alias': 'simple' 'description': 'Output template function only.',
}, 'alias': 'simple'
'N': { },
'type': 'string', 'N': {
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.', 'type': 'string',
'alias': 'name' 'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
}, 'alias': 'name'
'i': { },
'type': 'string', 'i': {
'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.', 'type': 'string',
'alias': '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', 'r': {
'description': 'Template root. Base value that will be stripped from template names.', 'type': 'string',
'alias': 'root' 'description': 'Template root. Base value that will be stripped from template names.',
}, 'alias': 'root'
'p': { },
'type': 'boolean', 'p': {
'description': 'Compiling a partial template', 'type': 'boolean',
'alias': 'partial' 'description': 'Compiling a partial template',
}, 'alias': 'partial'
'd': { },
'type': 'boolean', 'd': {
'description': 'Include data when compiling', 'type': 'boolean',
'alias': 'data' 'description': 'Include data when compiling',
}, 'alias': 'data'
'e': { },
'type': 'string', 'e': {
'description': 'Template extension.', 'type': 'string',
'alias': 'extension', 'description': 'Template extension.',
'default': 'handlebars' 'alias': 'extension',
}, 'default': 'handlebars'
'b': { },
'type': 'boolean', 'b': {
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.', 'type': 'boolean',
'alias': 'bom' 'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
}, 'alias': 'bom'
'v': { },
'type': 'boolean', 'v': {
'description': 'Prints the current compiler version', 'type': 'boolean',
'alias': 'version' 'description': 'Prints the current compiler version',
}, 'alias': 'version'
'help': { },
'type': 'boolean',
'description': 'Outputs this message'
}
});
'help': {
'type': 'boolean',
'description': 'Outputs this message'
}
})
.wrap(120)
.check(function(argv) {
if (argv.version) {
return;
}
});
var argv = optimist.argv;
argv.files = argv._; argv.files = argv._;
delete argv._; delete argv._;
var Precompiler = require('../dist/cjs/precompiler'); var Precompiler = require('../dist/cjs/precompiler');
Precompiler.loadTemplates(argv, function(err, opts) { Precompiler.loadTemplates(argv, function(err, opts) {
if (err) { if (err) {
throw err; throw err;
} }
if (opts.help || (!opts.templates.length && !opts.version)) { if (opts.help || (!opts.templates.length && !opts.version)) {
printUsage(argv._spec, 120); optimist.showHelp();
} else { } else {
Precompiler.cli(opts); Precompiler.cli(opts);
} }
}); });
function pad(n) {
var str = '';
while (str.length < n) {
str += ' ';
}
return str;
}
function parseArgs(spec) {
var opts = { alias: {}, boolean: [], default: {}, string: [] };
Object.keys(spec).forEach(function (arg) {
var opt = spec[arg];
opts[opt.type].push(arg);
if ('alias' in opt) opts.alias[arg] = opt.alias;
if ('default' in opt) opts.default[arg] = opt.default;
});
var argv = require('minimist')(process.argv.slice(2), opts);
argv._spec = spec;
return argv;
}
function printUsage(spec, wrap) {
var wordwrap = require('wordwrap');
console.log('Precompile handlebar templates.');
console.log('Usage: handlebars [template|directory]...');
var opts = [];
var width = 0;
Object.keys(spec).forEach(function (arg) {
var opt = spec[arg];
var name = (arg.length === 1 ? '-' : '--') + arg;
if ('alias' in opt) name += ', --' + opt.alias;
var meta = '[' + opt.type + ']';
if ('default' in opt) meta += ' [default: ' + JSON.stringify(opt.default) + ']';
opts.push({ name: name, desc: opt.description, meta: meta });
if (name.length > width) width = name.length;
});
console.log('Options:');
opts.forEach(function (opt) {
var desc = wordwrap(width + 4, wrap + 1)(opt.desc);
console.log(' %s%s%s%s%s',
opt.name,
pad(width - opt.name.length + 2),
desc.slice(width + 4),
pad(wrap - opt.meta.length - desc.split(/\n/).pop().length),
opt.meta
);
});
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "handlebars", "name": "handlebars",
"version": "4.7.8", "version": "4.5.1",
"main": "handlebars.js", "main": "handlebars.js",
"license": "MIT", "license": "MIT",
"dependencies": {} "dependencies": {}
+1 -1
View File
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
gem.date = Time.now.strftime("%Y-%m-%d") gem.date = Time.now.strftime("%Y-%m-%d")
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.} gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
gem.summary = %q{Handlebars.js source code wrapper} gem.summary = %q{Handlebars.js source code wrapper}
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/" gem.homepage = "https://github.com/wycats/handlebars.js/"
gem.version = package["version"].sub "-", "." gem.version = package["version"].sub "-", "."
gem.license = "MIT" gem.license = "MIT"
+3 -3
View File
@@ -2,10 +2,10 @@
<package> <package>
<metadata> <metadata>
<id>handlebars.js</id> <id>handlebars.js</id>
<version>4.7.8</version> <version>4.5.1</version>
<authors>handlebars.js Authors</authors> <authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl> <projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Extension of the Mustache logicless template language</description> <description>Extension of the Mustache logicless template language</description>
<releaseNotes></releaseNotes> <releaseNotes></releaseNotes>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "handlebars", "name": "handlebars",
"version": "4.7.8", "version": "4.5.1",
"license": "MIT", "license": "MIT",
"jspm": { "jspm": {
"main": "handlebars", "main": "handlebars",
+1 -1
View File
@@ -16,4 +16,4 @@ Decorators are executed when the block program is instantiated and are passed `(
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered. Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
The [inline partial](https://github.com/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. 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.
@@ -1,10 +1,12 @@
Add a new integration test by creating a new subfolder Add a new integration test by creating a new subfolder
Add a file "test.sh" to that runs the test. "test.sh" should exit with a non-zero exit code Add a file "test.sh" to that runs the test. "test.sh" should exit with a non-zero exit code
and display an error message, if something goes wrong. and display an error message, if something goes wrong.
- An integration test should reflect real-world setups that use handlebars. * An integration test should reflect real-world setups that use handlebars.
- It should compile a minimal template and compare the output to an expected output. * It should compile a minimal template and compare the output to an expected output.
- It should use "../.." as dependency for Handlebars so that the currently built library is used. * It should use "../.." as dependency for Handlebars so that the currently built library is used.
Currently, integration tests are only running on Linux, especially in our CI GitHub action. Currently, integration tests are only running on Linux, especially in travis-ci.
@@ -0,0 +1,12 @@
module.exports = {
"extends": "eslint:recommended",
"globals": {
"self": false
},
"env": {
"node": true
},
"rules": {
'no-console': 'off'
}
}
@@ -7,7 +7,7 @@
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"handlebars": "file:../../.." "handlebars": "file:../.."
}, },
"scripts": { "scripts": {
"test": "node run-handlebars.js", "test": "node run-handlebars.js",
@@ -5,7 +5,7 @@ var Handlebars = require('handlebars');
console.log('Testing built Handlebars with Node version ' + process.version); console.log('Testing built Handlebars with Node version ' + process.version);
var template = Handlebars.compile('Author: {{author}}'); var template = Handlebars.compile('Author: {{author}}');
var output = template({ author: 'Yehuda' }); var output = template({author: 'Yehuda'});
assert.strictEqual(output, 'Author: Yehuda'); assert.strictEqual(output, 'Author: Yehuda');
console.log('Success'); console.log('Success');
@@ -1,28 +1,26 @@
#!/bin/bash #!/bin/bash
set -e
cd "$( dirname "$( readlink -f "$0" )" )" || exit 1 cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
# shellcheck disable=SC1090 # shellcheck disable=SC1090
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# This script tests with precompiler and the built distribution with multiple NodeJS version. # 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. # 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. # 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 framework to make this part independent of the Node version. # And it does not use any NodeJS based testing framwork to make this part independent of the Node version.
unset npm_config_prefix
# A list of NodeJS versions is expected as cli-args
echo "Handlebars should be able to run in various versions of NodeJS" echo "Handlebars should be able to run in various versions of NodeJS"
for node_version_to_test in 0.10 0.12 4 5 6 7 8 9 10 11 12 13 14 15 16 18; do for i in 0.10 0.12 4 5 6 7 8 9 10 11 ; do
rm target node_modules package-lock.json -rf rm target node_modules package-lock.json -rf
mkdir target mkdir target
nvm install "$i"
nvm install "$node_version_to_test" nvm exec "$i" npm install
nvm exec "$node_version_to_test" npm install nvm exec "$i" npm run test || exit 1
nvm exec "$node_version_to_test" npm run test nvm exec "$i" npm run test-precompile || exit 1
nvm exec "$node_version_to_test" npm run test-precompile
echo Success echo Success
done done
@@ -1,15 +1,13 @@
#!/bin/bash #!/bin/bash
set -e cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
cd "$( dirname "$( readlink -f "$0" )" )"
for i in */test.sh ; do for i in */test.sh ; do
( (
echo "----------------------------------------" echo "----------------------------------------"
echo "-- Running integration test: $i" echo "-- Running integration test: $i"
echo "----------------------------------------" echo "----------------------------------------"
cd "$( dirname "$i" )" cd "$( dirname "$i" )" || exit 1
./test.sh ./test.sh || exit 1
) )
done done
@@ -12,7 +12,7 @@
"@roundingwellos/babel-plugin-handlebars-inline-precompile": "^3.0.1", "@roundingwellos/babel-plugin-handlebars-inline-precompile": "^3.0.1",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babel-plugin-istanbul": "^5.2.0", "babel-plugin-istanbul": "^5.2.0",
"handlebars": "file:../../..", "handlebars": "file:../..",
"handlebars-loader": "^1.7.1", "handlebars-loader": "^1.7.1",
"nyc": "^14.1.1", "nyc": "^14.1.1",
"webpack": "^4.39.3", "webpack": "^4.39.3",
@@ -0,0 +1,12 @@
import * as Handlebars from 'handlebars/runtime'
import hbs from 'handlebars-inline-precompile';
import {assertEquals} from "../../webpack-test/src/lib/assert";
Handlebars.registerHelper('loud', function(text) {
return text.toUpperCase();
});
const template = hbs`{{loud name}}`;
const output = template({name: 'yehuda'})
assertEquals(output, 'YEHUDA')
@@ -0,0 +1,5 @@
export function assertEquals(actual, expected) {
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
}
@@ -4,7 +4,7 @@ set -e
# Cleanup: package-lock and "npm ci" is not working with local dependencies # Cleanup: package-lock and "npm ci" is not working with local dependencies
rm dist package-lock.json -rf rm dist package-lock.json -rf
npm install --legacy-peer-deps npm install
npm run build npm run build
for i in dist/*-test.js ; do for i in dist/*-test.js ; do
@@ -0,0 +1,32 @@
const fs = require('fs');
const testFiles = fs.readdirSync('src');
const entryPoints = {};
testFiles
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
module.exports = {
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: false },
}
}
]
},
optimization: {
minimize: false
}
};
@@ -0,0 +1,21 @@
{
"name": "webpack-test",
"description": "Various tests with Handlebars and Webpack",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js",
"test": "node dist/main.js"
},
"private": true,
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"handlebars": "file:../..",
"handlebars-loader": "^1.7.1",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.7"
}
}
@@ -1,6 +1,6 @@
import Handlebars from 'handlebars/dist/handlebars'; import Handlebars from 'handlebars/dist/handlebars';
import { assertEquals } from './lib/assert'; import {assertEquals} from './lib/assert';
const template = Handlebars.compile('Author: {{author}}'); const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda'); assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
@@ -0,0 +1,6 @@
import Handlebars from 'handlebars';
import {assertEquals} from './lib/assert';
const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
@@ -0,0 +1,8 @@
import {assertEquals} from './lib/assert';
import testTemplate from './test-template.handlebars';
assertEquals(testTemplate({author: 'Yehuda'}).trim(), 'Author: Yehuda');
const testTemplateRequire = require('./test-template.handlebars');
assertEquals(testTemplateRequire({author: 'Yehuda'}).trim(), 'Author: Yehuda');
@@ -1,10 +1,10 @@
import * as HandlebarsViaImport from 'handlebars'; import * as HandlebarsViaImport from 'handlebars';
const HandlebarsViaRequire = require('handlebars'); const HandlebarsViaRequire = require('handlebars');
import { assertEquals } from './lib/assert'; import {assertEquals} from './lib/assert';
HandlebarsViaImport.registerHelper('loud', function(text) { HandlebarsViaImport.registerHelper('loud', function(text) {
return text.toUpperCase(); return text.toUpperCase();
}); });
const template = HandlebarsViaRequire.compile('Author: {{loud author}}'); const template = HandlebarsViaRequire.compile('Author: {{loud author}}');
assertEquals(template({ author: 'Yehuda' }), 'Author: YEHUDA'); assertEquals(template({author: 'Yehuda'}), 'Author: YEHUDA');
@@ -1,6 +1,6 @@
import * as Handlebars from 'handlebars/dist/handlebars'; import * as Handlebars from 'handlebars/dist/handlebars';
import { assertEquals } from './lib/assert'; import {assertEquals} from './lib/assert';
const template = Handlebars.compile('Author: {{author}}'); const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda'); assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
@@ -0,0 +1,5 @@
import * as Handlebars from 'handlebars';
import {assertEquals} from './lib/assert';
const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
@@ -0,0 +1,5 @@
export function assertEquals(actual, expected) {
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
}
@@ -7,5 +7,10 @@ rm dist package-lock.json -rf
npm install npm install
npm run build npm run build
node dist/bundle.js for i in dist/*-test.js ; do
echo "Success" echo "----------------------"
echo "-- Running $i"
echo "----------------------"
node "$i"
echo "Success"
done
@@ -0,0 +1,22 @@
const fs = require('fs');
const testFiles = fs.readdirSync('src');
const entryPoints = {};
testFiles
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
module.exports = {
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{test: /\.handlebars$/, loader: 'handlebars-loader'}
]
}
};
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
env: {
// Handlebars should not use node or browser-specific APIs
'shared-node-browser': true,
node: false,
browser: false
}
};
+1 -5
View File
@@ -2,11 +2,7 @@ import runtime from './handlebars.runtime';
// Compiler imports // Compiler imports
import AST from './handlebars/compiler/ast'; import AST from './handlebars/compiler/ast';
import { import { parser as Parser, parse, parseWithoutProcessing } from './handlebars/compiler/base';
parser as Parser,
parse,
parseWithoutProcessing
} from './handlebars/compiler/base';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler'; import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler'; import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import Visitor from './handlebars/compiler/visitor'; import Visitor from './handlebars/compiler/visitor';
+8 -22
View File
@@ -1,11 +1,10 @@
import { createFrame, extend, toString } from './utils'; import {createFrame, extend, toString} from './utils';
import Exception from './exception'; import Exception from './exception';
import { registerDefaultHelpers } from './helpers'; import {registerDefaultHelpers} from './helpers';
import { registerDefaultDecorators } from './decorators'; import {registerDefaultDecorators} from './decorators';
import logger from './logger'; import logger from './logger';
import { resetLoggedProperties } from './internal/proto-access';
export const VERSION = '4.7.8'; export const VERSION = '4.5.1';
export const COMPILER_REVISION = 8; export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7; export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
@@ -39,9 +38,7 @@ HandlebarsEnvironment.prototype = {
registerHelper: function(name, fn) { registerHelper: function(name, fn) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
if (fn) { if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
throw new Exception('Arg not supported with multiple helpers');
}
extend(this.helpers, name); extend(this.helpers, name);
} else { } else {
this.helpers[name] = fn; this.helpers[name] = fn;
@@ -56,9 +53,7 @@ HandlebarsEnvironment.prototype = {
extend(this.partials, name); extend(this.partials, name);
} else { } else {
if (typeof partial === 'undefined') { if (typeof partial === 'undefined') {
throw new Exception( throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
`Attempting to register a partial called "${name}" as undefined`
);
} }
this.partials[name] = partial; this.partials[name] = partial;
} }
@@ -69,9 +64,7 @@ HandlebarsEnvironment.prototype = {
registerDecorator: function(name, fn) { registerDecorator: function(name, fn) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
if (fn) { if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
throw new Exception('Arg not supported with multiple decorators');
}
extend(this.decorators, name); extend(this.decorators, name);
} else { } else {
this.decorators[name] = fn; this.decorators[name] = fn;
@@ -79,16 +72,9 @@ HandlebarsEnvironment.prototype = {
}, },
unregisterDecorator: function(name) { unregisterDecorator: function(name) {
delete this.decorators[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 let log = logger.log;
export { createFrame, logger }; export {createFrame, logger};
+6 -10
View File
@@ -5,28 +5,24 @@ let AST = {
// * it is an eligible helper, and // * it is an eligible helper, and
// * it has at least one parameter or hash segment // * it has at least one parameter or hash segment
helperExpression: function(node) { helperExpression: function(node) {
return ( return (node.type === 'SubExpression')
node.type === 'SubExpression' || || ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
((node.type === 'MustacheStatement' || && !!((node.params && node.params.length) || node.hash));
node.type === 'BlockStatement') &&
!!((node.params && node.params.length) || node.hash))
);
}, },
scopedId: function(path) { scopedId: function(path) {
return /^\.|this\b/.test(path.original); return (/^\.|this\b/).test(path.original);
}, },
// an ID is simple if it only has one part, and that part is not // an ID is simple if it only has one part, and that part is not
// `..` or `this`. // `..` or `this`.
simpleId: function(path) { simpleId: function(path) {
return ( return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
);
} }
} }
}; };
// Must be exported as an object rather than the root of the module as the jison lexer // Must be exported as an object rather than the root of the module as the jison lexer
// must modify the object to operate properly. // must modify the object to operate properly.
export default AST; export default AST;
+1 -3
View File
@@ -10,9 +10,7 @@ extend(yy, Helpers);
export function parseWithoutProcessing(input, options) { export function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in. // Just return if an already-compiled AST was passed in.
if (input.type === 'Program') { if (input.type === 'Program') { return input; }
return input;
}
parser.yy = yy; parser.yy = yy;
+25 -28
View File
@@ -1,12 +1,12 @@
/* global define, require */ /* global define */
import { isArray } from '../utils'; import {isArray} from '../utils';
let SourceNode; let SourceNode;
try { try {
/* istanbul ignore next */ /* istanbul ignore next */
if (typeof define !== 'function' || !define.amd) { if (typeof define !== 'function' || !define.amd) {
// We don't support this in AMD environments. For these environments, we assume that // We don't support this in AMD environments. For these environments, we asusme that
// they are running on the browser and thus have no need for the source-map library. // they are running on the browser and thus have no need for the source-map library.
let SourceMap = require('source-map'); let SourceMap = require('source-map');
SourceNode = SourceMap.SourceNode; SourceNode = SourceMap.SourceNode;
@@ -38,7 +38,7 @@ if (!SourceNode) {
this.src = chunks + this.src; this.src = chunks + this.src;
}, },
toStringWithSourceMap: function() { toStringWithSourceMap: function() {
return { code: this.toString() }; return {code: this.toString()};
}, },
toString: function() { toString: function() {
return this.src; return this.src;
@@ -46,6 +46,7 @@ if (!SourceNode) {
}; };
} }
function castChunk(chunk, codeGen, loc) { function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) { if (isArray(chunk)) {
let ret = []; let ret = [];
@@ -61,6 +62,7 @@ function castChunk(chunk, codeGen, loc) {
return chunk; return chunk;
} }
function CodeGen(srcFile) { function CodeGen(srcFile) {
this.srcFile = srcFile; this.srcFile = srcFile;
this.source = []; this.source = [];
@@ -92,22 +94,17 @@ CodeGen.prototype = {
}, },
empty: function() { empty: function() {
let loc = this.currentLocation || { start: {} }; let loc = this.currentLocation || {start: {}};
return new SourceNode(loc.start.line, loc.start.column, this.srcFile); return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
}, },
wrap: function(chunk, loc = this.currentLocation || { start: {} }) { wrap: function(chunk, loc = this.currentLocation || {start: {}}) {
if (chunk instanceof SourceNode) { if (chunk instanceof SourceNode) {
return chunk; return chunk;
} }
chunk = castChunk(chunk, this, loc); chunk = castChunk(chunk, this, loc);
return new SourceNode( return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
loc.start.line,
loc.start.column,
this.srcFile,
chunk
);
}, },
functionCall: function(fn, type, params) { functionCall: function(fn, type, params) {
@@ -116,28 +113,26 @@ CodeGen.prototype = {
}, },
quotedString: function(str) { quotedString: function(str) {
return ( return '"' + (str + '')
'"' + .replace(/\\/g, '\\\\')
(str + '') .replace(/"/g, '\\"')
.replace(/\\/g, '\\\\') .replace(/\n/g, '\\n')
.replace(/"/g, '\\"') .replace(/\r/g, '\\r')
.replace(/\n/g, '\\n') .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\r/g, '\\r') .replace(/\u2029/g, '\\u2029') + '"';
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') +
'"'
);
}, },
objectLiteral: function(obj) { objectLiteral: function(obj) {
let pairs = []; let pairs = [];
Object.keys(obj).forEach(key => { for (let key in obj) {
let value = castChunk(obj[key], this); if (obj.hasOwnProperty(key)) {
if (value !== 'undefined') { let value = castChunk(obj[key], this);
pairs.push([this.quotedString(key), ':', value]); if (value !== 'undefined') {
pairs.push([this.quotedString(key), ':', value]);
}
} }
}); }
let ret = this.generateList(pairs); let ret = this.generateList(pairs);
ret.prepend('{'); ret.prepend('{');
@@ -145,6 +140,7 @@ CodeGen.prototype = {
return ret; return ret;
}, },
generateList: function(entries) { generateList: function(entries) {
let ret = this.empty(); let ret = this.empty();
@@ -169,3 +165,4 @@ CodeGen.prototype = {
}; };
export default CodeGen; export default CodeGen;
+62 -97
View File
@@ -1,7 +1,7 @@
/* eslint-disable new-cap */ /* eslint-disable new-cap */
import Exception from '../exception'; import Exception from '../exception';
import { isArray, indexOf, extend } from '../utils'; import {isArray, indexOf, extend} from '../utils';
import AST from './ast'; import AST from './ast';
const slice = [].slice; const slice = [].slice;
@@ -24,11 +24,8 @@ Compiler.prototype = {
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i], let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i]; otherOpcode = other.opcodes[i];
if ( if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false; return false;
} }
} }
@@ -57,28 +54,34 @@ Compiler.prototype = {
options.blockParams = options.blockParams || []; options.blockParams = options.blockParams || [];
options.knownHelpers = extend( // These changes will propagate to the other compiler components
Object.create(null), let knownHelpers = options.knownHelpers;
{ options.knownHelpers = {
helperMissing: true, 'helperMissing': true,
blockHelperMissing: true, 'blockHelperMissing': true,
each: true, 'each': true,
if: true, 'if': true,
unless: true, 'unless': true,
with: true, 'with': true,
log: true, 'log': true,
lookup: true 'lookup': true
}, };
options.knownHelpers 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];
}
}
return this.accept(program); return this.accept(program);
}, },
compileProgram: function(program) { compileProgram: function(program) {
let childCompiler = new this.compiler(), // eslint-disable-line new-cap let childCompiler = new this.compiler(), // eslint-disable-line new-cap
result = childCompiler.compile(program, this.options), result = childCompiler.compile(program, this.options),
guid = this.guid++; guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial; this.usePartial = this.usePartial || result.usePartial;
@@ -104,7 +107,7 @@ Compiler.prototype = {
this.options.blockParams.unshift(program.blockParams); this.options.blockParams.unshift(program.blockParams);
let body = program.body, let body = program.body,
bodyLength = body.length; bodyLength = body.length;
for (let i = 0; i < bodyLength; i++) { for (let i = 0; i < bodyLength; i++) {
this.accept(body[i]); this.accept(body[i]);
} }
@@ -121,7 +124,7 @@ Compiler.prototype = {
transformLiteralToPath(block); transformLiteralToPath(block);
let program = block.program, let program = block.program,
inverse = block.inverse; inverse = block.inverse;
program = program && this.compileProgram(program); program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse); inverse = inverse && this.compileProgram(inverse);
@@ -156,7 +159,7 @@ Compiler.prototype = {
DecoratorBlock(decorator) { DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program); let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined), let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path; path = decorator.path;
this.useDecorators = true; this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original); this.opcode('registerDecorator', params.length, path.original);
@@ -172,20 +175,17 @@ Compiler.prototype = {
let params = partial.params; let params = partial.params;
if (params.length > 1) { if (params.length > 1) {
throw new Exception( throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
'Unsupported number of partial arguments: ' + params.length,
partial
);
} else if (!params.length) { } else if (!params.length) {
if (this.options.explicitPartialContext) { if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined'); this.opcode('pushLiteral', 'undefined');
} else { } else {
params.push({ type: 'PathExpression', parts: [], depth: 0 }); params.push({type: 'PathExpression', parts: [], depth: 0});
} }
} }
let partialName = partial.name.original, let partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression'; isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) { if (isDynamic) {
this.accept(partial.name); this.accept(partial.name);
} }
@@ -218,6 +218,7 @@ Compiler.prototype = {
this.DecoratorBlock(decorator); this.DecoratorBlock(decorator);
}, },
ContentStatement: function(content) { ContentStatement: function(content) {
if (content.value) { if (content.value) {
this.opcode('appendContent', content.value); this.opcode('appendContent', content.value);
@@ -240,8 +241,8 @@ Compiler.prototype = {
}, },
ambiguousSexpr: function(sexpr, program, inverse) { ambiguousSexpr: function(sexpr, program, inverse) {
let path = sexpr.path, let path = sexpr.path,
name = path.parts[0], name = path.parts[0],
isBlock = program != null || inverse != null; isBlock = program != null || inverse != null;
this.opcode('getContext', path.depth); this.opcode('getContext', path.depth);
@@ -263,27 +264,19 @@ Compiler.prototype = {
helperSexpr: function(sexpr, program, inverse) { helperSexpr: function(sexpr, program, inverse) {
let params = this.setupFullMustacheParams(sexpr, program, inverse), let params = this.setupFullMustacheParams(sexpr, program, inverse),
path = sexpr.path, path = sexpr.path,
name = path.parts[0]; name = path.parts[0];
if (this.options.knownHelpers[name]) { if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name); this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) { } else if (this.options.knownHelpersOnly) {
throw new Exception( throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
'You specified knownHelpersOnly, but used the unknown helper ' + name,
sexpr
);
} else { } else {
path.strict = true; path.strict = true;
path.falsy = true; path.falsy = true;
this.accept(path); this.accept(path);
this.opcode( this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
'invokeHelper',
params.length,
path.original,
AST.helpers.simpleId(path)
);
} }
}, },
@@ -292,8 +285,8 @@ Compiler.prototype = {
this.opcode('getContext', path.depth); this.opcode('getContext', path.depth);
let name = path.parts[0], let name = path.parts[0],
scoped = AST.helpers.scopedId(path), scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name); blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
if (blockParamId) { if (blockParamId) {
this.opcode('lookupBlockParam', blockParamId, path.parts); this.opcode('lookupBlockParam', blockParamId, path.parts);
@@ -304,13 +297,7 @@ Compiler.prototype = {
this.options.data = true; this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict); this.opcode('lookupData', path.depth, path.parts, path.strict);
} else { } else {
this.opcode( this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
} }
}, },
@@ -336,8 +323,8 @@ Compiler.prototype = {
Hash: function(hash) { Hash: function(hash) {
let pairs = hash.pairs, let pairs = hash.pairs,
i = 0, i = 0,
l = pairs.length; l = pairs.length;
this.opcode('pushHash'); this.opcode('pushHash');
@@ -352,11 +339,7 @@ Compiler.prototype = {
// HELPERS // HELPERS
opcode: function(name) { opcode: function(name) {
this.opcodes.push({ this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
opcode: name,
args: slice.call(arguments, 1),
loc: this.sourceNode[0].loc
});
}, },
addDepth: function(depth) { addDepth: function(depth) {
@@ -385,7 +368,8 @@ Compiler.prototype = {
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc. // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
if (isEligible && !isHelper) { if (isEligible && !isHelper) {
let name = sexpr.path.parts[0], let name = sexpr.path.parts[0],
options = this.options; options = this.options;
if (options.knownHelpers[name]) { if (options.knownHelpers[name]) {
isHelper = true; isHelper = true;
} else if (options.knownHelpersOnly) { } else if (options.knownHelpersOnly) {
@@ -413,7 +397,9 @@ Compiler.prototype = {
if (this.stringParams) { if (this.stringParams) {
if (value.replace) { if (value.replace) {
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.'); value = value
.replace(/^(\.?\.\/)*/g, '')
.replace(/\//g, '.');
} }
if (val.depth) { if (val.depth) {
@@ -431,7 +417,7 @@ Compiler.prototype = {
if (this.trackIds) { if (this.trackIds) {
let blockParamIndex; let blockParamIndex;
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) { if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
blockParamIndex = this.blockParamIndex(val.parts[0]); blockParamIndex = this.blockParamIndex(val.parts[0]);
} }
if (blockParamIndex) { if (blockParamIndex) {
let blockParamChild = val.parts.slice(1).join('.'); let blockParamChild = val.parts.slice(1).join('.');
@@ -440,9 +426,9 @@ Compiler.prototype = {
value = val.original || value; value = val.original || value;
if (value.replace) { if (value.replace) {
value = value value = value
.replace(/^this(?:\.|$)/, '') .replace(/^this(?:\.|$)/, '')
.replace(/^\.\//, '') .replace(/^\.\//, '')
.replace(/^\.$/, ''); .replace(/^\.$/, '');
} }
this.opcode('pushId', val.type, value); this.opcode('pushId', val.type, value);
@@ -469,13 +455,9 @@ Compiler.prototype = {
}, },
blockParamIndex: function(name) { blockParamIndex: function(name) {
for ( for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
let depth = 0, len = this.options.blockParams.length;
depth < len;
depth++
) {
let blockParams = this.options.blockParams[depth], let blockParams = this.options.blockParams[depth],
param = blockParams && indexOf(blockParams, name); param = blockParams && indexOf(blockParams, name);
if (blockParams && param >= 0) { if (blockParams && param >= 0) {
return [depth, param]; return [depth, param];
} }
@@ -484,14 +466,8 @@ Compiler.prototype = {
}; };
export function precompile(input, options, env) { export function precompile(input, options, env) {
if ( if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
input == null || throw new Exception('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
input
);
} }
options = options || {}; options = options || {};
@@ -503,19 +479,13 @@ export function precompile(input, options, env) {
} }
let ast = env.parse(input, options), let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options); environment = new env.Compiler().compile(ast, options);
return new env.JavaScriptCompiler().compile(environment, options); return new env.JavaScriptCompiler().compile(environment, options);
} }
export function compile(input, options = {}, env) { export function compile(input, options = {}, env) {
if ( if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
input == null || throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
input
);
} }
options = extend({}, options); options = extend({}, options);
@@ -530,13 +500,8 @@ export function compile(input, options = {}, env) {
function compileInput() { function compileInput() {
let ast = env.parse(input, options), let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options), environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile( templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
environment,
options,
undefined,
true
);
return env.template(templateSpec); return env.template(templateSpec);
} }
+20 -29
View File
@@ -4,12 +4,9 @@ function validateClose(open, close) {
close = close.path ? close.path.original : close; close = close.path ? close.path.original : close;
if (open.path.original !== close) { if (open.path.original !== close) {
let errorNode = { loc: open.path.loc }; let errorNode = {loc: open.path.loc};
throw new Exception( throw new Exception(open.path.original + " doesn't match " + close, errorNode);
open.path.original + " doesn't match " + close,
errorNode
);
} }
} }
@@ -41,26 +38,27 @@ export function stripFlags(open, close) {
} }
export function stripComment(comment) { export function stripComment(comment) {
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, ''); return comment.replace(/^\{\{~?!-?-?/, '')
.replace(/-?-?~?\}\}$/, '');
} }
export function preparePath(data, parts, loc) { export function preparePath(data, parts, loc) {
loc = this.locInfo(loc); loc = this.locInfo(loc);
let original = data ? '@' : '', let original = data ? '@' : '',
dig = [], dig = [],
depth = 0; depth = 0;
for (let i = 0, l = parts.length; i < l; i++) { for (let i = 0, l = parts.length; i < l; i++) {
let part = parts[i].part, let part = parts[i].part,
// If we have [] syntax then we do not treat path references as operators, // If we have [] syntax then we do not treat path references as operators,
// i.e. foo.[this] resolves to approximately context.foo['this'] // i.e. foo.[this] resolves to approximately context.foo['this']
isLiteral = parts[i].original !== part; isLiteral = parts[i].original !== part;
original += (parts[i].separator || '') + part; original += (parts[i].separator || '') + part;
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
if (dig.length > 0) { if (dig.length > 0) {
throw new Exception('Invalid path: ' + original, { loc }); throw new Exception('Invalid path: ' + original, {loc});
} else if (part === '..') { } else if (part === '..') {
depth++; depth++;
} }
@@ -82,9 +80,9 @@ export function preparePath(data, parts, loc) {
export function prepareMustache(path, params, hash, open, strip, locInfo) { export function prepareMustache(path, params, hash, open, strip, locInfo) {
// Must use charAt to support IE pre-10 // Must use charAt to support IE pre-10
let escapeFlag = open.charAt(3) || open.charAt(2), let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&'; escaped = escapeFlag !== '{' && escapeFlag !== '&';
let decorator = /\*/.test(open); let decorator = (/\*/.test(open));
return { return {
type: decorator ? 'Decorator' : 'MustacheStatement', type: decorator ? 'Decorator' : 'MustacheStatement',
path, path,
@@ -120,30 +118,21 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
}; };
} }
export function prepareBlock( export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
openBlock,
program,
inverseAndProgram,
close,
inverted,
locInfo
) {
if (close && close.path) { if (close && close.path) {
validateClose(openBlock, close); validateClose(openBlock, close);
} }
let decorator = /\*/.test(openBlock.open); let decorator = (/\*/.test(openBlock.open));
program.blockParams = openBlock.blockParams; program.blockParams = openBlock.blockParams;
let inverse, inverseStrip; let inverse,
inverseStrip;
if (inverseAndProgram) { if (inverseAndProgram) {
if (decorator) { if (decorator) {
throw new Exception( throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
'Unexpected inverse block on decorator',
inverseAndProgram
);
} }
if (inverseAndProgram.chain) { if (inverseAndProgram.chain) {
@@ -177,7 +166,7 @@ export function prepareBlock(
export function prepareProgram(statements, loc) { export function prepareProgram(statements, loc) {
if (!loc && statements.length) { if (!loc && statements.length) {
const firstLoc = statements[0].loc, const firstLoc = statements[0].loc,
lastLoc = statements[statements.length - 1].loc; lastLoc = statements[statements.length - 1].loc;
/* istanbul ignore else */ /* istanbul ignore else */
if (firstLoc && lastLoc) { if (firstLoc && lastLoc) {
@@ -203,6 +192,7 @@ export function prepareProgram(statements, loc) {
}; };
} }
export function preparePartialBlock(open, program, close, locInfo) { export function preparePartialBlock(open, program, close, locInfo) {
validateClose(open, close); validateClose(open, close);
@@ -217,3 +207,4 @@ export function preparePartialBlock(open, program, close, locInfo) {
loc: this.locInfo(locInfo) loc: this.locInfo(locInfo)
}; };
} }
+107 -240
View File
@@ -1,6 +1,6 @@
import { COMPILER_REVISION, REVISION_CHANGES } from '../base'; import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
import Exception from '../exception'; import Exception from '../exception';
import { isArray } from '../utils'; import {isArray} from '../utils';
import CodeGen from './code-gen'; import CodeGen from './code-gen';
function Literal(value) { function Literal(value) {
@@ -12,21 +12,29 @@ function JavaScriptCompiler() {}
JavaScriptCompiler.prototype = { JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide // PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics // alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name /*, type */) { nameLookup: function(parent, name/* , type*/) {
return this.internalNameLookup(parent, name); const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',"constructor")'];
if (name === 'constructor') {
return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
}
return _actualLookup();
function _actualLookup() {
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
return [parent, '.', name];
} else {
return [parent, '[', JSON.stringify(name), ']'];
}
}
}, },
depthedLookup: function(name) { depthedLookup: function(name) {
return [ return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
this.aliasable('container.lookup'),
'(depths, ',
JSON.stringify(name),
')'
];
}, },
compilerInfo: function() { compilerInfo: function() {
const revision = COMPILER_REVISION, const revision = COMPILER_REVISION,
versions = REVISION_CHANGES[revision]; versions = REVISION_CHANGES[revision];
return [revision, versions]; return [revision, versions];
}, },
@@ -54,12 +62,6 @@ JavaScriptCompiler.prototype = {
return this.quotedString(''); return this.quotedString('');
}, },
// END PUBLIC API // END PUBLIC API
internalNameLookup: function(parent, name) {
this.lookupPropertyFunctionIsUsed = true;
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
},
lookupPropertyFunctionIsUsed: false,
compile: function(environment, options, context, asObject) { compile: function(environment, options, context, asObject) {
this.environment = environment; this.environment = environment;
@@ -89,18 +91,14 @@ JavaScriptCompiler.prototype = {
this.compileChildren(environment, options); this.compileChildren(environment, options);
this.useDepths = this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
this.useDepths ||
environment.useDepths ||
environment.useDecorators ||
this.options.compat;
this.useBlockParams = this.useBlockParams || environment.useBlockParams; this.useBlockParams = this.useBlockParams || environment.useBlockParams;
let opcodes = environment.opcodes, let opcodes = environment.opcodes,
opcode, opcode,
firstLoc, firstLoc,
i, i,
l; l;
for (i = 0, l = opcodes.length; i < l; i++) { for (i = 0, l = opcodes.length; i < l; i++) {
opcode = opcodes[i]; opcode = opcodes[i];
@@ -122,28 +120,13 @@ JavaScriptCompiler.prototype = {
if (!this.decorators.isEmpty()) { if (!this.decorators.isEmpty()) {
this.useDecorators = true; this.useDecorators = true;
this.decorators.prepend([ this.decorators.prepend('var decorators = container.decorators;\n');
'var decorators = container.decorators, ',
this.lookupPropertyFunctionVarDeclaration(),
';\n'
]);
this.decorators.push('return fn;'); this.decorators.push('return fn;');
if (asObject) { if (asObject) {
this.decorators = Function.apply(this, [ this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
'fn',
'props',
'container',
'depth0',
'data',
'blockParams',
'depths',
this.decorators.merge()
]);
} else { } else {
this.decorators.prepend( this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
'function(fn, props, container, depth0, data, blockParams, depths) {\n'
);
this.decorators.push('}\n'); this.decorators.push('}\n');
this.decorators = this.decorators.merge(); this.decorators = this.decorators.merge();
} }
@@ -163,7 +146,7 @@ JavaScriptCompiler.prototype = {
ret.useDecorators = true; ret.useDecorators = true;
} }
let { programs, decorators } = this.context; let {programs, decorators} = this.context;
for (i = 0, l = programs.length; i < l; i++) { for (i = 0, l = programs.length; i < l; i++) {
if (programs[i]) { if (programs[i]) {
ret[i] = programs[i]; ret[i] = programs[i];
@@ -193,11 +176,11 @@ JavaScriptCompiler.prototype = {
if (!asObject) { if (!asObject) {
ret.compiler = JSON.stringify(ret.compiler); ret.compiler = JSON.stringify(ret.compiler);
this.source.currentLocation = { start: { line: 1, column: 0 } }; this.source.currentLocation = {start: {line: 1, column: 0}};
ret = this.objectLiteral(ret); ret = this.objectLiteral(ret);
if (options.srcName) { if (options.srcName) {
ret = ret.toStringWithSourceMap({ file: options.destName }); ret = ret.toStringWithSourceMap({file: options.destName});
ret.map = ret.map && ret.map.toString(); ret.map = ret.map && ret.map.toString();
} else { } else {
ret = ret.toString(); ret = ret.toString();
@@ -235,16 +218,12 @@ JavaScriptCompiler.prototype = {
// aliases will not be used, but this case is already being run on the client and // aliases will not be used, but this case is already being run on the client and
// we aren't concern about minimizing the template size. // we aren't concern about minimizing the template size.
let aliasCount = 0; let aliasCount = 0;
Object.keys(this.aliases).forEach(alias => { for (let alias in this.aliases) { // eslint-disable-line guard-for-in
let node = this.aliases[alias]; let node = this.aliases[alias];
if (node.children && node.referenceCount > 1) { if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
varDeclarations += ', alias' + ++aliasCount + '=' + alias; varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
node.children[0] = 'alias' + aliasCount; node.children[0] = 'alias' + aliasCount;
} }
});
if (this.lookupPropertyFunctionIsUsed) {
varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration();
} }
let params = ['container', 'depth0', 'helpers', 'partials', 'data']; let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
@@ -264,23 +243,18 @@ JavaScriptCompiler.prototype = {
return Function.apply(this, params); return Function.apply(this, params);
} else { } else {
return this.source.wrap([ return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
'function(',
params.join(','),
') {\n ',
source,
'}'
]);
} }
}, },
mergeSource: function(varDeclarations) { mergeSource: function(varDeclarations) {
let isSimple = this.environment.isSimple, let isSimple = this.environment.isSimple,
appendOnly = !this.forceBuffer, appendOnly = !this.forceBuffer,
appendFirst, appendFirst,
sourceSeen,
bufferStart, sourceSeen,
bufferEnd; bufferStart,
this.source.each(line => { bufferEnd;
this.source.each((line) => {
if (line.appendToBuffer) { if (line.appendToBuffer) {
if (bufferStart) { if (bufferStart) {
line.prepend(' + '); line.prepend(' + ');
@@ -306,6 +280,7 @@ JavaScriptCompiler.prototype = {
} }
}); });
if (appendOnly) { if (appendOnly) {
if (bufferStart) { if (bufferStart) {
bufferStart.prepend('return '); bufferStart.prepend('return ');
@@ -314,8 +289,7 @@ JavaScriptCompiler.prototype = {
this.source.push('return "";'); this.source.push('return "";');
} }
} else { } else {
varDeclarations += varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
if (bufferStart) { if (bufferStart) {
bufferStart.prepend('return buffer + '); bufferStart.prepend('return buffer + ');
@@ -326,25 +300,12 @@ JavaScriptCompiler.prototype = {
} }
if (varDeclarations) { if (varDeclarations) {
this.source.prepend( this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')
);
} }
return this.source.merge(); return this.source.merge();
}, },
lookupPropertyFunctionVarDeclaration: function() {
return `
lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
}
`.trim();
},
// [blockValue] // [blockValue]
// //
// On stack, before: hash, inverse, program, value // On stack, before: hash, inverse, program, value
@@ -355,10 +316,8 @@ JavaScriptCompiler.prototype = {
// replace it on the stack with the result of properly // replace it on the stack with the result of properly
// invoking blockHelperMissing. // invoking blockHelperMissing.
blockValue: function(name) { blockValue: function(name) {
let blockHelperMissing = this.aliasable( let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
'container.hooks.blockHelperMissing' params = [this.contextName(0)];
),
params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params); this.setupHelperArgs(name, 0, params);
let blockName = this.popStack(); let blockName = this.popStack();
@@ -375,10 +334,8 @@ JavaScriptCompiler.prototype = {
// On stack, after, if lastHelper: value // On stack, after, if lastHelper: value
ambiguousBlockValue: function() { ambiguousBlockValue: function() {
// We're being a bit cheeky and reusing the options value from the prior exec // We're being a bit cheeky and reusing the options value from the prior exec
let blockHelperMissing = this.aliasable( let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
'container.hooks.blockHelperMissing' params = [this.contextName(0)];
),
params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true); this.setupHelperArgs('', 0, params, true);
this.flushInline(); this.flushInline();
@@ -387,14 +344,9 @@ JavaScriptCompiler.prototype = {
params.splice(1, 0, current); params.splice(1, 0, current);
this.pushSource([ this.pushSource([
'if (!', 'if (!', this.lastHelper, ') { ',
this.lastHelper, current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
') { ', '}']);
current,
' = ',
this.source.functionCall(blockHelperMissing, 'call', params),
'}'
]);
}, },
// [appendContent] // [appendContent]
@@ -424,24 +376,14 @@ JavaScriptCompiler.prototype = {
// Otherwise, the empty string is appended // Otherwise, the empty string is appended
append: function() { append: function() {
if (this.isInline()) { if (this.isInline()) {
this.replaceStack(current => [' != null ? ', current, ' : ""']); this.replaceStack((current) => [' != null ? ', current, ' : ""']);
this.pushSource(this.appendToBuffer(this.popStack())); this.pushSource(this.appendToBuffer(this.popStack()));
} else { } else {
let local = this.popStack(); let local = this.popStack();
this.pushSource([ this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
'if (',
local,
' != null) { ',
this.appendToBuffer(local, undefined, true),
' }'
]);
if (this.environment.isSimple) { if (this.environment.isSimple) {
this.pushSource([ this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
'else { ',
this.appendToBuffer("''", undefined, true),
' }'
]);
} }
} }
}, },
@@ -453,14 +395,8 @@ JavaScriptCompiler.prototype = {
// //
// Escape `value` and append it to the buffer // Escape `value` and append it to the buffer
appendEscaped: function() { appendEscaped: function() {
this.pushSource( this.pushSource(this.appendToBuffer(
this.appendToBuffer([ [this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));
this.aliasable('container.escapeExpression'),
'(',
this.popStack(),
')'
])
);
}, },
// [getContext] // [getContext]
@@ -537,16 +473,14 @@ JavaScriptCompiler.prototype = {
resolvePath: function(type, parts, i, falsy, strict) { resolvePath: function(type, parts, i, falsy, strict) {
if (this.options.strict || this.options.assumeObjects) { if (this.options.strict || this.options.assumeObjects) {
this.push( this.push(strictLookup(this.options.strict && strict, this, parts, type));
strictLookup(this.options.strict && strict, this, parts, i, type)
);
return; return;
} }
let len = parts.length; let len = parts.length;
for (; i < len; i++) { for (; i < len; i++) {
/* eslint-disable no-loop-func */ /* eslint-disable no-loop-func */
this.replaceStack(current => { this.replaceStack((current) => {
let lookup = this.nameLookup(current, parts[i], type); let lookup = this.nameLookup(current, parts[i], type);
// We want to ensure that zero and false are handled properly if the context (falsy flag) // We want to ensure that zero and false are handled properly if the context (falsy flag)
// needs to have the special handling for these values. // needs to have the special handling for these values.
@@ -569,14 +503,7 @@ JavaScriptCompiler.prototype = {
// If the `value` is a lambda, replace it on the stack by // If the `value` is a lambda, replace it on the stack by
// the return value of the lambda // the return value of the lambda
resolvePossibleLambda: function() { resolvePossibleLambda: function() {
this.push([ this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
this.aliasable('container.lambda'),
'(',
this.popStack(),
', ',
this.contextName(0),
')'
]);
}, },
// [pushStringParam] // [pushStringParam]
@@ -616,7 +543,7 @@ JavaScriptCompiler.prototype = {
if (this.hash) { if (this.hash) {
this.hashes.push(this.hash); this.hashes.push(this.hash);
} }
this.hash = { values: {}, types: [], contexts: [], ids: [] }; this.hash = {values: {}, types: [], contexts: [], ids: []};
}, },
popHash: function() { popHash: function() {
let hash = this.hash; let hash = this.hash;
@@ -680,16 +607,11 @@ JavaScriptCompiler.prototype = {
// and inserts the decorator into the decorators list. // and inserts the decorator into the decorators list.
registerDecorator(paramSize, name) { registerDecorator(paramSize, name) {
let foundDecorator = this.nameLookup('decorators', name, 'decorator'), let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
options = this.setupHelperArgs(name, paramSize); options = this.setupHelperArgs(name, paramSize);
this.decorators.push([ this.decorators.push([
'fn = ', 'fn = ',
this.decorators.functionCall(foundDecorator, '', [ this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
'fn',
'props',
'container',
options
]),
' || fn;' ' || fn;'
]); ]);
}, },
@@ -705,32 +627,21 @@ JavaScriptCompiler.prototype = {
// If the helper is not found, `helperMissing` is called. // If the helper is not found, `helperMissing` is called.
invokeHelper: function(paramSize, name, isSimple) { invokeHelper: function(paramSize, name, isSimple) {
let nonHelper = this.popStack(), let nonHelper = this.popStack(),
helper = this.setupHelper(paramSize, name); helper = this.setupHelper(paramSize, name);
let possibleFunctionCalls = []; let possibleFunctionCalls = [];
if (isSimple) { if (isSimple) { // direct call to helper
// direct call to helper
possibleFunctionCalls.push(helper.name); possibleFunctionCalls.push(helper.name);
} }
// call a function from the input object // call a function from the input object
possibleFunctionCalls.push(nonHelper); possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) { if (!this.options.strict) {
possibleFunctionCalls.push( possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing'));
this.aliasable('container.hooks.helperMissing')
);
} }
let functionLookupCode = [ let functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')'];
'(', let functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams);
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
')'
];
let functionCall = this.source.functionCall(
functionLookupCode,
'call',
helper.callParams
);
this.push(functionCall); this.push(functionCall);
}, },
@@ -774,31 +685,22 @@ JavaScriptCompiler.prototype = {
this.emptyHash(); this.emptyHash();
let helper = this.setupHelper(0, name, helperCall); let helper = this.setupHelper(0, name, helperCall);
let helperName = (this.lastHelper = this.nameLookup( let helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
'helpers',
name,
'helper'
));
let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')']; let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
if (!this.options.strict) { if (!this.options.strict) {
lookup[0] = '(helper = '; lookup[0] = '(helper = ';
lookup.push( lookup.push(
' != null ? helper : ', ' != null ? helper : ',
this.aliasable('container.hooks.helperMissing') this.aliasable('container.hooks.helperMissing')
); );
} }
this.push([ this.push([
'(', '(', lookup,
lookup, (helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
helper.paramsInit ? ['),(', helper.paramsInit] : [], '(typeof helper === ', this.aliasable('"function"'), ' ? ',
'),', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))'
'(typeof helper === ',
this.aliasable('"function"'),
' ? ',
this.source.functionCall('helper', 'call', helper.callParams),
' : helper))'
]); ]);
}, },
@@ -811,7 +713,7 @@ JavaScriptCompiler.prototype = {
// and pushes the result of the invocation back. // and pushes the result of the invocation back.
invokePartial: function(isDynamic, name, indent) { invokePartial: function(isDynamic, name, indent) {
let params = [], let params = [],
options = this.setupParams(name, 1, params); options = this.setupParams(name, 1, params);
if (isDynamic) { if (isDynamic) {
name = this.popStack(); name = this.popStack();
@@ -848,9 +750,9 @@ JavaScriptCompiler.prototype = {
// Pops a value off the stack and assigns it to the current hash // Pops a value off the stack and assigns it to the current hash
assignToHash: function(key) { assignToHash: function(key) {
let value = this.popStack(), let value = this.popStack(),
context, context,
type, type,
id; id;
if (this.trackIds) { if (this.trackIds) {
id = this.popStack(); id = this.popStack();
@@ -876,13 +778,8 @@ JavaScriptCompiler.prototype = {
pushId: function(type, name, child) { pushId: function(type, name, child) {
if (type === 'BlockParam') { if (type === 'BlockParam') {
this.pushStackLiteral( this.pushStackLiteral(
'blockParams[' + 'blockParams[' + name[0] + '].path[' + name[1] + ']'
name[0] + + (child ? ' + ' + JSON.stringify('.' + child) : ''));
'].path[' +
name[1] +
']' +
(child ? ' + ' + JSON.stringify('.' + child) : '')
);
} else if (type === 'PathExpression') { } else if (type === 'PathExpression') {
this.pushString(name); this.pushString(name);
} else if (type === 'SubExpression') { } else if (type === 'SubExpression') {
@@ -897,9 +794,7 @@ JavaScriptCompiler.prototype = {
compiler: JavaScriptCompiler, compiler: JavaScriptCompiler,
compileChildren: function(environment, options) { compileChildren: function(environment, options) {
let children = environment.children, let children = environment.children, child, compiler;
child,
compiler;
for (let i = 0, l = children.length; i < l; i++) { for (let i = 0, l = children.length; i < l; i++) {
child = children[i]; child = children[i];
@@ -912,12 +807,7 @@ JavaScriptCompiler.prototype = {
let index = this.context.programs.length; let index = this.context.programs.length;
child.index = index; child.index = index;
child.name = 'program' + index; child.name = 'program' + index;
this.context.programs[index] = compiler.compile( this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
child,
options,
this.context,
!this.precompile
);
this.context.decorators[index] = compiler.decorators; this.context.decorators[index] = compiler.decorators;
this.context.environments[index] = child; this.context.environments[index] = child;
@@ -945,7 +835,7 @@ JavaScriptCompiler.prototype = {
programExpression: function(guid) { programExpression: function(guid) {
let child = this.environment.children[guid], let child = this.environment.children[guid],
programParams = [child.index, 'data', child.blockParams]; programParams = [child.index, 'data', child.blockParams];
if (this.useBlockParams || this.useDepths) { if (this.useBlockParams || this.useDepths) {
programParams.push('blockParams'); programParams.push('blockParams');
@@ -980,11 +870,7 @@ JavaScriptCompiler.prototype = {
pushSource: function(source) { pushSource: function(source) {
if (this.pendingContent) { if (this.pendingContent) {
this.source.push( this.source.push(
this.appendToBuffer( this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
this.source.quotedString(this.pendingContent),
this.pendingLocation
)
);
this.pendingContent = undefined; this.pendingContent = undefined;
} }
@@ -995,9 +881,9 @@ JavaScriptCompiler.prototype = {
replaceStack: function(callback) { replaceStack: function(callback) {
let prefix = ['('], let prefix = ['('],
stack, stack,
createdStack, createdStack,
usedLiteral; usedLiteral;
/* istanbul ignore next */ /* istanbul ignore next */
if (!this.isInline()) { if (!this.isInline()) {
@@ -1034,9 +920,7 @@ JavaScriptCompiler.prototype = {
incrStack: function() { incrStack: function() {
this.stackSlot++; this.stackSlot++;
if (this.stackSlot > this.stackVars.length) { if (this.stackSlot > this.stackVars.length) { this.stackVars.push('stack' + this.stackSlot); }
this.stackVars.push('stack' + this.stackSlot);
}
return this.topStackName(); return this.topStackName();
}, },
topStackName: function() { topStackName: function() {
@@ -1063,9 +947,9 @@ JavaScriptCompiler.prototype = {
popStack: function(wrapped) { popStack: function(wrapped) {
let inline = this.isInline(), let inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop(); item = (inline ? this.inlineStack : this.compileStack).pop();
if (!wrapped && item instanceof Literal) { if (!wrapped && (item instanceof Literal)) {
return item.value; return item.value;
} else { } else {
if (!inline) { if (!inline) {
@@ -1080,8 +964,8 @@ JavaScriptCompiler.prototype = {
}, },
topStack: function() { topStack: function() {
let stack = this.isInline() ? this.inlineStack : this.compileStack, let stack = (this.isInline() ? this.inlineStack : this.compileStack),
item = stack[stack.length - 1]; item = stack[stack.length - 1];
/* istanbul ignore if */ /* istanbul ignore if */
if (item instanceof Literal) { if (item instanceof Literal) {
@@ -1123,13 +1007,9 @@ JavaScriptCompiler.prototype = {
setupHelper: function(paramSize, name, blockHelper) { setupHelper: function(paramSize, name, blockHelper) {
let params = [], let params = [],
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
let foundHelper = this.nameLookup('helpers', name, 'helper'), let foundHelper = this.nameLookup('helpers', name, 'helper'),
callContext = this.aliasable( callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : (container.nullContext || {})`);
`${this.contextName(0)} != null ? ${this.contextName(
0
)} : (container.nullContext || {})`
);
return { return {
params: params, params: params,
@@ -1141,11 +1021,11 @@ JavaScriptCompiler.prototype = {
setupParams: function(helper, paramSize, params) { setupParams: function(helper, paramSize, params) {
let options = {}, let options = {},
contexts = [], contexts = [],
types = [], types = [],
ids = [], ids = [],
objectArgs = !params, objectArgs = !params,
param; param;
if (objectArgs) { if (objectArgs) {
params = []; params = [];
@@ -1163,7 +1043,7 @@ JavaScriptCompiler.prototype = {
} }
let inverse = this.popStack(), let inverse = this.popStack(),
program = this.popStack(); program = this.popStack();
// Avoid setting fn and inverse if neither are set. This allows // Avoid setting fn and inverse if neither are set. This allows
// helpers to do a check for `if (options.fn)` // helpers to do a check for `if (options.fn)`
@@ -1226,6 +1106,7 @@ JavaScriptCompiler.prototype = {
} }
}; };
(function() { (function() {
const reservedWords = ( const reservedWords = (
'break else new var' + 'break else new var' +
@@ -1246,26 +1127,21 @@ JavaScriptCompiler.prototype = {
' null true false' ' null true false'
).split(' '); ).split(' ');
const compilerWords = (JavaScriptCompiler.RESERVED_WORDS = {}); const compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
for (let i = 0, l = reservedWords.length; i < l; i++) { for (let i = 0, l = reservedWords.length; i < l; i++) {
compilerWords[reservedWords[i]] = true; compilerWords[reservedWords[i]] = true;
} }
})(); }());
/**
* @deprecated May be removed in the next major version
*/
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
return ( return !JavaScriptCompiler.RESERVED_WORDS[name] && (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/).test(name);
!JavaScriptCompiler.RESERVED_WORDS[name] &&
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
);
}; };
function strictLookup(requireTerminal, compiler, parts, i, type) { function strictLookup(requireTerminal, compiler, parts, type) {
let stack = compiler.popStack(), let stack = compiler.popStack(),
len = parts.length; i = 0,
len = parts.length;
if (requireTerminal) { if (requireTerminal) {
len--; len--;
} }
@@ -1275,16 +1151,7 @@ function strictLookup(requireTerminal, compiler, parts, i, type) {
} }
if (requireTerminal) { if (requireTerminal) {
return [ return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ', ', JSON.stringify(compiler.source.currentLocation), ' )'];
compiler.aliasable('container.strict'),
'(',
stack,
', ',
compiler.quotedString(parts[i]),
', ',
JSON.stringify(compiler.source.currentLocation),
' )'
];
} else { } else {
return stack; return stack;
} }
+12 -19
View File
@@ -24,14 +24,13 @@ PrintVisitor.prototype.pad = function(string) {
PrintVisitor.prototype.Program = function(program) { PrintVisitor.prototype.Program = function(program) {
let out = '', let out = '',
body = program.body, body = program.body,
i, i, l;
l;
if (program.blockParams) { if (program.blockParams) {
let blockParams = 'BLOCK PARAMS: ['; let blockParams = 'BLOCK PARAMS: [';
for (i = 0, l = program.blockParams.length; i < l; i++) { for (i = 0, l = program.blockParams.length; i < l; i++) {
blockParams += ' ' + program.blockParams[i]; blockParams += ' ' + program.blockParams[i];
} }
blockParams += ' ]'; blockParams += ' ]';
out += this.pad(blockParams); out += this.pad(blockParams);
@@ -53,14 +52,11 @@ PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}'); return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
}; };
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function( PrintVisitor.prototype.BlockStatement =
block PrintVisitor.prototype.DecoratorBlock = function(block) {
) {
let out = ''; let out = '';
out += this.pad( out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
);
this.padding++; this.padding++;
out += this.pad(this.SubExpression(block)); out += this.pad(this.SubExpression(block));
if (block.program) { if (block.program) {
@@ -70,16 +66,12 @@ PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock =
this.padding--; this.padding--;
} }
if (block.inverse) { if (block.inverse) {
if (block.program) { if (block.program) { this.padding++; }
this.padding++;
}
out += this.pad('{{^}}'); out += this.pad('{{^}}');
this.padding++; this.padding++;
out += this.accept(block.inverse); out += this.accept(block.inverse);
this.padding--; this.padding--;
if (block.program) { if (block.program) { this.padding--; }
this.padding--;
}
} }
this.padding--; this.padding--;
@@ -123,8 +115,8 @@ PrintVisitor.prototype.CommentStatement = function(comment) {
PrintVisitor.prototype.SubExpression = function(sexpr) { PrintVisitor.prototype.SubExpression = function(sexpr) {
let params = sexpr.params, let params = sexpr.params,
paramStrings = [], paramStrings = [],
hash; hash;
for (let i = 0, l = params.length; i < l; i++) { for (let i = 0, l = params.length; i < l; i++) {
paramStrings.push(this.accept(params[i])); paramStrings.push(this.accept(params[i]));
@@ -142,6 +134,7 @@ PrintVisitor.prototype.PathExpression = function(id) {
return (id.data ? '@' : '') + 'PATH:' + path; return (id.data ? '@' : '') + 'PATH:' + path;
}; };
PrintVisitor.prototype.StringLiteral = function(string) { PrintVisitor.prototype.StringLiteral = function(string) {
return '"' + string.value + '"'; return '"' + string.value + '"';
}; };
@@ -164,7 +157,7 @@ PrintVisitor.prototype.NullLiteral = function() {
PrintVisitor.prototype.Hash = function(hash) { PrintVisitor.prototype.Hash = function(hash) {
let pairs = hash.pairs, let pairs = hash.pairs,
joinedPairs = []; joinedPairs = [];
for (let i = 0, l = pairs.length; i < l; i++) { for (let i = 0, l = pairs.length; i < l; i++) {
joinedPairs.push(this.accept(pairs[i])); joinedPairs.push(this.accept(pairs[i]));
+1 -8
View File
@@ -15,14 +15,7 @@ Visitor.prototype = {
// Hacky sanity check: This may have a few false positives for type for the helper // 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. // methods but will generally do the right thing without a lot of overhead.
if (value && !Visitor.prototype[value.type]) { if (value && !Visitor.prototype[value.type]) {
throw new Exception( throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
'Unexpected node type "' +
value.type +
'" found when accepting ' +
name +
' on ' +
node.type
);
} }
node[name] = value; node[name] = value;
} }
+29 -47
View File
@@ -14,18 +14,18 @@ WhitespaceControl.prototype.Program = function(program) {
let body = program.body; let body = program.body;
for (let i = 0, l = body.length; i < l; i++) { for (let i = 0, l = body.length; i < l; i++) {
let current = body[i], let current = body[i],
strip = this.accept(current); strip = this.accept(current);
if (!strip) { if (!strip) {
continue; continue;
} }
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
_isNextWhitespace = isNextWhitespace(body, i, isRoot), _isNextWhitespace = isNextWhitespace(body, i, isRoot),
openStandalone = strip.openStandalone && _isPrevWhitespace,
closeStandalone = strip.closeStandalone && _isNextWhitespace, openStandalone = strip.openStandalone && _isPrevWhitespace,
inlineStandalone = closeStandalone = strip.closeStandalone && _isNextWhitespace,
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
if (strip.close) { if (strip.close) {
omitRight(body, i, true); omitRight(body, i, true);
@@ -41,7 +41,7 @@ WhitespaceControl.prototype.Program = function(program) {
// If we are on a standalone node, save the indent info for partials // If we are on a standalone node, save the indent info for partials
if (current.type === 'PartialStatement') { if (current.type === 'PartialStatement') {
// Pull out the whitespace from the final line // Pull out the whitespace from the final line
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; current.indent = (/([ \t]+$)/).exec(body[i - 1].original)[1];
} }
} }
} }
@@ -62,17 +62,17 @@ WhitespaceControl.prototype.Program = function(program) {
return program; return program;
}; };
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function( WhitespaceControl.prototype.BlockStatement =
block WhitespaceControl.prototype.DecoratorBlock =
) { WhitespaceControl.prototype.PartialBlockStatement = function(block) {
this.accept(block.program); this.accept(block.program);
this.accept(block.inverse); this.accept(block.inverse);
// Find the inverse program that is involed with whitespace stripping. // Find the inverse program that is involed with whitespace stripping.
let program = block.program || block.inverse, let program = block.program || block.inverse,
inverse = block.program && block.inverse, inverse = block.program && block.inverse,
firstInverse = inverse, firstInverse = inverse,
lastInverse = inverse; lastInverse = inverse;
if (inverse && inverse.chained) { if (inverse && inverse.chained) {
firstInverse = inverse.body[0].program; firstInverse = inverse.body[0].program;
@@ -112,11 +112,9 @@ WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.Decorat
} }
// Find standalone else statments // Find standalone else statments
if ( if (!this.options.ignoreStandalone
!this.options.ignoreStandalone && && isPrevWhitespace(program.body)
isPrevWhitespace(program.body) && && isNextWhitespace(firstInverse.body)) {
isNextWhitespace(firstInverse.body)
) {
omitLeft(program.body); omitLeft(program.body);
omitRight(firstInverse.body); omitRight(firstInverse.body);
} }
@@ -127,15 +125,13 @@ WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.Decorat
return strip; return strip;
}; };
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function( WhitespaceControl.prototype.Decorator =
mustache WhitespaceControl.prototype.MustacheStatement = function(mustache) {
) {
return mustache.strip; return mustache.strip;
}; };
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function( WhitespaceControl.prototype.PartialStatement =
node WhitespaceControl.prototype.CommentStatement = function(node) {
) {
/* istanbul ignore next */ /* istanbul ignore next */
let strip = node.strip || {}; let strip = node.strip || {};
return { return {
@@ -145,6 +141,7 @@ WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.Comme
}; };
}; };
function isPrevWhitespace(body, i, isRoot) { function isPrevWhitespace(body, i, isRoot) {
if (i === undefined) { if (i === undefined) {
i = body.length; i = body.length;
@@ -153,15 +150,13 @@ function isPrevWhitespace(body, i, isRoot) {
// Nodes that end with newlines are considered whitespace (but are special // Nodes that end with newlines are considered whitespace (but are special
// cased for strip operations) // cased for strip operations)
let prev = body[i - 1], let prev = body[i - 1],
sibling = body[i - 2]; sibling = body[i - 2];
if (!prev) { if (!prev) {
return isRoot; return isRoot;
} }
if (prev.type === 'ContentStatement') { if (prev.type === 'ContentStatement') {
return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test( return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
prev.original
);
} }
} }
function isNextWhitespace(body, i, isRoot) { function isNextWhitespace(body, i, isRoot) {
@@ -170,15 +165,13 @@ function isNextWhitespace(body, i, isRoot) {
} }
let next = body[i + 1], let next = body[i + 1],
sibling = body[i + 2]; sibling = body[i + 2];
if (!next) { if (!next) {
return isRoot; return isRoot;
} }
if (next.type === 'ContentStatement') { if (next.type === 'ContentStatement') {
return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test( return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
next.original
);
} }
} }
@@ -191,19 +184,12 @@ function isNextWhitespace(body, i, isRoot) {
// content is met. // content is met.
function omitRight(body, i, multiple) { function omitRight(body, i, multiple) {
let current = body[i == null ? 0 : i + 1]; let current = body[i == null ? 0 : i + 1];
if ( if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) {
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.rightStripped)
) {
return; return;
} }
let original = current.value; let original = current.value;
current.value = current.value.replace( current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
''
);
current.rightStripped = current.value !== original; current.rightStripped = current.value !== original;
} }
@@ -216,17 +202,13 @@ function omitRight(body, i, multiple) {
// content is met. // content is met.
function omitLeft(body, i, multiple) { function omitLeft(body, i, multiple) {
let current = body[i == null ? body.length - 1 : i - 1]; let current = body[i == null ? body.length - 1 : i - 1];
if ( if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) {
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.leftStripped)
) {
return; return;
} }
// We omit the last node if it's whitespace only and not preceded by a non-content node. // We omit the last node if it's whitespace only and not preceded by a non-content node.
let original = current.value; let original = current.value;
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
current.leftStripped = current.value !== original; current.leftStripped = current.value !== original;
return current.leftStripped; return current.leftStripped;
} }
+1
View File
@@ -3,3 +3,4 @@ import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) { export function registerDefaultDecorators(instance) {
registerInline(instance); registerInline(instance);
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { extend } from '../utils'; import {extend} from '../utils';
export default function(instance) { export default function(instance) {
instance.registerDecorator('inline', function(fn, props, container, options) { instance.registerDecorator('inline', function(fn, props, container, options) {
+6 -14
View File
@@ -1,20 +1,12 @@
const errorProps = [
'description', const errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack'];
'fileName',
'lineNumber',
'endLineNumber',
'message',
'name',
'number',
'stack'
];
function Exception(message, node) { function Exception(message, node) {
let loc = node && node.loc, let loc = node && node.loc,
line, line,
endLineNumber, endLineNumber,
column, column,
endColumn; endColumn;
if (loc) { if (loc) {
line = loc.start.line; line = loc.start.line;
@@ -1,9 +1,9 @@
import { appendContextPath, createFrame, isArray } from '../utils'; import {appendContextPath, createFrame, isArray} from '../utils';
export default function(instance) { export default function(instance) {
instance.registerHelper('blockHelperMissing', function(context, options) { instance.registerHelper('blockHelperMissing', function(context, options) {
let inverse = options.inverse, let inverse = options.inverse,
fn = options.fn; fn = options.fn;
if (context === true) { if (context === true) {
return fn(this); return fn(this);
@@ -22,11 +22,8 @@ export default function(instance) {
} else { } else {
if (options.data && options.ids) { if (options.data && options.ids) {
let data = createFrame(options.data); let data = createFrame(options.data);
data.contextPath = appendContextPath( data.contextPath = appendContextPath(options.data.contextPath, options.name);
options.data.contextPath, options = {data: data};
options.name
);
options = { data: data };
} }
return fn(context, options); return fn(context, options);
+25 -37
View File
@@ -1,10 +1,4 @@
import { import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils';
appendContextPath,
blockParams,
createFrame,
isArray,
isFunction
} from '../utils';
import Exception from '../exception'; import Exception from '../exception';
export default function(instance) { export default function(instance) {
@@ -14,20 +8,17 @@ export default function(instance) {
} }
let fn = options.fn, let fn = options.fn,
inverse = options.inverse, inverse = options.inverse,
i = 0, i = 0,
ret = '', ret = '',
data, data,
contextPath; contextPath;
if (options.data && options.ids) { if (options.data && options.ids) {
contextPath = contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
} }
if (isFunction(context)) { if (isFunction(context)) { context = context.call(this); }
context = context.call(this);
}
if (options.data) { if (options.data) {
data = createFrame(options.data); data = createFrame(options.data);
@@ -45,15 +36,10 @@ export default function(instance) {
} }
} }
ret = ret = ret + fn(context[field], {
ret + data: data,
fn(context[field], { blockParams: blockParams([context[field], field], [contextPath + field, null])
data: data, });
blockParams: blockParams(
[context[field], field],
[contextPath + field, null]
)
});
} }
if (context && typeof context === 'object') { if (context && typeof context === 'object') {
@@ -63,9 +49,9 @@ export default function(instance) {
execIteration(i, i, i === context.length - 1); execIteration(i, i, i === context.length - 1);
} }
} }
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) { } else if (global.Symbol && context[global.Symbol.iterator]) {
const newContext = []; const newContext = [];
const iterator = context[Symbol.iterator](); const iterator = context[global.Symbol.iterator]();
for (let it = iterator.next(); !it.done; it = iterator.next()) { for (let it = iterator.next(); !it.done; it = iterator.next()) {
newContext.push(it.value); newContext.push(it.value);
} }
@@ -76,16 +62,18 @@ export default function(instance) {
} else { } else {
let priorKey; let priorKey;
Object.keys(context).forEach(key => { for (let key in context) {
// We're running the iterations one step out of sync so we can detect if (context.hasOwnProperty(key)) {
// the last iteration without have to scan the object twice and create // We're running the iterations one step out of sync so we can detect
// an itermediate keys array. // the last iteration without have to scan the object twice and create
if (priorKey !== undefined) { // an itermediate keys array.
execIteration(priorKey, i - 1); if (priorKey !== undefined) {
execIteration(priorKey, i - 1);
}
priorKey = key;
i++;
} }
priorKey = key; }
i++;
});
if (priorKey !== undefined) { if (priorKey !== undefined) {
execIteration(priorKey, i - 1, true); execIteration(priorKey, i - 1, true);
} }
+1 -3
View File
@@ -7,9 +7,7 @@ export default function(instance) {
return undefined; return undefined;
} else { } else {
// Someone is actually trying to call something, blow up. // Someone is actually trying to call something, blow up.
throw new Exception( throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
);
} }
}); });
} }
+4 -14
View File
@@ -3,12 +3,8 @@ import Exception from '../exception';
export default function(instance) { export default function(instance) {
instance.registerHelper('if', function(conditional, options) { instance.registerHelper('if', function(conditional, options) {
if (arguments.length != 2) { if (arguments.length != 2) { throw new Exception('#if requires exactly one argument');}
throw new Exception('#if requires exactly one argument'); if (isFunction(conditional)) { conditional = conditional.call(this); }
}
if (isFunction(conditional)) {
conditional = conditional.call(this);
}
// Default behavior is to render the positive path if the value is truthy and not empty. // Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the // The `includeZero` option may be set to treat the condtional as purely not empty based on the
@@ -21,13 +17,7 @@ export default function(instance) {
}); });
instance.registerHelper('unless', function(conditional, options) { instance.registerHelper('unless', function(conditional, options) {
if (arguments.length != 2) { if (arguments.length != 2) { throw new Exception('#unless requires exactly one argument');}
throw new Exception('#unless requires exactly one argument'); return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
}
return instance.helpers['if'].call(this, conditional, {
fn: options.inverse,
inverse: options.fn,
hash: options.hash
});
}); });
} }
+2 -2
View File
@@ -1,7 +1,7 @@
export default function(instance) { export default function(instance) {
instance.registerHelper('log', function(/* message, options */) { instance.registerHelper('log', function(/* message, options */) {
let args = [undefined], let args = [undefined],
options = arguments[arguments.length - 1]; options = arguments[arguments.length - 1];
for (let i = 0; i < arguments.length - 1; i++) { for (let i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]); args.push(arguments[i]);
} }
@@ -14,6 +14,6 @@ export default function(instance) {
} }
args[0] = level; args[0] = level;
instance.log(...args); instance.log(... args);
}); });
} }
+5 -3
View File
@@ -1,9 +1,11 @@
export default function(instance) { export default function(instance) {
instance.registerHelper('lookup', function(obj, field, options) { instance.registerHelper('lookup', function(obj, field) {
if (!obj) { if (!obj) {
// Note for 5.0: Change to "obj == null" in 5.0
return obj; return obj;
} }
return options.lookupProperty(obj, field); if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
return undefined;
}
return obj[field];
}); });
} }
+4 -17
View File
@@ -1,20 +1,10 @@
import { import { appendContextPath, blockParams, createFrame, isEmpty, isFunction } from '../utils';
appendContextPath,
blockParams,
createFrame,
isEmpty,
isFunction
} from '../utils';
import Exception from '../exception'; import Exception from '../exception';
export default function(instance) { export default function(instance) {
instance.registerHelper('with', function(context, options) { instance.registerHelper('with', function(context, options) {
if (arguments.length != 2) { if (arguments.length != 2) { throw new Exception('#with requires exactly one argument');}
throw new Exception('#with requires exactly one argument'); if (isFunction(context)) { context = context.call(this); }
}
if (isFunction(context)) {
context = context.call(this);
}
let fn = options.fn; let fn = options.fn;
@@ -22,10 +12,7 @@ export default function(instance) {
let data = options.data; let data = options.data;
if (options.data && options.ids) { if (options.data && options.ids) {
data = createFrame(options.data); data = createFrame(options.data);
data.contextPath = appendContextPath( data.contextPath = appendContextPath(options.data.contextPath, options.ids[0]);
options.data.contextPath,
options.ids[0]
);
} }
return fn(context, { return fn(context, {

Some files were not shown because too many files have changed in this diff Show More