Repository: ProjectEvergreen/greenwood (js)

Issue:
HTML Include Plugin 
## Type of Change
- [x] New Feature Request
- [ ] Documentation / Website
- [ ] Improvement / Suggestion
- [ ] Bug
- [ ] Other (please clarify below)

## Summary
In the spirit of the since [abandoned HTML Imports spec](https://www.html5rocks.com/en/tutorials/webcomponents/imports/) that was originally part of the init Web Components "feature suite", and given the renewed [interest in bringing it back](https://github.com/whatwg/html/issues/2791), I thought it might be nice to bring this feature to Greenwood, in a more static context, as opposed to something that would happen purely at runtime.  _Effectively, this proposal / plugin would
would take the HTML include spec and realize it as more a build time templating system for HTML.  The goal here is to enable developers the ability to ship more static HTML while allowing the authoring context to be JavaScript **and** leveraging standard semantics and web expectations._. 💚 

> **Note**: I think if you want this feature in its most strictest sense of the word, I would recommend the [**<html-include>**](https://github.com/justinfagnani/html-include-element) custom element, which provides a runtime implementation of this as a Web Component.

Some of the work has already been spiked in #712 and so this issue will aim to formally bring it to live.  🙌 

## Details
I see two initial flavors of supporting this out of the gate that I would feel comfortable supporting and maintaining as a plugin and would provide HTML and JS based implementations.

> _It should be noted that this plugin would more or less be another way to achieve what the [`static` optimization setting]() already does._

### `<link>` Tag (HTML only)
The simplest flavor would follow the spec more closely and I think primarily address the use case of where you have static HTML that you want to reuse across your pages, like a global header or footer.

So given a snippet of HTML, e.g.
```html
<!-- src/includes/header.html -->
<style>
  header h1.my-include {
    text-align: center;
    color: red;
  }
</style>

<header class="my-include">
  <h1>Welcome to my website!<h1>
</header> 
```

In a page template, you could then do this
```html
<html>

  <body>
    <!-- the location can be anywhere in the workspace, I just chose to call it includes -->
    <!-- rel and href attributes would be required -->
    <link rel="html" href="/includes/header.html"></link>

    <h2>Hello 👋</h2>
  
  </body>

<html>
```

And Greenwood would statically generate this
```html
<html>

  <body>
    <style>
      header h1.my-include {
        text-align: center;
        color: red;
      }
    </style>

    <header class="my-include">
      <h1>Welcome to my website!<h1>
    </header> 

    <h2>Hello 👋</h2>
  
  </body>

<html>
```


### Custom Element (JavaScript)
For more advanced use cases where customization of the output may need to be done on a case by case basis, the other flavor would be a more JavaScript / web components flavor, allowing developers to return functions for generating markup and data and then Greenwood will build the HTML for them on the fly.  This effectively aims to fill the gap where just static HTML alone would not be sufficient enough.

So using the [Greenwood footer as an example](https://github.com/ProjectEvergreen/greenwood/blob/v0.19.0-alpha.2/www/components/footer/footer.js), that displays the project version based on reading the contents of a _package.json_ file, let's create a JS file that `export`s two functions; `getTemplate` and `getData`
```js
// src/components/footer/footer.js
const getTemplate = async (data) => {
  return `
    <app-footer>
      <style>
        footer {
          grid-area: footer;
        }
        ...
      </style>

      <footer class="footer">
        <h4>
          <a href="/">Greenwood v${data.version}</a> <span class="separator">&#9672</span> <a href="https://www.netlify.com/">This site is powered by Netlify</a>
        </h4>
      </footer>
    </app-footer>
  `;
};

const getData = async () => {
  const version = require('../../package.json').version;

  return { version };
};

module.exports = {
  getTemplate,
  getData
}; 
```

In a page template, you could then do this
```html
<html>

  <body>
    <h2>Hello 👋</h2>

    <app-footer src="../components/footer/footer.js"></app-footer>  
  </body>

<html>
```

And Greenwood would statically generate this
```html
<html>

  <body>
    <h2>Hello 👋</h2>

    <app-footer>
      <style>
        footer {
          grid-area: footer;
        }
      </style>

      <footer class="footer">
        <h4>
          <a href="/">Greenwood v0.19.0-alpha.2</a> <span class="separator">◈</span> <a href="https://www.netlify.com/">This site is powered by Netlify</a>
        </h4>
      </footer>
    </app-footer>  
  </body>

<html>
```

IMO, think the JS flavor will really come to shine more when Greenwood adds support for #708 , and then you could use this for displaying user / session data, or serverlessly at the edge!

Pull request:
HTML Include Plugin
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #788 

<details>
> _Note: [this issue will manifest in this PR](https://github.com/ProjectEvergreen/greenwood/pull/712#issuecomment-945019172) until #782 is merged_
</details>

## Summary of Changes
1. Created a new plugin to reflect the design derived from this spike (see below details)
1. Updated website documentation and implemented the custom element variant for the website footer

Interesting observation about the footer [here](https://github.com/ProjectEvergreen/greenwood/pull/712#issuecomment-961174082)

<details>
1. Spiking out the idea of what a plugin might look like for supporting the old deprecated standard of [HTML includes](https://github.com/whatwg/html/issues/2791) (not to be confused with [HTML modules / imports](https://github.com/WICG/webcomponents/issues/645))

![Screen Shot 2021-09-01 at 5 48 57 PM](https://user-images.githubusercontent.com/895923/131751097-27df1ac8-6428-4d06-a7ce-2dea3ac29ae5.png)
![Screen Shot 2021-09-01 at 5 49 22 PM](https://user-images.githubusercontent.com/895923/131751098-11601ab0-6b5c-456a-ba30-f01cde81278d.png)

## TODO
Going further, would like to see how far we can take it if we were to support something like this (notice the _.js_ extension)
```html
<link rel="html" href="/includes/html-include-test.html.js"></link>
```

```js
const getIncludeTemplate = async (data) => {
  const { name } = data;
  
  return `Hello ${name || 'World'}! 👋`;
}

export { getIncludeTemplate };
```

Then the plugin could just `import` from that file and statically stamp out the contents that way?  🤔 
</details>

Patch:
diff --git a/greenwood.config.js b/greenwood.config.js
index 42d7fe82..deed0dfd 100644
--- a/greenwood.config.js
+++ b/greenwood.config.js
@@ -1,5 +1,6 @@
 const path = require('path');
 const pluginGraphQL = require('@greenwood/plugin-graphql');
+const pluginIncludeHtml = require('@greenwood/plugin-include-html');
 const pluginImportCss = require('@greenwood/plugin-import-css');
 const pluginImportJson = require('@greenwood/plugin-import-json');
 const pluginPolyfills = require('@greenwood/plugin-polyfills');
@@ -45,7 +46,8 @@ module.exports = {
           })
         ];
       }
-    }
+    },
+    ...pluginIncludeHtml()
   ],
   markdown: {
     plugins: [
diff --git a/packages/plugin-include-html/README.md b/packages/plugin-include-html/README.md
new file mode 100644
index 00000000..ce06b36b
--- /dev/null
+++ b/packages/plugin-include-html/README.md
@@ -0,0 +1,157 @@
+# @greenwood/plugin-include-html
+
+## Overview
+In the spirit of the since [abandoned HTML Imports spec](https://www.html5rocks.com/en/tutorials/webcomponents/imports/) that was originally part of the init Web Components "feature suite", and given the renewed [interest in bringing it back](https://github.com/whatwg/html/issues/2791), this plugin adds expiremental support to realize the HTML Includes "spec" as a build time templating system for HTML.  The goal here is to enable developers the ability to ship more static HTML while allowing the authoring context to be JavaScript **and** leveraging standard semantics and web expectations._ 💚 
+
+> **Note**: I think if you want this feature in its most strictest sense of the word, I would recommend the [**<html-include>**](https://github.com/justinfagnani/html-include-element) custom element, which provides a runtime implementation of this as a Web Component.
+
+## Usage
+Add this plugin to your _greenwood.config.js_ and spread the `export`.
+
+```javascript
+const pluginIncludeHtml = require('@greenwood/plugin-include-html');
+
+module.exports = {
+  ...
+
+  plugins: [
+    ...pluginIncludeHtml() // notice the spread ... !
+  ]
+}
+```
+
+> _It should be noted that this plugin would more or less be another way to achieve what the [`static` optimization setting](https://www.greenwoodjs.io/docs/configuration/#optimization) already does._
+
+### `<link>` Tag (HTML only)
+This is the simplest "flavor" and follows the spec more closely to address the use case where you have static HTML that you want to reuse across your pages, like a global header or footer.  The location of this file can be anywhere in your workspace, I just chose to put it in an _includes/_ directory for the sake of this example.
+
+So given a snippet of HTML, e.g.
+```html
+<!-- src/includes/header.html -->
+<style>
+  header h1.my-include {
+    text-align: center;
+    color: red;
+  }
+</style>
+
+<header class="my-include">
+  <h1>Welcome to my website!<h1>
+</header> 
+```
+
+In a page template, you could then do this
+```html
+<html>
+
+  <body>
+    <!-- rel and href attributes would be required -->
+    <link rel="html" href="/includes/header.html"></link>
+
+    <h2>Hello 👋</h2>
+  
+  </body>
+
+<html>
+```
+
+And Greenwood will statically generate this
+```html
+<html>
+
+  <body>
+    <style>
+      header h1.my-include {
+        text-align: center;
+        color: red;
+      }
+    </style>
+
+    <header class="my-include">
+      <h1>Welcome to my website!<h1>
+    </header> 
+
+    <h2>Hello 👋</h2>
+  
+  </body>
+
+<html>
+```
+
+
+### Custom Element (JavaScript)
+For more advanced use cases where customization of the output may need to be done in a programmatic fashion, the custom element flavor supports declaring functions for generating markup and data that Greenwood will then build the HTML for on the fly.  This effectively aims to fill the gap where just static HTML alone would not be sufficient enough.
+
+So using the [Greenwood footer as an example](https://github.com/ProjectEvergreen/greenwood/blob/master/www/includes/footer.js), that displays the project version based on reading the contents of a _package.json_ file, create a JS file that `export`s two functions; `getTemplate` and `getData`
+```js
+// src/includes/footer.js
+const getTemplate = async (data) => {
+  return `
+    <app-footer>
+      <style>
+        footer {
+          grid-area: footer;
+        }
+        ...
+      </style>
+
+      <footer class="footer">
+        <h4>
+          <a href="/">Greenwood v${data.version}</a>
+        </h4>
+      </footer>
+    </app-footer>
+  `;
+};
+
+const getData = async () => {
+  const version = require('../../package.json').version;
+
+  return { version };
+};
+
+module.exports = {
+  getTemplate,
+  getData
+}; 
+```
+
+In a page template, you can now do this
+```html
+<html>
+
+  <body>
+    <h2>Hello 👋</h2>
+
+    <app-footer src="../includes/footer.js"></app-footer>  
+  </body>
+
+<html>
+```
+
+And Greenwood would statically generate this
+```html
+<html>
+
+  <body>
+    <h2>Hello 👋</h2>
+
+    <app-footer>
+      <style>
+        footer {
+          grid-area: footer;
+        }
+      </style>
+
+      <footer class="footer">
+        <h4>
+          <a href="/">Greenwood v0.19.0-alpha.2</a>
+        </h4>
+      </footer>
+    </app-footer>  
+  </body>
+
+<html>
+```
+
+> We think the JS flavor will really come to shine more when Greenwood adds support for [SSR](https://github.com/ProjectEvergreen/greenwood/issues/708), and then you could use this TECHNIQUE for displaying user / session data, or serverlessly at the edge!
\ No newline at end of file
diff --git a/packages/plugin-include-html/package.json b/packages/plugin-include-html/package.json
new file mode 100644
index 00000000..9da52473
--- /dev/null
+++ b/packages/plugin-include-html/package.json
@@ -0,0 +1,28 @@
+{
+  "name": "@greenwood/plugin-include-html",
+  "version": "0.19.0-alpha.2",
+  "description": "A Greenwood plugin to let you render server side JS from HTML or JS at build time as HTML.",
+  "repository": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-include-html",
+  "author": "Owen Buckley <owen@thegreenhouse.io>",
+  "license": "MIT",
+  "keywords": [
+    "Greenwood",
+    "Static Site Generator",
+    "NodeJS",
+    "HTML",
+    "HTML Include"
+  ],
+  "main": "src/index.js",
+  "files": [
+    "src/"
+  ],
+  "publishConfig": {
+    "access": "public"
+  },
+  "peerDependencies": {
+    "@greenwood/cli": "^0.4.0"
+  },
+  "devDependencies": {
+    "@greenwood/cli": "^0.19.0-alpha.2"
+  }
+}
diff --git a/packages/plugin-include-html/src/index.js b/packages/plugin-include-html/src/index.js
new file mode 100644
index 00000000..7e0e9e1b
--- /dev/null
+++ b/packages/plugin-include-html/src/index.js
@@ -0,0 +1,59 @@
+/* eslint-disable */
+const fs = require('fs');
+const path = require('path');
+const { ResourceInterface } = require('@greenwood/cli/src/lib/resource-interface');
+
+class IncludeHtmlResource extends ResourceInterface {
+  constructor(compilation, options) {
+    super(compilation, options);
+    this.extensions = ['.html'];
+    this.contentType = 'text/html';
+  }
+
+  async shouldIntercept(url, body, headers) {
+    return Promise.resolve(headers.response['content-type'] === this.contentType);
+  }
+
+  async intercept(url, body) {
+    return new Promise(async (resolve, reject) => {
+      try {
+        const includeLinksRegexMatches = body.match(/<link (.*)>/g);
+        const includeCustomElementssRegexMatches = body.match(/<[a-zA-Z]*-[a-zA-Z](.*)>(.*)<\/[a-zA-Z]*-[a-zA-Z](.*)>/g);
+
+        if (includeLinksRegexMatches) {
+          includeLinksRegexMatches
+            .filter(link => link.indexOf('rel="html"') > 0)
+            .forEach((link) => {
+              const href = link.match(/href="(.*)"/)[1];
+              const includeContents = fs.readFileSync(path.join(this.compilation.context.userWorkspace, href), 'utf-8');
+
+              body = body.replace(link, includeContents);
+            });
+        }
+
+        if (includeCustomElementssRegexMatches) {
+          const customElementTags = includeCustomElementssRegexMatches.filter(customElementTag => customElementTag.indexOf('src=') > 0)
+
+          for(const tag of customElementTags) {
+            const src = tag.match(/src="(.*)"/)[1];
+            const filepath = path.join(this.compilation.context.userWorkspace, this.getBareUrlPath(src.replace(/\.\.\//g, '')))
+            const { getData, getTemplate } = require(filepath);
+            const includeContents = await getTemplate(await getData());
+
+            body = body.replace(tag, includeContents);
+          }
+        }
+
+        resolve({ body });
+      } catch (e) {
+        reject(e);
+      }
+    });
+  }
+}
+
+module.exports = (options = {}) => [{
+  type: 'resource',
+  name: 'plugin-include-html',
+  provider: (compilation) => new IncludeHtmlResource(compilation, options)
+}];
\ No newline at end of file
diff --git a/www/components/footer/footer.css b/www/components/footer/footer.css
deleted file mode 100644
index 5f1e0dbf..00000000
--- a/www/components/footer/footer.css
+++ /dev/null
@@ -1,25 +0,0 @@
-:host {
-  grid-area: footer;
-
-  & .footer {
-    background-color: #192a27;
-    min-height: 30px;
-    padding-top: 10px;
-
-    & h4 {
-      width: 90%;
-      margin: 0 auto!important;
-      padding: 0;
-      text-align: center;
-    }
-
-    & a {
-      color: white;
-      text-decoration: none;
-    }
-
-    & span.separator {
-      color: white;
-    }
-  }
-}
\ No newline at end of file
diff --git a/www/components/footer/footer.js b/www/components/footer/footer.js
deleted file mode 100644
index 562e0009..00000000
--- a/www/components/footer/footer.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { css, html, LitElement, unsafeCSS } from 'lit';
-import json from '../../package.json?type=json';
-import footerCss from './footer.css?type=css';
-
-class FooterComponent extends LitElement {
-
-  static get styles() {
-    return css`
-      ${unsafeCSS(footerCss)}
-    `;
-  }
-
-  render() {
-    const { version } = json;
-
-    return html`
-      <footer class="footer">
-        <h4>
-          <a href="https://github.com/ProjectEvergreen/greenwood/releases/tag/v${version}" target="_blank" rel="noopener noreferrer">Greenwood v${version}</a> 
-          <span class="separator">&#9672</span> <a href="https://www.netlify.com/">This site is powered by Netlify</a>
-        </h4>
-      </footer>
-    `;
-  }
-}
-
-customElements.define('app-footer', FooterComponent);
\ No newline at end of file
diff --git a/www/includes/footer.js b/www/includes/footer.js
new file mode 100644
index 00000000..ef64a708
--- /dev/null
+++ b/www/includes/footer.js
@@ -0,0 +1,48 @@
+const getTemplate = async (data) => {
+  return `
+    <app-footer>
+      <style>
+        footer {
+          grid-area: footer;
+        }
+
+        footer {
+          background-color: #192a27;
+          min-height: 30px;
+          padding-top: 10px;
+        }
+        
+        footer h4 {
+          width: 90%;
+          margin: 0 auto!important;
+          padding: 0;
+          text-align: center;
+        }
+        
+        footer a {
+          color: white;
+          text-decoration: none;
+        }
+        
+        footer span.separator {
+          color: white;
+        }
+      </style>
+      <footer class="footer">
+        <h4>
+          <a href="/">Greenwood v${data.version}</a> <span class="separator">&#9672</span> <a href="https://www.netlify.com/">This site is powered by Netlify</a>
+        </h4>
+      </footer>
+    </app-footer>`;
+};
+
+const getData = async () => {
+  const version = require('../package.json').version;
+
+  return { version };
+};
+
+module.exports = {
+  getTemplate,
+  getData
+};
\ No newline at end of file
diff --git a/www/pages/plugins/custom-plugins.md b/www/pages/plugins/custom-plugins.md
index 52e3024f..832437d6 100644
--- a/www/pages/plugins/custom-plugins.md
+++ b/www/pages/plugins/custom-plugins.md
@@ -14,6 +14,7 @@ The available plugins built and maintained by Greenwood team and contributors ha
 |---|---|
 | [Babel](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-babel) | Allows usage of [**Babel**](https://babeljs.io/) plugins, presets, and configuration in your project. |
 | [Google Analytics](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-google-analytics) | Easily add usage tracking to your site with Google Analytics. |
+| [Include HTML](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-include-html) | Provides additional methods for generating static HTML at build time, inspired by the original [HTML Imports spec](https://www.html5rocks.com/en/tutorials/webcomponents/imports/). |
 | [Import CommonJs](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-commonjs) | Enables usage of `import` syntax for loading CommonJS modules from _node_modules_. |
 | [Import CSS](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-css) | Enables usage of `import` syntax for loading CSS files. |
 | [Import JSON](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-json) | Enables usage of `import` syntax for loading JSON files. |
diff --git a/www/templates/app.html b/www/templates/app.html
index a7b3b0e9..0fa7ce11 100644
--- a/www/templates/app.html
+++ b/www/templates/app.html
@@ -22,7 +22,6 @@
       import '@evergreen-wc/eve-container';
     </script>
     
-    <script type="module" src="../components/footer/footer.js" data-gwd-opt="static"></script>
     <script type="module" src="../components/header/header.js" data-gwd-opt="static"></script>
     
   </head>
@@ -33,8 +32,8 @@
       <app-header></app-header>
 
       <page-outlet></page-outlet>
-      
-      <app-footer></app-footer>
+
+      <app-footer src="../includes/footer.js"></app-footer2>
     </div>
 
   </body>


Test patch:
diff --git a/packages/plugin-include-html/test/cases/build.default-custom-element/build.default.custom-element.spec.js b/packages/plugin-include-html/test/cases/build.default-custom-element/build.default.custom-element.spec.js
new file mode 100644
index 00000000..dc4be13a
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-custom-element/build.default.custom-element.spec.js
@@ -0,0 +1,81 @@
+/*
+ * Use Case
+ * Run Greenwood build command with no config and this plugin.
+ *
+ * User Result
+ * Should generate a bare bones Greenwood build with correctly templated out HTML from a custom elements tag.
+ *
+ * User Command
+ * greenwood build
+ *
+ * User Config
+ * None (Greenwood Default)
+ *
+ * User Workspace
+ * src/
+ *   components/
+ *     footer.js
+ *   pages/
+ *     index.html
+ */
+const expect = require('chai').expect;
+const { JSDOM } = require('jsdom');
+const path = require('path');
+const runSmokeTest = require('../../../../../test/smoke-test');
+const { getSetupFiles, getOutputTeardownFiles } = require('../../../../../test/utils');
+const Runner = require('gallinago').Runner;
+
+describe('Build Greenwood With HTML Include Plugin: ', function() {
+  const LABEL = 'Default Greenwood Configuration and Workspace';
+  const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
+  const outputPath = __dirname;
+  let runner;
+
+  before(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('Default custom element include and page content for index.html', function() {
+      let dom;
+      let footerHeadingTags;
+      let headingTags;
+
+      before(async function() {
+        dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html'));
+        footerHeadingTags = dom.window.document.querySelectorAll('body footer h4');
+        headingTags = dom.window.document.querySelectorAll('body h1');
+      });
+
+      it('should have expected footer <h4> tag content in the <body>', function() {
+        const text = footerHeadingTags[0].textContent;
+        
+        expect(footerHeadingTags.length).to.be.equal(1);
+        expect(text.trim()).to.be.equal('Greenwood v1.0.0');
+      });
+
+      it('should have existing <h1> content in the <body>', function() {
+        const text = headingTags[0].textContent;
+        
+        expect(headingTags.length).to.be.equal(1);
+        expect(text).to.be.equal('Hello!');
+      });
+    });
+
+  });
+
+  after(function() {
+    runner.teardown(getOutputTeardownFiles(outputPath));
+  });
+});
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-custom-element/greenwood.config.js b/packages/plugin-include-html/test/cases/build.default-custom-element/greenwood.config.js
new file mode 100644
index 00000000..478e4633
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-custom-element/greenwood.config.js
@@ -0,0 +1,7 @@
+const pluginIncludeHtml = require('../../../src/index');
+
+module.exports = {
+  plugins: [
+    ...pluginIncludeHtml()
+  ]
+};
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-custom-element/package.json b/packages/plugin-include-html/test/cases/build.default-custom-element/package.json
new file mode 100644
index 00000000..7a162b66
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-custom-element/package.json
@@ -0,0 +1,3 @@
+{ 
+  "version": "1.0.0"
+}
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-custom-element/src/components/footer.js b/packages/plugin-include-html/test/cases/build.default-custom-element/src/components/footer.js
new file mode 100644
index 00000000..aaa343a5
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-custom-element/src/components/footer.js
@@ -0,0 +1,20 @@
+const getTemplate = async (data) => {
+  return `
+    <app-footer>
+      <footer class="footer">
+        <h4>Greenwood v${data.version}</h4>
+      </footer>
+    </app-footer>
+  `;
+};
+
+const getData = async () => {
+  const version = require('../../package.json').version;
+
+  return { version };
+};
+
+module.exports = {
+  getTemplate,
+  getData
+};
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-custom-element/src/pages/index.html b/packages/plugin-include-html/test/cases/build.default-custom-element/src/pages/index.html
new file mode 100644
index 00000000..990648c0
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-custom-element/src/pages/index.html
@@ -0,0 +1,8 @@
+<html>
+
+  <body>
+    <h1>Hello!</h1>
+    <app-footer src="../components/footer.js"></app-footer>
+  </body>
+
+</html>
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-link-tag/build.default.link-tag.spec.js b/packages/plugin-include-html/test/cases/build.default-link-tag/build.default.link-tag.spec.js
new file mode 100644
index 00000000..de30aa59
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-link-tag/build.default.link-tag.spec.js
@@ -0,0 +1,81 @@
+/*
+ * Use Case
+ * Run Greenwood build command with no config and this plugin.
+ *
+ * User Result
+ * Should generate a bare bones Greenwood build with correctly templated out HTML from the <link> tag.
+ *
+ * User Command
+ * greenwood build
+ *
+ * User Config
+ * None (Greenwood Default)
+ *
+ * User Workspace
+ * src/
+ *   includes/
+ *     header.html
+ *   pages/
+ *     index.html
+ */
+const expect = require('chai').expect;
+const { JSDOM } = require('jsdom');
+const path = require('path');
+const runSmokeTest = require('../../../../../test/smoke-test');
+const { getSetupFiles, getOutputTeardownFiles } = require('../../../../../test/utils');
+const Runner = require('gallinago').Runner;
+
+describe('Build Greenwood With HTML Include Plugin: ', function() {
+  const LABEL = 'Default Greenwood Configuration and Workspace';
+  const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
+  const outputPath = __dirname;
+  let runner;
+
+  before(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('Default <link> include and page content for index.html', function() {
+      let dom;
+      let pTags;
+      let headingTags;
+
+      before(async function() {
+        dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html'));
+        pTags = dom.window.document.querySelectorAll('body header p');
+        headingTags = dom.window.document.querySelectorAll('body h1');
+      });
+
+      it('should have expected <p> tag content in the <body>', function() {
+        const text = pTags[0].textContent;
+        
+        expect(pTags.length).to.be.equal(1);
+        expect(text.trim()).to.be.equal('This is the website header.');
+      });
+
+      it('should have existing <h1> content in the <body>', function() {
+        const text = headingTags[0].textContent;
+        
+        expect(headingTags.length).to.be.equal(1);
+        expect(text).to.be.equal('Hello!');
+      });
+    });
+
+  });
+
+  after(function() {
+    runner.teardown(getOutputTeardownFiles(outputPath));
+  });
+});
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-link-tag/greenwood.config.js b/packages/plugin-include-html/test/cases/build.default-link-tag/greenwood.config.js
new file mode 100644
index 00000000..478e4633
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-link-tag/greenwood.config.js
@@ -0,0 +1,7 @@
+const pluginIncludeHtml = require('../../../src/index');
+
+module.exports = {
+  plugins: [
+    ...pluginIncludeHtml()
+  ]
+};
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-link-tag/src/includes/header.html b/packages/plugin-include-html/test/cases/build.default-link-tag/src/includes/header.html
new file mode 100644
index 00000000..32f6d270
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-link-tag/src/includes/header.html
@@ -0,0 +1,5 @@
+<header>
+  <p class="include">
+    This is the website header.
+  </p>
+</header>
\ No newline at end of file
diff --git a/packages/plugin-include-html/test/cases/build.default-link-tag/src/pages/index.html b/packages/plugin-include-html/test/cases/build.default-link-tag/src/pages/index.html
new file mode 100644
index 00000000..760b7c48
--- /dev/null
+++ b/packages/plugin-include-html/test/cases/build.default-link-tag/src/pages/index.html
@@ -0,0 +1,8 @@
+<html>
+  <body>
+
+    <link rel="html" href="/includes/header.html"></link>
+    <h1>Hello!</h1>
+  
+  </body>
+</html>
\ No newline at end of file