Repository: ProjectEvergreen/wcc (js)

Issue:
support `innerHTML` in compiler for custom elements
## Type of Change

<!-- Delete what does not apply -->

- New Feature Request

## Summary
Although I did introduce a [`lightMode` configuration option](https://merry-caramel-524e61.netlify.app/docs/) for escaping the Shadow DOM `<template shadowroot="open">`, like for more "static" scenarios (e.g. documentation site), I've been thinking more about this implementation detail.  

Specifically, I just realized now a better way to support this from userland would be to just let users pick which one works best for them?  So if you want to just ship static content, use `innerHTML`, if it is a more interactive scenario, and say you plan to use hydration, then use Shadow DOM.

## Details
This is important because browsers don't render a `<template>` tag, so anything inside them, like text content, will not be rendered without either using a browser that supports Declarative Shadow DOM, or by adding the polyfill.

This could also help play nicely with global CSS libraries like Tailwinds.

Will need to consider:
- If `lightMode` makes sense anymore?
- Document that without Shadow DOM, you can't use `:host` and `<slot>` etc.
- Is #16 relavant?

Pull request:
Feature/issue 49 inner html
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #49 

## Summary of Changes
1. Added support for Light DOM (`innerHTML`) vs Shadow DOM (`shadowRoot.innerHTML`) rendering
1. Added test case

## TODO
1. [x] Does this remove the need for `lightMode` config (breaking change)? - https://github.com/thescientist13/wcc/issues/52
1. [x] Validate `template.cloneNode` approach for navigation component in test for browser / standards compliance - https://github.com/thescientist13/wcc/issues/51
    - looks like I may have it implemented incorrectly per - https://github.com/thescientist13/wcc/pull/50#issuecomment-1146860282
    - need to create a bug issue to track?
1. [x] documentation (maybe better as an example?) - added to Go Live checklist - https://github.com/thescientist13/wcc/issues/17

Patch:
diff --git a/src/wcc.js b/src/wcc.js
index cbed750..cc49905 100644
--- a/src/wcc.js
+++ b/src/wcc.js
@@ -29,11 +29,14 @@ async function renderComponentRoots(tree, includeShadowRoots = true) {
       const { tagName } = node;
       const { moduleURL } = definitions[tagName];
       const elementInstance = await initializeCustomElement(moduleURL, tagName, node.attrs);
-
-      const shadowRootHtml = elementInstance.getInnerHTML({ includeShadowRoots });
-      const shadowRootTree = parseFragment(shadowRootHtml);
-
-      node.childNodes = node.childNodes.length === 0 ? shadowRootTree.childNodes : [...shadowRootTree.childNodes, ...node.childNodes];
+      const elementHtml = elementInstance.shadowRoot
+        ? elementInstance.getInnerHTML({ includeShadowRoots })
+        : elementInstance.innerHTML;
+      const elementTree = parseFragment(elementHtml);
+      
+      node.childNodes = node.childNodes.length === 0 
+        ? elementTree.childNodes
+        : [...elementTree.childNodes, ...node.childNodes];
     }
 
     if (node.childNodes && node.childNodes.length > 0) {
@@ -130,7 +133,9 @@ async function renderToString(elementURL, options = {}) {
   const elementTagName = await getTagName(elementURL);
   const elementInstance = await initializeCustomElement(elementURL);
 
-  const elementHtml = elementInstance.getInnerHTML({ includeShadowRoots });
+  const elementHtml = elementInstance.shadowRoot
+    ? elementInstance.getInnerHTML({ includeShadowRoots })
+    : elementInstance.innerHTML;
   const elementTree = getParse(elementHtml)(elementHtml);
   const finalTree = await renderComponentRoots(elementTree, includeShadowRoots);
   const html = !lightMode && elementTagName ? `


Test patch:
diff --git a/test/cases/light-dom/light-dom.spec.js b/test/cases/light-dom/light-dom.spec.js
new file mode 100644
index 0000000..7f0a9cf
--- /dev/null
+++ b/test/cases/light-dom/light-dom.spec.js
@@ -0,0 +1,79 @@
+/*
+ * Use Case
+ * Run wcc against nested custom elements using just innerHTML to intentionally NOT render Shadow DOM.
+ *
+ * User Result
+ * Should return the expected HTML with no template tags or Shadow Roots.
+ *
+ * User Workspace
+ * src/
+ *   components/
+ *     navigation.js
+ *     header.js
+ *   pages/
+ *     index.js
+ */
+import chai from 'chai';
+import { JSDOM } from 'jsdom';
+import { renderToString } from '../../../src/wcc.js';
+
+const expect = chai.expect;
+
+describe('Run WCC For ', function() {
+  const LABEL = 'Nested Custom Element using only innerHTML (no Shadow DOM)';
+  let dom;
+
+  before(async function() {
+    const { html } = await renderToString(new URL('./src/pages/index.js', import.meta.url));
+
+    dom = new JSDOM(html);
+  });
+
+  describe(LABEL, function() {
+    it('should not have any <template> tags within the document', function() {
+      expect(dom.window.document.querySelectorAll('template').length).to.equal(0);
+    });
+
+    describe('static page content', function() {
+      it('should have the expected static content for the page', function() {
+        expect(dom.window.document.querySelector('h1').textContent).to.equal('Home Page');
+      });
+    });
+
+    describe('custom header element with nested navigation element', function() {
+      let headerContentsDom;
+
+      before(function() {
+        headerContentsDom = new JSDOM(dom.window.document.querySelectorAll('header')[0].innerHTML);
+      });
+
+      it('should have a <header> tag within the document', function() {
+        expect(dom.window.document.querySelectorAll('header').length).to.equal(1);
+      });
+
+      it('should have expected content within the <header> tag', function() {
+        const content = headerContentsDom.window.document.querySelector('a h4').textContent;
+  
+        expect(content).to.contain('My Personal Blog');
+      });
+
+      describe('nested navigation element', function() {
+        let navigationContentsDom;
+  
+        before(function() {
+          navigationContentsDom = new JSDOM(dom.window.document.querySelectorAll('wcc-navigation')[0].innerHTML);
+        });
+  
+        it('should have a <nav> tag within the <template> shadowroot', function() {
+          expect(navigationContentsDom.window.document.querySelectorAll('nav').length).to.equal(1);
+        });
+  
+        it('should have three links within the <nav> element', function() {
+          const links = navigationContentsDom.window.document.querySelectorAll('nav ul li a');
+    
+          expect(links.length).to.equal(3);
+        });
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/test/cases/light-dom/src/components/header.js b/test/cases/light-dom/src/components/header.js
new file mode 100644
index 0000000..6cf8811
--- /dev/null
+++ b/test/cases/light-dom/src/components/header.js
@@ -0,0 +1,37 @@
+import './navigation.js';
+
+class Header extends HTMLElement {
+  connectedCallback() {
+    this.innerHTML = this.render();
+  }
+
+  render() {
+    return `
+      <header>
+        <div>
+          <a href="/">
+            <img src="/www/assets/greenwood-logo.jpg" alt="Greenwood logo"/>
+            <h4>My Personal Blog</h4>
+          </a>
+        </div>
+
+        <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>
+      </header>
+    `;
+  }
+}
+
+export {
+  Header
+};
+
+customElements.define('wcc-header', Header);
\ No newline at end of file
diff --git a/test/cases/light-dom/src/components/navigation.js b/test/cases/light-dom/src/components/navigation.js
new file mode 100644
index 0000000..2ff6aa4
--- /dev/null
+++ b/test/cases/light-dom/src/components/navigation.js
@@ -0,0 +1,23 @@
+const template = document.createElement('template');
+
+template.innerHTML = `
+  <nav>
+    <ul>
+      <li><a href="/">Home</a></li>
+      <li><a href="/about">About</a></li>
+      <li><a href="/artists">Artists</a></li>
+    <ul>
+  </nav>
+`;
+
+class Navigation extends HTMLElement {
+  connectedCallback() {
+    this.appendChild(template.content.cloneNode(true));
+  }
+}
+
+export {
+  Navigation
+};
+
+customElements.define('wcc-navigation', Navigation);
\ No newline at end of file
diff --git a/test/cases/light-dom/src/pages/index.js b/test/cases/light-dom/src/pages/index.js
new file mode 100644
index 0000000..f1c52bb
--- /dev/null
+++ b/test/cases/light-dom/src/pages/index.js
@@ -0,0 +1,16 @@
+import '../components/header.js';
+
+export default class HomePage extends HTMLElement {
+
+  connectedCallback() {
+    this.innerHTML = this.getTemplate();
+  }
+
+  getTemplate() {
+    return `
+      <wcc-header></wcc-header>
+
+      <h1>Home Page</h1>
+    `;
+  }
+}
\ No newline at end of file