This project was scaffolded with
start- runs the wp-scripts start script
This project was initially scaffolded with npx @wordpress/create-block. Local development is similar to the standard IOP WordPress workflow, but for now, dev environment commands must be run in addition to our normal WordPress dev environment.
Local development is similar to the standard IOP WordPress workflow, but for now, it must be run in addition to our normal WordPress dev environment.
To get started, run npm run start from the wp-scripts default command set:
the resulting plugin includes the following example blocks:
This block is entirely JavaScript. Formatting options are provided by Block Supports.
TODO: Exiting this block is not as easy as native blocks like Paragraph, Quote or List. Check
RichText onSplit()for ways to improve this.
A basic dynamic block, this renders the current date from PHP using a format selected from the Block Editor. Supports colors and alignment via Block Supports.
This block uses a Composer autoloaded PHP library to process dynamic text before printing output.
The sidebar pulldown selector provides formatting choices for the dynamic date string.
This plugin adds two alternate metadata interfaces to the Block Editor. First, a Plugin Document Settings Panel is added to the list of Document attribute input. Second, a totally separate Plugin Sidebar and more-menu item provide the same options in a different place.
, but everyone agreed it was too hidden and easily forgotten. alternate interfaces for saving metadata to the currently edited post. These interfaces are completely interchangeable, values entered in one can be modified in the other. Values are not saved until the post is updated.
The two interfaces:
- A Document Settings Panel under the Document (Page) Settings tab.
- A Plugin Sidebar with an action-menu entry and logo-button next to the Update button.
My preference is the Document Settings Panel. This feels better integrated into the regular Block Editor experience, and is easily found and understood. The Plugin Sidebar introduces a completely new editing area which could be easily missed or forgotten about.
The core of current WordPress Block development is the block.json file, which is referenced from both PHP and JavaScript. Because of this, the JSON file must exist in the compiled output at a path relative to the JS files, but also somewhere known to PHP.
Since PHP is not processed, the plugin's source is separated into two like-named directories, one for PHP and one for processed assets like JS, SCSS and JSON.
The file tree of most blocks then looks something like this:
- includes/
- BlockName/ - PHP
- Block.php - initializes the block from PHP, and any other needed stuff
- BlockName/ - PHP
- src/
- block-name/ - JavaScript
- block.json - the main config file, also referenced from PHP
- index.js - Referenced from block.json's
editorScript - edit.js - The block's
editfunction and supporting code - view.js - Scripts for the front-end only, should import styles.
- styles.scss - Block specific styles
- block-name/ - JavaScript
For plugins, asset loading is mostly automated away thanks to @wordpress/scripts finding every block.json file anywhere in the src directory, then registering all referenced assets.
Random stuff and notes about poorly documented features
The @wordpress/dependency-extraction-webpack-plugin is a bit of a hack that sits on top of webpack externals and omits a subset of known scripts which are included with WordPress. Support for this plugin was added to our build toolchain in docker-build v0.12.0 and wp-theme-init v2.7.0, which now recognizes the plugin's *.asset.php snippet files.
While most all @wordpress scripts are available via the wp global, including these dependencies in development enables VS Code's JS-Intellisense helpers and creates a much more specific set of dependencies when including the scripts.
get_block_wrapper_attributes- returns a string of block attributes (class & style)
useBlockProps- Sometimes this will not include the full container className, but it will merge and return any arguments passed to it. Not sure why it would be missing the className, maybe this only happens for dynamic blocks where there's no save method?
While its values are stored in standard post_meta and can be retrieved with get_post_meta, using ACF values from the Block API requires a few additional considerations.
When enabled, the ACF plugin adds an acf attribute to the Post object. That attribute contains a key-value map of any defined ACF values. These can be accessed like this:
wp.data.select('core/editor').getEditedPostAttribute('acf');The catch is that the fields are not registered, so they do not appear alongside the Post's other meta values. The solution is to register the fields' metadata names from PHP with register_meta when the Block or block plugin is initialized:
// call from the init hook
$args = ['single' => true, 'show_in_rest' => true];
register_post_meta('page', 'acf_field_name', $args);Once the fields are registered, they can be accessed like this:
wp.data.select('core/editor').getEditedPostAttribute('meta');Important : If meta values are not registered from PHP using
register_post_meta, JS will be unable to save them.
More on registering WordPress metadata here: https://make.wordpress.org/core/2018/07/27/registering-metadata-in-4-9-8
I'm thinking about ways of migrating existing ACF field data into the Block Editor without additional plugins. The problem is that ACF doesn't seem to expose any of its configuration data to the API, so configuring field-data migrations can't be done automatically.
Another issue is that when editing posts, ACF input fields are saved after Block Editor metadata fields, so the ACF entries will overwrite the Block Editor entries, making it seem like the fields aren't saving.
This project was initially scaffolded by running npx @wordpress/create-block. To better fit our development practices, we added or modified the following:
@wordpress/scripts plugin-zip advanced configuration options references the package.json files property for specifying additional files to include in the distribution package.
TODO: Possibly move PHP files into block.json.render to be automatically included. Changelog, #43917
The Composer application is called from Docker via a set of basic script commands added to package.json and a simple docker-compose.yml wrapper. The vendor directory lives at the top level of the plugin, parallel to src, includes and node_modules.
Composer's autoloader is called from the plugin's entry PHP file.
We add version-everything, auto-changelog and Prettier's PHP Plugin.
a postbuild script which is just an alias to the plugin-zip script. When would we ever build without outputting a bundle?
A custom version-everything config was added to package.json config to enable version updates in the Stable tag: field of the plugin's readme.txt metadata.
Any files references from block.json are relative to the output, not the source file. I hate this. The references are somehow also used for webpack entry-points, which means they are simultaneously being used to determine the before while also knowing the after. It's really just madness.
Any block.json files in ./src will be scanned and used to generate entry-points for the plugin.
This will include any files referenced from all included block.json editorScript properties. File references are pre-compilation.
Sass and CSS files are are referenced using their post-compilation names. The source files should be imported into JS files so they can be handled by webpack. Output filenames are dependent upon where the source was imported.
For the Meta Sidebar example, a bare bones block.json file is used to assign entrypoints for the wp-scripts webpack build. This also handles loading the scripts, but since they're is no actual block to show, we can leave out nearly everything else. All that's needed for WordPress to load the scripts is a name. With that editorScript and other assets will load automatically when the block.json file is registered.
Complete block.json property reference
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "ideasonpurpose/example-meta-sidebar",
"title": "Example Meta Sidebar",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css"
}$schema- Helpful for developmentapiVersion- Not required since there are no blocks to save, but it's probably a good idea to include for future compatibility.name- The only truly required attribute, without this assets don't load.title- While the Block appears to work fine without this, the schema lists it as required.editorScript- This script will be loaded by the Block Editor.editorStyle- Styles for the editor. Paths for this and the editorScript property are both relative to the output which, as previously stated, is nuts.
Other useful attributes:
viewScript- Frontend-only javascriptscript- A common script for both the frontend and the block editor.style- Styles for both the frontend and the block editor.
These examples can be used in two forms. Each block can be individually added to a project, or the entire project can also be built and used as a plugin.
Each example includes two folders. One PascalCase containing PHP and the block.json file, and a kebab-case folder containing js and scss assets which need to be compiled.
To use the blocks individually, require the directory into your editor scripts, then require the view script into your front end scripts. The PHP class should be instantiated from functions.php.
// functions.php new Blocks\ExampleRichText\Block();// editor.js (editor scripts source) require('../blocks/example-rich-text');// main.js (frontend scripts source) require('../blocks/example-rich-text/view.js');A primary development goal is to easily migrate block code between themes and plugins. In most cases, blocks should exist as a plugin, so clients (and ourselves) are not stuck with a specific theme because content uses a block which only exists in there. Separation of concerns is important for longevity.
For now, all scripts should be added to
src/js/editor.jsandsrc/js/main.js. If the block becomes a plugin, then we can worry about separating individual scripts out for WordPress-optimized block asset loading.
There's a reason for this. Registering a block happens from PHP and from JavaScript. The register_block_type php function loads a JSON file which might also be loaded from JavaScript. If we compiled the JSON with the rest of the JS build, PHP would have nothing to load, or we'd have to come up with some sort of convoluted, fragile arrangement for preserving or delivering the JSON to a known location.
Place BlockName into <themename>/lib/ and block-name into <themename>/src/blocks
New Problem: The editor loads a ridiculous amount of scripts. If all the dependencies for blocks are loaded in the front end, we're suddenly transferring nearly 5 MB of JS on each page load.
Possible solution? Block assets need to be divided up into frontend and backend. If the wp-blocks script ends up as a dependency, WordPress will load several dozen additional scripts. So instead of one entrypoint index.js script, we'll have three available assets to match block.json scripts:
editorScript=>index.js(editor/backend only)script=>script.js(for both front and back end)viewScript=>view.js(frontend only)
Those filenames are terrible, I might switch them to editor, common and view. Or backend, common and frontend?
Huh, seems to actually be working!