A Complete Ecosystem
While we can create and compile our own blocks for integration into any FSE theme individually, the optimal efficiency is achieved by constructing an ecosystem that:
- Automates all build processes
- Establishes a clear folder structure
- Allows for scalable expansion
- Ensures stylistic consistency and facilitates the reuse of common elements across all blocks
So, let’s focus on the theme and develop a build system for our blocks, as well as for our SASS and JavaScript files. It’s important to note that all block-related computations occur only when the block is present on the page we’re viewing. Additionally, any globally used JavaScript functions should have their own file within the theme, following the same approach as traditional themes.
The Structure
In contrast to a basic FSE theme structure, we introduce a few additional folders:
src
: this is where we place our JavaScript and styles that need to be compiled
blocks/custom-blocks
: each block is housed in its own folder within this directory.
dist
: this directory is designated for the compiled theme files
And the two files that pertain to the build process:
package.json
webpack.config.js
Therefore, before building, our structure resembles the following. If you manage the header and footer exclusively through the editor, you can remove the parts
folder.
my-theme/
│
├── blocks/
│ └── custom-blocks/
│ └── first-block/
│ ├── block.json
│ ├── edit.js
│ ├── edit.scss
│ ├── index.js
│ ├── save.js
│ ├── save.scss
│ └── second-block/
│ ├── block.json
│ ├── edit.js
│ ├── edit.scss
│ ├── index.js
│ ├── save.js
│ ├── save.scss
│ └── ...
│
├── dist/
│
├── parts/
│ ├── footer.html
│ ├── header.html
│
├── src/
│ ├── css/
│ │ ├── style.scss
│ │ ├── dashboard-style.scss
│ ├── js/
│ ├── site.js
│
├── templates/
│ ├── index.html
│
├── functions.php
├── package.json
├── theme.json
└── webpack.config.js
Let’s briefly discuss the dashboard-style.scss
file.
In WordPress, styles are enqueued in various ways. Block styles, as previously mentioned, are handled directly within each block. Frontend styles are enqueued in functions.php
using wp_enqueue_style
within the wp_enqueue_scripts
action.
function zenfse_enqueue_style()
{
wp_enqueue_style('zenfse-style', get_stylesheet_directory_uri() . '/style.css', array(), filemtime(get_stylesheet_directory() . '/style.css'), false);
}
add_action('wp_enqueue_scripts', 'zenfse_enqueue_style');
However, if we need to enqueue parts of our CSS, such as resets, these won’t be loaded within the editor by default. Therefore, in my themes, I include a dashboard-style.scss
file to be loaded on the editor side through the admin_enqueue_scripts action
. This approach ensures that these styles are applied specifically within the WordPress editor.
function zenfse_admin_styles()
{
wp_enqueue_style('zenfse-admin-style', get_template_directory_uri() . '/dist/dashboard-style.css');
}
add_action('admin_enqueue_scripts', 'zenfse_admin_styles');
If I need to customize my dashboard, I can add specific styles to dashboard-style.scss
. To ensure our resets and styles don't impact WordPress's default styles, we should wrap our declarations within the .wp-admin .editor-styles-wrapper
selector. This confines our styles to apply only within the editor interface.
As a final touch, we apply our global styles to the iframe within the editor. This approach ensures consistency in how our theme appears across different parts and templates modified within the WordPress dashboard.
function zenfse_editor_style()
{
add_editor_style(array('style.css',));
}
add_action('after_setup_theme', 'zenfse_editor_style');
End of digression.
The Toolbox
We are operating within a Node environment, so our first step is to initialize our project by creating a package.json
where we define dependencies and commands. Personally, I use both webpack (on top of which @wordpress/scripts
is built) and Parcel. While using two different build engines may lack elegance, Parcel’s user-friendly approach compensates for this compared to webpack. Its commands are straightforward, requiring no additional configuration files. However, if you prefer using webpack exclusively, you can achieve this with just a few additions to your webpack.config.js
.
Within the js
and css
folders in src
, you can get creative with importing modules, exporting and importing functions, creating mixins, etc. In short, you have a modern development environment at your disposal, allowing you to tailor your workflow as needed.
The build process
My package.json
looks something like this:
{
"name": "zenfse",
"version": "1.3.0",
"description": "blank fse developer theme",
"scripts": {
"dev:js": "parcel watch src/js/site.js",
"dev:css": "sass --watch src/css/style.scss:style.css --style=expanded",
"dev:css-dashboard-style": "sass --watch src/css/dashboard-style/dashboard-style.scss:dist/dashboard-style.css --style=expanded",
"dev:wp-scripts-custom-blocks": "wp-scripts start --webpack-src-dir=blocks/custom-blocks/ --output-path=blocks/build-custom-blocks/",
"dev": "concurrently --kill-others \"npm run dev:js\" \"npm run dev:css\" \"npm run dev:css-dashboard-style\" \"npm run dev:wp-scripts-core-blocks\" \"npm run dev:css-gsap-animations-frontend\" \"npm run dev:css-image-frontend\" \"npm run dev:css-image-backend\" \"npm run dev:css-image-style\" \"npm run dev:wp-scripts-custom-blocks\"",
"clean": "rimraf ./blocks/build-custom-blocks ./dist",
"build:js": "parcel build src/js/site.js --no-source-maps --no-content-hash",
"build:css": "sass src/css/style.scss:style.css --style=compressed",
"build:css-dashboard-style": "sass src/css/dashboard-style/dashboard-style.scss:dist/dashboard-style.css --style=compressed",
"build:wp-scripts-custom-blocks": "NODE_ENV=production wp-scripts build --webpack-src-dir=blocks/custom-blocks/ --output-path=blocks/build-custom-blocks/",
"build:css-autoprefixer": "postcss **/*.css --use autoprefixer --replace",
"build": "npm run clean && run-s build:*"
},
"author": "Silvia Malavasi",
"license": "ISC",
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"devDependencies": {
"@parcel/transformer-sass": "^2.10.3",
"@wordpress/scripts": "^26.17.0",
"parcel": "^2.10.3",
"rimraf": "^5.0.5"
},
"dependencies": {
"concurrently": "^8.2.2",
}
}
While webpack.config.js
will be simply:
const defaultConfig = require("@wordpress/scripts/config/webpack.config");
module.exports = [defaultConfig];
When I run npm run build
, all of these commands will be executed:
"clean": "rimraf ./blocks/build-custom-blocks ./dist",
"build:js": "parcel build src/js/site.js --no-source-maps --no-content-hash",
"build:css": "sass src/css/style.scss:style.css --style=compressed",
"build:css-dashboard-style": "sass src/css/dashboard-style/dashboard-style.scss:dist/dashboard-style.css --style=compressed",
"build:wp-scripts-custom-blocks": "NODE_ENV=production wp-scripts build --webpack-src-dir=blocks/custom-blocks/ --output-path=blocks/build-custom-blocks/",
"build:css-autoprefixer": "postcss **/*.css --use autoprefixer --replace",
"build": "npm run clean && run-s build:*"
First, I clean up the project from the old build using rimraf
. Then, I compile all my files using Parcel along with its parcel/transformer-sass
, plugin, and I process CSS using autoprefixer
.
wp-scripts
handles the compilation of all my custom blocks based on the configuration specified in webpack.config.js
.
This build process setup is highly convenient because whenever I create a new custom block, I just need to place its folder in blocks/custom-blocks
, and it will be automatically compiled during the next npm run build
.
Credit for this approach goes to the WebberZone article: “WordPress block development: Building multiple blocks.”
Similarly, npm run dev
uses concurrently
to manage all the various watch commands simultaneously.
Let’s import everything into functions.php
Now that our files are ready, we can proceed with importing them into our functions.php
. We've already discussed some aspects related to styles. The complete process for importing global scripts and styles (which apply across the entire theme) is as follows:
// Enqueue scripts
function zenfse_enqueue_script()
{
wp_enqueue_script('zenfse-script', get_stylesheet_directory_uri() . '/dist/site.js', array(), filemtime(get_stylesheet_directory() . '/dist/site.js'), true);
}
add_action('wp_enqueue_scripts', 'zenfse_enqueue_script');
// Enqueue styles
function zenfse_enqueue_style()
{
wp_enqueue_style('zenfse-style', get_stylesheet_directory_uri() . '/style.css', array(), filemtime(get_stylesheet_directory() . '/style.css'), false);
}
add_action('wp_enqueue_scripts', 'zenfse_enqueue_style');
// Enqueue Dashboard styles
function zenfse_admin_styles()
{
wp_enqueue_style('zenfse-admin-style', get_template_directory_uri() . '/dist/dashboard-style.css');
}
add_action('admin_enqueue_scripts', 'zenfse_admin_styles');
// Enqueue Editor styles
function zenfse_editor_style()
{
add_editor_style(
array(
'style.css',
)
);
}
add_action('after_setup_theme', 'zenfse_editor_style');
Now let’s move on to importing our custom blocks, which we compiled into the directory blocks/build-custom-blocks
:
function zenfse_register_blocks()
{
add_filter('block_categories_all', 'zenfse_blocks_categories');
function zenfse_blocks_categories($categories)
{
array_unshift($categories, array(
'slug' => 'zenfse-blocks',
'title' => 'ZenFSE Blocks'
));
return $categories;
};
$blocks = array(
'navigation' => 'zenfse_render_navigation_block',
'gallery' => '',
);
foreach ($blocks as $block => $render_callback) {
$args = array();
if (!empty($render_callback)) {
$args['render_callback'] = $render_callback;
}
register_block_type(__DIR__ . '/blocks/build-custom-blocks/' . $block, $args);
}
}
add_action('init', 'zenfse_register_blocks');
In this example, I have imported two blocks: one is the gallery
block that we saw in Part 1 of the article. The other is a navigation
block, included here to illustrate its render_callback
.
Every time we want to add a new block, we simply add it to the $blocks
array, optionally with its callback function. It’s straightforward and efficient.
Conclusion — for real
We’ve delved into setting up a custom FSE theme in WordPress, organizing its structure, and managing the compilation process. Now, the next step is to innovate and create new blocks.
While the process I’ve described may seem somewhat intricate, in a way, it is. Building my ZenFse starter theme involved a lengthy journey of trial and error.
Initially, working with FSE left me feeling disoriented. The absence of PHP was unsettling. However, as I grasped the logic behind the block system and understood the unique integration between PHP and React, I discovered a powerful, versatile, and… enjoyable system. The capability to craft interfaces for nearly any attribute of my theme or blocks enables the development of intricate custom systems, fulfilling the visual builder objective of FSE in WordPress.
Maintaining custom blocks in separate environments proves remarkably convenient. To replicate blocks from one theme to another, you simply copy a folder and add a line in the $blocks
array within functions.php
.
From a developer’s viewpoint, once the theme is structured, we enjoy an intuitive environment. But what about users? Granted, every CMS demands some training for end users, but does FSE simplify matters? Yes and no. For existing WordPress users, starting anew can pose challenges. Moreover, the decision to develop a site using an FSE or classic theme depends on the client’s requirements.
For example, if I’m constructing a site for an agency that requires precise control over colors, spacing, and enjoys experimenting with diverse block combinations on each project page, FSE is your choice. If a client wishes to incorporate an unexpected video or lengthy text into an existing block, they can experiment and preview the final outcome directly from the dashboard.
Conversely, for a newbie needing a more structured system, I might opt for a classic theme, potentially integrating a series of fields with ACF to limit theme flexibility.
In conclusion, from my perspective, I find developing with FSE much more enjoyable. It feels cleaner and more elegant to me, though these preferences are subjective. Additionally, since FSE is still evolving, I’m excited to see what new tools the WordPress team will introduce in the future.
Let’s also create a dedicated block category for our custom blocks, making it easier to locate and insert them into pages.
Top comments (0)