Duplicate controller instantiation when using hot module replacement

I’ve got hot module replacement working in a Roots-based Wordpress site setup.

When I make a change to the controller, however, it’s getting re-applied to the page without disabling the previous instantiation.

If I add this to my controller, I can catch when it gets disposed via hot module replacement:

if (module.hot) {
  module.hot.dispose(data => {
    console.log('controller disposed')
  })
}

Any way to dynamically dispose of all instantiations of the controller from within the controller definition file?

When you pass an existing identifier to Application#load(), all controllers currently connected to the identifier should be disconnected first before the new controllers are connected.

You can follow the flow of execution from:

There is a test for reloading, but it may be insufficient. Please do open an issue on GitHub if you’ve found a bug!

(thanks for linking up that trail of references)

I found out what was happening.

Router#disconnectModule() was never being fired, which hinted that maybe the new module was being loaded independently from the old one (a different application).

That was true. Each time hot-module-replacement reloaded the controller, it also re-ran my application.start(), creating a duplicate of the whole thing.

Here’s my revised stimulus starter file, which tells hot-module-replacement how to handle hot replacements:

import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

let application = null

if (module.hot) {
  module.hot.accept() // tell hmr to accept updates

  if (module.hot.data) {
    application = module.hot.data.application // re-use old application if one was passed after update
  }
}

if (!application) {
  application = Application.start() // if no application so far, start it
}

if (module.hot) {
  module.hot.dispose(data => {
    data.application = application // on disposal of the old version (before reload), pass the old application to the new version
  })
}

const context = require.context("../controllers", true, /\.js$/)
application.load(definitionsFromContext(context))

That’s great! Would you mind sharing your webpack configuration too?

Oh my, it might be hard to extract out, but here’s my context:

I started with a fresh copy of Sage 9.0.0, a webpack-based Wordpress theme starter (see their build dir for the webpack config).

I swapped in babel instead of buble and configured babel to include everything I needed.

In resources/assets/scripts/main.js, I’m importing the autoload/_stimulus.js definition file above, and added a ../controllers/ directory for my controllers relative to autoload/.

1 Like

Can you help me with this situation? I don’t know if It can be a problem if I have two files that makes the Application.start() ? In you example you are starting making the application as a null and after that you are doing the module.hot this is needed for what?
I’m trying to do that because I want to make standalone packages and each one has different controllers