DOM Directive Reform

Summary

This RFC describes the formalization of the lwc:dom directive in LWC Templates. Specifically, it defines how to lift existing restrictions on lwc:dom="manual" and introduces lwc:dom="synthetic-portal" as a way to support legacy systems.

Quick recap on the lwc:dom="manual" directive

This directive, which is LWC specific (hence the prefix), is intended to help with the following issues:

These are the main three reasons why this directive was introduced. The determinism is enforce by throwing an error if a DOM node is inserted or removed from an element produced by a LWC Template, this is a dev-mode only enforcement.

Additionally, as a precaution, we decided to support lwc:dom="manual" in leaf elements only, and do not support it at all in custom elements or slot elements, and reevaluate if we could lift part of that restriction in the future.

Motivation

There are 3 main motivations for this reform:

  1. The existing restriction that only leaf elements can be marked with lwc:dom="manual" is limiting. There are valid use-cases that requires a mix of elements generated from a template plus manually inserted elements when the template syntax falls short.

  2. Legacy systems, specifically flexipages in Aura applications, are in need to support rendering legacy DOM structures that are not suitable, or were not designed, to work inside a ShadowRoot instance. In such cases, it is imperative that from the perspective of those elements, they should behave as inserted in the document.

  3. The formalization of this feature can lead to performance improvements, specifically, the considerations with respect to what elements need to be keyed, and which one can be safely ignored.

Basic example

This RFC lift some restrictions on the existing implementation and introduces the concept of synthetic-portal:

<template>

    <h1>Existing feature (for leaf element only):</h1>
    <div lwc:dom="manual"></div>

    <h1>Lifting restrictions on existing feature:</h1>
    <div lwc:dom="manual">
        <p>can have child elements in template to be combined with manually inserted nodes</p>
    </div>

    <h1>Introducing new feature (for leaf element only):</h1>
    <div lwc:dom="synthetic-portal"></div>

</template>

Why do we need to lift restrictions for lwc:dom="manual"?

One of uses cases is the use of a graph library that can connect elements (visually) where the nodes in the graph are controlled via the template declarative mechanism, while the edges are manually generated and inserted by a 3rd party library.

Note: the restriction about custom elements stays the same, this directive cannot be used in <slot> elements or custom elements.

Why do we need lwc:dom="synthetic-portal"?

It turns out that in Aura platform, we have many Aura components that were designed to work inside an Aura flexipage, which means they can make assumptions about the page, about the structure of the page. Many of those assumptions will not work if they are ever used inside a LWC flexipage (we call this RRH - raptorized record home). This happens to be a very important piece of the salesforce platform. Additionally, any library or legacy system that assumes a regular dom structure without shadow dom boundaries will suffer similar consequences.

Until today, we have ignored this problem, but now that LWC Flexipages are here, we need to have a consistent solution that works seamless. Until now, the LWC Flexipage will insert manual elements inside the container without specifying lwc:dom="manual", due to the implications of using that, which makes the inserted node to recognize the shadow boundary, which is not the intention. This works somehow, but introduces a bigger problem, the problem that LWC doesn't know what's going on there, and it is hard for LWC Team to predict the impact of changes in LWC and Synthetic Shadow. It also makes LWC Flexibpage an offender in dev-mode, reporting invalid insertion of nodes on an element generated from a template.

We are proposing to solve this problem by providing the correct hint via the template directive lwc:dom="synthetic-portal", which could be used by LWC to support the interactions with the portal. At the same time, it can be used by Synthetic Shadow polyfill to implement the proper semantics for the portal.

lwc:dom="manual" vs lwc:dom="synthetic-portal"

When using lwc:dom="manual":

When using lwc:dom="synthetic-portal":

Detailed design

Detailed design about lifting restriction for leaf elements only for lwc:dom="manual" directive

TBD

Detailed design of lwc:dom="synthetic-shadow" directive

TBD

Performance optimizations

Considering that lwc:dom is only useful when running in synthetic shadow, it is a no-op on native shadow, which means no perf penalty. However, when in synthetic shadow, we must take into consideration few things:

It seems that a Element.matches (https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) could provide a boost on the resolution time when analyzing an element. For example, if portal elements are decorated with a magic attributes, and host elements are decorated with another magic attribute, we could check, at any given time, if a element is within a portal directly, or indirectly, which is sufficient information to make a determination of what to do at any given time.

The cost of adding the magic attributes is almost depreciable since it happens once before inserting the custom elements and the portal elements into the DOM. No MO is needed for portals anymore because we can make determinations at any given time by matching the element, which crosses the bridge to C layer once and it is sufficiently optimized and cached in modern browsers.

Example:

For the following markup:

<body>
    <div>
        <x-foo lwc:host="synthetic-shadow">
            #shadowRoot
                <div lwc:dom="manual"> (portal)
                    <p class="first"> (manually inserted element)
                    <x-bar lwc:host="synthetic-shadow">
                        #shadowRoot
                            <p class="second">

At any given time, you can check if p qualifies as shadowed or not by running simple query matches:

Note: similar performance optimization can be applied to many other areas of the synthetic shadow patches.

Drawbacks

Why should we not do this? Please consider:

Alternatives

No apparent alternative has been identified.

Adoption strategy

The changes described in this RFC are backward compatible.

How we teach this

The restriction of lwc:dom="manual" that will be lifted do not require a change in the mental model on how this feature is used. No side-effects were identified when combining declarative and manual elements. The only thing needed is to update the documentation to remove the restriction, and maybe adding an example that showcases the combination of declarative and manual elements.

On the other hand, the lwc:dom="synthetic-portal" must be documented, and probably gated.

Unresolved questions

undefined