Gutenberg development practices and common pitfalls

Over the years of working on the GutenbergGutenberg The Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses ‘blocks’ to add richness rather than shortcodes, custom HTML etc. https://wordpress.org/gutenberg/ project, we developed some common practices. These help us preserve the quality and the long-term viability of the project. They differ and often contradict what people are used to in other projects (especially outside WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress.).

These practices should be applied during implementation and PR reviews. Sharing and embracing them helps contributors make the right decisions and be more autonomous when working on the project.

This list is non-exhaustive and subject to change. These are guidelines to always keep in mind but shouldn’t be treated as hard rules. Context is always important to consider.

Start APIs as private

Gutenberg is organized in packages. Implementing features means introducing new APIs to one or multiple packages. It’s important to understand that most of the packages APIs are public WordPress APIs. Anything exported from a package and consumed by another package becomes a defacto WordPress APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways.. This API needs to be maintained forever to adhere with the WordPress backwards compatibility strategy. Thus, it’s recommended to start with private APIs.

Be intentional about providing public APIs

Extensibility is important to WordPress. We do want pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party authors to extend what WordPress provides. We also want them to reuse tools that WordPress uses internally. This point can read as a contradiction with the previous one. But what’s important here is that it needs to be intentional. Before making an API public during the feature work, we should ask ourselves these questions:

  • Do we want to allow users to use this API? 
  • And are we willing to support it for the next 10 years without breaking changes? 

If the answer to both these questions is yes, then we can consider making this API public.

Avoid opening APIs for convenience, embrace copy/paste

Sometimes, for convenience and to abide to the DRY principle, we, developers create APIs and abstractions. This is seen as a good practice in most projects. But in the context of Gutenberg and WordPress, it is something to avoid. The cost of maintaining APIs is too high because of the backward compatibility strategy.

Say you have a low-level function or component already available to plugin authors or npm package users. To achieve their use-case, they maybe have to write some duplicate boilerplate code. Creating a higher-level abstraction to avoid the duplication should be avoided as much as possible. It is true that providing a great Developer Experience is important, though. There are cases where a higher-level abstraction is required. The main approach is that we should have very strong arguments for it and be intentional.

Prefer Semantic Extensibility

Plugin authors in WordPress want to be able to extend every piece of the UIUI User interface. They also want to give way too much prominence to their own extensions and upsells. As WordPress history teaches us, this quickly leads to a degraded user experience. Who has never seen a WP Adminadmin (and super admin) with hundreds of notices? Who has never seen a WP Admin with menu items and call to actions competing for the user’s attention?

A non curated set of extensibility APIs can lead to stagnation as well. One example here is the metaMeta Meta is a term that refers to the inside workings of a group. For us, this is the team that works on internal WordPress sites like WordCamp Central and Make WordPress. boxes API in the classic editor and how it created a lot of constraints for the development of the blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. editor. These kinds of APIs prevent iteration and evolution. It becomes very hard to improve the UI and UXUX User experience of these screens. Any change, no matter how small, can lead to breaking changes for plugin authors.

To alleviate these issues, extensibility in Gutenberg is curated and semantic. APIs to register/unregister semantic pieces (blocks, patterns, actions, fields) are preferred. APIs that are too broad and whose purpose is unclear should be avoided. For example: 

  • A slot in the editor headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. is not a very semantic API. It’s unclear what it will be used for. This will prevent any redesign and iteration to the header area of the editor.
  • The ability to set a priority or order to extensions is often an API that we want to avoid. It’s very prone to abuse. Often, plugins authors use these to give priority to their own extensions over what Core provides. It quickly leads to a degraded experience for users.

One package = one clear purpose

Developing features in Gutenberg often involves modifications to multiple packages at the same time. We need to ask ourselves: where should this piece of code live? To answer the question, it’s important to understand the purpose of each package. Here are some common examples:

  • Most of the editor UI is built within the @wordpress/block-editor package. So developers often change this package directly to accommodate their specific needs in the UI. The problem though is that it’s not the sole package responsible for adding UI to the editor. It is important to understand that this package is the Tinymce of Gutenberg. It can be used to build and integrate block editors within WordPress and outside WordPress. It’s a framework to build block editors. If a part of the UI is specific to WordPress, it’s not the right place to make this change. A recent example I noticed here is the addition of a button to the block settings menu. That button was used to open the WordPress editor sidebarSidebar A sidebar in WordPress is referred to a widget-ready area used by WordPress themes to display information that is not a part of the main content. It is not always a vertical column on the side. It can be a horizontal rectangle below or above the content area, footer, header, or any where in the theme., but editor sidebars are specific to WordPress block editors.
  • Making a REST APIREST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think “phone app” or “website”) can communicate with the data store (think “database” or “file system”) https://developer.wordpress.org/rest-api/. request in a UI component is another common pitfall that happens often. For example, you may want to build a Language Switcher UI component. And you want it to be reusable in the @wordpress/components package. You can’t use a REST API call within the component to fetch the available languages. The components package is a generic WP agnostic UI components package. So instead, it’s better to have a prop to pass of the available languages instead.

It’s also not recommended to add new packages just to share code between two existing packages. Usually, there’s a better solution to the code sharing problem.  @wordpress/utils package doesn’t exist for a reason. Utility packages are a place where we can just dump shared code without purpose. As mentioned earlier, copy/pasting is not always a bad idea when it comes to code sharing. It is preferable to avoid early abstractions as much as possible.

Measure then optimize

We, developers have a tendency to over optimize our code. Often these optimizations involve adding some complexity to the code, making it harder to maintain. It is better to avoid these early optimizations. Instead, measure the impact of the change you want to perform to validate that the optimization is worth the added complexity. Important metrics are tracked in the codevitals dashboard.

Other random common pitfalls

Besides the principles above that one needs to follow to make the right calls, here are some common pitfalls that we’ve learned to avoid over time:

  • Avoid passing editor settings as inline JSJS JavaScript, a web scripting language typically executed in the browser. Often used for advanced user interfaces and behaviors. from the backend to the frontend. It is a pattern we used heavily early in the project. We should ideally move away from it and prefer a better separation between client and server (use REST API calls).
  • Adding settings to the BlockEditorProvider (and the @wordpress/block-editor package) is ok. But we should be intentional about it: what does the setting mean for third-party block editors? Always remember the block editor framework use case.
  • Be declarative as much as possible. Using state actions and selectors to share UI state is acceptable. But know that it’s risky. It can lead to bugs and race conditions quickly if multiple components compete to set the right state. This happened to us at least in two recent situations:
    • Insertion position: We moved away from declaratively computing the insertion position based on the selected block. We used setInsertionPoint action and state instead. The issue is that the inserting point is now incorrectly set at times. Or it persists as we navigate away…
    • Block editing mode: To support different UI modes for blocks (content-only, locking, template-locked mode, zoom-out mode) we introduced a state to manipulate what we call “block editing mode” using setBockEditingMode action. We now have some hidden bugs because of that. These different modes compete to set the right block editing mode. 
  • Avoid using stores in new packages. Be careful when introducing new data stores. Each time we register a new store, there’s added complexity in the system. Stores are singletons by default, they can only be present once in the page. We do support multiple instances of the same store on the page but it’s not without complexity. We need to extend the global registry. We have also seen that using stores in bundled packages like @worpdress/interface is not a good idea. When a plugin wants to use that package/store it leads to the store trying to be registered twice. The wrong store will be accessed from the components when it happens. 

#gutenberg, #javascript