Block Editor Development with HMR Support

Find out how to use the @roots/wordpress-hmr library to build WordPress blocks and other block editor customizations with hot reload (HMR) support.


In this blog post it says “Moreover, the wordpress-hmr package can be used independently of bud.js or even webpack.”

I’m trying to achieve exactly that (we use vite & TS) but am struggling to get hot reloading working for our custom blocks. The documentation is a bit difficult to understand (even in the bud context). I checked the code to make sense of it and maybe derive from there how to get this working but without success.

I would be very grateful for some pointers in the right direction as hmr is something I’ve been wanting to get working in the block editor for ages. Thanks in advance.

The src/loader.ts module is just sugar for theme/plugin developers. It replaces roots.register with the actual call. Without the loader roots.register('./blocks') is desugared to:

import * as blocks from '@roots/wordpress-hmr/blocks';

  () => import.meta.webpackContext(
    {recursive: true, regExp: /.*\\.blocks\\..*$/}
  (id, context) => {
    if (import.meta.webpackHot) return import.meta.webpackHot.accept(id, context)

If you check out the source for the blocks module it defines what should happen before a hot reload event and what should happen after. This is a curried function that is passed to the editor module which executes the two callbacks (before and after). All of the entities recycle the editor module in the way I just described.

The interface looks like this:

What you’re trying to do is replace the editor.load function with a vite specific implementation.

You should check out the vite docs for the vite HMR API and the vite handleHotUpdate method from its plugin API:


Thanks a lot mate!!!

That helps a lot and gives me a good starting point for trying to figure this out. I’ll be back with either some questions, or with a finished solution to share with others.


I was able to make it work.

This is the code in the main file that is enqueued in the block editor

import { blocks } from '@roots/wordpress-hmr';
import createRequireContext from "./createRequireContext";

const modules = import.meta.glob('./**/*.block.*', { eager: true });
  () => createRequireContext(modules, import.meta.url),
  (id, context) => {
    if ( {
      return, context);

if ( {;

createRequireContext() transforms our modules into the format expected by blocks.register(). The latter is expecting __WebpackModuleApi.RequireContext so we needed to add @types/webpack-env to our devDependencies so ts knows what’s up with that.

import RequireContext = __WebpackModuleApi.RequireContext;

const createRequireContext = <T = any>(modules: Record<string, T>, moduleId: string): RequireContext => Object.assign(
    (id: string) => modules[id],
      keys: () => Object.keys(modules),
      resolve: (id: string) => id,
      id: moduleId

export default createRequireContext;

The only thing not working is the initial snackbar notice informing us that hot reload is enabled. It’s testing for import.meta.webpackHot which is always false because in vite this would have to be changed to Small inconvenience.

Thanks again for your input Kelly.