hotwired / stimulus

A modest JavaScript framework for the HTML you already have
https://stimulus.hotwired.dev/
MIT License
12.73k stars 426 forks source link

Question about Stimulus' life cycle #768

Open fritzmg opened 6 months ago

fritzmg commented 6 months ago

I have a question or may be a misconception about the life cycle of Stimulus controllers.

Let's say I have a simple controller that toggles a <body> class and stores that state in localStorage:

// foobar_controller.js
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
    toggle() {
        document.body.classList.toggle('foobar');
        localStorage.setItem('foobar', document.body.classList.contains('foobar'));
    }
}
…
<head>
    <script src="app.js" async></script>
</head>
<body>
    …
    <button type="button" data-controller="foobar" data-action="foobar#toggle">Toggle Class</button>
    …
</body>

Now I also want to restore that body class state which is saved in localStorage. My initial though was to put that into the initialize() method:

import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
    initialize() {
        if ('true' === localStorage.getItem('foobar')) {
            document.body.classList.add('foobar');
        }
    }

    …
}

My expectation was that (once app.js is loaded and cached by the browser) this would be immediately executed as soon as the node the controller is registered on is started to be processed by the browser.

However, in actuality it kind of looks like all Stimulus controllers are only initialized once the whole page finished loading - i.e. it almost looks like everything is only processed on DOMContentLoaded. I assumed that stimulus controllers would be initialized as soon as possible via Mutation observers. Thus in this case the body class is added too late for my use case.

Or another example would be the click action for a menu button - the event listener only seems to be added once the whole DOM was loaded, meaning that on a large page the menu button (which is at the very top of the page typically) does nothing for quite some time.

Was my expectation wrong? Or is there something wrong with my setup. In my case I use webpack - together with hotwired/stimulus-webpack-helpers. The app.js looks like this for example:

import { Application } from '@hotwired/stimulus'
import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers'
import './css/app.scss';

// Start Stimulus application
const application = Application.start()
const context = require.context('./controllers', true, /\.js$/)
application.load(definitionsFromContext(context));
application.debug = process.env.NODE_ENV === 'development';
ildarkayumov commented 6 months ago

However, in actuality it kind of looks like all Stimulus controllers are only initialized once the whole page finished loading - i.e. it almost looks like everything is only processed on DOMContentLoaded

you are right

please check this PR https://github.com/hotwired/stimulus/pull/131/files#diff-f3d6212b4c84ebef921d7f88435f66b4637ec31917aa74d48849e15773d6670cR25

so Application#start is async function that awaits for DOMContentLoaded runs all observers only after

fritzmg commented 6 months ago

I see, thank you for clarifying.

Ideally though it would be great if Stimulus controllers and actions are initialised as soon as possible - otherwise it might take a long time until event listeners are registered for example, e.g. when the DOM is large (see menu button example above).

However, I realise this might be tricky to implement, especially if controllers depend on each other.