Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: Templates that Don't Destroy Element References #777

Open
Lonniebiz opened this issue Dec 4, 2018 · 27 comments
Open

Discussion: Templates that Don't Destroy Element References #777

Lonniebiz opened this issue Dec 4, 2018 · 27 comments

Comments

@Lonniebiz
Copy link

Lonniebiz commented Dec 4, 2018

In the same manner that tagged template literals allow the embedding of interpolated JavaScript expressions, I think it would be awesome if HTML <template> tags (or some new tag type) allowed ${elementReferenceName} expressions also. Then, instead of cloning a template, you could just instantiate it in the same manner you would an object from a class (without losing your element references), and when you do this the interpolated values could be based on the original context from where the instantiation occurs (or through .bind to specify an alternative context), kind of like arrow function scoping but with bindablity.

Also, I think if an interpolation's output is anything other than an element, the template-instantiation should covert that output to a static string that cannot be modified. There's no need to update the template if you can instead update the dynamic elements that were place into it. Cloning destroys your element references. Template instantiation should keep those element references! This way, at the custom class level, you'll always be able to modify the vary same elements you created before injecting it into the template.

I need something like the <template> tag that I can stick inside my custom class's constructors, where each time I instantiate it, it will contain the same referenced element I created in that constructor. Cloning is a pain; you clone it and then you have modify it before it goes out so that you can maintain element references. Cloning is fine for some things, but not ideal inside a class constructor.

If we had a <template> tag like this; it would be all I'd ever need.

@bahrus
Copy link

bahrus commented Dec 6, 2018

There is a proposal along the lines of what you are suggesting, I think.

@Lonniebiz
Copy link
Author

Besides editing it. How can I get my comment above in front of the eyes of the people making that proposal?

@rniwa
Copy link
Collaborator

rniwa commented Dec 6, 2018

If you'd like, we can converse over email or video conf about this. Shoot me an email me at webkit.org or apple.com (username is same as my Github username).

@Lonniebiz
Copy link
Author

Yes, let's discuss. I sent an email @webkit.org (same handle as here).

@Lonniebiz
Copy link
Author

Lonniebiz commented Dec 6, 2018

This is how I'd like the process of creating a web component to be.

  1. Manually create the dynamic elements that will change in the web component. Pretend that every thing below is happening in the constructor of a web component:
this.div = document.createElement("div");
div.textContent = "Click Here to See an Alert";
div.addEventListener('click', (event)=>{alert(`You've click a div that contained this text: ${this.div.textContent}`)});
  1. Use a template literal to create a string where all dynamic parts are combined with element-interpolations (${expressionThatReturnsAnElement}):

let soonToBeDOM = `<h1>See Below</h1>${div}<p>Click the sentence above to see alert.</p>`;

  1. Now, feed this template literal to an HTML template:
let template =  document.createElement("template");
let dom = template.buildFromLiteral(soonToBeDOM);

At this point pretend we've created actual DOM that contains the static and dynamic parts. And most importantly, the reference to this.div remains accessible throughout the life cycle of our web component instance. Any update method would be able to modify this.div (fast as possible) with a direct reference and the event listener we created at the beginning continues to work.

The template tag, never needs to be re-rendered. If you need to update something. You modify the element directly (by reference).

This how I'd like it to work. hyperHTML and lit-html accomplish this same type of thing, but if that .buildFromLiteral method above worked natively, it would be even faster than they've accomplished. Because, in order for them to do it, they are having to replace interpolations with comment nodes before cloning the HTML template. Then, after cloning the template, they have to retrieve references for all those comment nodes, something like this:

const getCommentNodes = function(element)
{
	let treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
	let commentNodes = [];
	while(treeWalker.nextNode()) {commentNodes.push(treeWalker.currentNode);}
	return commentNodes;
};

Lastly, they are having to replace each comment node with the actual element created within the web-component's constructor.

And they are not just doing element-interpolations either. They support strings, fragments, promises and other interpolations/expression output types.

So if you could generate an HTML <template> by feeding it a template literal containing expressions/interpoations, AND if that template element natively knew how to handle ${expressionsThatOutputElements}, it would just simply be awesome....

The only thing I care about is element-interpolations. I'll separate static from dynamic myself. As long as I can reference the dynamic parts via element-reference, I've got all I'll ever need.

@rniwa
Copy link
Collaborator

rniwa commented Dec 7, 2018

@justinfagnani

@rniwa
Copy link
Collaborator

rniwa commented Dec 7, 2018

Thanks. I think what we proposed would achieve something very close to what you want / are proposing. Fundamentally, there are two important pieces to the template API we’ve been discussing.

  1. TemplatePart - It’s an object in DOM tree which keeps track of positions and be able to insert & replace any number of nodes at that location. This is basically a replacement for Comment node libraries like lit-html uses, and lets you very easily update the instance of a template.
  2. Template parser - This is the thing that finds instances of {{~}} and replaces it with TemplatePart.

Let's say we have the HTML like this:

<template id="myTemplate">
<h1>See Below</h1>{{content}}<p>Click the sentence above to see alert.</p>
</template>

Then we can do something like this (the exact shape & naming of API is not yet settled but so take it with a grain of salt; e.g. which properties exist where, etc... can change):

const div = document.createElement("div");
div.textContent = "Click Here to See an Alert";
div.addEventListener('click', (event)=>{alert(`You click a div that contained this text: ${this.div.textContent}`)});
const instance = myTemplate.createInstance({content: div});

Here, createInstance uses the builtin template parser to find all instances of {{~}}, and creates an instance of TemplatePart for each. Then it assigned the "value" based on its name. The mapping / assignment behavior is completely customizable by defining your own template processor but that's what the default behavior would be.

Once you created an instance, you can insert that context into anywhere in the DOM:

document.body.appendChild(instance.content);

Obviously, you can access & mutate div anyway you want:

div.innerHTML = '<b>Click Here to See an Alert</b>';

In addition, the TemplatePart object which has been created for this instance can be updated. e.g.

const b = document.createElement('b');
b.textContent = 'Open an alert';
instance.parts['content'].replace(b);

@Lonniebiz
Copy link
Author

Lonniebiz commented Dec 7, 2018

What I'm proposing is more fundamental (and more efficient) than Template Instantiation, but not mutually exclusive to Template Instantiation. Here's how the two would complement each other. First I'll discuss what I'm proposing and later I'll tie that into the Template Instantiation proposal.

Template Literals already know the context from which to determine the values of the ${interpolations/expressions} they contain. They also already inherently know which portions of the string are "parts".

Template Literals, without the help of any type of HTML template element, already contain everything that needs to be known to generate DOM. Therefore, I think it would be ideal for Browsers to add 2 prototype method to all strings. One called toDOM() and the other called toTemplate(). Here's how this would work.

toDOM()

If you want to create live Dom (that's ready for the appendChild method) you use the toDOM() method:

// Again, pretend this is happening in a web component's constructor
this.btn = document.createElement("button");
btn.textContent = "Click Here to See an Alert";
btn.addEventListener('click', (event)=>
{
	alert(`You've clicked a button that contains this text: ${this.btn.textContent}.`)
});

let soonToBeDOM = 
`<h1>See Below</h1>${this.btn}<p>Click the sentence above to see alert.</p>`;

document.body.appendChild(soonToBeDOM.toDOM()); 
//button says: Click Here to See an Alert

setTimeout(()=>
{
	this.btn.textContent = "Nevermind";}, 5000);
	// this.btn says "Nevermind" after 5 seconds.
}

Notice that the this.btn element reference, created at the beginning of the constructor, survived the template literal all the way to DOM, and so did its click eventListener; if you click this.btn it will still fire. this.btn remains accessible to the web component for efficient changes throughout the entire life cycle of the web component.

Above is what I'd primarily use, because if I already have a direct object reference to the dynamic elements that change (which what I'm proposing would give you), there is no need for template updating. Therefore, there's no need for an intermediary template element and the added change-evaluation processing it brings with it for each interpolation each time the template is updated. An event fires, and you change only what needs to change, without ever even evaluating any of the interpolations that didn't change! This is as efficient as it gets.

toTemplate()

Here's where your proposal comes into the picture.
The toTemplate() does the same thing that the toDOM() method does, except it returns an inert html <template> element where all interpolations/expressions have been replaced with unnamed-TemplateParts. Later when you call the htmlTemplate.createInstance(['You','Pass','an','array','of','replacements']);

If you want each template part to have a name, you can pass names in during the toTemplate() method:
templateLiteral.toTemplate('array','of','named','parts');

Then, later when you call htmlTemplate.createInstance(), you pass in an object literal instead of an array if you originally gave the parts a name during .toTemplate().

And also, the examples you gave, where the template element was created in HTML, this will be fine too.

Summary

These two methods are fundamentally different ways of updating the dynamic aspects of a web component. The first method only gets called once and stuff that doesn't change doesn't even get evaluated (an event tells you exactly what changed and at most you evaluation only the one thing the event said changed). The Template Instantiation method has to evaluation each part on each update, looking for everything that might have changed. By handling your updates at the more fundamental level, the events themselves inherently tell you exactly what changed. Evaluating things that didn't change is unnecessary, but hyperHTML and lit-html do this for every interpolation on every template update. You don't have to compare anything to see what changed, the event inherently tells you what changed and you can go update that alone.

What I'm proposing is fundamental, but simple. templateLiteral.toDOM() is like a fancy innerHTML that understands when a ${JavaScriptExpression} is an element. This is all the "templating" I'll ever need.

@matthewp
Copy link

matthewp commented Dec 7, 2018

This seems a bit weird to me. The interpolation of:

`<h1>See Below</h1>${this.btn}<p>Click the sentence above to see alert.</p>`

produces:

"<h1>See Below</h1>[object HTMLButtonElement]<p>Click the sentence above to see alert.</p>"

You want the thing the literal produces to not be a string. To do that you need a tagged template literal like lit-html does. Such a function could work with template instantiation to produce what you want.

@Lonniebiz
Copy link
Author

Lonniebiz commented Dec 7, 2018

@matthewp : Yes, you're right, that's what string alone produces, and that's what the .toDom() method would handle. It ultimately converts that [object HTMLButtonElement] you're seeing (along with everything else in the string) to a DOM element. Kind of like innerHTML converts a string to DOM, but with the additional feature of being able to convert ${variableOfHandCreatedElementNode} to that vary element created and referenced at the beginning.

I'm saying that, when javascript is running in a web browser. There could be a prototype method called toDOM() on string (for all strings, including template literal strings).

However, if the string is a template literal, the method should be smart enough to interpret the ${elementNode} expression just like what is accomplished using tagged template literals, before producing the DOM.

My basic need is the ability to just mix a string of HTML with my own hand-crafted elementNode objects. The toDOM() method would produce the DOM I need very easily with nothing more. The events on my web components tell me exactly what changed without having to evaluate every other interpolation. I would never want to do a string lookup on a clone to replace a node. Each dynamic thing in my web components can be directly accessed via this.elementName and nothing is faster than that.

What I'm saying is not better than Template Instantiation. It just seems like a more fundamental thing that should precede Template Instantiation. After I get the DOM, you can garbage collect any template that may have been needed to give it to me.

I agree that the end result can be accomplished by lit-html, hyperHTML, and the Template Instantiation proposal. I look forward to seeing what is ultimately the standard on these concepts.

@matthewp
Copy link

matthewp commented Dec 7, 2018

For that to work the string would need to keep a reference to btn so that if toDOM() is ever called it can interpolate the actual element. But strings are value types. So they can't keep state.

@Lonniebiz
Copy link
Author

Lonniebiz commented Dec 7, 2018

Well, then I'd expect .toDOM() to throw an error if this.btn is not accessible from within the scope where .toDOM() is called. I'm fine with that; I won't need that original template literal at all after I have the DOM.

When you pass a template literal into a tag function, the tag function receives an array of static strings and an argument of each expression result. Couldn't a method on a template literal receive these same parameters automatically? Does it have to become a literal string value during the dot of .toDom()?

@matthewp
Copy link

matthewp commented Dec 7, 2018

Sounds like to make this method work you're going to have to make a lot of changes to JavaScript. Is it really that important to you that the method be on the string prototype? Why not just have a method toDOM on the window object (or somewhere else)?

@justinfagnani
Copy link
Contributor

Maybe I'm missing something here, but what's wrong with a template tag? It cal already do all the things asked for here and makes it clear to the author that some special is going on compared to plain strings.

@rniwa
Copy link
Collaborator

rniwa commented Dec 7, 2018

Template Literals, without the help of any type of HTML template element, already contain everything that needs to be known to generate DOM. Therefore, I think it would be ideal for Browsers to add 2 prototype method to all strings. One called toDOM() and the other called toTemplate(). Here's how this would work.

...

Template Literals already know the context from which to determine the values of the ${interpolations/expressions} they contain. They also already inherently know which portions of the string are "parts".

There is a difference between the template literal and the string it generates. "~" is a template literal but the runtime semantics of a template literal is to generate a regular string. So once the template literal is evaluated (which happens once), then the generated string is no different from any other string and doesn't retain any information about the context or where the substations occurred.

This is precisely why our proposal uses {{~}} as opposed to ${~} so that we can retain these contexts beyond when the template is instantiated.

An alternative approach is to use tagged template literal like lit-html and hyperHTML. One big difference between a regular template literal and a tagged template literals is that the latter can return a non-string object.

Well, then I'd expect .toDOM() to throw an error if this.btn is not accessible from within the scope where .toDOM() is called. I'm fine with that; I won't need that original template literal at all after I have the DOM.

That's going to be an extremely exotic behavior because this .toDOM() function would be looking up things in its caller's scope. Since literally nothing else in the platform behaves like this, I don't think we should do this.

Template Literals, without the help of any type of HTML template element, already contain everything that needs to be known to generate DOM. Therefore, I think it would be ideal for Browsers to add 2 prototype method to all strings. One called toDOM() and the other called toTemplate(). Here's how this would work.

That would be a massive layering violation. Because ECMAScript is used in non-browser environments without DOM like node.js these days, adding any sort of DOM related API directly onto String would be a bad idea.

Having said that, I'm amendable to the idea that there probably is a valid use case for creating a template instance directly from a string. For example, I could imagine an alternative approach to instantiating a TemplateInstance perhaps via a constructor as in:

<template id="boldTemplate"><b>{{text}}</b></template>
<script>
const content = {text: 'hello, world'};
const instance1 = new TemplateInstance('<b>{{text}}</b>', content);
const instance2 = new TemplateInstance(boldTemplate, content);
</script>

where instance1 and instance2 are semantically equivalent template instances.

Alternatively, using tagged template literal, we could introduce something like htmlTemplate tag built into the browser which creates a HTMLTemplateElement or some kind of a DocumentFragment variant which knows it's a template fragment as in:

<template id="template3"><b>{{text}}</b></template>
<script>
const content = {text: 'hello, world'};
const template4 = htmlTemplate`<b>{{text}}</b>`;
const instance3 = template3.createInstance(content);
const instance4 = template4.createInstance(content);
</script>

Again, instance3 and instance4 are semantically identical. Now, because htmlTemplate is a tagged template literal, you can then insert a random DOM node as in:

const br = document.createElement('br');
const template5 = htmlTemplate`<b>{{text}}</b>${br}`;
const instance5 = template5.createInstance({text: "hello"});

Then instance5 would have the HTML fragment of <b>hello</b><br> as desired.

Note that instance5 would contain a different br element than the one we created for the template since each instance of a template must have its own br element. If, for example, you wanted to create a br in each template instance yourself, you'd do this instead:

const template5 = htmlTemplate`<b>{{text}}</b>{{suffix}}`;
const br = document.createElement('br');
const instance5 = template5.createInstance({text: "hello", suffix: br});

This is the closest thing I can come up with to what you're proposing without all the issues I listed above.

@Lonniebiz
Copy link
Author

Lonniebiz commented Dec 7, 2018

@matthewp : When you create a string, it becomes a string object instance having numerous methods. Since converting strings to DOM is such a common task in client-side development, it seems quite natural to me that browsers would add a .toDOM() prototype method there along with all the other string methods.

Tag Template literals are a wonderful but strange beast in JavaScript; somewhere between the template literal itself and the tag function, a magic hidden splitter-function occurs that feeds the template literal to your tag function in pieces: let template = function(aryStatic, ...expressionResults){}. It seems to me that similar Magic would be possible for a prototype method.

You mentioned the window object, while my first thought of an alternative would be for browsers to add a static class-method on String:
let element = String.toDOM(`<h1>String with ${elementNode}.</h1>`);


@justinfagnani : Actually, I must admit that the html template tag in lit-html is quite elegant. This talk about the exact syntax of things has gotten me on a tangent away from my main point Which is:

If I do not want an update-able template . . . if all I want is DOM from the template tag function, then why must I call a template function that returns a template result which is NOT DOM? It seems like some efficiency would be gained from not returning something that facilitates more than I need.

In my web components, I create the dynamic elements manually, so that I'll have direct object references to each thing that needs to change. When my web component receives an event from the browser, methods get fired and the only thing updated is exactly what changed (without having to re-evaluate a template at all). So, the only thing I want, is the ability to convert a template literal directly into DOM.

I realize that Template Instantiation, lit-html, and hyperHTML all allow me to do just that, but its done at the price of what it costs to make a fully functional update-able template, and I'd like to do it at the cost (in memory and performance) to just generate the DOM (once, and garbage collect the rest). Surely, more resources are required to make a fully functional, update-able template, compared to a tag function that is elusively dedicated to only creating DOM once? No?
let element = DOMonly`<h1>String with ${elementNode}.</h1>`; //returns DOM without template.

@justinfagnani
Copy link
Contributor

If I do not want an updat-able template . . . if all I want is DOM from the template tag function, then why must I call a template function that returns a template result which is NOT DOM? It seems like some efficiency would be gained from not returning something that facilitates more than I need.

hyperHTML works this way if you want that. lit-html returns a light-weight result object so that the cost of creating DOM is only done when rendering. You can convert the template approach to the immediate DOM approach easy enough:

import {render} from 'lit-html';
export const renderFragment = (v) => {
  const fragment = document.createDocumentFragment();
  render(v, fragment);
  return fragment;
};

As for the intermediate <template> that's created in these libraries (and so far would be in Template Instantiation), it's mainly there because for components we assume you'll need to create more than one, and subsequent clones and instance prep will be faster than the initial parsing and template prep.

It may be possible to create another code-path that directly creates DOM without the <template> (though I suspect updateable attributes will be tricky) but since the HTML string needs to be parsed either way, all we're really saving is the .clone(). As soon as you do this for more than one instance of a non-trivial component, I suspect the repeat parses will cost more than the repeat clones.

I should note that with lit-html you don't need to re-render at all. If you give a template your dynamic elements and only change those, that technique will work. Using a <template> under the hood should still be a performance improvement for multiple component instances.

If you think that re-parsing the HTML string is faster than cloning for multiple instances, it'd be good to verify that with some benchmarks. I'd be curious about the results.

@justinfagnani
Copy link
Contributor

justinfagnani commented Dec 7, 2018

@rniwa I wonder if we want to consider a more generic name for TemplatePart, since they can be attached directly to non-template DOM?

Also, another approach could be a template tag that does what you have in the very first example and directly creates an instance. Maybe something like:

let text = 'hello';
const br = document.createElement('br');
const instance5 = htmlInstance`<b>${text}</b>${suffix}`;
document.body.appendChild(instance5.fragment);
instance5.update({0: 'goodbye'});

edit: forgot instance5.fragment to get the actual DOM, so renamed the tag to htmlInstance

@WebReflection
Copy link

WebReflection commented Dec 7, 2018

in order for them to do it, they are having to replace interpolations with comment nodes before cloning the HTML template. Then, after cloning the template, they have to retrieve references for all those comment nodes, something like ...
Lastly, they are having to replace each comment node with the actual element created within the web-component's constructor.

@Lonniebiz just to clarify, hyperHTML doesn't replace comments, it uses these as "pin" in the DOM and relate changes to these without ever replacing a thing.

The eventual update is handled by domdiff which takes care of adding, removing, or swapping only nodes that have changed since last update.

Since I've recently refactored out all the major parts of hyperHTML, what might interest you here is the domtagger, which is the hyperHTML engine in charge of using <template> once, parse its normalized HTML/SVG once and find out all the holes in it.

A hole can be a textContent, as example if inside a textarea or a text only node such a style one, an attribute, or "any" hole, meaning any sparse hole within a node.

The most basic example (note that hyperHTML does way more than just this):

var html = domtagger({
  type: 'html',
  attribute: (node, name) => value => {
    var type = typeof value;
    if (type === 'boolean' || type === 'function')
      node[name] = value;
    else if (value == null)
      node.removeAttribute(name);
    else
      node.setAttribute(name, value);
  },
  any: comment => html => {
    const {parentNode} = comment;
    parentNode.innerHTML = html;
    parentNode.appendChild(comment);
  },
  text: node => textContent => {
    node.textContent = textContent;
  }
});

// render example
function render(model) {
  return html`
    <div onclick=${model.onclick}>
      <div>${model.html}</div>
      <textarea>${model.text}</textarea>
    </div>
  `;
}

document.body.appendChild(
  render({
    onclick: function (e) {
      alert(e.currentTarget.outerHTML);
    },
    html: 'Hello <strong>domtagger</strong>!',
    text: "isn't this cool?"
  })
);

So, pretty much all the primitives I need are there, the only missing part to me is a persistent fragment, so that I can keep comparing, or appending, a fragment without it ever losing its childNodes.

Such fragment would be smart enough to move its childNodes over a new node only if the node it's appended into changes from the previous one.

That is honestly the only thing the platform doesn't provide, everything else can be done with relative ease on user-land, and performances are already better than pretty much every relevant competitor.

@WebReflection
Copy link

Just to clarify, since apparently my latest example has been already misunderstood, hyperHTML does never replace same content and above example is just 1/16th of the logic involved in hyperHTML updates, absolutely not what hyperHTML does.

@mikesamuel
Copy link

@Lonniebiz

The toTemplate() does the same thing that the toDOM() method does, except it returns an inert html <template> element where all interpolations/expressions have been replaced with unnamed-TemplateParts.

What does inertness get you here?

Typically, in a template system you want to delay side-effects like network fetches or code loading until render time.

Inertness lets you do that for trusted template elements, but it's unclear to me how that helps fill holes.

For example, in <template><script>foo()</script><div>{{x}}</div></template>, if x is filled with <img src=/bogus onerror=alert(window.origin)> then you delay the call to foo() until render time, but you also need to parse x in an inert context to delay its side-effects.

So I'm unsure of the value of inertness for trusted template code without inertness for untrusted inputs, and I'm unclear on how the latter kind of inertness falls out of these changes.


It's also unclear to me how this helps with untrusted inputs that specify bytes in a language other than HTML, like javascript: URLs reaching href or src attribute values.

Having a DOM structure helps you derive context for holes, but it doesn't help preserve developer intent unless you use that context to reinterpret inputs. By the time you've parsed inputs to DOM fragments, I worry that it will be too late to reinterpret them.


https://github.com/wicg/trusted-types provides a type-safe taxonomy for distinguishing trusted and untrusted inputs in important contexts but depends on delaying parsing.

@justinfagnani
Copy link
Contributor

@mikesamuel in the most basic Template Instantiation proposal, the contents of {{}} expressions are just names, un-interpreted, and used in the update() API to route values to parts. I don't think they need to be parsed, but safe types could integrate well to prevent untrusted values from flowing there.

@Lonniebiz
Copy link
Author

Lonniebiz commented Dec 10, 2018

The fastest way to access an element is by direct object reference. What's the 2nd fastest way? A Map object maybe?

This got me thinking. What if you had a really huge template. Could performance be gained on first render if the "comments" knew how to replace themselves without having to walk the DOM to extract comment nodes?

So, instead of replacing expressions with comments, why not replace them with something smart enough to know how to manage itself: a custom-element:

export class PartResolver extends HTMLElement
{
		constructor()
		{
				super();
				this.replaceWith(Template.Map.get(this.textContent));
		}
}
customElements.define('part-resolver', PartResolver);

This is probably a silly idea, but here's a proof of concept.

@rniwa
Copy link
Collaborator

rniwa commented Dec 11, 2018

This got me thinking. What if you had a really huge template. Could performance be gained on first render if the "comments" knew how to replace themselves without having to walk the DOM to extract comment nodes?

That's exactly what TemplatePart in our proposal does. It's a high performance way of keeping track of objects. There is a lot of interesting optimization opportunities in the browser. Notably, not creating & replacing a node is definitely faster than simply remembering where to insert a node, then inserting it. We can also coalesce multiple insertions depending on the final shape of the API to get even more perf gains.

@WebReflection
Copy link

So, instead of replacing expressions with comments, why not replace them with something smart enough to know how to manage itself: a custom-element:

'cause you want to address attributes too, and. custom element inside an attribute isn't really working well, isn't is?

On top of that, a custom element breaks certain layouts, like tables:

<table>
  <tr>
    <broken-table><td></td></broken-table>
  </tr>
</table>

@WebReflection
Copy link

WebReflection commented Dec 16, 2018 via email

@Lonniebiz
Copy link
Author

Regarding templates, here's something to think about:
#807

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants