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

[dom-parts] Declarative API for defining DOM parts #990

Open
tbondwilkinson opened this issue Mar 21, 2023 · 13 comments
Open

[dom-parts] Declarative API for defining DOM parts #990

tbondwilkinson opened this issue Mar 21, 2023 · 13 comments

Comments

@tbondwilkinson
Copy link
Contributor

@justinfagnani @mfreed7 @sorvell @kevinpschaaf and a few folks from Angular have been thinking about the DOM parts API fresh.

One thing that seems lacking is the ability to declaratively create DOM parts with some type of HTML instruction or node that the parser processes and exposes via an imperative JavaScript API to the page.

Use Cases

Hydration

Server side rendered code that undergoes some type of hydration soon after the page loads is becoming more popular in external frameworks and being added to formerly client-side rendering frameworks like React. Other frameworks like Qwik are entirely based around the idea of server-side rendering first and then hydrating on demand.

Most of these frameworks do some type of DOM query or walk to locate nodes that need to be hydrated. DOM parts, if they could be generated declaratively, could speed up these operations by caching nodes that the page will likely need to visit later.

Templates

Using <template> fragments is a good way to avoid repeatedly parsing the same piece of DOM, and can be used to update pieces of the page as well. When using <template> fragments for rendering, the <template> typically needs to be updated immediately after clone for anything that isn't statically defined. To do this, frameworks walk the DocumentFragment looking for placeholder attributes and comments. DOM parts could remove the need for this walk by parsing instructions in the <template> and storing those nodes.

Requirements

  1. Doesn't affect rendering
  2. Doesn't affect tree hierarchy
  3. Fast to find

Proposal

Processing instructions provide one mechanism of adding content to the HTML that does not affect rendering or tree hierarchy but is available to the parser.

<html>
  <section>
    <h1 id="name"><?child-node-part?><?/child-node-part?></h1>
    Email:
    <?node-part metadata?><a id="link"></a>
  </section>
</html>

Here's one example drawn from a recent proposal based on DOM parts.

  • <?node-part?> would create a NodePart attached to the next sibling node.
  • <?child-node-part?> would mark the start of a ChildNodePart range and <?/child-node-part?> would mark the end of the range.

Anything after the processing instruction identifier would be parsed as metadata.

There would need to be some imperative API for retrieving these parts.

interface PartRoot {
  // In-order DOM array of parts.
  getParts(): Part[];
}

interface DocumentPart implements PartRoot {
  getParts(): Part[];
}

declare global {
  interface Document {
    getDocumentPart(): DocumentPart;
  }

  interface DocumentFragment {
    getDocumentPart(): DocumentPart;
  }

This API would add a new DocumentPart that is retrievable from a Document or DocumentFragment and has a getParts() method that returns the parts parsed from the DOM.

@rniwa
Copy link
Collaborator

rniwa commented Mar 21, 2023

I don't think we want to add this independent of template instantiation syntax. We need to be able to combine them into a singular syntax/API.

@tbondwilkinson
Copy link
Contributor Author

I think template instantiation syntax is definitely a nice to have feature, but it isn't to me an essential v1 feature. Can you talk a bit more about why they need to be combined?

@rniwa
Copy link
Collaborator

rniwa commented Mar 21, 2023

I think template instantiation syntax is definitely a nice to have feature, but it isn't to me an essential v1 feature. Can you talk a bit more about why they need to be combined?

Because we eventually want to support that use case as well. And we sure as hell not introducing two completely different syntaxes for each use case.

@tbondwilkinson
Copy link
Contributor Author

If we want to support the server-side rendered HTML use case, we'll need some way of annotating HTML without a <template>. So, in some ways, if we want to have a single syntax for defining DOM parts in HTML, we'll need to start with that more basic use case, rather than the <template> instantiation use case.

The DOM parts API is solving use cases outside template instantiation - I think the server-side hydration use case (locate some nodes in a big chunk of HTML if you know ahead of time which nodes you'll need) is really exciting, and can't be solved easily if we only have a <template> based syntax.

@rniwa
Copy link
Collaborator

rniwa commented Mar 21, 2023

I don't think we should come up with two completely different ways of solving the basically same problem. I would not be comfortable moving forward with just non-template case. Again, we might as well as not do the feature at all at that point.

@tbondwilkinson
Copy link
Contributor Author

I'm confident that if we focus on the use cases that we have in front of us and the requirements of the solution space, we can come up with a good solution that satisfies those. I don't think I'm anywhere near the "might as well not do the feature at all" decision yet :)

@bahrus
Copy link

bahrus commented Apr 20, 2023

I would like to submit for consideration the possibility of using empty template tags instead of processing instructions. I'm finding that the weight of the two seems to be about the same, and it could allow for easier analysis by developers and search engines.

Issue raised here: #999

Also, at least in some cases (maybe all), using counters to count nodes as way of grouping them, which might reduce the number of non visible tags by maybe 1/3. I can see, though, that this could cause problems if the DOM elements the template instantiation is managing, take it upon themselves to create siblings.

@bahrus
Copy link

bahrus commented Apr 20, 2023

I'm remembering more of my thinking now, when I did this (quite a while ago): https://github.com/bahrus/be-repeated#example-v----compatibility-with-server-rendered-lists

The count isn't really that critical for anything other than the last one. All the others, it's possible to know where it begins and ends, esssentially each inner empty template acts as a begin and an end. I think I was planning to put an end marker for the last one, but I can't recall if I did (I think I did, but never updated the documentation):

<ul>
    <li>Head Item</li>
    <template be-repeated='{
        "transform": {"li": "."},
        "list": {"observe": "obj-ml", "on": "value-changed", "vft": "value.prop1"},
        "deferRendering": true
    }'><li>...</li></template>
    <template data-cnt="2" data-idx="0"></template>
    <li>hello</li>
    <template data-cnt="2" data-idx="1"></template>
    <li>world</li>
    <li>Footer Item</li>
</ul>

The count is still useful to know if some element did create or remove one or more siblings, in which I case I reran the transform. Something like that.

@justinfagnani
Copy link
Contributor

Using templates doesn't make semantic sense to me, practically doesn't work because they don't render their text contents, affects the DOM more than comments (they are child elements), and likely have a negative performance cost because each creates a DocumentFragment.

@bahrus
Copy link

bahrus commented Apr 21, 2023

Ok, thanks for considering. I agree the data element would make much more sense semantically, but there are issues using it. I'm not seeing the practical difference between empty templates and comments. The empty templates could, I think, serve the same purpose as the processing instructions (but I guess I'm not understanding how they are to be used, then).

But anyway your assurances on performance give me comfort we aren't making a mistake. I've been led to believe it's always best to test performance, not rely on intuition. Your explanation makes sense and matches my intuition. I just wasn't able to measure it, which is why I brought this up.

@keithamus
Copy link
Collaborator

keithamus commented Apr 21, 2023

WCCG had their spring F2F in which this was discussed. Present members of WCCG identified an action item to take the topic of DOM Parts and break it out into extended discussions. You can read the full notes of the discussion (#978 (comment)) in which this was discussed, heading entitled "DOM Parts API".

As this issue pertains to DOM parts, I'd like to call out that #999 has been raised for extended discussions and this topic may be discussed during those sessions.

@shgysk8zer0
Copy link

Would these be Node.PROCESSING_INSTRUCTION_NODEs?

I'm not particularly fond of borrowing from XML in syntax that looks like a blend between PHP (<?=...?>) and custom elements (<my-tag>). Could this not be done without introducing a new node type to HTML that resembles custom elements?

My (admittedly ignorant) instinct is to have a set of attributes (I know, part is taken) where a patent element has an attribute that lists which parts it contains and the children use a corresponding attribute to declare which part they fulfill. This wouldn't work for text or attribute nodes, but it would be more familiar and pre-existing syntax that doesn't require a new node type.

Using attributes instead of a new node type would also make this much easier/possible to polyfill (also a benefit of an attribute on the parent element since it'd be more efficient for querying).

If not, I suggest just adding processing instruction nodes to HTML as a separate proposal first. Introduce the new node type before some specific processing instructions.

Just my two cents on the matter. I'm not going to pretend like I know the HTML parser in nearly enough detail to know if a new node type is better than attributes here. I'm just thinking about this as someone who writes some polyfills and who recognizes the barrier that introducing a new node type might add compatibility issues.

@bahrus
Copy link

bahrus commented Jun 10, 2023

I'm not particularly fond of borrowing from XML in syntax that looks like a blend between PHP () and custom elements (). Could this not be done without introducing a new node type to HTML that resembles custom elements?

I agree 100%. In a previous comment, a supporter of this proposal raised an objection to a counter suggestion I made, because my suggestion didn't make semantic sense. I agree, and have effectively withdrawn my counter proposal for that reason. But it was kind of the pot calling the kettle black. This is precisely why using processing instructions seems like a bad idea -- it is clearly distorting the semantic purpose of what processing instructions are for, where the "target" is supposed to represent a target application that the processing instruction is for. It makes sense for php, because php is an application.

If there is a performance benefit to the server identifying parts via markers, before downloading the template which would also indicate where the parts are, there are so many existing semantically meaningful markers we could choose from, including microdata and aria- attributes, such as aria-rowindex, aria-owns, etc. If there is a scenario where these fall short, it seems like a wasted opportunity not to work on enhancing these existing vocabularies.

Perhaps a middle ground would be this:

We support a name for a meta tag that could go in the header of the HTML document, or in an HTTP header, that specifies what markers to look for that indicates parts. The content could include a list of xpath and/or css matches to search for.

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

No branches or pull requests

6 participants