Skip to content

Conversation

@frehner
Copy link
Contributor

@frehner frehner commented Oct 8, 2025

Description

Updated examples to use document.importNode() exclusively, and references to refer to both importNode and Node.cloneNode.

Motivation

document.importNode and Node.cloneNode are largely similar, but have one important difference; the timing of when the node is adopted. When using Node.cloneNode(), the content isn't adopted until it's either explicitly adopted or when it's appended to the document, so at a later point after calling cloneNode(). This falls into an edge-case annoyance where calling upgrade on nodes that aren't yet adopted causes them to not actually get upgraded. This real example from the Web Components discord demonstrates the edge-case:

class MyEl extends HTMLElement {
  constructor() {super(); console.log('MyEl constructor')}
}
customElements.define('my-el', MyEl)

const template = document.createElement('template')
template.innerHTML = '<my-el></my-el>'

const frag = template.content.cloneNode(true)

console.log(customElements.get('my-el')) // MyEl

customElements.upgrade(frag) // crickets
console.log('My el constructor should have ran already')

document.body.append(frag) // logs "MyEl constructor"

If you change from content.cloneNode() to document.importNode() then the constructor will run and the content is upgraded correctly.

So we would like to update the examples to show document.importNode() being used by default, and also update references/links to show it as an option next to cloneNode.

Additional details

Web components discord discussion https://discord.com/channels/767813449048260658/767813449048260661/1421037073829199994

Related issues and pull requests

@frehner frehner requested review from a team as code owners October 8, 2025 18:55
@frehner frehner requested review from estelle and wbamberg and removed request for a team October 8, 2025 18:55
@github-actions github-actions bot added Content:HTML Hypertext Markup Language docs Content:WebAPI Web API docs size/s [PR only] 6-50 LoC changed labels Oct 8, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 8, 2025

Preview URLs (13 pages)
Flaws (3)

Note! 12 documents with no flaws that don't need to be listed. 🎉

URL: /en-US/docs/Web/API/Web_components/Using_templates_and_slots
Title: Using templates and slots
Flaw count: 3

  • macros:
    • Macro produces link /en-US/docs/Web/Web_Components/Using_custom_elements which is a redirect
    • Macro produces link /en-US/docs/Web/Web_Components/Using_shadow_DOM which is a redirect
    • Macro produces link /en-US/docs/Web/Web_Components/Using_templates_and_slots which is a redirect

(comment last updated: 2025-11-25 05:08:09)

@estelle estelle removed their request for review October 14, 2025 09:32
@Josh-Cena
Copy link
Member

If this is important, could you add a sentence to cloneNode (and importNode)?

@frehner
Copy link
Contributor Author

frehner commented Oct 20, 2025

If this is important, could you add a sentence to cloneNode (and importNode)?

How does that look?

@Josh-Cena
Copy link
Member

If your argument for using importNode is for upgrade, why not point that out directly? There are significant use cases of <template> that aren't related to custom elements at all, and even some that do, do not suffer from this issue if the <template> is just used within the custom element constructor to populate the content—as the majority of our examples do. So it's not totally clear to me how big the impact is. I searched for .cloneNode(true) and we have a lot more usages with template.content; should all of them be replaced as well?

@frehner
Copy link
Contributor Author

frehner commented Oct 21, 2025

If your argument for using importNode is for upgrade, why not point that out directly? There are significant use cases of <template> that aren't related to custom elements at all, and even some that do, do not suffer from this issue if the <template> is just used within the custom element constructor to populate the content—as the majority of our examples do. So it's not totally clear to me how big the impact is. I searched for .cloneNode(true) and we have a lot more usages with template.content; should all of them be replaced as well?

I’m under the impression from the community that yes, we would like all these updated and that importNode should be the default.

I’ll see if I can get some people to chime in here.

@wbamberg wbamberg requested review from Josh-Cena and removed request for wbamberg November 6, 2025 04:08
@Josh-Cena
Copy link
Member

Hi @frehner, any updates? Do you want to propose an updated version first so we can get other people to look at it? Perhaps someone from WHATWG?

@bunyarit1980panpa-wq

This comment was marked as spam.

@justinfagnani
Copy link
Contributor

I think this is an important change to make.

If you don't know exactly what's in your template (like many libraries that accept or produce user-defined templates), then importNode() is nearly always the better API to use since custom elements will be upgraded.

@sorvell
Copy link

sorvell commented Nov 20, 2025

I would also suggest clearly explaining that the nodes in content, the template document fragment, exist in a separate document, not the main document. This helps explain why it's typically desirable to copy them with importNode rather than cloneNode.

Otherwise you get fairly unintuitive behavior that could manifest in odd ways. This could also be addressed in the pitfalls section.

const clone = template.content.cloneNode(true);
console.assert(clone.firstElementChild.ownerDocument === document) // fails.
const imported = document.importNode(template.content, true);
console.assert(imported.firstElementChild.ownerDocument === document) // works.

@Josh-Cena
Copy link
Member

Thanks, both. Would you agree to globally replace cloneNode with importNode? Do you have specific wording suggestions for the warning?

@Josh-Cena Josh-Cena requested review from a team as code owners November 24, 2025 21:20
@Josh-Cena Josh-Cena requested review from estelle and removed request for a team November 24, 2025 21:20
Copy link
Member

@Josh-Cena Josh-Cena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting until the note gets rewritten

@github-actions github-actions bot added size/m [PR only] 51-500 LoC changed and removed size/s [PR only] 6-50 LoC changed labels Nov 25, 2025
@Josh-Cena
Copy link
Member

Hi @frehner @justinfagnani @sorvell I've committed an updated version of the note:

The {{domxref("Node.cloneNode()")}} and {{domxref("Document.importNode()")}} methods both create a copy of a node. The difference is that importNode() clones the node in the context of the calling document, whereas cloneNode() uses the document of the node being cloned. The document context determines the {{domxref("CustomElementRegistry")}} for constructing any custom elements. For this reason, use document.importNode() to clone the content fragment so that custom element descendants are constructed using the definitions in the current document, rather than the separate document that owns the template content. See the {{domxref("Node.cloneNode()")}} page's examples for more details.

WDYT?


const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(templateContent.cloneNode(true));
shadowRoot.appendChild(document.importNode(templateContent, true));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For cases like this, I think it's actually fine to use cloneNode because it's immediately appended, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be fine, but IMO it's a lot easier to be consistent and use importNode everywhere.

@Josh-Cena Josh-Cena requested a review from a team as a code owner November 25, 2025 05:06
@github-actions github-actions bot added the Content:CSS Cascading Style Sheets docs label Nov 25, 2025
@Josh-Cena
Copy link
Member

I've done a global replacement for all cloneNode(true) calls on template.content.

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

Labels

Content:CSS Cascading Style Sheets docs Content:HTML Hypertext Markup Language docs Content:WebAPI Web API docs size/m [PR only] 51-500 LoC changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants