Gutenberg made developing with WordPress 100x better

|

I started this blog around a month ago, and I was searching for the easiest WordPress plugin for formatting code, because if you couldn’t tell from my previous blog posts this is a pretty technical blog. I happened to stumble upon a SLEEK, and not to mention FREE, code formatting plugin: Code Block Pro.

It was so easy to install, and despite getting attached to one of the free themes, I liked the plugin so much I even paid for the extra themes!

The plugin was almost perfect! The only thing missing was the ability to pull code from GitHub. While I do have the ability to copy/paste the code from GitHub to the CBP text field, I wanted a more streamlined experience. Thus began my exploration of what the WordPress development experience looked like today, and how it’s grown since the last time I had to do any WordPress development back in 2018.

My experience in 2018

My first experiences with WordPress was back in 2018, when I worked for an advertising agency that predominantly did WordPress work. At that point in time I didn’t really do much development with *code*, I mainly used a page builder plugin called Cornerstone and a bit of CSS when I need to do something *extra* fancy.

After that job was when I had to really start digging deeper into PHP because at the next job I started developing custom WooCommerce themes. At that point in time, themes would require you to leverage what are known as WordPress “actions”. Actions were hooks that allow you to execute custom code at specific points during the WordPress lifecycle, and it was at those points where you would “output” the HTML and styles for your custom theme. Need a header? There’s an action for that, it would look something like this:

<?php

function output_custom_header() {
    echo '<div class="custom-header">';
    echo '<h1>Welcome to My Custom Header!</h1>';
    echo '</div>';
}

add_action('wp_head', 'output_custom_header');

Now, to some this may look completely normal, in fact they might even feel comfortable with this! Thing is though, the tooling around this style of development is so limited, that you really have to leverage your knowledge of the WordPress lifecycle in order to know how to modify certain components.

This was my experience using WordPress back in 2018, so naturally this is what I expected to have to deal with in order to make some changes to the CBP plugin.

The React & WordPress ecosystems converging

After doing some initial exploration of the CBP plugin’s source code, I found that they were actually using TypeScript now! I was absolutely floored to see TypeScript being used for something like WordPress. Not to mention, JSX!

When I was getting out of the WordPress world that was the point when Gutenberg had JUST been announced, and the reception was absolutely awful. Everyone hated it! To this day, if you look at the reviews on the Gutenberg plugin, they are mostly negative.

Younger Daniel was already on his way out, so I left just in time to not really have to learn what the deal with what Gutenberg was or how it worked. Little did I know, this plugin was a nice “eject” button out of the WordPress action centered UI development and into the JavaScript ecosystem.

Meanwhile, the JavaScript ecosystem had been maturing and creating things like JSX and UI libraries like React. React was designed to be declarative, and fits the style of development necessary for making UI pages and components. JSX had really helped the developer experience for making UI components by allowing you to have your HTML, CSS, and JavaScript all in one single file.

For those that aren’t developers or familiar with why React/JSX were good things, a key property of your component code is the concept of “co-location”, a principle that states that related code should be in the same file, or at least close together. The idea is to make it easier to find and modify pieces of a UI component that are related.

Convergence Point

You can actually get a bit of history on the Gutenberg repository from this discussion on it’s GitHub repository. Referencing that comment, Gutenberg had actually started out as a single package and eventually splintered into a ton of other extremely useful packages.

From those packages spawned a few that were being heavily used by CBP:

I never in a million years would have expected this level of change from the old “actions” way of doing things. This really felt like the JavaScript ecosystem that I was so used to. I instantly felt at home, and these packages instantly gave me the confidence that I could indeed create the CBP plugin features I had wanted to make.

Finishing the plugin changes

With all of the tools at my disposal, it was actually quite easy to implement the new features. Not gonna lie, it felt quite foreign to me using typical React hooks like useEffect.

I was also able to leverage the code created by the plugin author that updates the settings for code formatting. Thankfully the plugin’s frontend determines what settings were being saved per-block. Here is the code that I wrote for the plugin:

import {BaseControl, Button, TextControl,} from '@wordpress/components';
import {useEffect, useRef} from '@wordpress/element';
import {__} from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';

interface GitHubRepositoryControlProps {
    value: string;
    onChange: (value: string) => void;
}

export const GitHubRepositoryControl = ({value, onChange, onCodeFetched}: GitHubRepositoryControlProps) => {
    const inputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
        if (isValidUrl(value)) {
            fetchFile(value).then((code) => {
                if (value.indexOf("#L")) {
                    onCodeFetched({ code, lineNumbers: extractLineNumbers(value) });
                } else {
                    onCodeFetched({ code });
                }
            });

            inputRef.current
                ?.querySelector('input')
                ?.classList.remove('cbp-input-error');
        } else {
            inputRef.current
                ?.querySelector('input')
                ?.classList.add('cbp-input-error');
        }
    }, [value]);
    return (
        <BaseControl id="code-block-pro-show-highlighting">
            <div ref={inputRef}>
                <TextControl
                    spellCheck={false}
                    autoComplete="off"
                    type="text"
                    data-cy="github-repository-link"
                    label={__('Github Repository Link', 'code-block-pro')}
                    help={__(
                        'The link to your file. Supports github.com links, gists, and raw content links.',
                        'code-block-pro',
                    )}
                    value={value}
                    onChange={onChange}
                />
            </div>
            {isValidUrl(value) && (
                <Button
                    data-cy="manage-themes"
                    variant="secondary"
                    isSmall
                    onClick={() => fetchFile(value).then((code) => {
                        if (value.indexOf("#L")) {
                            onCodeFetched({code, lineNumbers: extractLineNumbers(value)});
                        } else {
                            onCodeFetched({code});
                        }
                    })}>
                    {__('Fetch Code', 'code-block-pro')}
                </Button>
            )}
        </BaseControl>
    );
};

function isValidUrl(str: string) {
    try {
        const url = new URL(str);

        return url.host === 'raw.githubusercontent.com' ||
            url.host === 'gist.githubusercontent.com' ||
            url.host === 'github.com';
    } catch (e) {
        return false;
    }
}

function extractLineNumbers(url) {
    const hashIndex = url.indexOf("#L");
    if (hashIndex === -1) {
        return null;
    }

    const strAfterHash = url.substring(hashIndex + 1);
    const strSplitByDash = strAfterHash.split('-');
    const lineNumbers = strSplitByDash.map(getLineNumberFromToken);

    const isFirstNumberValid = lineNumbers[0] > 0;
    const isSecondNumberValid = lineNumbers[1] > 0;
    if (!isFirstNumberValid) {
        return null;
    }

    return {
        startLine: lineNumbers[0],
        endLine: isSecondNumberValid ? lineNumbers[1] : lineNumbers[0],
    };
}

function getLineNumberFromToken(token: string) {
    const splitByColumn = token.split('C');
    const lineNumber = splitByColumn[0].substring(1);

    return parseInt(lineNumber);
}


async function fetchFile(url: string) {
    const response = await apiFetch({
        path: 'code-block-pro/v1/code',
        method: 'POST',
        data: {url},
    });

    return response.code;
}

I was even able to implement code that can update the lines that are highlighted!

Conclusion

I hope that this shows how much the WordPress developer experience has changed over the years, and inspires those that were skeptical of trying out WordPress to give it a shot! I know I was absolutely shocked at how much smoother the experience has become. Gone are the days of having to echo out HTML through actions, and gone are the days of having to painfully debug which action echo’d out some HTML.

If you’re used to the JavaScript and React ecosystem, you’ll feel right at home with the Create React App-like scripts. If you’re curious about what the rest of the code looks like, here is a link to the Pull Request on GitHub:

https://github.com/KevinBatdorf/code-block-pro/pull/252

Overall, I’m quite hopeful for the direction WordPress theme development is taking!