Register block methods & how to enqueue block assets?

Hi there,

I’m struggling a bit what in 2024 the best method is to register a custom Gutenberg block. You’ve got quite a few options these days;

Bud
roots.register.blocks

JS
registerblocktype

ACF Composer
wp acorn acf:block Example

PHP
register_block_type

I’ve been reading the Block Editor Development with HMR Support article and am wondering how other people handle individual block styling when they move their blocks to a Clover plugin?

What if these blocks are part of the overall theme design and you want to use the same style config (tailwind config / bootstrap variables) in these blocks?

I like the Bud method of registering blocks, filters etc. so I would like to use this approach in my new project. The only thing I’m still struggling with is how to enqueue indivdual block assets? Let’s say I have a carousel block using Swiper and I want to enqueue a separate js & css file only when this block is being used on a page:

/* Block name */
export const name = `sage/swiper`

/* Block title */
export const title = `Swiper block`

/* Block category */
export const category = `widgets`

/* Block edit */
export const edit = () => <></>

/* Block save */
export const save = () => <></>

I used to do this like this for JS blocks:

function register_blocks() {
	wp_register_script(
		'sage/swiper-editor',
		asset('scripts/swiper.editor.js')->uri(),
		['ua/vendor.js']
	);

	wp_register_style(
		'sage/swiper',
		asset('styles/swiper.css')->uri()
	);

	register_block_type(
		'sage/swiper', [
			'api_version'     => 2,
			'editor_script'   => 'sage/swiper-editor',
			'editor_style'    => 'sage/swiper',
			'render_callback' => function($block_attributes, $content) {
				wp_enqueue_style('sage/swiper');
				return $content;
			}
		]
	);
}
add_action('init', 'register_blocks);

or for ACF blocks:

namespace App\Blocks;

use Log1x\AcfComposer\Block;
use StoutLogic\AcfBuilder\FieldsBuilder;
use function Roots\bundle;

class Swiper extends Block
{
	....
	public function enqueue() {
		bundle('swiper')->enqueue();
	}

But that meant adding every individual block assets to the bud config entries.
Is there a way to enqueue these assets automatically with Sage, Poet or Bud without having to list them separately in the config?

Or what is the best way to do this?
Are you just adding these assets to your main app.css and app.js? I thought it was best practice to enqueue these only when a block is added?

Any tips are welcome, thanks!

Hey @Twansparant, I think dynamic imports solves asset loading for all of these scenarios.

Personally I’m still outputting multiple files and importing them in ACF Blocks. We had a few issues with dynamic imports + dev server, but that was a looong time ago so I’m pretty sure that’s all resolved now.

There are a few posts with ref to dynamic imports in discourse + discord if need examples. But this Slider example in the Bud docs is pretty solid.

1 Like

As for registering custom Gutenberg blocks, all those methods are great, but vary slightly in implementation/needs.

I guess it comes down to:

  • If you want the full Gutenberg experience, JS/Bud is the way to go
  • If you prefer working with ACF Blocks, go with ACF Composer

But I’m sure others might have different thoughts, looking forward to hearing them :slight_smile:

Yes, thanks!
That was exactly what I was looking for!

1 Like

How would you use this technique to also enqueue those assets in the editor?
If I also add the example script to my editor.js entrypoint:

const editor = async () => {
  if (document.querySelector('.slider')) {
    const {slider} = await import('./slider.js')
    slider()
  }
}

editor()

The slider isn’t triggered since the querySelector won’t find anything here:
document.querySelector('.slider')

I also tried wrapping it around the domReady call:

domReady(async () => {
  editor()
});

But same result, is there a callback available for when the editor has loaded all blocks?
Thanks!

Ah I fixed that by using this example:

import { select, subscribe } from '@wordpress/data'

export function whenEditorIsReady() {
    return new Promise((resolve) => {
        const unsubscribe = subscribe(() => {
            // This will trigger after the initial render blocking, before the window load event
            // This seems currently more reliable than using __unstableIsEditorReady
            if (select('core/editor').isCleanNewPost() || select('core/block-editor').getBlockCount() > 0) {
                unsubscribe()
                resolve()
            }
        })
    })
}

whenEditorIsReady().then(() => {
  if (document.querySelector('.slider')) {
    const {slider} = await import('./slider.js')
    slider()
  }
})
2 Likes