Repository: ProjectEvergreen/wcc
Pull request #171 (2024-11-05 12:02:03)

feature/issue 177 Replaced deprecated method getInnerHTML with getHTML
## Summary of Changes
- replaced deprecated method getInnerHTML with getHTML and made this return the HTML wrapped in a DSD template when the serializableShadowRoots option is set to true, which is according to the specs
- removed adding `<template shadowrootmode="open">` when `innerHTML` of `<template>` is set since this will now correctly be added when the `getHTML` method of the element is called with the option `{ serializableShadowRoots: true }`
- updated tests
- added missing closing square bracket in selector: `this.shadowRoot.querySelector('script[type="application/json"')`

This change makes sure that a web component that has its Shadow DOM set through innerHTML instead of a template like this:

```
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `...`;
```

can also be server-side rendered.

DannyMoerkerke: Sure Owen, happy to chat further! Let’s schedule something at your convenience 
thescientist13: Hey @DannyMoerkerke , let me know if you're able to make the docs update, otherwise happy to take it on in a couple days. 

Just want to help land this so the next couple PRs in the queue can start building off this 👍 
DannyMoerkerke: @thescientist13 sure, we're talking about https://merry-caramel-524e61.netlify.app/examples/#serverless-and-edge-functions right?
thescientist13: @DannyMoerkerke 
Yup!  I think that's the only one I found but a simple find / replace for `getInnerHTML` -> `getHTML` should catch any I may have missed.  🔍 
DannyMoerkerke: I’ll go over all the docs. 

Patch:
diff --git a/docs/pages/examples.md b/docs/pages/examples.md
index 7c6a371..605d208 100644
--- a/docs/pages/examples.md
+++ b/docs/pages/examples.md
@@ -53,7 +53,7 @@ export default async function (request, context) {
     <html lang="en">
       <body>
         <wc-greeting>
-          ${greeting.getInnerHTML({ includeShadowRoots: true })}
+          ${greeting.getHTML({ serializableShadowRoots: true })}
           <details slot="details">
             <pre>
               ${JSON.stringify(context.geo)}
diff --git a/src/dom-shim.js b/src/dom-shim.js
index be289a3..cbf5398 100644
--- a/src/dom-shim.js
+++ b/src/dom-shim.js
@@ -45,10 +45,9 @@ class Element extends Node {
     return this.shadowRoot;
   }
 
-  // https://github.com/mfreed7/declarative-shadow-dom/blob/master/README.md#serialization
-  // eslint-disable-next-line
-  getInnerHTML() {
-    return this.shadowRoot ? this.shadowRoot.innerHTML : this.innerHTML;
+  getHTML({ serializableShadowRoots = false }) {
+    return this.shadowRoot && serializableShadowRoots ?
+      `<template shadowrootmode="open">${this.shadowRoot.innerHTML}</template>` : this.innerHTML;
   }
 
   setAttribute(name, value) {
@@ -116,11 +115,7 @@ class HTMLTemplateElement extends HTMLElement {
   // TODO open vs closed shadow root
   set innerHTML(html) {
     if (this.content) {
-      this.content.innerHTML = `
-        <template shadowrootmode="open">
-          ${html}
-        </template>
-      `;
+      this.content.innerHTML = html;
     }
   }
 
diff --git a/src/wcc.js b/src/wcc.js
index 68c7a2e..c687239 100644
--- a/src/wcc.js
+++ b/src/wcc.js
@@ -54,7 +54,7 @@ async function renderComponentRoots(tree, definitions) {
         if (elementInstance) {
           const hasShadow = elementInstance.shadowRoot;
           const elementHtml = hasShadow
-            ? elementInstance.getInnerHTML({ includeShadowRoots: true })
+            ? elementInstance.getHTML({ serializableShadowRoots: true })
             : elementInstance.innerHTML;
           const elementTree = parseFragment(elementHtml);
           const hasLight = elementTree.childNodes > 0;
@@ -222,14 +222,14 @@ async function initializeCustomElement(elementURL, tagName, node = {}, definitio
 
     attrs.forEach((attr) => {
       elementInstance.setAttribute(attr.name, attr.value);
-  
+
       if (attr.name === 'hydrate') {
         definitions[tagName].hydrate = attr.value;
       }
     });
-  
+
     await elementInstance.connectedCallback();
-  
+
     return elementInstance;
   }
 }
@@ -244,7 +244,7 @@ async function renderToString(elementURL, wrappingEntryTag = true, props = {}) {
   // in case the entry point isn't valid
   if (elementInstance) {
     const elementHtml = elementInstance.shadowRoot
-      ? elementInstance.getInnerHTML({ includeShadowRoots: true })
+      ? elementInstance.getHTML({ serializableShadowRoots: true })
       : elementInstance.innerHTML;
     const elementTree = getParse(elementHtml)(elementHtml);
     const finalTree = await renderComponentRoots(elementTree, definitions);


Test patch:
diff --git a/test/cases/attributes/src/components/counter.js b/test/cases/attributes/src/components/counter.js
index 753fe99..4c8e5eb 100644
--- a/test/cases/attributes/src/components/counter.js
+++ b/test/cases/attributes/src/components/counter.js
@@ -39,7 +39,7 @@ class Counter extends HTMLElement {
   }
 
   hydrate() {
-    this.count = parseInt(JSON.parse(this.shadowRoot.querySelector('script[type="application/json"').text).count, 10);
+    this.count = parseInt(JSON.parse(this.shadowRoot.querySelector('script[type="application/json"]').text).count, 10);
 
     const buttonDec = this.shadowRoot.querySelector('button#dec');
     const buttonInc = this.shadowRoot.querySelector('button#inc');
@@ -54,13 +54,11 @@ class Counter extends HTMLElement {
 
   render() {
     return `
-      <template shadowrootmode="open">
         <div>
           <button id="inc">Increment</button>
           <span>Current Count: <span id="count">${this.#count}</span></span>
           <button id="dec">Decrement</button>
         </div>
-      </template>
     `;
   }
 }
diff --git a/test/cases/get-data/src/components/counter.js b/test/cases/get-data/src/components/counter.js
index 3aa23b1..c30aa9c 100644
--- a/test/cases/get-data/src/components/counter.js
+++ b/test/cases/get-data/src/components/counter.js
@@ -37,7 +37,7 @@ class Counter extends HTMLElement {
 
   hydrate() {
     console.debug('COUNTER => hydrate');
-    this.count = parseInt(JSON.parse(this.shadowRoot.querySelector('script[type="application/json"').text).count, 10);
+    this.count = parseInt(JSON.parse(this.shadowRoot.querySelector('script[type="application/json"]').text).count, 10);
 
     const buttonDec = this.shadowRoot.querySelector('button#dec');
     const buttonInc = this.shadowRoot.querySelector('button#inc');
@@ -52,17 +52,15 @@ class Counter extends HTMLElement {
 
   render() {
     return `
-      <template shadowrootmode="open">
-        <script type="application/json">
-          ${JSON.stringify({ count: this.count })}
-        </script>
+      <script type="application/json">
+        ${JSON.stringify({ count: this.count })}
+      </script>
 
-        <div>
-          <button id="inc">Increment</button>
-          <span>Current Count: <span id="count">${this.count}</span></span>
-          <button id="dec">Decrement</button>
-        </div>
-      </template>
+      <div>
+        <button id="inc">Increment</button>
+        <span>Current Count: <span id="count">${this.count}</span></span>
+        <button id="dec">Decrement</button>
+      </div>
     `;
   }
 }
diff --git a/test/cases/nested-elements/src/components/header.js b/test/cases/nested-elements/src/components/header.js
index 92f0fe2..927b583 100644
--- a/test/cases/nested-elements/src/components/header.js
+++ b/test/cases/nested-elements/src/components/header.js
@@ -11,29 +11,27 @@ class Header extends HTMLElement {
 
   render() {
     return `
-      <template shadowrootmode="open">
-        <header class="header">
-          <div class="head-wrap">
-            <div class="brand">
-              <a href="/">
-                <img src="/www/assets/greenwood-logo.jpg" alt="Greenwood logo"/>
-                <h4>My Personal Blog</h4>
-              </a>
-            </div>
+      <header class="header">
+        <div class="head-wrap">
+          <div class="brand">
+            <a href="/">
+              <img src="/www/assets/greenwood-logo.jpg" alt="Greenwood logo"/>
+              <h4>My Personal Blog</h4>
+            </a>
+          </div>
 
-            <wcc-navigation></wcc-navigation>
+          <wcc-navigation></wcc-navigation>
 
-            <div class="social">
-              <a href="https://github.com/ProjectEvergreen/greenwood">
-                <img
-                  src="https://img.shields.io/github/stars/ProjectEvergreen/greenwood.svg?style=social&logo=github&label=github"
-                  alt="Greenwood GitHub badge"
-                  class="github-badge"/>
-              </a>
-            </div>
+          <div class="social">
+            <a href="https://github.com/ProjectEvergreen/greenwood">
+              <img
+                src="https://img.shields.io/github/stars/ProjectEvergreen/greenwood.svg?style=social&logo=github&label=github"
+                alt="Greenwood GitHub badge"
+                class="github-badge"/>
+            </a>
           </div>
-        </header>
-      </template>
+        </div>
+      </header>
     `;
   }
 }