Bud Dev __hmr 404

I’m attempting to use Bud to compile assets for a Drupal 9 theme. Everything is working properly to compile my JS and SCSS assets both using bud build and bud dev, but I cannot get Hot Module Replacement working. Requests to http://siteurl/__hmr result in a 404 error. The main.XXX.hot-update.js and .json files are being generated properly.

Am I missing any components to making HMR work? Any tips on troubleshooting why /__hmr returns a 404?

I’m including all of the JS file that Bud creates in my theme, and all of my CSS and JS are loading properly in the browser with no errors:

  • dist/main.js
  • dist/vendor/bud.main.js
  • dist/vendor/main.js

bud.config.js

/**
 * @typedef {import('@roots/bud').Bud} bud
 *
 * @param {bud} app
 */
module.exports = (app) => {
  app
    /**
     * Application entrypoints
     *
     * Paths are relative to your resources directory
     */
    .entry({
      main: ['scripts/main.js', 'styles/main.scss'],
    })

    /**
     * These files should be processed as part of the build
     * even if they are not explicitly imported in application assets.
     */
    .assets(['images'])

    /**
     * These files will trigger a full page reload
     * when modified.
     */
    .watch([
      'templates/**/*.html.twig',
    ])

    /* Create vendor chunk(s) */
    .splitChunks()
    /* Environment specific configuration */
    .when(app.isProduction,
      () => app.minimize().runtime('single'),
      () => app.devtool()
    )

    .proxy(
      'https://siteurl'
    )

    /**
     * Development URL to be used in the browser.
     */
    .serve('http://0.0.0.0:3000');
}

main.js (single entrypoint)

const main = async (err) => {
  if (err) {
    // handle hmr errors
    console.error(err);
  }

  // application code
  (($, Drupal) => {

    Drupal.behaviors.smwygNav = {
      attach: function (context, settings) {
        context.querySelector('.hamburger').addEventListener('click', function () {
          this.classList.toggle('is-active');
          context.querySelector('#block-smwyg22-main-menu').classList.toggle('is-active');
        });
      }
    }

  })(jQuery, Drupal);
};

const domReady = onReady => {
  window.requestAnimationFrame(function check() {
    document.body ? onReady() : window.requestAnimationFrame(check);
  });
};

/**
 * Initialize
 *
 * @see https://webpack.js.org/api/hot-module-replacement
 */
domReady(main);
import.meta.webpackHot?.accept(main);

package.json (partial)

"devDependencies": {
    "@roots/bud": "^5.8.4",
    "@roots/bud-babel": "^5.8.4",
    "@roots/bud-postcss": "^5.8.4",
    "@roots/bud-sass": "^5.8.4"
  },

Just a quick clarification, were you getting 404 on http://siteurl/__hmr or on http://0.0.0.0:3000/__hmr because I believe it should be the latter.

I don’t know what could cause that to be incorrect, or a fix but think that is an important clue to the issue.

I get a 404 from either URL.

This project is setup in Docker, so the “siteurl” redirects port 80 → 3000. I have a few WordPress + Sage sites that use a very similar configuration and these all work properly.

I’ve tested both outside and inside the container running bud, and /__hmr returns a 404 error either way, which has me wondering if there was an additional setup step I’m missing for HMR that is included in Sage.

Super cool that you’re using Drupal with bud.js! I’m not a drupal user but I’m really happy to see bud used outside a wordpress context.

Can you try with this?

.setPublicPath(app.isDevelopment ? "/" : "")

Full conf:

/**
 * @typedef {import('@roots/bud').Bud} bud
 *
 * @param {bud} app
 */
module.exports = (app) => {
  app
    /**
     * Application entrypoints
     *
     * Paths are relative to your resources directory
     */
    .entry({
      main: ['scripts/main.js', 'styles/main.scss'],
    })

    /**
     * These files should be processed as part of the build
     * even if they are not explicitly imported in application assets.
     */
    .assets(['images'])

    /**
     * These files will trigger a full page reload
     * when modified.
     */
    .watch([
      'templates/**/*.html.twig',
    ])

    /* Set public path */
    .setPublicPath(app.isDevelopment ? "/" : "")

    /* Create vendor chunk(s) */
    .splitChunks()

    /* Environment specific configuration */
    .when(app.isProduction,
      () => app.minimize().runtime('single'),
      () => app.devtool()
    )

    .proxy(
      'https://siteurl'
    )

    /**
     * Development URL to be used in the browser.
     */
    .serve('http://0.0.0.0:3000');
}

This isn’t ideal (it should be default) but I think it might very well fix the issue.

2 Likes

Yes! that is the missing piece. Everything appears to be working properly now.

I’d be happy to create a PR adding a Drupal theme for bud/examples if you think that would be valuable.

@noahott that could be interesting! I appreciate the offer. Wondering if a guide might be worthwhile, either alongside the example or in place of it.

With Bud 5.8.5 my hope is that you’re able to remove the .setPublicPath() call. You may need to adjust however you are enqueuing the file as the default public path will be / and will prepend manifest.json / entrypoints.json values. But, with that handled, everything should work without making the call. Or, you could make the call (assuming drupal is smarter than wordpress about this) but pass the actual public path.

@kellymears I updated to Bud 5.8.5 and was able to remote the setPublicPath() call. The paths in manifest.json are fine since Drupal does not use this file directly. Assets need to be referenced directly by the theme libraries.yml file, or imported dynamically with a custom function to parse manifest.json. In that case it is trivial to make the paths relative to the theme folder.

I will work on a guide and example theme once I make some more progress with this and confirm everything is working as expected.

Thanks!

2 Likes