From c8af581887e13cb8cfe86dc2fe3f2129fa14a41a Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 14 Oct 2023 15:47:08 -0400 Subject: [PATCH 1/5] constructor props --- src/wcc.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/wcc.js b/src/wcc.js index b27278f..f9b3998 100644 --- a/src/wcc.js +++ b/src/wcc.js @@ -126,7 +126,7 @@ async function getTagName(moduleURL) { return tagName; } -async function initializeCustomElement(elementURL, tagName, attrs = [], definitions = [], isEntry) { +async function initializeCustomElement(elementURL, tagName, attrs = [], definitions = [], isEntry, props = {}) { if (!tagName) { const depth = isEntry ? 1 : 0; registerDependencies(elementURL, definitions, depth); @@ -138,7 +138,11 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti ? customElements.get(tagName) : (await import(pathname)).default; const dataLoader = (await import(pathname)).getData; - const data = dataLoader ? await dataLoader() : {}; + const data = props + ? props + : dataLoader + ? await dataLoader(props) + : {}; if (element) { const elementInstance = new element(data); // eslint-disable-line new-cap @@ -160,11 +164,11 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti } } -async function renderToString(elementURL, wrappingEntryTag = true) { +async function renderToString(elementURL, wrappingEntryTag = true, props = {}) { const definitions = []; const elementTagName = wrappingEntryTag && await getTagName(elementURL); const isEntry = !!elementTagName; - const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions, isEntry); + const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions, isEntry, props); const elementHtml = elementInstance.shadowRoot ? elementInstance.getInnerHTML({ includeShadowRoots: true }) From 4ac77a3ae1e7349aae950e79e46f065bdeef16dd Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 14 Oct 2023 16:00:19 -0400 Subject: [PATCH 2/5] document constructor props --- docs/pages/docs.md | 67 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/docs/pages/docs.md b/docs/pages/docs.md index 9277eb5..2a3eb35 100644 --- a/docs/pages/docs.md +++ b/docs/pages/docs.md @@ -144,10 +144,73 @@ The benefit is that this hint can be used to defer loading of these scripts by u ## 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! +WCC provide a couple mechanisms for data loading. + +### Constructor Props + +Often for frameworks that might have their own needs for data loading and orchestration, a top level "constructor prop" can be provided to `renderToString` as the final param. The prop will then be passed to top level custom element definition's `constructor`. -For example, you could preload a counter component with an initial counter state ```js +const request = new Request({ /* ... */ }); +const { html } = await renderToString(new URL(moduleUrl), false, request); +``` + +This pattern plays really with file-based routing and SSR. +```js +export default class PostPage extends HTMLElement { + constructor(request) { + super(); + + const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))); + this.postId = params.get('id'); + } + + async connectedCallback() { + const { postId } = this; + const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`).then(resp => resp.json()); + const { title, body } = post; + + this.innerHTML = ` +

${title}

+

${body}

+ `; + } +} +``` + +### Data Loader + +To support component-level data loading and hydration scenarios, a file with a custom element definition can also export a `getData` function to inject into the custom elements constructor at execution time. This can be serialized right into the component's Shadow DOM! + +For example, you could preload a counter component with an initial counter state, which would also come through the `constructor` +```js +class Counter extends HTMLElement { + constructor(props = {}) { + super(); + + this.count = props.count; + this.render(); + } + + // .... + + render() { + return ` + + `; + } +} + export async function getData() { return { count: Math.floor(Math.random() * (100 - 0 + 1) + 0) From f59468327f8210470d44c56d56037b04c6a4dc55 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 14 Oct 2023 16:15:06 -0400 Subject: [PATCH 3/5] document constructor props --- docs/pages/docs.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/pages/docs.md b/docs/pages/docs.md index 2a3eb35..343e4fc 100644 --- a/docs/pages/docs.md +++ b/docs/pages/docs.md @@ -150,6 +150,7 @@ WCC provide a couple mechanisms for data loading. Often for frameworks that might have their own needs for data loading and orchestration, a top level "constructor prop" can be provided to `renderToString` as the final param. The prop will then be passed to top level custom element definition's `constructor`. + ```js const request = new Request({ /* ... */ }); const { html } = await renderToString(new URL(moduleUrl), false, request); @@ -182,7 +183,9 @@ export default class PostPage extends HTMLElement { To support component-level data loading and hydration scenarios, a file with a custom element definition can also export a `getData` function to inject into the custom elements constructor at execution time. This can be serialized right into the component's Shadow DOM! -For example, you could preload a counter component with an initial counter state, which would also come through the `constructor` +For example, you could preload a counter component with an initial counter state, which would also come through the `constructor`. + + ```js class Counter extends HTMLElement { constructor(props = {}) { From c84e35aabbce0f4eaaa9580f4c59718af38faad6 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 14 Oct 2023 16:15:30 -0400 Subject: [PATCH 4/5] add test case for constructor props --- .../constructor-props.spec.js | 43 +++++++++++++++++++ test/cases/constructor-props/src/index.js | 19 ++++++++ 2 files changed, 62 insertions(+) create mode 100644 test/cases/constructor-props/constructor-props.spec.js create mode 100644 test/cases/constructor-props/src/index.js diff --git a/test/cases/constructor-props/constructor-props.spec.js b/test/cases/constructor-props/constructor-props.spec.js new file mode 100644 index 0000000..816b986 --- /dev/null +++ b/test/cases/constructor-props/constructor-props.spec.js @@ -0,0 +1,43 @@ +/* + * Use Case + * Run wcc against a custom element passing in constructor props. + * + * User Result + * Should return the expected HTML output based on the fetched content from constructor props. + * + * User Workspace + * src/ + * 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 = 'Custom Element w/ constructor props'; + const postId = 1; + let dom; + + before(async function() { + const { html } = await renderToString(new URL('./src/index.js', import.meta.url), false, postId); + + dom = new JSDOM(html); + }); + + describe(LABEL, function() { + it('should have a heading tag with the postId', function() { + expect(dom.window.document.querySelectorAll('h1')[0].textContent).to.equal(`Fetched Post ID: ${postId}`); + }); + + it('should have a second heading tag with the title', function() { + expect(dom.window.document.querySelectorAll('h2')[0].textContent).to.equal('sunt aut facere repellat provident occaecati excepturi optio reprehenderit'); + }); + + it('should have a heading tag with the body', function() { + expect(dom.window.document.querySelectorAll('p')[0].textContent.startsWith('quia et suscipit')).to.equal(true); + }); + }); +}); \ No newline at end of file diff --git a/test/cases/constructor-props/src/index.js b/test/cases/constructor-props/src/index.js new file mode 100644 index 0000000..9f7bfd1 --- /dev/null +++ b/test/cases/constructor-props/src/index.js @@ -0,0 +1,19 @@ +export default class PostPage extends HTMLElement { + constructor(postId) { + super(); + + this.postId = postId; + } + + async connectedCallback() { + const { postId } = this; + const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`).then(resp => resp.json()); + const { id, title, body } = post; + + this.innerHTML = ` +

Fetched Post ID: ${id}

+

${title}

+

${body}

+ `; + } +} \ No newline at end of file From a14b320de38f1cb8b768f490bcdbe853e72ba085 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Fri, 27 Oct 2023 20:46:06 -0400 Subject: [PATCH 5/5] docs grammar fixes --- docs/pages/docs.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/pages/docs.md b/docs/pages/docs.md index 343e4fc..4e8c64d 100644 --- a/docs/pages/docs.md +++ b/docs/pages/docs.md @@ -148,7 +148,7 @@ WCC provide a couple mechanisms for data loading. ### Constructor Props -Often for frameworks that might have their own needs for data loading and orchestration, a top level "constructor prop" can be provided to `renderToString` as the final param. The prop will then be passed to top level custom element definition's `constructor`. +Often for frameworks that might have their own needs for data loading and orchestration, a top level "constructor prop" can be provided to `renderToString` as the final param. The prop will then be passed to the custom element's `constructor` when loading the module URL. ```js @@ -156,7 +156,7 @@ const request = new Request({ /* ... */ }); const { html } = await renderToString(new URL(moduleUrl), false, request); ``` -This pattern plays really with file-based routing and SSR. +This pattern plays really nice with file-based routing and SSR! ```js export default class PostPage extends HTMLElement { constructor(request) { @@ -181,7 +181,7 @@ export default class PostPage extends HTMLElement { ### Data Loader -To support component-level data loading and hydration scenarios, a file with a custom element definition can also export a `getData` function to inject into the custom elements constructor at execution time. This can be serialized right into the component's Shadow DOM! +To support component-level data loading and hydration scenarios, a file with a custom element definition can also export a `getData` function to inject into the custom elements constructor at build time. This can be serialized right into the component's Shadow DOM! For example, you could preload a counter component with an initial counter state, which would also come through the `constructor`.