Using htm instead of JSX. Or, building blocks without a build step part 2

Last post I tried to make a case that you can indeed build blocks without a build process if you want to. The downside is that vanilla javascript gets a bit cumbersome when you get into rendering markup. Using wp.createElement is just plain annoying, and it gets hard to reason about when you’re looking at something like this…

[
    wp.element.createElement(
        MediaUpload,
        {
            onSelect: selectImage,
            render(renderProps) {
                return wp.element.createElement(
                    'button',
                    {
                        onClick: renderProps.open,
                    },
                    wp.element.createElement(
                        'img',
                        {
                            src: props.attributes.imgUrl,
                        },
                        null
                    ),
                )
            }
        },
        null
    ),
    wp.element.createElement(
        RichText,
        {
            tag: 'div',
            className: 'heading',
            value: props.attributes.headingText,
            onChange: function(value) {

So luckily there are some tools that make this much easier! htm is a run-time JSX-like tool that lets you ‘kind of’ use JSX.

Okay, so let’s get this set up and see how it feels…

First: credit to where it is due

A shoutout and thanks to Weston Ruter for pointing out the power of htm. If I hadn’t seen his post, I wouldn’t have known about this. He has a great example of extending an existing code block. Check it out.

Now, to add htm to your project

I’m going to use the repo that I had used in my previous post. The astute of you may have noticed that I was enqueuing a script in the plugin:

function htm_editor_script() {
    //Add htm to be available on the window!
	wp_enqueue_script(
		'htm',
		plugins_url( '/node_modules/htm/dist/htm.js', __FILE__ ),
		array(),
		filemtime( plugin_dir_path( __FILE__ ) . $htm_path )
	);
}
add_action( 'enqueue_block_editor_assets', 'htm_editor_script', 0 );

htm has ways for you to add it using a CDN. It has some nice pre-packaged preact+htm options which is intriguing to me. But in our project we need to install just the node module. I chose to go the npm route because I want to avoid using javascript modules for the time being, and that is the default mode for the cdn that htm supplies.

So, if you haven’t already and you’re following along, we need to run:

npm install htm

Our script is just loading htm from the node modules folder and putting it first in the enqueueing stack (that’s why there’s the 0 argument in the add_action function). If it wasn’t one of the first scripts, there’s a chance that htm won’t be present on the window by the time your block looked for htm.

The basic block

Just to prove that it works, here is my test-block-with-htm block:

(function () {

    const html = htm.bind(wp.element.createElement);

    const blockConfig = {
        title: 'Test Block with Htm',
        icon: 'smiley',
        category: 'common',
        edit(props) {
            return (html `
            <p>It works!</p>
        `);
        },
        save(props) {
            return (html `
            <p>It works!</p>
        `);
        }
    };

    wp.blocks.registerBlockType('no-build-blocks/test-block-with-htm', blockConfig);

})();

Some notes:

  1. Notice that I’m using an IIFE to make sure that things like my blockConfig variable are scoped to only this file
  2. I’ve started to separate out my config object from the registerBlockType function. I find this will be handy as I do more complex things, like modifying, mixing, or extending blocks
  3. We need to bind htm to the thing that actually creates elements in the editor- in this case wp.element.createElement(relative of react.createElement)
  4. But check it out! I get to use regular <p> tags!

Okay, so that works dandy, but it doesn’t show it’s benefits really. Let’s re-create the bio block (in the repo and from our last post) using htm…

Bio block with htm

Here is the vanilla block rewritten with htm syntax:

(function() {

    const html = htm.bind(wp.element.createElement);

    const {
        MediaUpload, 
        RichText
    } = wp.editor;

    const blockConfig = {
        title: 'Bio block with htm',
        icon: 'smiley',
        category: 'common',

        attributes: {
            imgUrl: {
                type: 'string',
                default: 'http://placehold.it/300'
            },
            heading: {
                type: 'string',
            }
        },

        edit(props) {

            return html`
            <div class="bio">
                <${MediaUpload}
                    onSelect=${({url}) => props.setAttributes({ imgUrl: url })}
                    render=${({open}) => (
                        html`
                            <button onClick=${open}>
                                <img src=${props.attributes.imgUrl} />
                            </button>
                        `)
                    }
                />
                <${RichText}
                    tagName="div"
                    className="heading"
                    value=${props.attributes.heading}
                    onChange=${heading => props.setAttributes({heading})}
                    placeholder="Heading text"
                />
                <${RichText}
                    tagName="p"
                    value=${props.attributes.bodyText}
                    onChange=${bodyText => props.setAttributes({bodyText})}
                    placeholder="Body text"
                />
            </div>
        `},
        save(props) {
            return html`
            <div class="bio">
                <img src=${props.attributes.imgUrl} />
                <${RichText.Content}
                    tagName="div"
                    className="heading"
                    value=${props.attributes.heading}
                />
                <${RichText.Content}
                    tagName="p"
                    value=${props.attributes.bodyText}
                />
            </div>
        `},
    }

    wp.blocks.registerBlockType('no-build-guy/bio-block-with-htm', blockConfig);

})();

And it looks identical to the vanilla implementation:

Cool, but worth it?

I have a few issues with this as I was porting my bio block over.

  1. Code syntax highlighting and code formatting won’t work here…. yet. It’s probably a dealbreaker for me. There’s no reason it can’t in the future, but out of the box JSX already supports that.
  2. You need to nest html template calls in any declaration of an htm element. In my media block I needed to re-call html`` because it had a render prop. This is just an annoyance- a necessary evil for using htm.

However, in the end, it worked well. I was able to get my block up and running, and it was rather JSX-eqsue feeling.

Not convinced

I’m giving these things a good run to see what they have to offer, but I’m still not convinced you want to do any large, production-level project with them. In the end, you might be better off with a regular webpack build that allows you to do modules (faster than js modules) and JSX (with syntax highlighting and autoformatting).

But hey, that’s my preference right now. I am hopeful that there will be modules that work in all platforms and can dynamically import assets while being just as performant as bundling. I hope that something like JSX will be a standard and considered “vanilla.” Someday…

Let me know what you think in the comments below, or @jschof

Leave a Reply

Your email address will not be published. Required fields are marked *