WICG / webcomponents

Web Components specifications
Other
4.39k stars 376 forks source link

[dom-parts] HTMLTemplateElement.fromStrings() API #1019

Open justinfagnani opened 1 year ago

justinfagnani commented 1 year ago

This is an API for template instantiation / DOM Parts suggested recently by Google's security team (edit: by @engelsdamien specifically) that caught my eye.

Essentially it's a imperative way to create a template and a list of parts from template strings, but without having to specify a syntax for expressions.

fromStrings() would be a static method that takes an array of strings (security wants this to be a TemplateStrings array to be unforgeable developer-controlled value) and create DOM parts between each string.

class HTMLTemplateElement {
  static fromStrings(strings: TemplateStringsArray): HTMLTemplateElement;
}

A template system can be built with this quite easily:

const templateCache = new Map();
const html = (strings, ...values) => {
  let template = templateCache.get(strings);
  if (template === undefined) {
    template = HTMLTemplateElement.fromStrings(strings);
    templateCache.set(strings, template);
  }
  return {
    template,
    values,
  };
}

function render(templateResult, node) {
  // clones the template, sets the part values, etc, using:
  // templateResult.template.content.getPartRoot().clone()
}

// Example usage
function renderName(firstName, lastName, about) {
  render(html`<x-foo name="${lastName}, ${firstName}">${about}</x-foo>`);
}

Where template strings are allowed to break and what type of parts those break create would have to be figured out, which is the same for a syntax. It could be that the syntax is the easier piece of the puzzle once that is done, but if syntax were a roadblock, this approach could side-step it for a bit.

EisenbergEffect commented 1 year ago

My first impressions of this are positive. It's an interesting way to sidestep the syntax question and still potentially get a lot from the platform.

rictic commented 1 year ago

I needed almost this exact API to add DOM Parts support to our template system workbench: https://github.com/rictic/web-template-workbench/pull/1

As part of writing up a polyfill, I had to make some choices around representing bindings inside an element tag. Code like:

html`<div ${x}></div>`

The user intent here is clearly for a NodePart. The template system needs a reference to the <div> and will do something to it involving x.

But for code like:

html`<img src="https://example.com/${name}.png?size=${size}px">`

We need to communicate that we've got bindings into the src attribute interspersed with constant parts. I represented this with metadata on a node part, with a format similar to a TemplateStringsArray:

<?node-part attr src "https://example.com/" ".png?size=" "px" ?>

This worked well. Though looking forward towards template instantiation, I think we will want an actual AttributePart and the attribute value setter from Ryosuke's proposal, or something filling the same role, to coordinate multiple AttributeParts, and static strings, without performing multiple writes to the attribute each time the template is updated.

rictic commented 1 year ago

Another thing that came up: this could use an option to parse the template in an <svg> context.