How to create a PHP class for a WordPress plugin that uses the same React and Webpack setup for Gutenberg blocks.
If you read through the WordPress Developer’s Handbook, there seems to be two main options when it comes to developing plugins:
- If you’re creating a plugin, you should use mostly PHP.
- If you’re creating a block, you should use React.
But what if you’re not creating a block, but you prefer working in JavaScript over PHP? What do you do then?
My solution is to start as though I’m creating a block, and then modify the project code so it’s more suited for being a plugin. This is what I’m currently doing for a plugin I’m working on that shows admin users what UI components are built in to WordPress.
Creating a plugin using create-block
You create a new block plugin using WordPress’ NPM script:
npx @wordpress/create-block sample-block
The result will be a plugin that is configured to use WordPress’ preferred Webpack configuration, which will import src/index.js
and transpile it to build/index.js
. In addition, the Webpack configuration creates an index.asset.php
file which contains any JavaScript dependencies that the WordPress software includes. For example, WordPress ships with a version of React, Lodash, Backbone, jQuery and other libraries. Instead of having to bundle these with your app, they can be listed as dependencies, thus reducing your bundle size.
If you look in the plugin’s only PHP file, you’ll only see this function:
function create_block_sample_block_block_init() {
register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'create_block_sample_block_block_init' );
This is a function for registering a WordPress block, using the transpiled code along with a block.json file for configuring the block.
Okay, but if we’re not creating a block, what do we do? We simply need to modify the PHP code to function more like a standard plugin, while keeping the bundling setup intact.
Changing the PHP functions
Let’s first review what we want or don’t want this plugin to do:
- It should not register a block for the WordPress block editor.
- It should show a menu or submenu item in the admin menu.
- When a user selects the menu item, it should show a page.
- It should only load related JavaScript or CSS when the user is on the plugin’s page.
Creating a class
First, I’m going to create a class like this:
if ( ! class_exists( ‘My_Plugin' ) ) {
class My_Plugin {
static $instance = false;
private function __construct() {
/* TBD */
}
public static function getInstance() {
if ( !self::$instance ) {
self::$instance = new self;
return self::$instance;
}
}
$My_Plugin = My_Plugin::getInstance();
}
What this does is creates a class that will contain all the functions necessary for setting up the plugin. I could just call functions directly, but encapsulating them in a class, and then confirming the class doesn’t exist better ensures there won’t be a conflict with other plugins.
Right now this code wouldn’t really do anything, so let’s add some functions.
Adding a plugin submenu item
public function add_menu_page() {
$hook = add_management_page(
‘My Plugin',
‘My Plugin',
'install_plugins',
‘my-plugin',
array( $this, 'admin_page'
) );
}
First we need to create the submenu page. In this case, we’re using add_management_page
to add a submenu item under Tools in the WordPress admin menu. There are other functions for adding submenu items or even menu items, but since this is an admin tool, I believe it belongs under tools.
In this function, we refer to a callback function called admin_page which is responsible for outputting the page’s content. We now need to create that function.
Creating a plugin page
public function admin_page(){
?>
<div class="wrap">
<h1>My Plugin</h1>
<div id=“my-plugin-app"></div>
</div>
<?php
}
All this function does is output, or render HTML that will appear on the My Plugin page. Since the plugin is going to use React, I have an empty div element with an ID that React will target. I’ll explain how that will work in a later post.
Loading plugin JavaScript and CSS
The next function is responsible for loading the JavaScript and CSS required for the plugin to function.
public function load_assets( $hook ){
if ( ‘tools_page_my-plugin' !== $hook ) {
return;
}
$asset_file = include plugin_dir_path( __FILE__ ) . 'build/index.asset.php';
foreach ( $asset_file['dependencies'] as $style ) {
wp_enqueue_style( $style );
}
wp_register_script(
'my-plugin',
plugins_url( 'build/index.js', __FILE__ ),
$asset_file['dependencies'],
$asset_file['version']
);
wp_enqueue_script( 'my-plugin' );
wp_register_style(
'my-plugin',
plugins_url( 'build/index.css', __FILE__ ),
array(),
$asset_file['version']
);
wp_enqueue_style( 'my-plugin' );
}
This function has a lot happening:
- First, the function checks to make sure the user is on the plugin’s page. It checks to see if the page’s $hook has
tools_page_
(for the Tools menu) followed by the slug used in theadd_management_page
function. If it doesn’t match, the function returns and ignores to subsequent code. - Next, we set
$asset_file
to use the generated index.asset.php in the build folder. This file is what Webpack creates normally for WordPress block plugins, but we can use it for any other plugin we want. - The function then loops through any CSS dependencies and enqueues them. React doesn’t have a CSS dependency, but WordPress components have JavaScript and CSS dependencies.
- Lastly, the function registers and enqueues the JavaScript and CSS.
Updating the class constructor
Try to run this code now and nothing will happen, because the My_Plugin class won’t ever call the add_menu_page or load_assets functions. We need to call them in the class constructor using WordPress’ add_action function like this:
private function __construct() {
add_action( 'admin_menu', array( $this, ‘add_menu_page' ) );
add_action( 'admin_enqueue_scripts', array( $this, ‘load_asset' ) );
}
One of the reasons we’re using WordPress’ add_action function is to call our functions at the right time when the WordPress application loads.
Right now, if you activated this plugin, selected Tools > My Plugin, you’d see a title with an empty page. That’s because we haven’t actually built an app to display on the page. Next time, I’ll share how to display a React app on a plugin page.