Hey there, lit-html author and former Dart team member here :)
I commented on https://github.com/dart-lang/sdk/issues/30558 why JavaScript
template literals work so well for this HTML templating. I think general
the technique is a good fit for Dart because it doesn't require any
user-land expression parsing or evaluation, or reflection at all. The
template system only receives static strings and expression values, not the
expression text, but it does lean pretty heavily on unique properties of
template literals.
A few points:
1. Being based on strings is actually important, because:
a. HTML is open ended, so you can't define a function for every possible
tag and attribute.
b. The result is passed to innerHTML of a <template> element, so it's
more direct and efficient to start with strings.
c. Strings parse much faster than generic expressions.
2. Template literals separate static and dynamic portions of a template in
a way that JSX (as usually compiled) and most other builder APIs don't.
An expression like:
html`<div foo=${bar}></div>`;
calls the html() function with an immutable object containing the literal
parts and array of the expression values. Any embedded DSL builder API
trying to achieve the same efficiency will have to have a way to denote the
static and dynamic portions, conceptually like:
const div({'foo': expr bar})
where expr is the inverse of const and delineates a dynamic expression
embedded in a const expression (I know, doesn't quite make sense w/ actual
const semantics) and all the other parts are unchangeable.
The closest thing I can come up with in Dart to preserving the static and
dynamic structures is something like:
class TemplateResult {
final List<String> strings;
final List<*> values;
TemplateResult(this.strings, this.values);
}
const List<String> _myTemplate = const ['<div foo=', '></div>'];
myTemplate(foo) => new TemplateResult(_myTemplate, [foo]);
render() {
return myTemplate('bar');
}
And now you can use TempalteResult.strings as the cache key for template
prep work. But this is pretty unsatisfying because you can't write a
template that looks like HTML anymore.
It's possible to do a variation where you write closures mixed with strings:
List render() {
return ['<div foo=', () => this.foo, '></div>'];
}
Which would be called exactly once, and then the closures would be
re-evaluated on updates (unlike lit-html which just reevaluates the whole
template expression), but this is not very nice to write either.
So I really do think that tagged template literals are a really unique,
surprisingly powerful feature and to get the same benefits and ergonomics
that Dart would need something very similar. Very curious what my old
teammates think :)
Cheers,
Justin
Post by Greg Lowe(Apologies if you get this twice - groups ate the first one.)
I agree some of the template building could be done as a build time step.
I'm not convinced it will be much of a bottleneck compared to a react
render. On first render a single tree walk of a template node is likely to
be similar or less work than an initial react full render. Subsequent
rerenders can then be much quicker as only the template parts need to
checked and not the entire dom. Will be interesting to see how the
performance pans out. I agree it's always worth being sceptical until the
numbers are in.
What's the advantage of having a second element tree and synthetic events
vs having a single tree, i.e. the dom, and using native dom events? I don't
understand this - a single tree seems better to me.
i.e. I can already do this using the dom.
void clickListener(listener, child) => html('<div
on-click="${listener}">${child}</div>');
or
void clickListener(listener, child) => daml(div(on_click: $(listener),
child: $(child)));
Btw - both of these examples should actually work with the code in the
lit_example repo.
The `div()(...)` syntax was an interesting experiment, but it doesn't
look great and it's hard to format correctly. I'm in the process of
replacing that now with something which looks more or less like Flutter.
new Div(id: 'asd', children: [
new Text('testing!'),
]);
One advantage of a virtual tree over templating is that you can have
nodes which don't actually correspond to real dom. For example, adding an
event handler could be done by wrapping the div in a new kind of widget.
new ClickListener(onClick: ..., child: ....);
Anyway...
One big disadvantage I see in lit-html is that since it uses the browser
APIs to parse those template fragments, you're going to basically be
running a "build" step in your user's browser. Probably okay for smaller
apps, but might get noticeable quickly. A code generation solution in Dart
could easily beat the performance of lit-html by doing this work ahead of
time.
Post by Matan LureyAh I see what you mean. It's definitely possible.
Today we have the restriction that you can't modify user-authored code
* // Generated by looking at all functions that return
`TemplateResult`. part 'my_file.g.dart';*
* // Always true, so in dart2js the expression will be
tree-shaken/folded.*
* const $G = true;*
* TemplateResult fooTemplate(String id) {*
* return $G ? _$fooTemplate(id) : div( id: ...,*
* children: ...,*
* );*
* }*
... basically, always use the generated function, but keep the "syntax"
for your "nice" code next to the function you're going to be using/calling,
and generate a _$<name> function for every <name> function returning
TemplateResult.
Yes this is hacky, but you could get a proof of concept working and we
could always have more options for codegen in the future. If you or someone
https://github.com/dart-lang/build
https://pub.dartlang.org/packages/source_gen
Let me know if you have any other questions!
Post by Greg LoweGood points.
Could provide an escape hatch for non-standard attributes and data
attributes.
div(id: "foo", attrs: {"--webkit-special": "bar"})
I don't think any of this requires advanced meta programming. I think a
simple build step is enough.
TemplateResult fooTemplate(String id, String value) => div(id: bind(id
), children: [span(text: bind(value))]);
const String fooTemplateParts = ['<div id="', '"><span>',
'</span></div>'];
TemplateResult fooTemplate(String id, String value) =>
lit.html(fooTemplateParts, [id, value]);
lit.html is just the html javascript function directly from the
javascript function lit-html.
I believe this is all that is required to make this work.
Of course if the entire lit-html library is rewritten in/for Dart then
other possibilities may emerge.
Post by Matan Lurey* div({'id': 'foo'})*
... would be trivial, while your example would require lots of named
parameters, and it would be very difficult to keep updated as web standards
evolve/change. What do you do if you need to add `--webkit-special` and it
doesn't exist in your API etc.
Your other idea of dev-time only properties, i.e. some sort of meta
programming would be nice, but we don't currently have a good mechanism to
expose that to anyone - even our own code generation packages cannot edit
existing code or change the Dart language to allow otherwise invalid syntax.
*div((b) => b..id = 'foo')*
Where "b" here is "DivBuilder", and is represented by some
* class DivBuilder extends DomBuilder { set id(String id) =>
this['id'] = id;*
* // ... }*
Dart2JS would tree-shake away unused setters in this scenario.
Post by Greg LoweLooks pretty good.
What I'm looking for is something where I can code completion in my
IDE and has the same syntax as plain Dart code.
i.e. div(id: "foo", children: [ span(text: "bar") ])
This is probably a hard problem because I assume dart2js or DDC will
not generate decent code for functions with 100+ arguments - I haven't
checked though. This is why I wonder if it makes sense to use Dart
compatible syntax at IDE time, but to compile it to something else behind
the scenes.
Post by Tobe OsakweYou might want to check out https://github.com/thosakwe/html_builder.
Technically, the library just produces HTML AST's and includes a "renderer"
that produces HTML strings. However, I was hacking around with the AST and
https://github.com/html_builder_vdom
Post by Matan LureyPost by Greg LoweI was looking at lit-html, and was wondering if it is posible to
implement something similar in Dart, except specifying the html templates
using Dart syntax rather than html inside literals. i.e. A similar syntax
to how flutter builds UI components.
I assume to make this efficient that some type of build step would
be required (but maybe there are some more creative hackers out there who
have a clever idea to avoid this).
Doesn't necessarily *need* a build-step. One of the reasons some
of these "immediate mode" APIs are popular is that they don't require code
generation.
Post by Greg LoweBelow is a rough example of what I mean. This will look better if
Dart gets rest arguments at some point.
I would love to hack on a prototype, but I'm time constrained, so
just putting this out there to see if anyone else is interested in
experimenting.
I recommend reading the lit-html readme to understand how it works.
https://github.com/PolymerLabs/lit-html
I know some people prefer html syntax and will be horrified by
using Dart syntax for html - but another library can solve the the html use
case - each to their own. I'd rather not discuss the pros and cons of using
Dart syntax vs html syntax in this thread.
import 'dart:html' show document;
import "package:dom_template/dom_template.dart" show html, render,
bind, repeat, TemplateResult;
import "package:dom_template/html.dart" show div, input;
TemplateResult inputTemplate(InputField field) =>
html(
div(children: [
div(class: "text-input", text: bind(field.value)),
input(value: bind(field.value)),
]));
TemplateResult formTemplate(Form form) =>
html(
div(children: [
div(
class: "form-title",
children: [
div(text: bind(form.title)),
repeat(form.fields, inputTemplate),
])
])
);
main() {
var form = new Form("Foo", [new InputField("bar", "Bar", "42"
)]);
render(document.body, formTemplate(form));
}
class InputField {
InputField(this.id, this.label, this.value);
final String id;
final String label;
final String value;
}
class Form {
Form(this.title, this.fields);
final String title;
final List<InputField> fields;
}
Yup, something roughly like that would work for static rendering.
* A framework (with code generation) to watch and update the DOM.
* A DOM differ to take previous/next state and apply the difference.
* Something else.
* OverReact <https://pub.dartlang.org/packages/over_react> (most
production ready, but does use JS interop w/ React)
* Butterfly <https://github.com/yjbanov/butterfly> (prototype-y)
* Uix <https://github.com/localvoid/uix> (abandoned, but the code is there)
--
Post by Matan LureyPost by Greg LoweFor other discussions, see https://groups.google.com/a/da
rtlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to
http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google
Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it,
--
For other discussions, see https://groups.google.com/a/da
rtlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to
http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google
Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it,
--
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to
http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google
Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it,
--
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to
http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google
Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send
--
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to
http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google
Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send
--
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups
"Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an
--
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+***@dartlang.org.