Html templating in Dart (lit-html alternative)

505 views
Skip to first unread message

Greg Lowe

unread,
Nov 10, 2017, 6:51:45 PM11/10/17
to Dart Misc
I 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).

Below 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.

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;
}



Matan Lurey

unread,
Nov 10, 2017, 6:59:42 PM11/10/17
to mi...@dartlang.org
On Fri, Nov 10, 2017 at 3:51 PM Greg Lowe <greg...@gmail.com> wrote:
I 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.
Yup, something roughly like that would work for static rendering.

Of course if you want data-binding you'd need either:
* 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.

There are a couple of existing libraries in this area:
* OverReact (most production ready, but does use JS interop w/ React)
* Butterfly (prototype-y)
* Uix (abandoned, but the code is there)
 

--
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+uns...@dartlang.org.

Tobe Osakwe

unread,
Nov 10, 2017, 7:25:41 PM11/10/17
to mi...@dartlang.org
You 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 Incremental DOM and found it to work decently well: https://github.com/html_builder_vdom

Greg Lowe

unread,
Nov 10, 2017, 7:53:24 PM11/10/17
to Dart Misc
> Doesn't necessarily need a build-step

Without a build step I imagine the div function is going to have > 100 named parameters. Not sure if this is practical.

I've looked at overreact and butterfly, and the div()(...) syntax isn't great, but it is a good work around given the available options.

Note the difference between lit-html and react is that lit separates static template content from dynamic template content. My example probably wasn't clear enough.

Consider the following template:

div(className: "foo", text: bind((value) => value.toUpperCase()));

This would get compiled behind the scenes to an html template tag like:

<template><div class="foo">{{ --placeholder-- }}</div></template>

At runtime the template will get stamped into the dom and the placeholder will be replaced with the evaluated content of the bind expression. If the dom is updated only the placeholder node will need to be updated. In this case a single call to div.textContent.

Greg Lowe

unread,
Nov 10, 2017, 7:58:25 PM11/10/17
to Dart Misc
Looks 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.

Matan Lurey

unread,
Nov 10, 2017, 8:05:37 PM11/10/17
to mi...@dartlang.org
Depends if the auto-complete is that important to you, for example:

  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.

Another idea would be some sort of "builder" syntax:

  div((b) => b..id = 'foo')

Where "b" here is "DivBuilder", and is represented by some auto-generated facade:

  class DivBuilder extends DomBuilder {
    set id(String id) => this['id'] = id;
    // ...
  }

Dart2JS would tree-shake away unused setters in this scenario.

Greg Lowe

unread,
Nov 10, 2017, 8:19:25 PM11/10/17
to Dart Misc
Good 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.

Let's assume I have a build step which takes the following:

TemplateResult fooTemplate(String id, String value) => div(id: bind(id), children: [span(text: bind(value))]);

And turns it into:

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.

Matan Lurey

unread,
Nov 10, 2017, 8:30:11 PM11/10/17
to mi...@dartlang.org
Ah I see what you mean. It's definitely possible.

Today we have the restriction that you can't modify user-authored code as part of the build process, so you could do:

    // 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 else reading is interested, our codegen platform is currently here:

https://github.com/dart-lang/build 

And we have various utilities here:

https://pub.dartlang.org/packages/source_gen 

Let me know if you have any other questions!

tatumizer-v0.2

unread,
Nov 10, 2017, 10:37:29 PM11/10/17
to Dart Misc
Interestingly, we discussed very similar idea here long before javascript came up with tagged string literals. It's not identical, but the idea is the same: call user-defined function to concatenate the parts of the literal.

Speaking of the above example: "children" is what makes it boring to write, and difficult to read. I mean, you are trying hard to improve it, go in great lengths with code generation, but still, "children" remains an eyesore.
(for the proof, look no further than Flutter examples)
It can be fixed by adding support for named vararg (as discussed recently)  

Greg Lowe

unread,
Nov 10, 2017, 10:41:07 PM11/10/17
to Dart Misc
Thanks for the tips, that approach makes sense.

The only think I can't really figure out is how to get the types right in the IDE without union types.

Ideally I'd be able to something like this:

div({
 
String|StringBinding  className,
 
Map<String,String|StringBinding> attrs,
 
List<Element|ElementBinding> children, ...})

But I guess using dynamic for the types isn't totally awful, as at least you get the IDE completions for parameter names which is most of the problem.

Or maybe generic method trickery will be enough... I guess you may not even need to specify the type at a call site as type inference should be able to figure that out.

T bind<T>(dynamic value) {}

Anyways... plenty to think about.

Greg Lowe

unread,
Nov 10, 2017, 10:56:14 PM11/10/17
to Dart Misc
Agreed, children can be a pain ;) With flutter becoming more popular varargs is surely on the radar.


I think even without varargs I prefer this style over writing html inside string literals - choose your poison I guess.

Greg Lowe

unread,
Nov 12, 2017, 2:13:16 AM11/12/17
to Dart Misc
The codegen stuff works pretty smoothly. I managed to whack together a proof of concept pretty quickly and there weren't any big surprises.

I wrote a simple example web app using this codegen. If anyone's curious you can see the code here: https://github.com/xxgreg/lit_example/blob/master/web/main.dart

Beware - loads of crude hacks.

On Saturday, 11 November 2017 14:30:11 UTC+13, Matan Lurey wrote:

Matan Lurey

unread,
Nov 13, 2017, 12:18:27 PM11/13/17
to mi...@dartlang.org
Great I'm glad you were able to hack on something :) 

Jonah Williams

unread,
Nov 13, 2017, 12:34:22 PM11/13/17
to mi...@dartlang.org
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.


Greg Lowe

unread,
Nov 13, 2017, 4:55:37 PM11/13/17
to Dart Misc
(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.

Justin Fagnani

unread,
Nov 17, 2017, 1:36:52 PM11/17/17
to General Dart Discussion
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

Greg Lowe

unread,
Nov 17, 2017, 6:45:06 PM11/17/17
to Dart Misc
Thanks for the input.

One difference with the JS usecase is that Dart always has a build step. RIP Dartium. If we assume a build step, then Dart's existing template literal is enough.

Btw, the "Daml" syntax that I prototyped, just gets turned into strings by the buildscript. Support for unknown tags should be easy to support, via tag(tagName: "Foo"), or end users can just add their own tag function stub.

I like the idea of a unified markup syntax, rather that html and Dart. (Too much lisp in me perhaps).

Keep up the good work with lit ;) Exciting to see the standards work on this too.

I'm looking forward to seeing some examples of reactive style apps with web components and lit. I can't figure out how invalidation should work with a tree of components.

王坤

unread,
Sep 1, 2020, 12:26:10 AM9/1/20
to Dart Misc, greg...@gmail.com
Microsoft Edge(Chromium) is climbing to be the 2nd browser in the market. https://www.polymer-project.org/blog/2020-01-15-edge-79-release
And Storybook supports Web Components since v5.3 https://medium.com/storybookjs/storybook-5-3-83e114e8797c
Is there any chance we could bring those back?
Reply all
Reply to author
Forward
0 new messages