Gutenberg track at JavaScript for WordPress conference

Growing JavaScript Skills with WordPress

This is a written version of the talk I gave at the JavaScript for WordPress conference on July 12th.

WordPress has always been recognized as a very welcoming platform for developers at any level of expertise. The block editor introduced in WordPress 5.0 release is not only an entirely new editing experience for users, but it also redefines the way plugins and themes are developed.

In this post, I want to explain many of the architectural decisions that have sought to make the transition as smooth as possible for those familiar with WordPress development. I will discuss all the tooling that the Gutenberg project uses behind the scenes to benefit from the massive growth of the JavaScript ecosystem. Finally, I’d like to demonstrate how you can leverage the same software in your projects using the @wordpress/scripts npm package to improve your skills.

History

First, let’s take a step back and try to understand better what role JavaScript has played in WordPress software since its initial 1.0 release. Thanks to version control systems we can travel back in time and inspect the old source code shipped 15 years ago. It turns out that there isn’t much JavaScript code there. There was only one external file loaded with the script tag. All remaining client-side code is inlined in PHP files with the logic that generates HTML output. I don’t find it surprising at all because it perfectly reflects what role JavaScript played at the beginning of the twenty-first century. It was used mostly for page redirects, showing alerts, handling forms and all that jazz. The example of the page load handler from the actual code seems to be a good illustration of how state-of-the-art looked back then.

<script type="text/javascript">
<!--
function focusit() {
    // focus on the first input field
    document.post.title.focus();
}
window.onload = focusit;
//-->
</script>

In the following years, we observed an increasing role of JavaScript, which resulted in the arrival of first libraries. They played a crucial role in making cross-browser development much more manageable, allowing to create advanced user interactions with less work. Besides, AJAX marked the mainstream breakthrough of JavaScript making full page reload no longer the only possibility. TinyMCE is an online rich-text editor, and the first advanced JavaScript library added to WordPress core in the 2.0 release in December 2005. It proved to be a long-lasting piece of technology, shaping the evolution of WordPress content editing for the next 13 years.

Classic Editor in WordPress 2.0
Classic Editor in WordPress 2.0

It didn’t take much time to see jQuery integrated into the codebase less than a year after its debut in August 2006. As it turned out later, jQuery – the most popular JavaScript library ever – was added in WordPress 2.2 release. Around the year 2010, first JavaScript frameworks were born. WordPress core developers bet on Backbone – a library with a RESTful JSON interface based on the Model–view–presenter application design paradigm. Backbone was included in WordPress 3.5 release at the end of 2012. All those technologies shaped how users edit their content, customize themes, or manage media in WordPress.

There are also other notable events that happened in parallel which initially didn’t impact WordPress software. I see them as the turning point for JavaScript to become a genuinely general-purpose language and let it dominate front-end development. In November 2009, Ryan Dahl announced Node.js – a server-side JavaScript platform – which allowed programmers not only to code in a familiar language but also share the same logic between client and server. Soon after, development started on the Node Package Manager (or npm for short) intending to make it easy to share code between projects. The npm Registry eventually became the world’s largest software registry with over 1 million open-source JavaScript packages hosted as of today.

The number of modules in various repositories
Module counts (http://www.modulecounts.com/)

In May 2013, Facebook announced they open-source a new library called React for building complex user interfaces. React took a revolutionary approach of declarative views composed of components that manage their state and keep it out of the DOM. A few years later, React can also render on the server using Node.js, and it powers native mobile apps using React Native. React’s model was influential, and today’s framework landscape (see Vue, Angular or Preact) is very different thanks to it.

Architecting Gutenberg

Now, let’s completely change the topic for a brief moment and discuss how the Gutenberg project is transforming the way content is created on WordPress. The block editor was the first product launched, introducing a new approach for working with posts and pages.

Blocks

The editor will endeavor to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg

Blocks are an abstract unit for organizing and composing the content for a webpage. Blocks are hierarchical, in that a block can be a child or parent to another block. One example is the Group block, which can be the parent block to multiple child blocks. Under the umbrella term block, we can also unify multiple different existing WordPress interfaces into one.

Demo of the Group block

If we look under the hood, blocks are a helpful tool to describe how to edit content stored as HTML. What stands out are the HTML comments used which can contain additional attributes encoded in JSON format. It’s a way to provide explicit, but invisible for the user, demarcation between blocks.

<!-- wp:image {"id":6616} -->
<figure class="wp-block-image">
    <img src="https://gziolo.pl/wp-content/uploads/2019/06/gutenberg-block.png"
        alt="Gutenberg block" class="wp-image-6616"/>
    <figcaption>Gutenberg block</figcaption>
</figure>
<!-- /wp:image -->

However, during runtime, the HTML markup gets parsed and converted to a tree of objects and associated attributes which are kept in memory. It allows recreating the internal data tree of the collection of blocks whenever a post is to be edited again. The example included demonstrates the idea in a very simplified way.

const blocks = [
    {
        name: 'core/image',
        attributes: {
            alt: 'Gutenberg block',
            caption: 'Gutenberg block',
            id: 6616,
            linkDestination: 'none',
            url: 'https://gziolo.pl/wp-content/uploads/2019/06/gutenberg-block.png',
        }, 
    },
];

Putting all that together, I want to emphasize that the content produced by Gutenberg isn’t designed for manual modification. That’s why we can think of the individual block as an independent app, which in the browser happens to be implemented with JavaScript, HTML, and CSS.

Gutenberg block
Implementation of Gutenberg block in web application
If you want to learn more about using HTML as the format for blocks, I recommend reading The Language of Gutenberg by Miguel Fonseca. 

Components and State

Gutenberg, to fulfill all the requirements stated above, uses technology stack entirely different than the one described when I was talking about WordPress history. Let’s try to discover why using jQuery, Backbone, or TinyMCE could only take us so far and how it led to extended explorations to find some suitable replacements. I’m sure there is more, but I want to highlight some significant issues with those libraries based on my experience.

jQuery did an exceptional job of hiding all the complexity of all those small differences between DOM implementation in different browsers. It ironically forced browser vendors to fix them on their side eventually, so it isn’t that much of an issue anymore. More importantly, the fact that jQuery highly depends on DOM makes it nearly impossible to manage shared state when multiple sources want to modify it. In other words, using HTML data attributes has limited application in this context.

Backbone helps to solve some of the issues discussed, jQuery struggles with, by introducing the concept of views and models. However, it turns out that this approach doesn’t scale well, either. The biggest challenge remains unsolved. That’s how to ensure that all models which communicate with REST API can stay in sync with each other in an environment where everything needs to react to events from multiple highly interconnected sources.

React

So you are probably wondering now why would React be any better? I wasn’t involved in the development process of the block editor early on. I joined the core team sometime after everyone could install and test the Gutenberg plugin. With that in mind, I want to share massive admiration for how the block paradigm aligns with the React architecture and the concept of components.

Build encapsulated components that manage their own state, then compose them to make complex UIs.

React website

Suddenly, you can easily pass data through your app and keep the state out of the DOM. In principle, you no longer have to worry about manipulating DOM anymore because React handles it behind the scenes by using its own Virtual DOM. It not only improves performance but also allows to process React-based code outside of the browser.

Redux

Centralizing your application’s state and logic enables powerful capabilities like undo/redo, state persistence, and much more.

Redux website

There is another aspect which another library solves, how to efficiently manage the application’s shared state like the list of blocks inserted in the editor. Redux has a reputation for being difficult to follow, though. However, there was an excellent way to address it by sticking as close as possible to its most straightforward concepts and limiting exposure of the internal machinery. The final solution includes support for multiple stores where each plugin has the freedom to define its isolated state, which plays nicely with the need for having the WordPress core highly extensible.

Extensibility

Continuing on the last point, WordPress’s extensibility lies in the thousands of hooks at your disposal on the PHP side of things. The WordPress APIs allow you to change the default behavior within WordPress and add your functionality without modifying core files at all. However, it wasn’t possible to replicate the same patterns on the client-side in the pre-Gutenberg world because formal APIs were missing. Developers could still work around it by using hooks on the server to customize the initial HTML output rendered and then sprinkle some jQuery code to change user interactions. This approach is extremely fragile, as it highly depends on the DOM structure.

WordPress combines simplicity for users and publishers with under-the-hood complexity for developers. This makes it flexible while still being easy-to-use.

WordPress Features page

Applying the concept of WordPress hooks in JavaScript was essential when architecting the block editor. Beyond mere compatibility, it embraces the potential of the plugin and offers the tooling necessary to enhance the experience. Actions and filters are undeniably familiar to WordPress developers and largely contribute to the success of this CMS. Besides, the patterns exposed to plugin and theme authors are the same that the block editor’s interface or core blocks use internally.

The way extensibility is implemented still evolves to accommodate all the challenges that asynchronous nature of JavaScript introduces. It’s also a big subject on its own and therefore better suited for separate considerations.

The best way to start exploring this topic is this fantastic blog post One thousand and one way to extend Gutenberg today by Riad Benguella who is the current technical lead for the project.

Modular Architecture

Finally, WordPress 5.0 introduces many new bundled script handles, including many specific to the new block editor, as well as a few vendor dependencies. All new WordPress-specific JavaScript code is registered using wp- as a prefix for the script handle. Each WordPress module assigns itself to the global scope under the wp namespace. It’s the same pattern that much of the existing WordPress’s custom JavaScript functionality used before on the admin and editor screens. For example, if your theme or plugin enqueues wp-components, it makes it available at window.wp.components in your browser. The idea is to bridge these generations in a way where new code remains approachable for those who’ve become accustomed to the old way, while still pushing the boundaries toward modern development.

WordPress modules under wp namespace

In addition to that, all new modules are regularly published to npm as independent packages under the @wordpress organization. The best part is that you can use any of over 60 packages in other JavaScript-based projects. It’s possible because they are shaped after the task they are trying to solve, which makes them genuinely general purpose and thus maximizes towards code reuse. It’s also self-evident that you cannot expect any developer to have a full understanding of a complex system, so having small modules with dependencies defined between each other helps a lot. This approach so far seems to be successful as it has already provided a viable way to integrate the Gutenberg editor in Drupal and Laravel projects. Isn’t it powerful?

Gutenberg integrated with Drupal
Gutenberg integrated with Drupal

It also allowed sharing the same codebase to build the native mobile version of Gutenberg written in React Native. Both Android and iOS versions of the WordPress mobile apps offer support for the most popular core blocks when editing posts or pages. There are exciting plans for the future where we envision that developers are equipped with ways to build cross-platform blocks or UI plugins which adapt to the context where they are used. To get us there, we still need to develop a compatibility layer which hides HTML syntax used for web. Such a collection of components should abstract away DOM and let it translate to native alternatives included in React Native.

Demo of Gutenberg in iOS app

Embracing Modern Tooling

Now, let’s try to find all those points that might help us, developers, with a day to day work. I hope that you agree that the JavaScript ecosystem has reached an exciting point in its history. On the one hand, there are members of the community overwhelmed by the learning curve required to discover the magnitude of newly introduced language features and accompanying tools. It makes it feel like starting a new project is a big challenge on its own, and based on that, some programmers express JavaScript fatigue.

On the other hand, some celebrate this era as the Renaissance of JavaScript. It stems from the fact that currently, you can create anything you want using this language: clients and servers, command-line utilities, games, and the like. You can even control hardware devices. The sky’s the limit.

That’s are clear signs of the Renaissance for JavaScript. Renaissance was a time when people became creative, and JavaScript is currently a place where creativity thrives.

Alexander Beletsky

WordPress JavaScript core team from the start of the project has to battle an apparent difficulty, how to take advantage of all variety of existing modern tools and new language features to empower developers. At the same time, it’s essential to change the first impression that building JavaScript-based user interfaces is so complicated. There is an obvious solution which Matt Mullenweg suggested in his keynote at WordCamp US back in 2015:

Learn JavaScript, deeply.

Matt Mullenweg

However, it takes time, a massive amount of time, as we can learn from the progress in the last 4 years. That’s why I’d like to discuss some strategies which WordPress core embodied to make it easier to get up to speed when working with Gutenberg.

JavaScript Versions

For simplicity, it’s still possible to program using ECMAScript 5 (or ES5 for short). This well-established edition of JavaScript standard can run straight in the vast majority of browsers and does not require an additional build step. It’s also what you would use in WordPress projects in the past. In many cases, it’s perfectly fine to follow the same approach to start experimenting with your plugin or theme quickly. The following example demonstrates the idea.

( function() {
	var el = wp.element.createElement;
	var __ = wp.i18n.__;
	var registerPlugin = wp.plugins.registerPlugin;
	var PluginPostStatusInfo = wp.editPost.PluginPostStatusInfo;

	function MyPostStatusInfoPlugin() {
		return el(
			PluginPostStatusInfo,
			{
				className: 'my-post-status-info-plugin',
			},
			__( 'My post status info' )
		);
	}

	registerPlugin( 'my-post-status-info-plugin', {
		render: MyPostStatusInfoPlugin
	} );
} )();

As soon as your codebase grows to hundreds of lines, it might be a good idea to get familiar with the build tools and set up a development environment to use ESNext and JSX syntax. Let me ensure we all are on the same page. ESNext is JavaScript code written using features that are only available in specification updates published yearly starting from 2015. JSX is a custom syntax extension to JavaScript used with React which helps you to describe the UI similar to a templating language based on HTML. The previous example would look as follows:

import { __ } from '@wordpress/i18n';
import { registerPlugin } from '@wordpress/plugins';
import { PluginPostStatusInfo } from '@wordpress/editPost';

const MyPostStatusInfoPlugin = () => (
	<PluginPostStatusInfo className="my-post-status-info-plugin">
		{ __( 'My post status info' ) }
	</PluginPostStatusInfo>
);

registerPlugin( 'my-post-status-info-plugin', {
	render: MyPostStatusInfoPlugin,
} );

Most browsers cannot interpret or run all ESNext features and JSX syntax, so we use a transformation step to convert this code back to the representation which browsers can understand – ES5-based JavaScript. However, there are a few reasons to use ESNext even though it adds extra effort. First, it offers a simpler code that is easier to read and write. Using a transformation step allows for tools to optimize the code to work on a large variety of browsers. Also, by using a build step, you can organize your code into smaller modules and files that can be bundled together into a single download. I hope to encourage you to set up a development environment to use the latest JavaScript syntax. I’m going to explain how to do it later.

Code Reusability

I mentioned earlier that all modules developed for the Gutenberg project are exposed in the global scope under the wp namespace in WordPress. This approach makes it possible to reuse every part of public API even when you decide to stick with ES5 standard only. JavaScript as a language is fully backward compatible, which very closely aligns with the philosophy that WordPress shares. In this place, I should also make it clear that even when you opt-in for the build step to write your program with ESNext or JSX, it’s perfectly fine to mix and match it with chunks of ES5 code freely.

It’s also possible to use all npm packages discussed earlier outside of WordPress. Core JS team put lots of care to ensure that the majority of them can work in any JavaScript project. You can even create your customized version of the block editor using all the Lego sets available. There is an example included in the Gutenberg project, as well. This playground works fully outside of the WordPress context.

Gutenberg Playground
Gutenberg Playground built out of WordPress packages

Furthermore, there are also UI Components contained in @wordpress/components package, which are my favorite. I hope they are going to evolve in the design system and pattern library for all WordPress projects. When you decide to use them, you get an essential theming, unified behavior and accessibility support out of the box. It should help to bring some consistency in your work and positively impact the broader ecosystem.

Code Quality

Software testing helps protect the code from incoming bugs and improves the general quality of the functionalities exposed to the users. When you look at it from the developer’s standpoint, the first thing that comes to mind is unit testing. However, it turns out that verification comes in many flavors. Gutenberg itself contains both PHP and JavaScript code and encourages testing and code style linting for both.

Tests are essential not only because they help to ensure that our application behaves as it should, but also because they provide concise examples of how to use a piece of code. In particular, End-to-End (E2E) testing involves ensuring that the integrated components of an application function as expected. The entire application is tested in a real-world scenario, such as communicating with the database, network, hardware, and other systems. When your plugin’s codebase grows, and the UI becomes complex, it pays off to invest time on building test coverage using E2E approach.

End-to-end tests running for Gutenberg

Linter is a tool that looks for issues in programs by scanning its source code. If it finds a problem like a syntax error, it returns a message describing it and an approximate location within the source file. Linter looks at some style convention violations or structural problems, too. It doesn’t prove that your code is correct but only provides another set of eyes to help spot issues early on. It makes it extremely useful when you want to use new language features, or new team member joins the project.

Reusable Scripts

Lastly, let’s discuss how tools used internally in the Gutenberg project can positively impact workflows used to develop plugins and themes for WordPress. Undeniably, when working seamlessly, sophisticated command-line interfaces help to turn work with a project into a more pleasant experience. However, it’s a misleading assumption that developers can easily pick the proper tools in the first place and then ensure that they play along with each other, including all their extensions. Besides, it’s still not enough because developers are left on their own to keep all configurations and dependent tools up to date. This problem multiplies when they support more than one project which shares the same setup.

Fortunately, there is a pattern that can simplify maintainers life – reusable scripts. The idea boils down to moving all the necessary configurations and scripts to one single tool dependency. @wordpress/scripts is such collection of reusable scripts tailored for WordPress development. It’s distributed as an npm package which allows to seamlessly incorporate more advanced JavaScript tooling like build step, code transpilation, linting or testing. It certainly isn’t a novel approach. react-scripts and kcd-scripts packages were the source of inspiration and profoundly influenced the actual implementation.

Setup

To set it up, you need to install one npm module:

npm install @wordpress/scripts --save-dev

It offers a command-line interface and exposes a binary called wp-scripts so you can call it directly with npx – an npm package runner. However, this module is designed to be configured using the scripts section in the package.json file of your project. This comprehensive example demonstrates the most of the capabilities included.

Example:

{
    "scripts": {
        "build": "wp-scripts build",
        "check-engines": "wp-scripts check-engines",
        "check-licenses": "wp-scripts check-licenses",
        "lint:css": "wp-scripts lint-style",
        "lint:js": "wp-scripts lint-js",
        "lint:pkg-json": "wp-scripts lint-pkg-json",
        "start": "wp-scripts start",
        "test:e2e": "wp-scripts test-e2e",
        "test:unit": "wp-scripts test-unit-js"
    }
}

In most cases, it should be possible to accomplish all tasks using the recommended settings. While it’s possible to override every single config file provided, if you have to do it, it means that your use case is far more complicated than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and set up your project with full control over tooling used.

Building

Based on the feedback received so far, I can surely tell that setting up a development environment is the biggest hurdle for plugin developers when they want to build their features on top of Gutenberg. That’s why there is build script which transforms your code according to the configuration provided, so it’s ready for production and optimized for the best performance. The entry point for your project’s code should be located in src/index.js. The output generated is written to build/index.js. This script exits after producing a single build. For incremental builds, better suited for development, there is also start script.

With a setup in place, the standard workflow looks as follows:

  1. Install dependencies: npm install.
  2. Start development builds: npm start.
  3. Develop. Test. Repeat.
  4. Create production build: npm run build.

I probably should mention that those scripts under the hood use highly popular tools like webpack and Babel. Again, the default configuration should cover the most common use cases, but you can refer to the @wordpress/script’s documentation if you want to use multiple entry points, a different output directory or something along those lines.

Other Tasks

I also wanted to highlight other handy tasks which are invaluable when you want to improve the quality of your code shipped regularly to users:

  • lint-js – helps enforce coding style guidelines for your JavaScript files and uses ESLint with the set of recommended rules defined in @wordpress/eslint-plugin npm package.
  • test-e2e – launches the End-To-End (E2E) tests runner. Writing tests can be done using the Jest API in combination with the Puppeteer API. Puppeteer provides a high-level API to control Chrome or Chromium over the DevTools Protocol. It runs headless (in the terminal) by default but can be configured to run an actual browser. Mozilla team works on Firefox integration with Puppeteer, and they track progress using the test suite written for the Gutenberg project.
  • test-unit – launches the unit tests runner. Writing tests can be done using the Jest API. By default, it uses the set of recommended options defined in @wordpress/jest-preset-default npm package.
The result of running lint-js script
The result of running lint-js script

There is more. You can learn more by browsing more detailed documentation for @wordpress/scripts package on npm.

Key Takeaways

There are a few things I would like to emphasize before I wrap up:

  • Good old ES5 standard is still relevant and can be used to extend the WordPress block editor features.
  • ESNext and JSX will make your life easier, but it takes lots of effort to get intimately familiar with them. 
  • You can reuse pieces of the Gutenberg project at any level of granularity: packages, blocks, components, or functions. You aren’t tied to the WordPress ecosystem at all.
  • Get familiar with modern JavaScript tools and start using only those which fit your use case. Exploring @wordpress/scripts package should be an excellent way to start your journey.

Learn JavaScript deeply and prosper!


Kudos to my colleagues Andrew Duthie, Miguel Fonseca and Stefanos Togoulidis for all their feedback shared. If you found this post inspiring, you should consider joining us at Automattic as a JavaScript Engineer.

Published by

Grzegorz Ziółkowski

Husband, father, web enthusiast, programmer and basketball fan living in Oleśnica, Poland. JavaScript Wrangler at Automattic

One thought on “Growing JavaScript Skills with WordPress”

Comments are closed.