Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save marvinhagemeister/0672fed6c18a81f759bbd6ebbe9e98c7 to your computer and use it in GitHub Desktop.
Save marvinhagemeister/0672fed6c18a81f759bbd6ebbe9e98c7 to your computer and use it in GitHub Desktop.
Preact SSR collect web components
import { options, VNode } from 'preact';
import { renderToString } from 'preact-render-to-string';
// List of web components that were rendered
let webComponents = new Set<string>();
// Called when a vnode is rendered
let oldDiffedHook = options.diffed;
options.diffed = (vnode: VNode) => {
if (typeof vnode.type === 'string' && vnode.type.includes('-')) {
webComponents.add(vnode.type);
}
if (oldDiffedHook) oldDiffedHook(vnode);
};
// Turn `my-element-name´ into `MyElementName`
function toPascalCase(str: string) {
return (
str[0].toUpperCase() +
str
.slice(1)
.replace(/\w-\w/g, (m) => m[0] + m[2].toUpperCase() + m.slice(3))
);
}
// Render a vnode to HTML and add web component scripts
function renderWithWc(vnode: VNode) {
// RTS is synchronous, so we can rely on the global
let html = renderToString(vnode);
let rendered = webComponents;
// Make sure next render has an empty state to begin with
webComponents = new Set();
let scripts = '';
for (const name of rendered.values()) {
const pascalCase = toPascalCase(name);
scripts += `<script>class ${pascalCase} extends HTMLElement {}</script>\n`;
}
return scripts + html;
}
function App() {
return (
<my-element>
<div>
<my-other-element />
</div>
</my-element>
);
}
console.log(renderWithWc(<App />));
// Output:
// <script>class MyOtherElement extends HTMLElement {}</script>
// <script>class MyElement extends HTMLElement {}</script>
// <my-element><div><my-other-element></my-other-element></div></my-element>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment