aslamhus/wordpress-hmr

HMR setup with webpack and wp-scripts for wordpress development and production

Maintainers

Package info

github.com/aslamhus/WordpressHMR

pkg:composer/aslamhus/wordpress-hmr

Statistics

Installs: 96

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

3.0.15 2026-03-30 17:41 UTC

README

Features

  • Hot module replacement (HMR) for theme development (no CSS reloads!)
  • Containerized WordPress environment with Docker
  • Simple whr.json config for defining, conditionally enqueuing, and managing all theme assets
  • Optimized production builds via wp-scripts with statically generated asset enqueuing
  • Child theme scaffolding, WP-CLI, and PHPMyAdmin included out of the box
  • One-command setup — vendor/bin/whr install gets a full containerized WP site running from scratch
  • CLI tool — run WP-CLI commands, manage containers, and scaffold themes without touching Docker directly

Introduction

This is an experimental package inspired by wp-env, which allows you to develop containerized wordpress sites with Docker, Webpack and hot module replacement (HMR). It uses the Wordpress wp-scripts package in tandem with a custom enqueuing algorithm to enqueue assets in your theme.

WordpressHMR now comes with a CLI tool that allows you to spin up a containerized wordpress site with one command.

Defining and enqueuing assets is as simple as managing a single whr.json (Wordpress Hot module Replacement) file. This file contains the configuration for your assets, including the path to your scripts and styles, the hooks where they should be enqueued, and any conditions for enqueuing them.

Who is this package for?

Anyone who wants to develop Wordpress themes with the fast paced development experience of HMR in a containerized environment. If you're familiar with wp-env, setup is a breeze.

Why not just use wp-env?

wp-env is fantastic for custom block development, but it lacks the ability to hot reload styles and scripts. While you can leverage hot module replacement in custom block development, it is not currently possible to do so for theme development. This package combines the power of wp-scripts to manage asset dependencies with a dynamic enqueuing algorithm to provide hot module replacement for your theme development.

Requirements

This package requires Docker, PHP, Composer, and Node.js, similar to the setup for wp-env.

Getting started

  1. Install WordpressHMR package
composer require --dev aslamhus/wordpress-hmr
  1. Run the installer and follow the prompts.
vendor/bin/whr install
  1. Create a child theme

The default installation creates a wordpress site for you, but it is recommended that you create a child theme. To do so, simply run:

vendor/bin/whr create-child-theme

Follow the prompts to use a default theme or install your own. If you want to manually install a theme, simply add its directory to the public/wp-content/themes directory in your project.

  1. Start your container!
# start your container
vendor/bin/whr start
# start your container with HMR
vendor/bin/whr start --hot

That's it! Try changing the css in your resources/assets/screen.scss file to see the changes immediately reflected in your browser. By default, the package adds two scripts to your theme, screen.js and editor.js. Each of these scripts is defined in the whr.json file with conditions to enqueue them in the public facing area of your wordpress site nd editor respectively. Each script imports a corresponding css file in your theme. Add more scripts or more css / scss files as you like. Try updating the css and see the changes reflected without a reload.

Webpack HMR Entry points limitation

This package supports multiple entry points, however HMR will only work with one entry point per page. This is a known issue with Webpack HMR when multiple entry points are loaded on the same page.

In development, I recommend having a single entry point per page so that you can take advantage of the fast paced development experience of HMR, and then use a separate config for your production environment. This can easily be facilitated with separate whr.json files, according to your environment.

Import a database by default

If you have a mysql dump of a database you'd like installed, place it in the Docker/data/db directory. To rebuild the container with this dump file imported, run vendor/bin/whr start --reset. Make sure your dump file uses the database wp_database.

If you want to use a different database name, you'll need to change it in the compose.yml file, found in your project root. Search for all references to wp_database and replace it with the database name of your choice.

Configuration (whr.json)

The whr.json file is where you define assets.

It is a simple JSON configuration file that contains the following properties:

  • config - This is where you define the configuration for your assets.
  • assets - This is where you define your scripts. Each property of the assets object is a hook where you want to enqueue your script. The value of each property is an array of script objects.

Config

  • handlePrefix - This is the prefix for your asset handles. This is useful if you want to avoid conflicts with other plugins or themes.
  • host - This is the host of your local development server.
  • site - The container url.
  • port - This is the port of your local development server.
  • protocol - This is the protocol of your local development server.
  • theme - the name of your theme. Change your theme manually in whr.json or create it using the CLI tool, vendor/bin/whr create-child-theme. If you change it manually, the theme will be activated automatically when you start your container (as long as that theme is available in your themes directory)
  • src - Points to the src directory where your webpack entry points live. Files here will be emitted to the theme you specify.
  • themePath - This is the path to your themes directory.

Assets

An array of script objects. Each script object contains the following properties:

  • handle - The handle of the script. This is the name that you will use to enqueue the script.
  • hooks - An array of hooks where the script will be enqueued.
  • path - The path to the script file.
  • ext - The file extension of the script file.
  • dependencies - An array of dependencies the script requires.
  • condition - Set a condition to enqueue the asset. For more on conditional enqueuing, see the Conditional enqueuing section below.

Example

{
  "config": {
    "handlePrefix": "my-asset-handle",
    "host": "localhost",
    "site": "http://localhost:8889",
    "port": "8889",
    "protocol": "http",
    "theme": "twentytwentyfive",
    "src": "resources",
    "themePath": "public/wp-content/themes/"
  },
  "assets": {
    "enqueue_block_editor_assets": [
      {
        "handle": "editor-js",
        "path": "/js/editor",
        "ext": "js"
      }
    ],
    "wp_enqueue_scripts": [
      {
        "path": "/js/screen",
        "ext": "js",
        "condition": ["is_user_logged_in", null, false]
      }
    ]
  }
}

Adding a new asset / HMR entry point

To add a new asset, simply add a new object to the assets array in the whr.json file. You will have to run vendor/bin/whr build to generate the necessary asset files.

Let's add some styles to our theme which we only want to appear on our custom template, "My Custom Template".

  1. Create a scss/css file in your resources/assets/css directory. For example, my-custom-template.scss.

  2. Create a script in your resources/js directory that will import the scss file. For example, my-custom-template.js.

    import "../css/my-custom-template.scss";
  3. Add the following to your whr.json file:

    {
      "assets": {
        "wp_enqueue_scripts": [
          {
            "path": "/js/my-custom-template",
            "ext": "js",
            "condition": ["is_page_template", "My Custom Template", true]
          }
        ]
      }
    }
  4. Run vendor/bin/whr build to generate the necessary asset files.

  5. Restart your development server with vendor/bin/whr start. The styles should now be enqueued on the "My Custom Template" page.

You're done! Try changing the styles in your my-custom-template.scss file and see the changes reflected in your browser only on pages that use the "My Custom Template" page template.

For a list of condition functions that you can use, see the Wordpress Conditional Tags.

A note on block theme hooks

When enqueuing assets for block themes, you will need to use the following hooks:

enqueue_block_editor_assets - to load only in editor view enqueue_block_assets - loads both on frontend and editor view

Conditional enqueuing

Example 1: Enqueue a script only on the block editor

In this example, we only want to enqueue a script in the block editor. We set the path to the script file as well as the extension. We don't need to specify a condition for this script, as it will be enqueued in the block editor by default.

{
  "assets": {
    "enqueue_block_editor_assets": [
      {
        "handle": "editor-js",
        "path": "/js/editor",
        "ext": "js"
      }
    ]
  }
}
Example 2: Enqueue a script only on the front end

Same as above, but we want to enqueue a script only on the front end. We use the wp_enqueue_scripts hook to enqueue the script.

{
  "assets": {
    "wp_enqueue_scripts": [
      {
        "path": "/js/screen",
        "ext": "js"
      }
    ]
  }
}
Example 3: Enqueue a script only on a specific page template

In this example, we only want to enqueue a script on a specific page template. We use the get_page_template_slug function to get the page template slug and compare it to our custom template slug. You can find the page template slug by looking at the classname of the body tag of any page given the template.

// for custom template
{
  "assets": {
    "wp_enqueue_scripts": [
      {
        "path": "/js/screen",
        "ext": "js",
        "condition": ["get_page_template_slug", "", "my-custom-template"]
      }
    ]
  }
}
// for default page template
  {
        "handle": "pages-feature-image",
        "path": "/js/templates/pages-feature-image",
        "ext": "js",

        "condition": ["get_page_template_slug", null, "page-template-default"]
      }

// for a custom post type
{
    "handle": "instructor-js",
    "path": "/js/instructor-single",
    "ext": "js",
    "condition": ["get_post_type", ["function", "get_the_id"], "custom-instructors"]
  }

// get the id of the page
{
 "condition": ["get_the_id", null, "2039"]
}

// targeting block editor styles only
 "enqueue_block_assets": [
      {
        "handle": "editor-js",
        "path": "/js/editor",
        "ext": "js",
        "condition": ["is_admin", null, true]
      }
    ],
Example 4: Use a conditional argument that takes the result of a function as an argument

You can use a function as an argument by declaring the type in an array, followed by the function name, and then a list of optional arguments.

In this example, we only want to enqueue an asset on the about page. We need the id value of the current page to use the get_the_title method. We can use the get_the_id function to get the id of the current page.

This will evaluate to get_the_title(get_the_id()) === 'about'.

{
  "assets": {
    "wp_enqueue_scripts": [
      {
        "path": "/js/screen",
        "ext": "js",
        "condition": ["get_the_title", ["function", "get_the_id"], "about"]
      }
    ]
  }
}

Build for production

Building is a very simple process, and leverages wp-scripts to generate the necessary asset files. A little magic is performed by Aslamhus\WordpressHMR\EnqueueAssets to create a static script which enqueues the assets in your theme, avoiding the performance overhead of enqueuing assets dynamically. Run the build command and then check out your enqueue-assets.php file in your build folder public/wp-content/themes/your-theme/inc/enqueue-assets.php

vendor/bin/whr build

Project directory structure

The installer will create the following folders/files:

  • Docker directory, which serves as an entrypoint for files in your project root and the docker container.
  • public directory, with the latest version of wordpress installed. The installer will also prompt you to create a custom theme directory with the name you specify.
  • resources directory where all your theme files will reside. Webpack copies all the files from your resources folder to the current active theme.
  • resources/assets directory where your css / scss files will reside.
  • resources/inc directory where the enqueue-assets.php and functions.php file will reside.
  • resources/js directory where your entry points will reside.
  • vendor directory where your composer packages are stored. This directory is mounted in the docker container in the parent directory of your wordpress site (/var/www/).
  • compose.yml Docker compose settings.
  • whr.json where you define your assets and theme

Accessing Docker containers

Commands

WordpressHMR provides some convenient commands to access your container. To run wp-cli commands inside your container you can use:

vendor/bin/whr wp --info

To run commands inside your wordpress container you can use:

vendor/bin/whr exec <commands>

Note that not all common binaries exist in the wordpress container. Therefore, it's recommended to run commands in the wordpress container like so:

vendor/bin/whr exec bash -c 'cd ../ && ls -1'

Container entry points

Your containers can access files in your local project through Docker/data/tmp. You'll find any files you add to this directory at the container path /tmp/Docker. If you want to change this directory, please see volumes in your compose.yml file, located in your project root.

When is this useful? Say you want to import / export a database. Add your .sql dump file to Docker/data/tmp in your project. Then import it into your container's database by running vendor/bin/whr wp db import /Docker/tmp.

Likewise, you can export your database by running vendor/bin/whr wp db export /tmp/Docker/dump.sql

Container settings

Wordpress

If you want to change values in the wp-config.php file, you can do so through your compose.yml file in your project root. In the services.wordpress.environment section, you'll find default values you can change. For example, if you'd like to display more error reporting with ini_set, you can add those values under WORDPRESS_CONFIG_EXTRA. Your container will evaluate that string value directly into the wp-config.php file.

Note: For changes to take effect, you'll need to restart your container with vendor/bin/whr start

services:
  wordpress:
    ...
    environment:
      WORDPRESS_DB_NAME: wp_database
      WORDPRESS_TABLE_PREFIX: wp_
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: admin
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DEBUG: true
      WORDPRESS_CONFIG_EXTRA: |
        /* Remove https for admin */
        define('FORCE_SSL_ADMIN', false );
        define('WP_DEBUG_LOG', true);
        define('WP_DEBUG_DISPLAY',true);

        /* add error reporting */
        ini_set('display_errors', true);
        ini_set('display_startup_errors', true);
        error_reporting(E_ALL);

Adding files to your wordpress container

By default, only your wp-content directory is mounted in the container. It is the bridge between your local project and the container. If you'd like to mount another file in the container, you can add it in your compose.yml file.

For example, say you wanted to add your own .htaccess file. Add it to your public directory and then edit your compose.yml file to include it as a volume:

services:
  wordpress:
    volumes:
      # htaccess
      - ./public/.htaccess:/var/www/html/.htaccess
      # Note: docker splits the volume string by a the colon (:)
      # You specify the path to your file / directory you'd like to mount,
      # followed by location in the container.
      # /var/www/html is where your wordpress site lives.

WARNING: Be careful adding a file like wp-config.php since the wordpress Docker image uses a specific wp-config.php with its own logic for setting environment variables.

Troubleshooting

Webpack Errors

If you encounter an error such as Module parse failed: 'import' and 'export' may appear only with 'sourceType: module', please make sure your root directory of your project does not contain a package.json file where the type is set to "commonjs". While aslamhus/wordpress-hmr uses ES6 syntax for its webpack configuration, wp-scripts uses CommonJS and specifying a type can cause errors.

HMR isn't working

If you aren't seeing changes in your wordpress reflected in your site, try:

  1. You've started your container with the --hot option.

  2. Checking if the following line is in your resources/functions.php file. It should be added automatically when you install or change your theme.

/** Enqueue Custom Theme Assets */
 require_once  get_stylesheet_directory() . '/inc/enqueue-assets.php';

Wordpress Errors

Can't login to my admin

Here's where the whr command line tool comes in handy. Simply add a new user with administrator privileges to login:

vendor/bin/whr wp user create admin admin@example.com --role=administrator
# a password will be generated for you. Now you can log in!

Modifying the Webpack configurations

You'll find all the webpack config files in the vendor/aslamhus/wordpress-hmr/build.

Contributing

If you would like to contribute to this package, please feel free to submit a pull request. I would love to hear your feedback and suggestions for improvement.

This package was proudly built without AI, just one single human clacking away at the keyboard.