Repository: ProjectEvergreen/wcc (js)

Issue:
mark entry point custom element when returning `metadata`
## Type of Change

<!-- Delete what does not apply -->

- New Feature Request

## Summary
It would be great for returned metadata to mark the initial custom element as the entry point, which can be helpful for more [app like usages of WCC](https://github.com/ProjectEvergreen/wcc/pull/73/files#diff-ace925ac48f91c9439187a9ba10fb1e60aebad19e14519a42d745d14d0651438R35).

## Details
For example
```js
const { metadata } = await renderToString(new URL('./src/pages/home.js', import.meta.url));

/*
 * {
 *   metadata: [
 *     'wcc-home': { instanceName: 'HomePage', moduleURL: [URL], entry: true },
 *     'wcc-footer': { instanceName: 'Footer', moduleURL: [URL] },
 *     'wcc-header': { instanceName: 'Header', moduleURL: [URL] },
 *     'wcc-navigation': { instanceName: 'Navigation', moduleURL: [URL] }
 *   ]
 * }
 *
```

However, if there is no `tagName` provided, like for a layout route or page, the next level of custom elements should be used instead.

Pull request:
Feature/issue 79 mark entry points in metadata graph
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #79 

## Summary of Changes
1. Mark "entry" points in `metadata` results
1. Add documentation and expand example

Patch:
diff --git a/docs/pages/docs.md b/docs/pages/docs.md
index 980f16f..13eee6b 100644
--- a/docs/pages/docs.md
+++ b/docs/pages/docs.md
@@ -79,6 +79,20 @@ For example, even if `Header` or `Footer` use `import` to pull in additional cus
 
 `renderToString` and `renderFromHTML` return not only HTML, but also metadata about all the custom elements registered as part of rendering the entry file.
 
+So for the given HTML:
+```html
+<wcc-header></wcc-header>
+
+<h1>Hello World</h1>
+
+<wcc-footer></wcc-footer>
+```
+
+And the following conditions:
+1. _index.js_ does not define a tag of its own, e.g. using `customElements.define` (e.g. it is just a ["layout" component](/examples/#static-sites-ssg))
+1. `<wcc-header>` imports `<wcc-navigation>`
+
+The result would be:
 ```js
 const { metadata } = await renderToString(new URL('./src/index.js', import.meta.url));
 
@@ -86,9 +100,9 @@ console.log({ metadata });
 /*
  * {
  *   metadata: [
- *     'wcc-footer': { instanceName: 'Footer', moduleURL: [URL] },
- *     'wcc-header': { instanceName: 'Header', moduleURL: [URL] },
- *     'wcc-navigation': { instanceName: 'Navigation', moduleURL: [URL] }
+ *     'wcc-footer': { instanceName: 'Footer', moduleURL: [URL], isEntry: true },
+ *     'wcc-header': { instanceName: 'Header', moduleURL: [URL],  isEntry: true },
+ *     'wcc-navigation': { instanceName: 'Navigation', moduleURL: [URL],  isEntry: false }
  *   ]
  * }
  *
diff --git a/src/wcc.js b/src/wcc.js
index d6fd235..f31ae15 100644
--- a/src/wcc.js
+++ b/src/wcc.js
@@ -57,8 +57,9 @@ async function renderComponentRoots(tree, definitions) {
   return tree;
 }
 
-function registerDependencies(moduleURL, definitions) {
+function registerDependencies(moduleURL, definitions, depth = 0) {
   const moduleContents = fs.readFileSync(moduleURL, 'utf-8');
+  const nextDepth = depth += 1;
   const customParser = getParser(moduleURL);
   const parser = customParser ? customParser.parser : acorn;
   const config = customParser ? customParser.config : {
@@ -76,7 +77,7 @@ function registerDependencies(moduleURL, definitions) {
       if (!isBareSpecifier) {
         const dependencyModuleURL = new URL(node.source.value, moduleURL);
 
-        registerDependencies(dependencyModuleURL, definitions);
+        registerDependencies(dependencyModuleURL, definitions, nextDepth);
       }
     },
     ExpressionStatement(node) {
@@ -84,12 +85,14 @@ function registerDependencies(moduleURL, definitions) {
         const { arguments: args } = node.expression;
         const tagName = args[0].value;
         const tree = parseJsx(moduleURL);
+        const isEntry = nextDepth - 1 === 1;
 
         definitions[tagName] = {
           instanceName: args[1].name,
           moduleURL,
           source: escodegen.generate(tree),
-          url: moduleURL
+          url: moduleURL,
+          isEntry
         };
       }
     }
@@ -119,8 +122,11 @@ async function getTagName(moduleURL) {
   return tagName;
 }
 
-async function initializeCustomElement(elementURL, tagName, attrs = [], definitions = []) {
-  registerDependencies(elementURL, definitions);
+async function initializeCustomElement(elementURL, tagName, attrs = [], definitions = [], isEntry) {
+  if (!tagName) {
+    const depth = isEntry ? 1 : 0;
+    registerDependencies(elementURL, definitions, depth);
+  }
 
   // https://github.com/ProjectEvergreen/wcc/pull/67/files#r902061804
   const { pathname } = elementURL;
@@ -147,7 +153,8 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
 async function renderToString(elementURL) {
   const definitions = [];
   const elementTagName = await getTagName(elementURL);
-  const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions);
+  const isEntry = !!elementTagName;
+  const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions, isEntry);
 
   const elementHtml = elementInstance.shadowRoot
     ? elementInstance.getInnerHTML({ includeShadowRoots: true })
@@ -171,7 +178,7 @@ async function renderFromHTML(html, elements = []) {
   const definitions = [];
 
   for (const url of elements) {
-    await initializeCustomElement(url, undefined, undefined, definitions);
+    await initializeCustomElement(url, undefined, undefined, definitions, true);
   }
 
   const elementTree = getParse(html)(html);


Test patch:
diff --git a/test/cases/metadata/metadata.spec.js b/test/cases/metadata/metadata.spec.js
index 4c2b790..0babddd 100644
--- a/test/cases/metadata/metadata.spec.js
+++ b/test/cases/metadata/metadata.spec.js
@@ -48,5 +48,19 @@ describe('Run WCC For ', function() {
 
       expect(hydrateScripts[0][0]).to.equal('wcc-footer');
     });
+
+    describe('Entry Points', () => {
+      it('should mark the footer module as an entry point', function() {
+        expect(assetMetadata['wcc-footer'].isEntry).to.equal(true);
+      });
+
+      it('should mark the header module as an entry point', function() {
+        expect(assetMetadata['wcc-header'].isEntry).to.equal(true);
+      });
+
+      it('should mark the navigation module as NOT entry point', function() {
+        expect(assetMetadata['wcc-navigation'].isEntry).to.equal(false);
+      });
+    });
   });
 });
\ No newline at end of file
diff --git a/test/cases/render-from-html/render-from-html.spec.js b/test/cases/render-from-html/render-from-html.spec.js
index 8bacb56..9027c4d 100644
--- a/test/cases/render-from-html/render-from-html.spec.js
+++ b/test/cases/render-from-html/render-from-html.spec.js
@@ -110,9 +110,12 @@ describe('Run WCC ', function() {
 
       it('should have the correct attributes for each asset', function() {
         Object.entries(assetMetadata).forEach((asset) => {
+          const isEntry = asset[0] === 'wcc-header';
+
           expect(asset[0]).to.not.be.undefined;
           expect(asset[1].instanceName).to.not.be.undefined;
           expect(asset[1].moduleURL).to.not.be.undefined;
+          expect(asset[1].isEntry).to.equal(isEntry);
         });
       });
     });