diff --git a/docs/pages/docs.md b/docs/pages/docs.md
index 6b7919f5..777e3b9a 100644
--- a/docs/pages/docs.md
+++ b/docs/pages/docs.md
@@ -1,9 +1,8 @@
# Documentation
-
## API
-### `renderToString`
+### renderToString
This function takes a `URL` to a JavaScript file that defines a custom element, and returns the static HTML output of its rendered contents.
@@ -11,15 +10,83 @@ This function takes a `URL` to a JavaScript file that defines a custom element,
const { html } = await renderToString(new URL('./src/index.js', import.meta.url));
```
-#### Options
+```js
+// index.js
+import './components/footer.js';
+import './components/header.js';
+
+const template = document.createElement('template');
+
+template.innerHTML = `
+
+
+
+
+
+
My Blog Post
+
+
+
+`;
+
+class Home extends HTMLElement {
+
+ connectedCallback() {
+ if (!this.shadowRoot) {
+ this.attachShadow({ mode: 'open' });
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
+ }
+ }
+}
+
+export default Home;
+```
-`renderToString` also supports a second parameter that is an object, called `options`, which supports the following configurations:
+### renderFromHTML
+
+This function takes a string of HTML and an array of any top-level custom elements used with `import`, and returns the static HTML output of the rendered content.
+
+```js
+const { html } = await renderToString(`
+
+
+ WCC
+
+
+
+
Home Page
+
+
+
+`,
+[
+ new URL('./src/components/footer.js', import.meta.url),
+ new URL('./src/components/header.js', import.meta.url)
+]);
+```
+
+### Options
+
+`renderToString` and `renderFromHTML` also supports a second and third parameter respectively, that is an object, called `options`
+```js
+// default values
+{
+ lightMode: false
+}
+```
+
+It supports the following configuration(s):
- `lightMode`: For more static outcomes (e.g. no declarative shadow DOM), this option will omit all wrapping `` tags when rendering out custom elements. Useful for static sites or working with global CSS libraries.
+
## Metadata
-`renderToString` returns not only HTML, but also metadata about all the custom elements registered as part of rendering the top level custom element.
+`renderToString` and `renderFromHTML` return not only HTML, but also metadata about all the custom elements registered as part of rendering the top level custom element.
```js
const { metadata } = await renderToString(new URL('./src/index.js', import.meta.url));
@@ -61,7 +128,7 @@ The benefit is that this hint can be used to defer loading of these scripts by u
> _See our [examples page](/examples/) for more info._
-## `getData`
+## Data
To further support SSR and hydration scenarios where data is involved, a file with a custom element definition can also export a `getData` function to inject into the custom elements constructor at server render time, as "props". This can be serialized right into the component's Shadow DOM!
diff --git a/src/wcc.js b/src/wcc.js
index 157aacdb..c1dc1eec 100644
--- a/src/wcc.js
+++ b/src/wcc.js
@@ -3,7 +3,7 @@ import './dom-shim.js';
import * as acorn from 'acorn';
import * as walk from 'acorn-walk';
-import { parseFragment, serialize } from 'parse5';
+import { parse, parseFragment, serialize } from 'parse5';
import fs from 'node:fs/promises';
@@ -94,6 +94,7 @@ async function initializeCustomElement(elementURL, tagName, attrs = []) {
async function renderToString(elementURL, options = {}) {
definitions = [];
+
const { lightMode = false } = options;
const includeShadowRoots = !lightMode;
@@ -108,8 +109,26 @@ async function renderToString(elementURL, options = {}) {
};
}
-// TODO renderToStream
+async function renderFromHTML(html, elements = [], options = {}) {
+ definitions = [];
+
+ const { lightMode = false } = options;
+ const includeShadowRoots = !lightMode;
+
+ for (const url of elements) {
+ await initializeCustomElement(url);
+ }
+
+ const elementTree = parse(html);
+ const finalTree = await renderComponentRoots(elementTree, includeShadowRoots);
+
+ return {
+ html: serialize(finalTree),
+ metadata: definitions
+ };
+}
export {
- renderToString
+ renderToString,
+ renderFromHTML
};
\ 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
new file mode 100644
index 00000000..f4191e78
--- /dev/null
+++ b/test/cases/render-from-html/render-from-html.spec.js
@@ -0,0 +1,101 @@
+/*
+ * Use Case
+ * Run wcc against nested custom elements with declarative shadow dom from an HTML string.
+ *
+ * User Result
+ * Should return the expected HTML output for all levels of element nesting.
+ *
+ * User Workspace
+ * src/
+ * components/
+ * navigation.js
+ * header.js
+ */
+import chai from 'chai';
+import { JSDOM } from 'jsdom';
+import { renderFromHTML } from '../../../src/wcc.js';
+
+const expect = chai.expect;
+
+describe('Run WCC For ', function() {
+ const LABEL = 'Using renderFromHTML';
+ let dom;
+ let assetMetadata;
+
+ before(async function() {
+ const { html, metadata } = await renderFromHTML(`
+
+
+ WCC
+
+
+
+
Home Page
+
+
+ `, [
+ new URL('./src/components/header.js', import.meta.url)
+ ]);
+
+ dom = new JSDOM(html);
+ assetMetadata = metadata;
+ });
+
+ describe(LABEL, function() {
+ 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('wcc-header template[shadowroot="open"]')[0].innerHTML);
+ });
+
+ it('should have a tag within the shadowroot', function() {
+ expect(headerContentsDom.window.document.querySelectorAll('header').length).to.equal(1);
+ });
+
+ it('should have expected content within the tag', function() {
+ const content = headerContentsDom.window.document.querySelector('header a h4').textContent;
+
+ expect(content).to.contain('My Personal Blog');
+ });
+
+ describe('nested navigation element', function() {
+ let navigationContentsDom;
+
+ before(function() {
+ navigationContentsDom = new JSDOM(headerContentsDom.window.document.querySelectorAll('wcc-navigation template[shadowroot="open"]')[0].innerHTML);
+ });
+
+ it('should have a