How to circumvent importing Stimulus within every controller

posted by Ayush Newatia
2 July, 2021



If you’re familiar with Stimulus at all, the below bog standard controller should look very familiar. In fact I’ve nicked it from their homepage!

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = ["name", "output"]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

I’m a Rubyist and unashamedly rather lazy; so having that import statement at the top of every Stimulus controller always irked me. If you’re using Webpack, there’s a super simple way of circumventing this annoying import statement.

First thing you need to do is create a file called application_controller.js and place it in your controllers directory. This is good practice nevertheless as you can put app specific utilities in there; such as getting some data out of meta tags for example.

// application_controller.js
import { Controller } from 'stimulus'

export default class extends Controller {
}

And then change your controllers to inherit from ApplicationController and remove the import statement. So the above controller would become:

export default class extends ApplicationController {
  static targets = ["name", "output"]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

And finally we need to make a change to our Webpack configuration so the controllers know where to find ApplicationController without the import statement. We’ll use the Webpack Provide plugin to accomplish this. Add the following lines to the plugins array in your webpack.config.js:

new webpack.ProvidePlugin({
  ApplicationController: ['./application_controller', 'default']
})

If you’re using Webpacker 5 or older, add the following lines to your config/webpack/environment.js:

const webpack = require('webpack')

environment.plugins.append('Provide', new webpack.ProvidePlugin({
  ApplicationController: ['./application_controller', 'default']
}))

And if you’re using Webpacker 6, change your config/webpack/base.js to:

const { webpackConfig, merge } = require('@rails/webpacker')
const webpack = require('webpack')

const customConfig = {
  plugins: [
    new webpack.ProvidePlugin({
      ApplicationController: ['./application_controller', 'default']
    })
  ]
}

module.exports = merge(webpackConfig, customConfig)

And that should do the trick! You no longer need to import Controller manually within every Stimulus controller!

Shout out to Konnor Rogers for showing me how to do this :)