james

Preact is a fantastic fit for bringing the power of a virtual DOM, and JSX, to existing projects. For one, it's small and light, enough not to exacerbate any pre-existing issues from other frameworks that might be in use. It's also API compatible with React, so you get access to the powerful ecosystem of third party components that exist today.

However, for non trivial uses, manually triggering the render of your components within an existing code base can become messy.

For example, say you want to Preact-ify some component that's rendered after a user interaction. Say the code base is largely built in jQuery, for the sake of argument. Now, we could call Preact's render function with our component directly in the event handler that displays this piece of content, or setup some kind of event system.

import { h, render } from 'preact';
import { SocialShare } from './socialShare';

$('.menu').click(() => {
render(h(SocialShare, {}), $('.output').get(0));
});

That works, but we're getting into a pretty muddy world that will only grow in complexity as time goes on. What we really need, is some way to make use of Preact in an isolated, well organised way. Here enters Web Components, or more accurately, HTML Custom Elements

TL/DR: The package preactement allows you to easily wrap any Preact component in a custom element. It provides both synchronous, and asynchronous rendering. React version of this library is also available.

Anatomy of the DOM

HTML Custom Elements, or "Web Components", allow us to tap into the underlying processes beneath our beloved elements. There are many great examples of how to make use of these, however, for the benefit of this article, we're going to focus on two of them; connectedCallback and disconnectedCallback.

These two methods allow us to hook into whenever the DOM recognises an element exists, or has been removed. This is significant, as it allows us to trigger code based on the existance of our custom elements.

For example, take this custom element: <social-share>

import { h, render } from 'preact';
import { SocialShare } from './socialShare.component';

/*[...]*/

class SocialElement extends HTMLElement {
/*[...]*/

public connectedCallback() {
render(h(SocialShare, {}), this); // magic!
}

public disconnectedCallback() {
// triggers componentWillUnmount
render(null, this);
}
}

/*[...]*/

customElements.define('social-share', SocialElement);

Now that we've defined our custom element, whenever <social-share> exists in the DOM, connectedCallback will be fired. If the element were to be removed, disconnectedCallback would likewise be called.

We can make use of these DOM lifecyle methods to trigger our Preact component's render, completely isolated from the rest of our code base. If we go back to our hypothetical jQuery project, all we'd need to do is define our component as seen above, and then use jQuery to add the respective element, <social-share> when needed:

$('.menu').click(() => {
$('<social-share/>').appendTo('.output');
});

Split all the things

The above example is a great start, but what if we're super performance savvy, and wanted to only load the component when actually needed? In the above example, we're statically importing SocialShare, which means our bundler of choice will just add this into our entry file. What we need is a dynamic import:

/*[...]*/

public async connectedCallback() {
const { SocialShare } = await import('./socialShare.component');

render(h(SocialShare, {}), this); // chunked!
}

This is better, but we're still introducing lots of boilerplate. For every component we create, a custom element must be defined with the above pattern. A handful of these don't pose to much of an issue, but if your goal is a gradual refactor towards Preact (or React, etc) components, this will grow over time.

What we now need, is some kind of re-usability for the above.

NPM all the things

Luckily for us, there's a great library created by the guys behind Preact: preact-custom-element. This provides the fundamental benefits of above, but without the repetition and ever growing boilerplate.

I honestly cannot express enough what an incredible job the team behind Preact are doing, they all deserve huge appreciation from everyone.

This package is fantastic if you're happy to statically import your components. It doesn't, however, provide (at time of writing) the ability to defer loading of your code until needed via dynamic imports. I needed just this usecase, so, standing on the shoulders and all of that, I borrowed a good chunk of this library to solve my needs.

The result of this was preactement. Having borrowed the general workings of preact-custom-element, I went to work extending this slightly to allow for asynchronous loading of Preact components within connectedCallback().

Now, whenever you want to Preact-ify some part of your existing code base, all you need to do is the following:

import { define } from 'preactement';

/*[...]*/

define('social-share', () => import('./socialShare.component'));

Your shiny new component will now be chunked via your bundler, and only loaded when the <social-share> element exists on your page, wherever that might be.

This package also provides some other useful bits and bobs. Say you want to provide some pre-built markup for your Preact component to consume via props.children. All you need to do is add this markup within your custom element:

<social-share>
<h3>Share this</h3>
<p>Get me likes</p>
</social-share>

When the component is rendered via preactement, this markup will be available via the children prop. This is useful if your custom element is being rendered by something other than Preact, like PHP etc. All you need to do is render your element using your template system of choice, and then run define() with your tag name, and component instance.

Thanks DOM.