I’m using Stimulus to handle a fairly complex bit of UI, and finding performance to be an issue.
I have 2 “summary” controllers on my page. Each summary controller represents a simple view of some details about a user, and also contains a link to edit these details.
Clicking edit fetches new HTML (with stimulus attributes) from the server - this is very quick, around 20ms. I then “replace” the existing summary controller’s HTML with the new edit HTML (
this.element.innerHTML = newContent), which destroys the controller for the summary, and adds the new one for the edit.
Digging into this, I’m seeing these kinds of numbers:
So there we’re looking at 850ms for processMutations to resolve, and that is my cause of the lag from the user clicking the edit link to seeing the resolving new UI.
Are there optimisations I can do to make this more performant? As an experiment I tried appending my new edit HTML to an existing empty div at the bottom of the page (so that the existing summary controller isn’t destroyed), but that doesn’t seem to make much difference?
Any helpers here would be welcome, before I get forced into re-writing this lot in react!
@Matt_Roberts - Very interesting. Typically, Stimulus is incredibly fast. Like our React devs can’t believe how fast our Stimulus/PJAX driven features are - that kind of fast. I wonder if this is a specific use case that’s problematic.
A few questions:
What browser are you using?
What kind of hardware are you running on?
How much HTML are you adding/removing? A huge amount or handful of divs and text?
Alrighty, I just tried to reproduce this with a very contrived test. I’m using Chrome 81 on a 2012 iMac. I had an outer controller and an inner controller. When I clicked the outer controller, it removes the inner controller (and confirmed Stimulus registers a disconnect) and added new innerHTML which was also another controller (and I confirmed it was registering a connect when being added). I’m not fetching the new HTML remotely, I’m just grabbing it from elsewhere on the page, which shouldn’t matter since the problem you’re seeing isn’t in the remote load time.
The HTML in the inner controllers is an h1 tag, p tag, some text, and a bunch of container divs. Not terribly complex, but more than just a div.
My results for the entire remove + add, measured using
performance.now(), is 0.5949ms (Firefox and Safari are similar). So less than one millisecond. The basic use case is plenty fast enough - presumably there’s something more happening in your example that could lead to this.
@welearnednothing Thanks for this. I’m actually on a brand spanking new maxed out 16" MBP, which makes me even more worried!
Ok so, here is a screenshot of the edit mode that is loaded from the server.
It’s medium-complex I would say. One thing of note is that each of those dropdowns (28 of them in total) is styled using the “chosen” js lib, so there is complexity there, and the resulting HTML is fairly large as a consequence.
If I remove the
data-controller from this HTML (so that it renders but isn’t hooked up to stimulus), then it appears to render instantly (no visible lag). In either case I"m struggling to get meaningful performance numbers - using
performance.now() from the start of fetching the remote HTML to the setting of the innerHTML takes around 50ms. However the visible delay before you see this is about 1 second, which more or less matches the time that Chrome is reporting that the processMutations method is taking.
I’ll keep poking - any particular direction you would recommend taking this?
I think this could actually be an issue with the JS that’s attached to this controller…
Each of those dropdowns, doesn’t have any values - to save on HTML size. So, instead, the options are inserted via a JS method, which is triggered in
connect. It executes pretty quickly, but I think the huge noise that this generates in changes which are then being observed by stimulus is causing the performance hit…
I’ll continue down this road for now…
That sounds like it would do it. Are you doing a lot of individual DOM manipulations? E.g. inserting a new option over and over and over? If so, perhaps you could do all of that in an unattached element. After that’s all created, attach it to the controller’s DOM tree.
Tangentially related, are you referring to this Chosen? It’s officially deprecated, hasn’t been maintained for years, and wasn’t without its own performance issues. You may want to try something like Select2, instead.
Let us know how it goes!
I think I’m winning! Yes - loads of DOM manipulation on loading. I’m going to replace this with server-side logic to populate this instead, I think this will sort it!
I’ll report back when I get this working