Repository: ProjectEvergreen/greenwood (js)

Issue:
`prerender` configuration for SSR pages not generating output HTML
## Type of Change
- [ ] New Feature Request
- [ ] Documentation / Website
- [ ] Improvement / Suggestion
- [x] Bug
- [ ] Other (please clarify below)

## Summary
I noticed that when viewing my built site on Netlify, I was getting a 404 page!
https://deploy-preview-49--laughing-kare-be3077.netlify.app
<img width="844" alt="Screen Shot 2022-10-21 at 8 54 20 AM" src="https://user-images.githubusercontent.com/895923/197200729-24ef2b2e-0eb2-4087-972b-11f57598cb86.png">

Any WC for src/pages/index.js will do
```js
import fetch from 'node-fetch';
import '../components/upcoming-events/upcoming-events.js';

export default class Home extends HTMLElement {
  async connectedCallback() {
    const events = (await fetch('https://.../api/events').then(resp => resp.json()))

    this.innerHTML = `
      <tt-upcoming-events
        events='${JSON.stringify(events).replace(/'/g, '\\"')}'
      ></tt-upcoming-events>
    `;
  }
}
```

[deploy-63514e686abc6800082c9f5c.zip](https://github.com/ProjectEvergreen/greenwood/files/9839189/deploy-63514e686abc6800082c9f5c.zip)


## Details
Looking into it, I noticed that there indeed was no _index.html_ in the public folder.  😱 
```sh
 % ls -l public 
total 120
-rw-r--r--  1 analogstudios  staff   7977 Oct 20 14:57 404.html
drwxr-xr-x  5 analogstudios  staff    160 Oct 20 14:47 assets
-rw-r--r--  1 analogstudios  staff  15086 Oct 20 14:57 favicon.ico
-rw-r--r--  1 analogstudios  staff    307 Oct 20 14:57 graph.json
drwxr-xr-x  2 analogstudios  staff     64 Oct 20 14:57 styles
```


----

FWIW, I noticed that `greenwood serve` _kind of_ was working, likely because of #946 .  That was definitely a very confusing false positive, especially when dealing with SSR routes.


Pull request description:
Bug/issue 986 fix ssr pages using prerender configuration
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #986 

## Summary of Changes
1. Addressed issue with inconsistent handling of pre-rendering and SSR pages 
1. Validated with test cases

Patch:
diff --git a/packages/cli/src/commands/serve.js b/packages/cli/src/commands/serve.js
index 2852060e..4ed04faa 100644
--- a/packages/cli/src/commands/serve.js
+++ b/packages/cli/src/commands/serve.js
@@ -6,7 +6,7 @@ const runProdServer = async (compilation) => {
 
     try {
       const port = compilation.config.port;
-      const hasDynamicRoutes = compilation.graph.filter(page => page.isSSR && !page.data.static);
+      const hasDynamicRoutes = compilation.graph.filter(page => page.isSSR && ((page.data.hasOwnProperty('static') && !page.data.static) || !compilation.config.prerender));
       const server = hasDynamicRoutes.length > 0 ? getHybridServer : getStaticServer;
 
       (await server(compilation)).listen(port, () => {
diff --git a/packages/cli/src/lifecycles/bundle.js b/packages/cli/src/lifecycles/bundle.js
index e04817c2..84d1a99b 100644
--- a/packages/cli/src/lifecycles/bundle.js
+++ b/packages/cli/src/lifecycles/bundle.js
@@ -5,7 +5,7 @@ const bundleCompilation = async (compilation) => {
 
   return new Promise(async (resolve, reject) => {
     try {
-      compilation.graph = compilation.graph.filter(page => !page.isSSR || (page.isSSR && page.data.static));
+      compilation.graph = compilation.graph.filter(page => !page.isSSR || (page.isSSR && page.data.static) || (page.isSSR && compilation.config.prerender));
 
       // https://rollupjs.org/guide/en/#differences-to-the-javascript-api
       if (compilation.graph.length > 0) {
diff --git a/packages/cli/src/lifecycles/prerender.js b/packages/cli/src/lifecycles/prerender.js
index b9f52bf5..cec3591f 100644
--- a/packages/cli/src/lifecycles/prerender.js
+++ b/packages/cli/src/lifecycles/prerender.js
@@ -57,7 +57,7 @@ async function optimizePage(compilation, contents, route, outputPath, outputDir)
 }
 
 async function preRenderCompilationWorker(compilation, workerPrerender) {
-  const pages = compilation.graph.filter(page => !page.isSSR);
+  const pages = compilation.graph.filter(page => !page.isSSR || (page.isSSR && page.data.static) || (page.isSSR && compilation.config.prerender));
   const outputDir = compilation.context.scratchDir;
 
   console.info('pages to generate', `\n ${pages.map(page => page.route).join('\n ')}`);


Test patch:
diff --git a/packages/cli/test/cases/build.default.ssr-prerender/build.default.ssr-prerender.spec.js b/packages/cli/test/cases/build.default.ssr-prerender/build.default.ssr-prerender.spec.js
new file mode 100644
index 00000000..4d9bbfd2
--- /dev/null
+++ b/packages/cli/test/cases/build.default.ssr-prerender/build.default.ssr-prerender.spec.js
@@ -0,0 +1,88 @@
+/*
+ * Use Case
+ * Run Greenwood with an SSR route that is prerender using configuration.
+ *
+ * User Result
+ * Should generate a bare bones Greenwood build for hosting a prerender SSR application.
+ *
+ * User Command
+ * greenwood build
+ *
+ * User Config
+ * None
+ *
+ * User Workspace
+ *  src/
+ *   components/
+ *     footer.js
+ *   pages/
+ *     index.js
+ *   templates/
+ *     app.html
+ */
+import chai from 'chai';
+import { JSDOM } from 'jsdom';
+import path from 'path';
+import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js';
+import { runSmokeTest } from '../../../../../test/smoke-test.js';
+import { Runner } from 'gallinago';
+import { fileURLToPath, URL } from 'url';
+
+const expect = chai.expect;
+
+describe('Build Greenwood With: ', function() {
+  const LABEL = 'A Server Rendered Application (SSR) with prerender configuration';
+  const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
+  const outputPath = fileURLToPath(new URL('.', import.meta.url));
+  let runner;
+
+  before(async function() {
+    this.context = {
+      publicDir: path.join(outputPath, 'public')
+    };
+    runner = new Runner();
+  });
+
+  describe(LABEL, function() {
+
+    before(async function() {
+      await runner.setup(outputPath, getSetupFiles(outputPath));
+      await runner.runCommand(cliPath, 'build');
+    });
+
+    runSmokeTest(['public', 'index'], LABEL);
+
+    describe('Build command that prerenders SSR pages', function() {
+      let dom;
+
+      before(async function() {
+        dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, 'index.html'));
+      });
+
+      it('should have the expected SSR page content in the HTML', function() {
+        const headings = dom.window.document.querySelectorAll('body h1');
+
+        expect(headings.length).to.equal(1);
+        expect(headings[0].textContent).to.equal('This is the home page.');
+      });
+
+      it('should have one top level <app-footer> element with a <template> with an open shadowroot', function() {
+        expect(dom.window.document.querySelectorAll('app-footer template[shadowroot="open"]').length).to.equal(1);
+        expect(dom.window.document.querySelectorAll('template').length).to.equal(1);
+      });
+
+      it('should have the expected SSR Shadow DOM content for the footer', function() {
+        const wrapper = new JSDOM(dom.window.document.querySelectorAll('app-footer template[shadowroot="open"]')[0].innerHTML);
+        const footer = wrapper.window.document.querySelectorAll('footer');
+
+        expect(footer.length).to.equal(1);
+        expect(footer[0].textContent).to.equal('This is the footer component.');
+      });
+    });
+  });
+
+  after(function() {
+    runner.teardown(getOutputTeardownFiles(outputPath));
+  });
+
+});
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.ssr-prerender/greenwood.config.js b/packages/cli/test/cases/build.default.ssr-prerender/greenwood.config.js
new file mode 100644
index 00000000..8dc4be46
--- /dev/null
+++ b/packages/cli/test/cases/build.default.ssr-prerender/greenwood.config.js
@@ -0,0 +1,3 @@
+export default {
+  prerender: true
+};
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.ssr-prerender/src/components/footer.js b/packages/cli/test/cases/build.default.ssr-prerender/src/components/footer.js
new file mode 100644
index 00000000..80b35600
--- /dev/null
+++ b/packages/cli/test/cases/build.default.ssr-prerender/src/components/footer.js
@@ -0,0 +1,16 @@
+const template = document.createElement('template');
+      
+template.innerHTML = '<footer>This is the footer component.</footer>';
+
+export default class FooterComponent extends HTMLElement {
+  constructor() {
+    super();
+    this.attachShadow({ mode: 'open' });
+  }
+
+  connectedCallback() {
+    this.shadowRoot.appendChild(template.content.cloneNode(true));
+  }
+}
+
+customElements.define('app-footer', FooterComponent);
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.ssr-prerender/src/pages/index.js b/packages/cli/test/cases/build.default.ssr-prerender/src/pages/index.js
new file mode 100644
index 00000000..fa3d8e44
--- /dev/null
+++ b/packages/cli/test/cases/build.default.ssr-prerender/src/pages/index.js
@@ -0,0 +1,7 @@
+export default class HomePage extends HTMLElement {
+  connectedCallback() {
+    this.innerHTML = `
+      <h1>This is the home page.</h1>
+    `;
+  }
+}
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.ssr-prerender/src/templates/app.html b/packages/cli/test/cases/build.default.ssr-prerender/src/templates/app.html
new file mode 100644
index 00000000..939d14bb
--- /dev/null
+++ b/packages/cli/test/cases/build.default.ssr-prerender/src/templates/app.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en" prefix="og:http://ogp.me/ns#">
+  <head>
+    <title>Default Title</title>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1"/>
+    <script type="module" src="/components/footer.js"></script>
+  </head>
+  <body>
+    <app-footer></app-footer>
+    <page-outlet></page-outlet>
+  </body>
+</html>
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js b/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js
index 1a8b3ddd..afba49aa 100644
--- a/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js
+++ b/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js
@@ -124,14 +124,7 @@ describe('Build Greenwood With: ', function() {
         ...litReactiveElementDecorators,
         ...litReactiveElementPackageJson
       ]);
-
-      return new Promise(async (resolve) => {
-        setTimeout(() => {
-          resolve();
-        }, 10000);
-
-        await runner.runCommand(cliPath, 'build');
-      });
+      await runner.runCommand(cliPath, 'build');
     });
 
     runSmokeTest(['public', 'index'], LABEL);
diff --git a/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js b/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js
index 1458a668..593ab4d1 100644
--- a/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js
+++ b/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js
@@ -14,7 +14,8 @@
  * User Workspace
  *  src/
  *   components/
- *     footer.js
+ *     card.js
+ *     counter.js
  *   pages/
  *     about.md
  *     artists.js