Skip to content

Instantly share code, notes, and snippets.

@Ruzgfpegk
Created March 12, 2021 08:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Ruzgfpegk/1fd16dcdaeb4efbf16ca3b81d3c1a243 to your computer and use it in GitHub Desktop.
Save Ruzgfpegk/1fd16dcdaeb4efbf16ca3b81d3c1a243 to your computer and use it in GitHub Desktop.
WordPress: Migrating to Gutenberg (WIP)

WordPress: Migrating to Gutenberg (WIP)

Introduction

One of the tasks that will happen more and more with time will be to convert existing WordPress websites that are based on third-party builders (Elementor, Divi, ...) to the new native builder of WordPress: Gutenberg.

The benefits are cost (if you paid for a full license of a builder), support (you won't risk compatibility issues by updating WordPress) and performance (most if not all contents of the pages are computed once on-save, instead as once on every load as with builders).

However, the switch is not easy at all from a developer perspective. The user experience can also be way worse, and some blocks required for the conversion may not exist.

On top of that, for developers it's like learning a whole new framework in functions, pipeline (if you never worked with NodeJS) and language (if you never worked with ESNext JavaScript). You thought you'd be a happy back-end developer, using only PHP and basic HTML/CSS? Well, too bad, now you also have a bunch of weird new JS and processes to deal with. Thanks, WordPress team!

So, in short, it's like a completely different project within your project.

The Gutenberg framework is a project started in 2017, and is still far from being finished (some would say "far from being usable" if you look at the notes and comments of the plugin), making it a constantly moving target for developers. For instance, the block API got quite a big change at the end of 2020 (WordPress 5.6) in how elements are embedded. The following will include information for this latest API version (2).

Prerequisites

Gutenberg relies heavily upon ReactJS, a JavaScript library from Facebook that is widely used to build interfaces. If you don't need to learn ReactJS to build most Gutenberg blocks, as the WordPress team managed to build an abstraction layer, having up-to-date knowledge of JavaScript could help a lot, especially when using (as recommended) the latest ESNext iteration of the language.

The two prerequisites for development are a WordPress directory and NodeJS: I'll suppose you already have the WordPress part, and for NodeJS you just have to download and install a LTS or Current version of the project from its homepage. Be sure to have it added to your OS's PATH.

Development process overview

The simple overview is like this:

1. Create a new plug-in
2. Plug-in folder overview
3. Reorganize the scaffold
4. Prepare JS+SCSS code for the editor
5. Prepare JS+SCSS code for the website
6. Compile the project
7. Use the blocks

Development process

Much of what follows is a revised version of the official Create a Block Tutorial.

One of the issues you may have is that there are many ways to do something with Gutenberg, and many tutorials will come with their own way or advise to use one of the previous official methods that are now obsolete.

To avoid adding chaos to an already chaotic situation, I'll mostly stick to the current recommended method even if it's not the most efficient. It's enough to start, understand how things work and doing stuff.

Create a new plug-in

You may be tempted to use WP CLI's wp scaffold block? Too bad, it's not the recommended way anymore. You may be tempted to use the create-guten-block script? Same thing. Avoid tutorials using one of those two, as there could be issues later on in instructions.

For the rest of this document we'll call our work plug-in "guttyblock". Change it to yours as necessary.

The "right way" is now to go to your plugins folder and to launch the scaffolder like this (change the last argument):

[plugins]$ npx @wordpress/create-block guttyblocks

This will download... more than 1000 dependencies using more than 250 MB, in the node_modules folder. Is it justified? No, this is complete bullshit, and part of why working with projects using NodeJS is a pain (besides the security issues and stability risks). And the same thing will happen in the build pipeline if you have one, unnecessarily using up precious write cycles of SSDs if any (hint: do that in ramdisk instead). Yes, you can tell NodeJS developers who think they have ecological values that they have a huge cognitive dissonance.

At this stage you may ask yourself "wait, do I have to write one plugin for every block? and download all this mess for each one?". Well, you surely can, as this is "the default way", but you shouldn't. More on that later.

For now, go to the newly created directory and edit its package.json file to adjust your name, the project description, add a "private": true, line if this is internal code (you never know), and the licence (but be aware that you're starting from "GPL-2.0-or-later" code... do your license should be compatible with that).

Also, add the following to top comment of the main PHP file of your plugin, as the generated scaffold uses the block API v2 that is only available starting WordPress 5.6:

* Requires at least: 5.6

Refer to the official documentation to know which other comments you could add.

Plug-in folder overview

What you have here is a scaffold, which is to say a project skeleton you'll build upon. Let's examine the most important files and folders:

/wp-content/plugins/guttyblocks/

  • node_modules/ : the cursed folder with all the dependencies. Never enter it. Exclude it in your IDE.
  • src/ : where you'll write your JS/JSX/SCSS code
    • index.js : the entry point of the JS compilation. Imports JS and SCSS files and links them.
    • edit.js : the JS/JSX code read in the WordPress editor to have dynamic blocks
    • save.js : the JS/JSX code that outputs the HTML which will be saved in the database
    • editor.scss : the CSS that will only be used inside the editor
    • style.scss : the CSS that will be used both inside the editor and on the front-end
  • build/ : where the final compiled JS/CSS will end up
    • index.js : the file loaded up in the editor to manage block interactions
    • index.css : the compiled version of editor.scss
    • style-index.css : the compiled version of style.scss
    • index.asset.php : how WordPress manages JS dependencies through PHP (let it do its thing)
  • package.json : the file read by npm to know what the project is about and what it needs.
  • guttyblocks.php : in your case [plugin_name].php. The file that registers the plugin and its block(s) to WordPress.

Reorganize the scaffold

If your plugin only contains one block, you could skip this section. It's not even in the official documentation.

But you'll likely want to create more, from two to a whole collection.

As described above, everything you'll need to edit (on top of the main PHP file) is in the src/ directory. The method I use and suggest is to create, inside the src/ directory, one subfolder per block. If you only have one block but don't know if you'll add more later, you could also do this.

Once the subfolders are created, just copy+move the files from src/ to all of them, and create an empty index.js in src/.

You'll end up with this hierarchy:

/wp-content/plugins/guttyblocks/src/

  • block1/
    • index.js
    • edit.js
    • save.js
    • editor.scss
    • style.scss
  • block2/
    • (same as above)
  • index.js (empty)

You can also keep the files in a special "template" folder for adding new blocks later.

At this stage, we broke everything and nothing would build. Congratulations.

Fix src/index.js

At the root of /src, you now have an empty index.js. This file is the "main" setting in the root's package.json and will be the default entry point of all commands, so it should link to all the files! In it, import all the modules from the subfolders:

import * as block1 from './block1';
import * as block2 from './block2';

Note that JS namespaces don't take dashes, so if you use them you'll have to do:

import * as block_1 from './block-1';

Update the blocks

Right now all blocks come from the same template, so they have the same name.

In each index.js, rename the block name in registerBlockType()'s first argument to:

namespace\blockname

namespace would logically be the plug-in folder name, and blockname the block subfolder name. In our example it would be guttyblocks\block1.

Also change the translation namespace in strings using the __() localization function, if needed (it should match the "Text Domain" setting in the head comment of the plug-in PHP file, don't worry if you don't have translation files for now).

Fix the plugin PHP

As you now have multiple blocks, you have to declare them to WordPress. The PHP file at the root of the plugin uses the same functions as themes and plugins to register JS and CSS files (wp_register_script() and wp_register_style()), but also uses the register_block_type() function to register each block to WordPress by assigning registered scripts and styles to block properties.

How you'll do it depends on how you want to want your blocks to be compiled (compiled into one set of files for all blocks or in one set per block), so this will be described for each case below.

Compiling all blocks into the same JS and CSS files

This is the way that leads to the less changes from the scaffold. The NodeJS "build" and "run" commands only produce one editor CSS, one main CSS and one editor JS, by grouping the different sources together. If you only have few simple blocks, this is probably the best way.

The output files in /build would be the same as shown above.

In your main PHP file at the root of the plugin, you'd just have to duplicate the register_block_type() function call for each block, only changing the first parameter to match the name of each block.

At this point the PHP side should be done, and you should be able to enable the plug-in without receiving error messages.

Compiling all blocks into different JS and CSS files

In this case you'll have to edit the package.json file to chain custom commands and bypass the main index.js completely.

Before:

"build": "wp-scripts build",

After:

"build": "wp-scripts build src/block1/index.js --output-path=build/block1 && wp-scripts build src/block2/index.js --output-path=build/block2",

You can also name the command differently, like "build-split", and/or split the command into multiple subcommands to call them from an "upper" one:

"build-split:block1": "wp-scripts build src/block1/index.js --output-path=build/block1",
"build-split:block2": "wp-scripts build src/block2/index.js --output-path=build/block2",
"build-split": "npm run build-split:block1 && npm run build-split:block2",

It's up to you. Just know that you can't have a single package.json script definition taking multiple lines.

The output files will then have the same names as seen before, but each block will have them in its own folder specified by --output-path (relative to package.json).

For more information about build customization, head over to the node_modules@wordpress\scripts\README.md document.

During development (not for the final build!), if you want to change the "start" command that compiles automatically on file change then change the "start" command the same way you did for "build" (the same settings and options apply).

In your main PHP file at the root of the plugin, you'd have to duplicate the register_block_type() function call for each block, specifying a different set of (newly declared) script and styles for each block.

At this point the PHP side should be done, and you should be able to enable the plug-in without receiving error messages.

Having multiple sets of files each with their own group of blocks

You can adapt the previous methods to have multiple sets of files, each containing the compiled result of a group of similar blocks for instance. I'll let it as an exercise to the reader.

Prepare PHP+JS+SCSS code for the editor

From now on, keep in mind that changes made to the CSS and JS files won't be seen until you recompile.

For now, refer to WordPress's official block editor documentation.

Prepare PHP+JS+SCSS code for the website

For now, refer to WordPress's official block editor documentation.

Compile the project

Either use your IDE's Run/Debug functionality (in PhpStorm: Run/Edit Configurations... , + on the top left, select npm, and the step you want in Scripts, either the default build or a variant you created, see above), or launch npm commands manually from the plug-in's root folder:

[guttyblocks]$ npm run build

Additional tips

Adding a custom block category

Putting all your blocks in a custom new category would help finding them in the editor.

Code is based on Loomo's article .

Add the following to your main PHP file:

/**
 * Define the "Guttyblocks" block category
 * 
 * @param $categories array Existing categories, sent by the block_categories filter
 * 
 * @return array
 */
function register_guttyblocks_category( array $categories ) : array {
	return array_merge(
		$categories, [[
			'slug'  => 'guttyblocks',
			'title' => __( 'Guttyblocks', 'guttyblocks' ),
			'icon'  => 'wordpress', // WordPress Dashicon slug or custom SVG
		]]
	);
}
add_filter( 'block_categories', 'register_guttyblocks_category' );

See the Dashicons main page for a list of icons you can use for your new category. The slug is the URL anchor, or what follows "dashicons-" in the description. This is completely optional (you can skip the "icon" parameter), but as no native WordPress category uses one you would find your category faster like that.

Then, in the registerBlockType() function call in the index.js of each block, register the block to the new category by changing the "category" parameter to the category slug:

category: 'guttyblocks',

Use the blocks

That should be the easiest part, right? Yeah, when the interface doesn't act all tsundere on you, it is.

Converting posts and pages

TODO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment