tildeio / htmlbars Goto Github PK
View Code? Open in Web Editor NEWA variant of Handlebars that emits DOM and allows you to write helpers that manipulate live DOM nodes
License: MIT License
A variant of Handlebars that emits DOM and allows you to write helpers that manipulate live DOM nodes
License: MIT License
The part I haven't done yet but wanted to do is to unwrap ambiguous mustaches (like in both of the above examples) into a plain ID node. This is just a minor optimization.
Concretely, foo={{bar}}
should have an attr value of
id('bar')
instead of the current
sexpr([ id('bar') ])
and similarly foo="a{{b}}{{c d}}"
should have a value of
[ string("a"), id('b'), sexpr([ id('c'), id('d') ]) ]
instead of the current
[ string("a"), sexpr([ id('b') ]), sexpr([ id('c'), id('d') ]) ]
This seems natural and will remove some unnecessary hooks.subexpr
boilerplate in the template.
getElementById(detachedDocumentFragmentOrElement, id)
Block params need to be reimplemented to not rely on JavaScript's lexical scoping at compile time. This is so that {{partial}}
will continue to work as if you had cut and paste the contents of the partial into the template.
The strategy will be to use env.set(context, {blockParamName}, blockArguments[i])
to stash the block param on the context (in ember this will probably be on context._keywords
). We will need to rollback the "pass by value" changes and instead just pass the path to helpers like we were already doing.
An example attribute in HTMLBars today:
The implementation (based on setAttribute
) seems to be well supported across recent browser versions across the board.
Boolean attributes are a different story. A first stab at a list of boolean attributes (ignoring read-only attributes):
checked irrelevant draggable disabled
scoped reversed isMap seamless autoplay
controls muted pause active async defer
open multiple hidden default autosubmit
These attributes are always true if present. From the living spec:
A number of attributes are boolean attributes. The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.
If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute's canonical name, with no leading or trailing whitespace.
The values "true" and "false" are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.
So this means:
can only ever be checked with HTMLBars today. Kicked this around with @bantic today and we came up with three options for a syntax. Would appreciate feedback:
null
removes and attribute@mixonic is leaning toward this
If the value of context.checked
is null, and only if it is null, the checked attribute will be removed with removeAttribute
. For all other values it is setAttribute
as we do today. The template syntax would be unchanged:
When context.checked
is null, the attribute is removed. When it is anything else, it is set.
if
works inside an elementWe don't have an unbound if
yet, but possibly this could be handled by an if handler inside nodes?
@mmun is this possible? If it is this might be the best option. Or maybe it is already supported by your helper stab and just needs a test.
The worst option would be some special helper or hook:
It seems that attributeValueUnquoted in simple-html-tokenizer.js (https://github.com/tildeio/htmlbars/blob/master/lib/vendor/simple-html-tokenizer.js#L270) is not handling '/' character properly.
I.e. this:
<p foo=bar/>
will get parsed incorrectly (i.e. '/' will be treated as part of attribute value, and 'p' tag will not be closed.
Might be intentional, but it seems like a bug. Could anybody verify that, please?
should parse to the same AST as
Currently as |day month year|
will be treated as 4 different attributes: as
, |day
, month
, year|
.
Want to track some probably issues with IE9, in prep for supporting it.
It would be nice to have a runtime distribution that exposes window.HTMLBars
in environments that don't use a module system.
Is this possible given the fact that the project is relying heavily on modules?
should be
var ast = (typeof html === 'object') ? html : parse(html);
This will allow Emblem to hook into HTMLBars.
Add hash
to the arguments: function fooHelper(params, hash, options, env)
.
export function compile(string) {
var program = compileSpec(string);
return new Function("return " + program)();
}
Needs to be changed to
export function compile(string) {
var program = compileSpec(string);
var template = new Function("return " + program)();
template.isTop = true;
return template;
}
This is to support the {{outlet}}
helper in Ember. Should also add a test that confirms the root template has isTop === true
but that child templates don't have this property.
If someone could explain to me how exactly isTop
is used in Ember that would be great too :D Maybe there's a better solution.
The tildeio/simple-html-tokenizer project underwent some refactoring recently so htmlbars should be updated to track the changes.
The only time createUnsafeMorphAt should be used is for an escaped mustache {{{foo}}}
.
This:
<div class="{{path}}"></div>
Will generate this:
attribute(env, element14, "class", concat(env, [get(env, context, "path")]));
It calls the concat hook when only a get()
will suffice. When there is only one mustache in an attribute, concat should not be called.
An AttrNode is composed of a name
and a value
. The value
can be either a string primitive or a MustacheNode
. Here is a summary of the different cases today (the MustacheNodes are written as their equivalent mustache template syntax for brevity).
<div foo="bar"> --> name: "foo", value: "bar"
<div foo="bar{{baz}}"> --> name: "foo", value: {{concat 'bar' baz}}
<div foo="{{baz}}"> --> name: "foo", value: {{concat baz}}
<div foo={{baz}}> --> name: "foo", value: {{concat baz}}
Note that 3 & 4 both call concat
on baz
. Instead we need 4 to be just
<div foo={{baz}}> --> name: "foo", value: {{baz}}
The work required is pretty minimal. Right now we use the same handler for unquoted and quoted attribute values. See https://github.com/tildeio/htmlbars/blob/master/packages/htmlbars-compiler/lib/html-parser/token-handlers.js#L9-L11. Just need to add the new behavior in the unquoted case.
(function() {
return (function() {
function build(dom) {
var el0 = dom.createElement("div");
var el1 = dom.createTextNode("Hi!");
dom.appendChild(el0, el1);
return el0;
}
var cachedFragment;
return function template(context, env, contextualElement) {
var dom = env.dom, hooks = env.hooks, get = env.get;
dom.detectNamespace(contextualElement);
if (cachedFragment === undefined) {
cachedFragment = build(dom);
}
var fragment = dom.cloneNode(cachedFragment, true);
var element0 = fragment;
hooks.attribute(element0, "data-name", undefined, context, [hooks.subexpr("name", context, [], {},{type:"id",paramTypes:[],hashTypes:{}}, env)], {paramTypes:["sexpr"],hashTypes:{}}, env);
return fragment;
};
}())
})
The hook.attribute
line should be the same indentation as the line directly above it.
As continued discussion of emberjs/ember.js#9498, I think using empty text node for morph might be too fragile since the node is practically invisible. That might be the whole point but at the same time this makes it hard for it to work with other libraries (particularly jQuery plugins) that may want to wrap the DOM node.
If the reason to use empty text node is to avoid CSS selector issue, then there are other non-element DOM nodes that can be used, like comment node.
Instead of
var morph0 = dom.createMorph(fragment,0,1,contextualElement);
hooks.content(morph0, "foo", context, [], {escaped:true}, env);
we should generate
var morph0 = dom.createEscapedMorph(fragment,0,1,contextualElement);
hooks.content(morph0, "foo", context, [], {}, env);
Valid
<p class="{{foo}}">Omg</p>
Invalid
<p class={{foo}}>Omg</p>
TODO
This: <p class=foo>Omg</p>
Is presently treated as: <p class="foo">Omg</p>
Should probably be auto-converted shorthand for: <p class="{{foo}}">Omg</p>
The way this breaks is weird:
<p class={{foo}}>Omg</p>
Should either break loudly or autoconvert to
<p class="{{foo}}">Omg</p>
There is no change for 3 weeks and still a lot to be done. Checking the EmberJS website this library should have gone 1.0.0 around beginning of this year. So my question is:
What happened and when can we look at the first release?
There is a bunch of ES6 in this module, which doesn't work in any actual place. How can this module be consumed?
To speed up initial renders we should not cache & clone the fragment on the first execution of the template. Instead we should cache the fragment on the second execution.
There is a package.json here and working code, but htmlbars isn't on npm. Publish it before somebody else does!
Source | Generated Hook |
---|---|
Block params | set(env, context, path, value) |
PathExpression | get(env, context, path) |
SubExpression | subexpr(env, context, path, params, hash) |
Attr. Interpolation | concat(env, parts) |
{{foo}} |
content(env, context, path, morph) |
{{foo bar}} |
inline(env, context, path, params, hash, morph) |
{{#foo}}{{/foo}} |
block(env, context, path, params, hash, morph, template, inverse) |
<x-foo></x-foo> |
component(env, context, path, attributes, morph, template) |
<p id={{id}}></p> |
attribute(env, element, name, value) |
<img {{foo bar}}> |
element(env, context, path, params, hash, element) |
I am willing to take a stab at it if no major changes are required.
Hey!
Is there a donation we can do? :-)
A think a lot lot lot of devs are waiting for this to happen (myself and my team included).
Loading >300 records, every one with some metamorph if's, couple of relations, and it's not usable.
It's profiling hell, and it's not getting any better. We're hacking the hell out of handlebars, but we all know it's BS.
So, I'll bring you guys evetyghing you need to code this jewel (well.. ember) just to finish this in the best way you can :-)
Of course this is kind-of haha-issue, but I'm sure I'm not the only one cheering on this thing to be completed, it means a lot to the community :-)
Have a great one!
Hi,
I've read that HTMLbars should improve performance of most Ember apps with 2-3 times. But, running latest ember canary
with latest ember-cli-htmlbars
results in htmlbars begin 2 times slower than Handlebars 1.3;
For example, these are screenshots of the same Ember app rendering a list of 10 items in a table. You can see that Handlebars is 2x faster:
Why is this happening? I know it is still in beta, but would like to know if this will be better later on.
Ambiguous helpers like {{outlet}}
and {{yield}}
currently don't get a hash passed to them. They should get passed an empty hash.
Though it's clear HTMLBars is licensed MIT, the build output in the npm package does not contain a license comment or version information.
If HTMLBars is bundled in other projects, it's important to indicate its license without requiring that the LICENSE
file itself be bundled.
Hi, I just updated from Ember#1.6 to Ember#1.8 and i'm having a problem here:
this function assumes html is a string so it has replace, but in some cases this html it a number and replace is not available. Does this make sense?
This is related to #9971
Currently both in htmlbars and handlebars there is a common occurrence of the following:
import {compile, compileSpec} from "./htmlbars-compiler/compiler";
export {compile, compileSpec};
import Morph from "./morph/morph";
export var Morph;
import DOMHelper from "./morph/dom-helper";
export var DOMHelper;
Which transpiles into:
"use strict";
var compile = require("./htmlbars-compiler/compiler").compile;
var compileSpec = require("./htmlbars-compiler/compiler").compileSpec;
exports.compile = compile;
exports.compileSpec = compileSpec;
var Morph = require("./morph/morph")["default"];
var Morph;
exports.Morph = Morph;
var DOMHelper = require("./morph/dom-helper")["default"];
var DOMHelper;
exports.DOMHelper = DOMHelper;
As you can see we end up with a static error by re-declaring identifiers. I would propose adjusting to the following:
export {compile, compileSpec} from "./htmlbars-compiler/compiler";
import _Morph from "./morph/morph";
export var Morph = _Morph;
import _DOMHelper from "./morph/dom-helper";
export var DOMHelper = _DOMHelper;
Which transpiles to:
"use strict";
exports.compile = require("./htmlbars-compiler/compiler").compile;
exports.compileSpec = require("./htmlbars-compiler/compiler").compileSpec;
var _Morph = require("./morph/morph")["default"];
var Morph = _Morph;
exports.Morph = Morph;
var _DOMHelper = require("./morph/dom-helper")["default"];
var DOMHelper = _DOMHelper;
exports.DOMHelper = DOMHelper;
There are better ways to write this technically and in relation to the ES6 Modules spec but the transpiler doesn't produce good results from them currently. It might with the latest branch of es6-module-transpiler coming eventually.
Currently IE 10 and 11 don't pass the htmlbars test suite, but are only marked as finished and not as failed. Also the test output is not published as expected.
Example: https://saucelabs.com/tests/2e3b919384ea4ce48261a61447a2935c
Workaround to get the test output:
With this markup:
I would hit an exception trying to read setAttribute
on undefined
.
Shuffling the attributes around made it work:
We need to run the dom helper tests on more browsers, ideally back to IE6. Probably with saucelabs?
These three cases should throw an error:
<img class=foo{{bar}}>
<img class={{foo}}{{bar}}>
<img class={{foo}}bar>
The template {{foo.bar.baz}}
currently generates the hydration hook
hooks.content(morph0, "foo.bar.baz", context, [], {...}, env);
Instead it should split the path at runtime so that the strings are interned:
hooks.content(morph0, ["foo", "bar", "baz"], context, [], {...}, env);
Single segment paths can just pass a string, e.g. . Edit: Just keep it simple for now and always pass an array.{{foo}}
should result in
hooks.content(morph0, "foo", context, [], {...}, env);
This should also affect params and hashes.
The AttrNode AST node is confusing. We should remove quoted
from the AST and make value
a simple node, instead of an array. Specific proposal:
interface AttrNode {
type: "AttrNode";
name: string;
value: TextNode | MustacheStatement | ConcatStatement,
}
interface ConcatStatement {
type: "ConcatStatement";
parts: [ Expression ];
}
which would map to
<div id></div>
- TextNode
<div id=foo></div>
- TextNode
<div id="foo"></div>
- TextNode
<div id={{foo}}></div>
- MustacheStatement
<div id={{foo bar}}></div>
- MustacheStatement
<div id="{{bar}}"></div>
- ConcatStatement
<div id="foo{{bar}}baz"></div>
- ConcatStatement
Creating the cached fragment make initial rendering slower than desired. Since inital page load is such an important metric, it makes sense to defer the cachedFragment saving until the second time a template is called.
Whenever I try to build with "npm run-script build", I got this:
Build failed.
Error: ENOENT, no such file or directory '/Users/xxxx/ruby-edge/htmlbars/tmp/tree_merger-tmp_dest_dir-qLGm9Yds.tmp/htmlbars/'
at Object.fs.lstatSync (fs.js:690:18)
at Object.copyRecursivelySync (/Users/xxxx/ruby-edge/htmlbars/node_modules/broccoli-static-compiler/node_modules/broccoli-kitchen-sink-helpers/index.js:142:21)
at /Users/xxxx/ruby-edge/htmlbars/node_modules/broccoli-static-compiler/index.js:20:15
at $$$internal$$tryCatch (/Users/xxxx/ruby-edge/htmlbars/node_modules/rsvp/dist/rsvp.js:470:16)
at $$$internal$$invokeCallback (/Users/xxxx/ruby-edge/htmlbars/node_modules/rsvp/dist/rsvp.js:482:17)
at $$$internal$$publish (/Users/xxxx/ruby-edge/htmlbars/node_modules/rsvp/dist/rsvp.js:453:11)
at $$rsvp$asap$$flush (/Users/xxxx/ruby-edge/htmlbars/node_modules/rsvp/dist/rsvp.js:1531:9)
at process._tickCallback (node.js:419:13)
at Function.Module.runMain (module.js:499:11)
at startup (node.js:119:16)
at node.js:906:3
Is there anything I did wrong here?
This is required for compatibility with IE6 to abstract away these kinds of hacks:
content(morph, helperName, context, params, options)
webComponent(morph, tagName, context, options, helpers)
element(domElement, helperName, context, params, options)
subexpr(helperName, context, params, options)
HTMLBars:
webComponentFallback(morph, tagName, context, options, helpers)
lookupHelper(helperName, context, options)
simple(context, name, options)
bound-templates:
streamifyArgs(context, params, options, hooks)
streamFor(context, path)
lookupHelper(name, helpers)
HTMLBars:
attribute(context, params, options)
concat(context, params, options)
bound-templates:
attribute(element, params, options)
concat(params, options)
They should have these signatures:
content( morph, context, path, params, options, env)
subexpr( context, path, params, options, env)
element( element, context, path, params, options, env)
component( morph, context, path, options, env)
options
contains information specific to the invocation of that particular hook/mustache, like types
, hash
, hashTypes
, render
, inverse
, escaped
/raw
.env
contains stuff that is intended to be inherited to child templates, like dom
, hooks
, lookupHelper
, keywords
, data
. Purposely omitted is helpers
. See below.content
should be built up from subexpr
.lookupHelper
rather than direct lookups on a helpers
hash? "Scoped" helpers could be implemented by overriding the lookupHelper
method on env
. var newHelpers = Object.create(oldHelpers)
may be good enough.helper(morphOrElement, values, options, env)
values
is an array of primitives or streams. Basically, for each param, if the type is id
streamify that param against the context, otherwise just pass the primitive through. Not sure if context
should be included in the signature, since all the id
-values should have been resolved. Is there any reason to include context
?concat
or query-params
.
bind-attr
and action
be allowed to have different signatures than content helpers like link-to
or if
? I think yes. Element helpers should be passed the element as their first arg. Content helpers may need the morph as their first arg, so that bound helpers can update their morph. I think the each
helper probably would benefit from access to the morph.attribute
continue to be an element helper or should it be promoted to a hook?keywords
belong in options.data
or env
? What goes in options.data
?env
? newEnv = Object.create(env)
?I'm not sure if I'm logging this issue in the right place or not. This is the first time I've ever created an issue on GitHub.
I experienced this issue in Ember using HTMLBars so I downloaded this repository and attempted to add a test - it looked like the following file would be the most appropriate place:
htmlbars/packages/htmlbars-compiler/tests/combined_ast_node-test.js
The template is defined as:
<ul>{{#each foo}} {{#if startNewList}}</ul><ul>{{/if}} <li>item</li> {{/each}}</ul>
The test failed because an error was thrown - the same error I experienced in Ember.
Error: Closing tag 'ul' (on line 1) did not match last open tag 'undefined'.
at HTMLProcessor.tokenHandlers.EndTag (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:3079:17)
at HTMLProcessor.acceptToken (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:3211:47)
at forEach (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:3260:20)
at HTMLProcessor.nodeHandlers.ContentStatement (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:2889:16)
at HTMLProcessor.acceptNode (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:3206:43)
at HTMLProcessor.nodeHandlers.Program (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:2833:16)
at HTMLProcessor.acceptNode (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:3206:43)
at HTMLProcessor.nodeHandlers.BlockStatement (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:2863:60)
at HTMLProcessor.acceptNode (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:3206:43)
at HTMLProcessor.nodeHandlers.Program (http://0.0.0.0:4200/amd/htmlbars-compiler.amd.js:2833:16)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.