Let’s Get Hands-On with WordPress FSE Theme and Custom Blocks — Part 2
Tags: wordpress, development, fse, blockthemes
Published: 2024-06-28 09:45:24


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ht13qa7yxpvdoizfkrc.jpg)

#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](https://webpack.js.org/) (on top of which [`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) is built) and [Parcel](https://parceljs.org/). 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`](https://www.npmjs.com/package/rimraf). Then, I compile all my files using Parcel along with its [`parcel/transformer-sass`](https://www.npmjs.com/package/@parcel/transformer-sass), plugin, and I process CSS using [`autoprefixer`](https://www.npmjs.com/package/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](https://webberzone.com/) article: “[WordPress block development: Building multiple blocks.](https://webberzone.com/wordpress-block-development-building-multiple-blocks/)”

Similarly, `npm run dev` uses [`concurrently`](https://www.npmjs.com/package/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](https://dev.to/silviamalavasi/lets-get-hands-on-with-wordpress-fse-theme-and-custom-blocks-part-1-lmj). 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](https://github.com/SilviaMalavasi/zenfse_full) 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](https://www.advancedcustomfields.com/) 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.