PHP Web Components for WordPress
Update 2: See this post about using Laravel Blade in WordPress: https://junaid.dev/laravel-blade-components-in-wordpress/
Update: See the concepts in this article used in a project structure: https://github.com/junaidbhura/wp-project-base
The state of web components
Modern front-end web development encourages breaking up of designs into reusable, repeatable and unique components. A component is a piece of markup capable of holding – but is separate from – content within it.
The Web Components specification provides a great way to work with components using the Shadow DOM and other things that are native to the browser. This would (should?) be the go-to solution for web components – since it is an actual W3C standard. However, there are several reasons I personally do not like to use them – the main reason being the fact that components are rendered in the browser using JavaScript. For websites where SEO metrics like CLS are important, it seems almost inconceivable to use them.
Other front-end technologies like React reach for Server Side Rendering or similar methods to overcome CLS and other issues with rankings, where the user is served a static HTML page.
Components using PHP
The problems stated above do not exist in the world of WordPress where we use PHP for templates, since users are always served dynamically generated static HTML pages. Static in the sense that they are not generated by the browser.
So creating components using PHP seems to hit the sweet spot:
- Markup is already generated by the server
- It works similar to how Web Components do – in that JavaScript functionality is achieved by manipulating the DOM – rather than the Virtual DOM like what React and others do, which hydrate the front-end once a Server Side Rendered page is served
PHP Components in WordPress
Creating PHP components in the WordPress context basically boils down to structuring the theme in a specific way, and making use of template parts. In order to get all the benefits that Web Components (and React and other components) provide, we need to first define what we want our components to do:
- Components need to exist in a single place (file). Updating the component in this file must update it everywhere it is used on the website
- Components need to be void of context: A component’s only purpose is to be a container of content
- Components need to be flexible enough to work with any front-end methodology or utility-class-driven framework like Tailwind CSS
- Components need to be flexible, depending on the context or content passed into it
- Bonus: Components need to work in the Gutenberg block editor, and can be changed or updated without affecting any posts that have already used it
- Bonus: Components must handle their own CSS and JavaScript
Solution Source Code
Here’s a Git repository I put together to illustrate my solution:
https://github.com/junaidbhura/wp-php-web-components-demo
The Component File
<?php
/**
* Hello World.
*/
$attributes = wp_parse_args(
$attributes ?? [],
[
'title' => '',
'subtitle' => '',
]
);
if ( empty( $attributes['title'] ) ) {
return;
}
wp_enqueue_style( 'hello-world', get_template_directory_uri() . '/components/hello-world/style.css' );
?>
<div class="hello-world">
<h1><?php echo esc_html( $attributes['title'] ); ?></h1>
<?php if ( ! empty( $attributes['subtitle'] ) ): ?>
<h2><?php echo esc_html( $attributes['subtitle'] ); ?></h2>
<?php endif ?>
</div>
The component is essentially a template part. This is the easiest implementation of a component and is similar to WordPress core’s get_template_part
implementation.
What this component does:
- Sets default values
- Does not render at all if a required field is missing
- Enqueues a stylesheet – so the stylesheet is added to the page only if this component is called at least once – reducing CSS bloat on the front-end
- Shows the subtitle only if it was passed into the component
- Handles all escaping
We would call this component like this:
<?php
jb_component(
'hello-world',
[
'title' => 'Title goes here',
'subtitle' => 'Subtitle goes here',
]
);
?>
Example: Click here
This is in contrast to Laravel’s implementation of components, which tries to replicate the standard Web Components specification in PHP – slots and all. If we were to use its implementation, we would do something like:
<x-hello-world :title="Title goes here" :subtitle="Subtitle goes here"/>
Although this is admittedly better looking, and much closer to the official Web Components spec and follows an XML-like syntax which React developers would love – it adds overhead to the project – both in terms of build tools and complexity.
What we have done instead achieves the exact same goal, is more flexible and allows us to easily enqueue assets (more on that later) – in a super simple way with no overhead whatsoever.
The Template
You’re probably wondering how the enqueue works within the component, since the component would technically be rendered after the header – and therefore wp_head
hook is called. This is achieved with two template tag functions:
/**
* Start building template content.
*/
function jb_template_header() {
ob_start();
}
/**
* Finish building template content.
*/
function jb_template_footer() {
// Get built content.
$content = ob_get_clean();
// Render built content after header.
// This is to trigger all hooks before the header is called.
get_header();
echo $content; // phpcs:ignore -- No need to escape it, since all escaping happens in the template / components.
get_footer();
}
So you would call jb_template_header
instead of get_header
– and jb_template_footer
instead of get_footer
in the template. This builds all the content before wp_head
has had a chance to execute.
Note: This is similar to how blocks work – they are rendered before the header is called, so enqueues will still work in blocks.
The Block
Gutenberg blocks in WordPress have two functions:
- Edit: This function executes within the block editor and contains editor components as well as markup from the component
- Save: This function is responsible for what we save into the database which contains HTML markup
So we essentially have two copies of the markup we need to maintain when working with blocks. Saving markup in the database poses a big problem: If the markup ever changes, all the blocks need to be updated within the database. So one needs to be very careful when designing a block to decide what markup needs to be saved to the database. This is especially a problem if you use something like Tailwind CSS, which essentially works by saving a whole bunch of utility CSS classes in the markup. You could just use @apply
in Tailwind to overcome this – but it still doesn’t solve our problem.
After a lot of consideration and actually having worked on projects where we save the markup in the database, I now recommend against doing this. I would instead recommend saving all blocks as dynamic blocks to future-proof them. Simple blocks / inner content can still be saved without a problem.
Dynamic blocks work by saving all data to the block’s attributes. We still need to maintain two copies of the markup – but one copy would be in the block’s Edit function – and the other would be in the component partial – which is called within the block like this:
<?php
/**
* Block: Hello World.
*/
namespace JB\Theme\Blocks\HelloWorld;
const BLOCK_NAME = 'jb/hello-world';
const COMPONENT = 'hello-world';
add_action( 'template_redirect', __NAMESPACE__ . '\\register' );
/**
* Register block on the front-end.
*/
function register() {
add_filter( 'pre_render_block', __NAMESPACE__ . '\\render', 10, 2 );
}
/**
* Render this block.
*
* @param mixed $content Original content.
* @param array $block Parsed block.
*
* @return mixed
*/
function render( $content = null, $block = [] ) {
// Check for block.
if ( BLOCK_NAME !== $block['blockName'] ) {
return $content;
}
// Build component attributes.
$attributes = [
'title' => $block['attrs']['title'] ?? '',
'subtitle' => $block['attrs']['subtitle'] ?? '',
];
// Return rendered component.
return jb_component( COMPONENT, $attributes, false );
}
A few things to note:
- We use the
template_redirect
hook to register this block – because we don’t want to unnecessarily execute this code in the back-end for performance reasons - We render the block using the
pre_render_block
hook rather than registering the block via register_block_type. This is again for performance reasons – we short-circuit the block by adding our own render function - Notice how the render function simply takes the attributes from Gutenberg and passes it to our component
So we have a clear separation of concerns here:
- The block is only responsible to maintain data, not presentation
- The component is only responsible for presentation, not data
Conclusion
To summarise, this is what we achieved:
- We created a single component called “Hello World”
- We used this component directly in our template by calling it via
jb_component
- We created a block, also called “Hello World” which called the exact same component
Looking back at our goals, lets see how we did:
✅ Single component file. If the design were to ever change, we change it in this one place (and related blocks for the editor)
✅ Component has no context – it simply accepts data – whether it comes from a template or a block
✅ It can work with any CSS methodology or framework
✅ Works with Gutenberg – blocks can be safely updated since no markup is ever saved in the database
✅ Our component handles its own assets
Looks like we have a winner! 💪
2 responses to “PHP Web Components for WordPress”
I read the article about the component approach with interest, thank you. It was interesting to look at your code-style, since I myself came to approximately the same principle. I wrote an article about this and was soon sent a link to your blog.
It’s great that sometimes developers from different places and with different work experience come up with approximately the same things.
I would like to know your opinion about the render function – the second argument there is a named array $block. Isn’t it better in this case to write a separate class that will either contain all the necessary keys, or have a method that will check for the presence of values? It seems that this option will help avoid problems with data typing – at least on fairly large projects.
I haven’t had time to read other articles on your blog yet, so perhaps the answer is already there.
Hi Anatoly!
wp_parse_args provides a basic fail-safe for this! Yes, this could be expanded to provide more type-safe components, but this approach is similar to what JSX does with it’s components.
You might be interested in an update to this post: https://junaid.dev/laravel-blade-components-in-wordpress/