Skip to content

Should parse5 support each template insertion targeting behavior (e.g. declarative shadow dom, template for), or just a general mechanism which tree adapters hook into? #1773

@Jamesernator

Description

@Jamesernator

So I have an incomplete PR for declarative shadow DOM support in parse5, there is now a new proposed API with implementation in Chrome beta for <template for> that similarly has behavior wherein the template gets removed and the contents get put somewhere else during parsing.

I do have interest in implementing support for some of these proposals still, but I would like to ask what the general approach that would be preferable for parse5 going forward with such proposals. In particular, should we implement the template behavior as spec-ed which mean whenever new parts of these proposals are added we have to keep changing the parse5 behavior.

Alternatively would it be preferable instead to just implement the necessary hooks for changing the insertion location (and adding additional children for serialization)? Whoever implements the tree adapter would be responsible for detecting such templates and changing the parsing behaviour themselves, but it does mean that there is no need to wait for parse5 to support future proposals to be able to implement them.

Or possibly some combination of the above? (e.g. Supporting declarative shadow roots directly, while also allowing general hooks for changing the insertion target)

cc @43081j


Just to show a difference, in my current incomplete PR, declarative shadow dom would be supported essentially just by providing the appropriate tree adapter methods:

class MyTreeAdapter {
    // ...rest of tree adapter

    // Called when a <template shadowrootmode=""> is encountered
    createDeclarativeShadowRoot(
        element: Element,
        shadowRootInit: { ...other shadowroot attrs },
    ): ShadowRoot | null {}

    // Called during serialization so that a <template shadowrootmode=""> can be created if appropriate
    getShadowRoot(element: Element): ShadowRoot | null {}

    // Called to check if shadow root mode is serializable
    getShadowRootInit(shadowRoot: ShadowRoot): { serializable: boolean, ...other shadowroot attrs } {}
}

In the alternative approach, instead we would leave the tree adapter to read the attributes and deal with creating shadow roots / insertion targets themselves:

class TemplateElement extends Element {
    _insertionTarget: ParentNode | null;
    _insertionStartMarker: ChildNode | null;
    _insertionEndMarker: ChildNode | null;
}

class MyTreeAdapter {
    // Gets called after createElement, if the element has an insertion target then parse5 will insert
    // nodes there instead
    getInsertionTarget(template: TemplateElement): { target: ParentNode | null, startMarker: ChildNode | null, endMarker: ChildNode | null } | null {
        // get targets from template 
    }

    #createTemplateElement(attrs: Attr[], parentNode: ParentNode | null): TemplateElement {
        const template = new TemplateElement();
        if (attrs.some(attr => attr.name === "for")) {
            // find relevant markers, and set _insertion{Target,StartMarker,
        } else if (attrs.some(attr => attr.name === "shadowrootmode")) {
            // if parentNode is an element, create a shadow root using other shadowroot attrs
            // and set _insertionTarget to the shadow root
        }
        return template;
    }

    // We get a new parentNode argument to createElement so that we can get the element to attach
    // declarative shadow roots to
    createElement(elementName: string, namespace: NS, attrs: Attr[], parentNode: ParentNode | null) {
        if (elementName === "template" && namespace === HTML_NS) {
            return this.#createTemplateElement(attrs);
        }
        // create element normally
    }

    // Primarily for serializing declarative shadow roots
    // but flexible if declarative templates or anything adds
    // additional markers or the like
    getExtraPrependedChildren(element: Element): ChildNode[] {
        if (element._shadowRoot) {
            const template = new TemplateElement();
            template.setAttribute('shadowrootmode', element._shadowRoot.mode);
            // ...other shadow root attrs;
            return [template];
        }
        return [];
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions