Building Blocks Based on block.json Using @roots/bud Instead of @wordpress/scripts

I’m currently considering a transition from @wordpress/scripts to @roots/bud. My project presently involves a plugin with multiple blocks, and I’m looking to maintain the same structure during this transition, specifically the compilation based on block.json files. Here’s a example of my project’s structure:

├── block1
│   ├── block.json
│   └── index.ts
└── block2
    ├── block.json
    └── index.ts

I see that @roots/bud-preset-wordpress offers a function roots.register.blocks. I’m curious if anyone has experience or suggestions on how to make this function work with block.json files in a similar manner to how wp-scripts does? wp-scripts automatically identifies all directories within src that contain a block.json and compiles the blocks accordingly.

Any advice or guidance from those who have experience with this kind of transition, or insights about handling block.json files in @roots/bud, would be greatly appreciated!


I’d also love to know if this is possible and if one could register block-specific stylesheets for the editor and frontend like you can in block.json.

I haven’t tested this, but can you just re-export the data from block.json?


import block from './block.json'
import edit from './edit.js'
import save from './save.js'

export const {name, attributes, supports} = block
export {edit, save}

This puts block.json in the chain of dependencies and should instigate recompilation when it is modified.

Thank you for the suggestion! I will test this approach in the coming weeks. However, I already see that this method may require more boilerplate than I’d prefer.

In the block.json, there are aspects such as editorScript, editorStyle, style, viewScript, and render. One of the conveniences with @wordpress/scripts is that it requires no additional configuration. It automatically detects these elements within the block.json and handles the compilation and copying. This also allows WordPress to load only the necessary styles and viewScript (i.e., only the assets used by the blocks in a given post).

Here’s an example of a block.json file for reference:

  "$schema": "",
  "apiVersion": 2,
  "name": "my/block-1",
  "title": "Block 1",
  "attributes": {
  "supports": {
  "editorScript": "file:./index.ts",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.ts"
  "render": "file:./template.php"

Again, I appreciate your guidance and will provide an update once I have tested this out!

@aaronjpitts For editor-specific stylesheets you can just import the css directly in the *.block.js module.

I think our setup is more flexible than @wordpress/scripts, but maybe not as seamless? An example setup I threw together with “lazy” block css & js:




import './editor.css';

export default {
  name: 'example/example',
  title: 'Example Block',
  attributes: {},
  edit: () => <div className="example__example-block">👀</div>,
  save: () => <div className="example__example-block">👀</div>,


  ?.addEventListener(`click`, () => console.log(`👀`));


.example__example-block {
  font-weight: bold;
  font-size: 3rem;


.example__example-block {
  font-weight: bold;


export default async (bud) => {
    // editor entrypoint
    .entry('blocks', ['blocks'])
    // public entrypoint
    .entry('blocks/example', ['blocks/example/public.js', 'blocks/example/public.css'])


add_action('wp_enqueue_scripts', fn () =>
    has_block('example/example') && bundle('blocks/example')->enqueue(),
add_action('enqueue_block_editor_assets', fn () =>


It’s worth pointing out that we don’t actually register any blocks with the server. We just enqueue or don’t enqueue scripts and styles as needed, depending on the context of the request provided by functions like \has_block.

So, if you wanted to register your block using block.json it should be possible to only use the Roots helpers in development:

if (import.meta.webpackHot) {

And then handle registration how WordPress documents it. You’ll have to forgive my ignorance, but I imagine you need to reference the compiled file paths (your example seems to be specifying .ts files for editorScript, etc., does it really work that way using @wordpress/scripts?) I would think it would need to be something like:

  "$schema": "",
  "apiVersion": 2,
  "name": "example/example",
  // ...,
  "editorScript": "file:./path/from/src/to/compiled-editor.js",
  "editorStyle": "file:./path/from/src/to/compiled-editor.css",
  "style": "file:./path/from/src/to/compiled.css",
  "viewScript": "file:./path/from/src/to/compiled.js",
  "render": "file:./template.php"

Overall, I would prefer five minutes of initial setup for the hot reload benefits and general customizability, especially if I am going to be working with a block or set of blocks for a long time.

But, I will give it to WP that if you want to use block.json without any additional effort and that is your primary goal, bud.js might not be the best solution.

Thank you very much, @kellymears. The above setup is working great :slight_smile: I agree the little extra setup is well worth the benefits of HMR.

I like to use wp_enqueue_block_style to inline the block CSS on the front-end. So in the wp_enqueue_scripts action hook, I use the below instead to get it working with this setup in case anyone else comes across this:

 add_action('wp_enqueue_scripts', function () {
  wp_enqueue_block_style('example/example', [
    'handle' => 'wp-block-example',
    'src' => Roots\asset('blocks/example.css')->uri(),
    'path' => Roots\asset('blocks/example.css')->path()
}, 100);
1 Like