Gutenberg Local Environment Rewrite

With the introduction of the WordPress Local Environment, the next step was to make this shiny new development environment available for Gutenberg to use.

tl;dr: If you currently run ./bin/ to use the Gutenberg environment, switch to running npm run env install, instead.

Gutenberg’s Local Environment was originally an experiment in making it easy to setup a local development environment. It was a success in some ways, in that it was easier than other methods, but it was by no means easy overall, particularly for non-developer contributors. Over time, it grew into a complex set of shell scripts, with surprising interdependencies and side effects. Being written in Bash made the scripts harder to maintain, and limited the potential audience to those who had a Bash-compatible terminal installed.

Basing Gutenberg’s Local Environment on Core’s has several immediate advantages:

  • It’s cross-platform, only requiring Docker to be installed and running. It also works with Docker Toolbox, allowing all Windows users (not just those with Windows 10 Pro) to use it.
  • The WordPress environment uses Docker images that we maintain for the purpose of Core development, which allows us to to customise them as we need.
  • It ensures that tests for Core and Gutenberg run in the same environment.
  • It’s a single environment, which helps avoid the naming clashes we’ve occasionally seen between the Core and Gutenberg environments.

More broadly for WordPress in the long term, tying the development processes of Core and Gutenberg a little closer together helps pave the way for the processes to merge over time.

Architectural Decisions

Removing the Docker config from Gutenberg entirely (barring a minimal template to hook into the Core environment), and requiring wordpress-develop to run on top of gives us significantly more future flexibility with the development environment.

Additionally, this continues to lay the groundwork for an environment that works as easily as possible for non-developer contributors. In particular, this environment is intended to be usable by all feature plugins, allowing them to easily enable new contributions in a familiar environment. (Or, an environment that will become familiar, at least. 😉)

There are several reasons for putting the main Docker config in Core, and extending it in Gutenberg:

  • Having a single docker-compose.yml file in Core, as opposed to having one in Core, one in Gutenberg, and many more in other feature plugins that will be able to use this, means that there’s only set of Docker containers running. Docker has been observed getting a little weird when trying to run containers with the same names, which mount volumes with the same names.
  • Having multiple docker-compose.yml files doesn’t lend itself to testing plugins together: each plugin ends up with its own environment which can’t talk to others.
  • Managing how plugins can mount older versions of WordPress becomes exponentially more difficult if the docker-compose.yml config needs to work for every WordPress branch (this is a requirement, so that auto updates can be worked on in old branches), rather than being able to tweak the config for each branch.
  • Ideally, feature plugins shouldn’t need to do much customisation of the Docker environment, beyond mounting their particular volumes. Exposing the entire docker-compose.yml file is overkill.

That said, not every Gutenberg contributor will have a WordPress source clone, or want to manage one. So, there’s also a helper which will download and install a copy of the WordPress source repo, when needed.

Much like the WordPress environment, however, the Gutenberg environment doesn’t automate everything. As we found with the original Gutenberg environment, too much automation tended to make it difficult (particularly for more advanced contributors) to customise or debug their environment. For example, the scripts no longer install NVM, switch your Node version, or run npm install.

Using the Gutenberg Local Environment

If you don’t have a WordPress repo to work with, just run npm run env install, that’ll take care of downloading WordPress, building it, and connecting Gutenberg into it.

If you prefer to hook into your own WordPress source checkout (particularly useful for fixing issues that span Core and Gutenberg), set the WP_DEVELOP_DIR environment variable to your WordPress source directory, and run npm run env connect. This will write a docker-compose.override.yml file to the WordPress source directory, and restart the Docker containers. The Docker override file contains the appropriate volume settings for mounting Gutenberg within the WordPress environment.

Running npm run env will give you a complete list of the available commands, and what they’re for.

The WordPress Local Environment configuration options are also available for configuring the Gutenberg environment.

Adding the Local Environment to another feature plugin

The @wordpress/scripts documentation includes information about including and configuring the local environment.

There currently isn’t a clean method for sharing a managed WordPress checkout (ie, one installed by npm run env install) between plugins. Let’s figure out how that could work!

Also, this process is still mostly untested, so please ping me earlier rather than later if you run into problems. 🙂

Developing Plugins/Themes with the Local Environment

At this stage, the Local Environment is intended for developing Core, and Feature Plugins: it doesn’t have the config options that the wider plugin/theme ecosystem needs, and the base Docker images are built for Core purposes.

Of course, I know y’all will try to use it, anyway. 😉 Testing and feedback is welcome, but please be aware that fixes and features will prioritise Core/Gutenberg requirements for the foreseeable future. Patches and PRs which recognise and allow for this are the best way for additional changes to land. 💖

What’s Next?

Please try out the new Gutenberg Local Environment, and report any issues you run into over on the Gutenberg repository.

If you have thoughts about making this aspect of contributing to Core/Gutenberg easier, I’d love to hear about them!

#block-editor, #gutenberg

Block Editor Detection Improvements in 5.2

In 5.0, WP_Screen::is_block_editor() was introduced to allow developers to conditionally execute code depending on whether the block editor is being loaded. This method returns the is_block_editor property of the WP_Screen instance. However, there were some large gaps in the loading process where an incorrect value could potentially be returned.

For example, when using the current_screen action hook, the value was always false, even when the user was visiting a block editor enabled screen. This happened because block editor support is flagged much later in the loading process when edit-form-blocks.php is included.

function myplugin_current_screen( $screen ) {
	if ( $screen->is_block_editor ) {
		// This conditional would never execute.
add_action( 'current_screen', 'myplugin_current_screen' );

This has been fixed in 5.2 to account for all possible scenarios when a post is edited. However, there are still a few very narrow edge cases when a new post is created where WP_Screen::is_block_editor() may still incorrectly indicate block editor support.

Edge Cases When Creating New Posts

The use_block_editor_for_post() function and replace_editor filter require a WP_Post object to be passed as a parameter. Because a new post has not yet been created when WP_Screen is instantiated, it can only make its best guess whether the page is loading the block editor.

When creating a new post, WP_Screen will set is_block_editor property to the value returned by use_block_editor_for_post_type() for the current post type. In most cases, this guess will be correct. But, the following scenarios have edge cases to consider.

  • When the replace_editor filter is used to replace the editor, this value may be incorrect.
  • When the use_block_editor_for_post filter is used to change block editor support.

For both of these scenarios, the use_block_editor_for_post_type filter can be used to ensure the correct value in most circumstances.

function myplugin_replace_editor_filter( $replace_editor, $post ) {
	// Logic to replace editor.
add_filter( 'replace_editor', 'myplugin_replace_editor_filter', 10, 2 );

function myplugin_replace_editor_post_type( $use_block_editor, $post_type ) {
	// Similar logic to replace editor, but without a WP_Post object to work with.
add_filter( 'use_block_editor_for_post_type', 'myplugin_replace_editor_post_type', 10, 2 );

With this filter, all scenarios that do not require checking a specific property of a post (a taxonomy term, meta value, etc.) can be accounted for. For example, filtering based on user capability, site option, or user meta value for editor preference are all possible using use_block_editor_for_post_type.

When WordPress creates a new post, it uses the get_default_post_to_edit() function. This function creates a new post in the database using wp_insert_post() and then allows the default content, title, and excerpt to be filtered. When terms, post meta, or content are added to posts with actions such as save_post or wp_insert_post, it is possible that this could change the block editor support for the post being created.

This scenario would result in WP_Screen:is_block_editor possessing an incorrect value from the current_screen action until roughly the load-{$pagenow} action.

Logic should be added to the use_block_editor_for_post_type filter to account for these scenarios (which are normally post type specific) and guarantee the accuracy of WP_Screen::is_block_editor().

For more information on this, consult the ticket on Trac (#46195), or the changeset ([45224]).

#5-2, #block-editor, #dev-notes, #gutenberg