Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
6533fd9
Add tests for mustache on node.js
gwicke Jan 17, 2014
49395ef
Add package.json and node_modules
gwicke Jan 17, 2014
470ea32
Disable print in js loop test
gwicke Jan 17, 2014
13d7f68
Fix mustache lambda tests
gwicke Jan 17, 2014
fee58fa
Add raw bast1001 results
gwicke Jan 17, 2014
5236a0d
Add HHVM benchmark and -results
gwicke Jan 21, 2014
6d89ee8
Add handlebars benchmark
gwicke Jan 22, 2014
0cc06a7
Add handlebars results
gwicke Jan 22, 2014
3429ffe
Add 'quicktemplate' micro lib
gwicke Jan 27, 2014
3add400
First draft of template substitution engine using htmljs.
cscott Jan 28, 2014
c17b07f
Improve speed of Tag.toHTML in htmljs.
cscott Jan 28, 2014
69a44a0
Merge pull request #1 from cscott/htmljs
gwicke Jan 30, 2014
f665cc2
Minor quicktemplate cleanup
gwicke Jan 30, 2014
1251ab9
First cut of Knockoutjs to JSON IR compiler
gwicke Jan 30, 2014
776a8e1
Add domino & package.json
gwicke Jan 30, 2014
642fa0d
Fix if predicate handling
gwicke Jan 30, 2014
6479137
Use evalExpr everywhere, various fixes, test execution too
gwicke Jan 30, 2014
66d6e02
Fix test data
gwicke Jan 30, 2014
932fd37
Strip data-bind and prettier test output
gwicke Jan 30, 2014
9505691
Minor testKnockoutCompiler formatting / comments
gwicke Jan 31, 2014
b3f8b7c
Speed up expression eval by handling common case inline
gwicke Jan 31, 2014
e2b9a2c
Fix jshint warnings in Knockout expression parser
gwicke Jan 31, 2014
825c719
Clean up HTML escaping
gwicke Jan 31, 2014
08bd8d6
Small performance improvements
gwicke Jan 31, 2014
2f5cbc4
Fix bug in attribute serialization
gwicke Jan 31, 2014
98cfa90
Comments and minor cleanup
gwicke Jan 31, 2014
ecb0ad9
Bugfix & cleanup in DOMCompiler
gwicke Jan 31, 2014
7b694f0
Bugfix & cleanup in DOMCompiler
gwicke Jan 31, 2014
02adf66
Use callbacks to accumulate output in htmljs.
cscott Jan 30, 2014
491d717
Import packages for Spacebars compiler from meteor and nodify them.
cscott Jan 31, 2014
304fb6c
Add tests using spacebars-compiler.
cscott Feb 1, 2014
9cf0148
First draft of handlebars to JSON IR compiler.
cscott Feb 1, 2014
d877ee0
Merge pull request #2 from cscott/sbqt
gwicke Feb 3, 2014
a28e0fe
Added Handlebars lightncandy in PHP as new test suite
shahyar Mar 14, 2014
8848ff9
Rewrote runall to be cleaner, simpler, and to output min/max/avg.
shahyar Mar 14, 2014
bc437e9
Merge pull request #3 from shahyar/master
gwicke Mar 14, 2014
3025415
Update README.md
gwicke Mar 14, 2014
c83767d
Utilize the JIT when running HHVM
ebernhardson Mar 14, 2014
83bbf0a
Merge pull request #4 from ebernhardson/master
gwicke Mar 14, 2014
2c4124d
Add knockoff and remove QuickTemplate; include compilation time
gwicke Mar 15, 2014
3c1729f
Update knockoff-node deps
gwicke Mar 15, 2014
ff100cc
Update knockoff deps
gwicke Mar 15, 2014
5df4069
Update tassembly dependency for knockoff
gwicke Mar 15, 2014
8c5178c
Update tassembly
gwicke Mar 15, 2014
656a0d6
Fix heading for Knockoff test
gwicke Mar 15, 2014
078ca48
Update tassembly
gwicke Mar 15, 2014
52956e8
Update tassembly
gwicke Mar 15, 2014
356215e
Update knockoff
gwicke Mar 22, 2014
352f6d2
Update tassembly
gwicke Mar 22, 2014
dc5fac6
Update spacebars-tassembly to be compatible with latest tassembly
gwicke Mar 22, 2014
2ae0f30
Second part of spacebars-tassembly fixes
gwicke Mar 22, 2014
2e1e2cf
Reorder tests by performance
gwicke Mar 22, 2014
d031d0e
50 iterations & prettier output; -q parameter
gwicke Mar 22, 2014
30dc195
Relegate slow htmljs tests to run last
gwicke Mar 22, 2014
48ce5ab
Add results of last run
gwicke Mar 24, 2014
13b5d40
Remove old results
gwicke Mar 24, 2014
02f546b
Update knockoff
gwicke Apr 4, 2014
bce71c7
Tassembly php tests
mattofak Apr 4, 2014
7eb342d
Add missing TAssembly copy
gwicke Apr 4, 2014
5ecf413
Update tassembly.js
gwicke Apr 4, 2014
1e40a8c
Add full set of tassembly-php tests
gwicke Apr 4, 2014
defc179
Fix spacebars-tassembly
gwicke Apr 4, 2014
66f1aee
Run tassembly / PHP before lightncandy as that takes forever
gwicke Apr 4, 2014
d09a0cc
Update TAssembly and its benchmarks to namespace-less version
gwicke Apr 5, 2014
cc30581
Update knockoff dependencies
gwicke Apr 14, 2014
85539df
Latest results
gwicke Apr 16, 2014
fc4abd0
Add comment on how to strip ascii color codes
gwicke Apr 16, 2014
3c331f2
Update knockoff to 0.1.3 release
gwicke Jul 8, 2014
c6731ba
Add some setup instructions in the README
gwicke Jul 8, 2014
6e7b520
Add hogan.js tests, based on the handlebars tests
gwicke Jul 8, 2014
549931e
Add sed line comment for rough result conversion to wikitext table
gwicke Jul 8, 2014
409a86f
Update results
gwicke Jul 9, 2014
a549901
Clean up results
gwicke Jul 9, 2014
6a20971
Add plain TAssembly & pre-compiled handlebars benchmarks
gwicke Jul 9, 2014
a5e6437
Add results to README
gwicke Jul 9, 2014
01d6215
Fix formatting
gwicke Jul 9, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
66 changes: 64 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,64 @@
TwigPerf
========
TemplatePerf
============

See https://www.mediawiki.org/wiki/Requests_for_comment/HTML_templating_library#Performance

## Usage
Apart from a checkout of this repository, you need nodejs, php5-cli and HHVM. After adding the appropriate [HHVM nightly repo](http://hhvm.com/blog/3203/nightly-packages), this should get you these dependencies on Debian / Ubuntu:
```bash
apt-get install nodejs php5-cli hhvm-nightly
```
After this preparation, you should be able to run all benchmarks with:
```bash
./runall.sh
```
This will produce output as in
[results.txt](https://github.com/gwicke/TemplatePerf/blob/master/results.txt):

```
Set: Knockoff (node.js) (knockoff-node)
Test: test1 [050] Avg: 0.0815 Min: 0.0530 Max: 0.1050
Test: test1b [050] Avg: 0.1334 Min: 0.0760 Max: 0.1510
Test: test2 [050] Avg: 0.8882 Min: 0.6440 Max: 1.1260
Test: test2 lambda [050] Avg: 0.6492 Min: 0.4730 Max: 0.7880
Test: test3 [050] Avg: 0.3166 Min: 0.1960 Max: 0.5110
Set: Handlebars (node.js) (handlebars-node)
Test: test1 [050] Avg: 0.2093 Min: 0.1270 Max: 0.2750
Test: test1b [050] Avg: 0.2911 Min: 0.1660 Max: 0.3850
Test: test2 [050] Avg: 0.8271 Min: 0.6050 Max: 1.0400
Test: test2 lambda [050] Avg: 1.4567 Min: 1.1430 Max: 2.0150
Test: test3 [050] Avg: 0.2920 Min: 0.1870 Max: 0.3680
...
```

Each test is repeated 50 times for stable results, so this will take quite a
while to finish for all libraries, runtimes & tests.

## Current results
Also see [the on-wiki version](https://www.mediawiki.org/wiki/Requests_for_comment/HTML_templating_library#Performance).

Library / runtime | Test 1 (attr/text interpolation) | Test1b (same with random attr) | Test2 (iterate obj array) | Test2 (iterate obj array, lambda) | Test3 (iterate item array)
|-----|:------|:--------|:-------|:--------|:----------|
|TAssembly (node.js) | 0.0420 | 0.0630 | 0.6190 | 0.5010 | 0.1860
|Knockoff (node.js) | 0.0530 | 0.0750 | 0.6370 | 0.4690 | 0.1970
|Handlebars (node.js) | 0.1300 | 0.1750 | 0.6060 | 1.1450 | 0.1870
|Hogan (node.js) | 0.1110 | 0.1370 | 0.6690 | 4.8780 | 0.6770
|Spacebars/TAssembly (node.js) | 0.0650 | n/a | 0.6430 | n/a | n/a
|Mustache (node.js) | 0.3570 | 0.3900 | 2.5200 | 3.8260 | 0.8830
|TAssembly (PHP) | 1.7434 | 1.7654 | 18.6634 | 81.8341 | 12.3270
|TAssembly (HHVM) | 0.5199 | 0.5317 | 3.3146 | 16.7471 | 2.1257
|Handlebars lightncandy (PHP) | 0.5395 | 0.6025 | 7.0456 | 133.2025 | 3.9837
|Handlebars lightncandy (HHVM) | 0.1919 | 0.1991 | 0.8164 | 1.3319 | 0.5254
|Mustache (PHP) | 1.7991 | 1.8606 | 11.3725 | 24.0264 | 3.6850
|Mustache (HHVM) | 0.4783 | 0.5016 | 1.2869 | 3.9279 | 0.6229
|MediaWiki Templates (PHP) | 1.6926 | 1.7354 | 17.0633 | n/a | 7.4075
|MediaWiki Templates (HHVM) | 0.5305 | 0.5147 | 4.0606 | n/a | 1.9745
|Twig String (No Cache) (PHP) | 1.3954 | 1.4701 | 6.0498 | n/a | 3.6746
|Twig String (No Cache) (HHVM) | 0.5631 | 0.5137 | 0.9394 | n/a | 0.6560
|Twig File (No Cache) (PHP) | 1.7074 | 1.7649 | 6.0364 | n/a | 3.6477
|Twig File (No Cache) (HHVM) | 0.6085 | 0.6324 | 0.9831 | n/a | 0.8150
|Twig File (Cached) (PHP) | 1.7352 | 1.7783 | 6.0423 | n/a | 3.6068
|Twig File (Cached) (HHVM) | 0.4170 | 0.4460 | 0.7116 | n/a | 0.4475
|Handlebars HTMLJS (node.js) | 0.5610 | 0.6540 | 7.1110 | n/a | 2.9330
|Spacebars/HTMLJS (node.js) | 1.1450 | 1.3080 | 59.0910 | 143.3680 | 42.2020

148 changes: 148 additions & 0 deletions handlebars-htmljs-node/html-tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# html-tools

A lightweight HTML tokenizer and parser which outputs to the HTMLjs
object representation. Special hooks allow the syntax to be extended
to parse an HTML-like template language like Spacebars.

```
HTML.parseFragment("<div class=greeting>Hello<br>World</div>")

=> HTML.DIV({'class':'greeting'}, "Hello", HTML.BR(), "World"))
```

This package is used by the Spacebars compiler, which normally only
runs at bundle time but can also be used at runtime on the client or
server.

## Invoking the Parser

`HTML.parseFragment(input, options)` - Takes an input string or Scanner object and returns HTMLjs.

In the basic case, where no options are passed, `parseFragment` will consume the entire input (the full string or the rest of the Scanner).

The options are as follows:

#### getSpecialTag

This option extends the HTML parser to parse template tags such as `{{foo}}`.

`getSpecialTag: function (scanner, templateTagPosition) { ... }` - A function for the parser to call after every HTML token and at various positions within tags. If the function returns a non-null value, that value is wrapped in an `HTML.Special` node which is inserted into the HTMLjs tree at the appropriate location. The function is expected to advance the scanner if it succeeds at parsing a template tag (see the section on `HTML.Scanner`).

There are four possible outcomes when `getSpecialTag` is called:

* Not a template tag - Leave the scanner as is, and return `null`. A quick peek at the next character should bail to this case if the start of a template tag is not seen.
* Bad template tag - Call `scanner.fatal`, which aborts parsing completely. Once the beginning of a template tag is seen, `getSpecialTag` will generally want to commit, and either succeed or fail trying).
* Good template tag - Advance the scanner to the end of the template tag and return an object.
* Comment tag - Advance the scanner and return `null`. For example, a Spacebars comment is `{{! foo}}`.

The `templateTagPosition` argument to `getSpecialTag` is one of:

* `HTML.TEMPLATE_TAG_POSITION.ELEMENT` - At "element level," meaning somewhere an HTML tag could be.
* `HTML.TEMPLATE_TAG_POSITION.IN_START_TAG` - Inside a start tag, as in `<div {{foo}}>`, where you might otherwise find `name=value`.
* `HTML.TEMPLATE_TAG_POSITION.IN_ATTRIBUTE` - Inside the value of an HTML attribute, as in `<div class={{foo}}>`.
* `HTML.TEMPLATE_TAG_POSITION.IN_RCDATA` - Inside a TEXTAREA or a block helper inside an attribute, where character references are allowed ("replaced character data") but not tags.
* `HTML.TEMPLATE_TAG_POSITION.IN_RAWTEXT` - In a context where character references are not parsed, such as a script tag, style tag, or markdown helper.

It's completely normal for `getSpecialTag` to invoke `HTML.parseFragment` recursively on the same scanner (see `shouldStop`). If it does so, the same value of `getSpecialTag` must be passed to the second invocation.

At the moment, template tags must begin with `{`. The parser does not try calling `getSpecialTag` for every character of an HTML document, only at token boundaries, and it knows to always end a token at `{`.

**XXX Better error message for `<div {{k}}={{v}}>`.**

**XXX Do something with `<input type=checkbox {{#if foo}}checked{{/if}}>`**

**XXX Why both IN_ATTRIBUTE and IN_RCDATA?**

**XXX Fix Markdown**

#### textMode

The `textMode` option, if present, causes the parser to parse text (such as the contents of a `<textarea>` tag or part of an attribute) instead of HTML. In a text mode, for example, the input `"<"` is not a parse error (because a bare `<` is allowed in a textarea or attribute).

The value of `textMode` must be one of:

* `HTML.TEXTMODE.RCDATA` - Interpret character references (the usual case)
* `HTML.TEXTMODE.STRING` - Don't interpret character references (the RAWTEXT case)

#### shouldStop

`shouldStop: function (scanner) { ... }` - A function that the parser invokes between tokens to check whether it should stop parsing. The function should return a boolean value.

The `shouldStop` function provides a way to put a "wall" in the input stream for the purpose of parsing HTML content embedded in a template tag. For example, take the template `{{#if happy}}yay{{/if}}`. The scanner will be advanced to the start of the word `yay` before `parseFragment` is called to parse the contents of the tag. (Note that the caller happens to be the `getSpecialTag` function of an enclosing `parseFragment`.) When parsing from `yay`, the `shouldStop` function is used to end the fragment at `{{/if}}`, which, like `{{/blah}}` or `{{else}}`, couldn't possibly be actual content that belongs in the fragment. Even if HTML tags are not closed, as in the malformed template `{{#if foo}}<div>{{else}}`, the fragment stops at the `{{else}}`, and the error is an unclosed `<div>` (before the parser notices the unclosed `{{#if}}`).

**XXX This option doesn't seem very elegant, or at least the way it's passed around internally isn't.**

## HTML.Scanner class

To write `getSpecialTag` and `shouldStop` functions, you have to
interface with the `HTML.Scanner` class used by html-tools. It's a
general class that could be used by any parser/lexer/tokenizer.

A Scanner has an immutable source document and a mutable pointer into
the document.

* `new Scanner(input)` - constructs a Scanner with source string `input`
* `scanner.input` (read-only) - the entire source string
* `scanner.pos` (read/write) - the current index into the source string

Scanners provide these methods for convenience:

* `scanner.rest()` - `input.slice(pos)` (the rest of the document)
* `scanner.peek()` - `input.charAt(pos)` (the next character)
* `scanner.isEOF()` - true if `pos` is at or beyond the end of `input`
* `scanner.fatal(msg)` - throw an error indicating a problem at `pos`

Even though `scanner.rest()` performs a substring operation, it should be considered fast and O(1), because all known JavaScript runtimes in use have constant-time substring. It would be possible, but extremely clumsy, to avoid such a substring operation while performing the usual business of a parser, which is to try to match a regex anchored at a particular index.

Functions that take scanners generally have three possible outcomes:

* Success: Advance `scanner.pos` and return some truthy value
* Failure: Leave `scanner.pos` alone and return `null`
* Fatal: Throw an exception via `scanner.fatal`

It's particularly important that in the Failure case, the function restores the scanner to the state it found it. This makes it possible to immediately try another parse function when one fails and form alternations such as `foo(scanner) || bar(scanner)`.

It's often easiest to avoid the Failure case altogether, writing parse functions that always succeed or throw. This requires less bookkeeping and leads to good error messages. A Failure case may be added if it is simple to check for up front and makes the function easier to use in an alternation. We say a function has "committed" or "will succeed or fail fatally trying" when it has reached a point where it must return a value or throw. Any parse function that has moved the scanner position and not remembered the original position is necessarily committed. Usually, committing is completely natural in the context of the language being parsed; for example, `{{` in a template always starts a template tag or throws an error about a malformed template tag.

## HTML Dialect

HTML has many dialects and potential degrees of permissiveness. We
use the WHATWG syntax spec and are pretty strict, failing on any
"parse error" cases, which basically means the input has to be
valid "HTML5" (except for the template tags).

HTML syntax references:

* [Human-readable syntax guide](http://developers.whatwg.org/syntax.html#syntax)
* [Tokenization state machine](http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html)

The WHATWG parser without error recovery is strict compared to
browsers (which will recover from almost anything), but lenient
compared to the now-defunct XHTML spec (which required lowercase tag
names and lots more escaping of special characters).

The following are examples of **errors**:

* A stray or unclosed `<` character
* An unknown character reference like `&asdf;`
* Self-closing tags like `<div/>` (except for BR, HR, INPUT, and other "void" elements)
* End tags for void elements (BR, HR, INPUT, etc.)
* Missing end tags, in most cases (e.g. missing `</div>`)

The following are **permitted**:

* Bare `>` characters
* Bare `&` that can't be confused with a character reference
* Uppercase or lowercase tag and attribute names (case insensitive)
* Unquoted and valueless attributes - `<input type=checkbox checked>`
* Most characters in attribute values - `<img alt=x,y>`
* Embedded SVG elements

**XXX Currently you have to close your Ps, LIs, and other tags for which the spec allows the end tag to be omitted in many cases**

## Character References

This package contains a lookup table for all known named character references in HTML, of which there are over 2,000, from `&Aacute;` (capital A, acute accent) to `&zwnj;` (zero-width non-joiner), as well as code for interpreting numeric character entities like `&#65;`.

Since character references are parsed into `HTML.CharRef` objects which contain both the raw and interpreted form, we never have to convert between the forms except at parse time.

Loading