Lazy Stimulus Controllers
Keep on Learning!
If you liked what you've learned so far, dive in!
Subscribe to get
access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeIt's getting messy in here: let me close a few files... then crack open assets/controllers/goodbye-controller.js
. Pretend, for a moment, that this controller is huge. Or, more likely, it imports a big third-party package like d3
for charts. But, we're only using this controller on some pages.
Here's the deal. In order to register your controllers with Stimulus, all of these files are downloaded immediately. So the page loads, Stimulus starts up, all of these files are downloaded, and any files they import are also downloaded. That's often ok, but if you're importing something big, that can be wasteful.
To fix that, above the class, you can add a very special syntax - /* stimulusFetch: 'lazy' */
.
// ... lines 1 - 2 | |
/* stimulusFetch: 'lazy' */ | |
export default class extends Controller { | |
// ... lines 5 - 7 | |
} |
This works thanks to StimulusBundle. When it spots this, it tells Stimulus to hold its horses and not download this JavaScript file or anything it imports until an element that matches this is on the page.
Watch. Before making that change, if we searched for "goodbye", that controller was being loaded, even though it's not used on this page. But now, refresh and search for "goodbye". It's not there! Inspect the data-controller="hello"
element. Change that to goodbye
and... boom! It works! You can see that it activated (that's what our Goodbye controller!
does), and if we look at the Network tab, now it downloaded. I love this feature.
This can also be done for third-party packages. If you look in assets/controllers.json
... Turbo isn't a very good example of this, but if we said "fetch": "lazy"
on any of these, they would have the same behavior that we just saw.
{ | |
"controllers": { | |
"@symfony/ux-turbo": { | |
"turbo-core": { | |
"enabled": true, | |
"fetch": "eager" | |
}, | |
// ... lines 8 - 11 | |
} | |
}, | |
// ... line 14 | |
} |
That's it! Easiest chapter ever! Use this to keep your initial page lightweight if you have some heavy Stimulus controllers that are only used on certain page.
Next: sometimes, deep sigh, the tech gods frown upon us and things don't work. Let's learn a few tips to help debug when that happens.
Hello !
I just run into an issue with lazy controllers not playing nice with turbo drive.
Apparently, when turbo is loading a first page (not navigating from a page to another one), it loads well.... the full page.
Then, when the user navigates to another page, it only replaces/morphs the body. For the header, it merges things like meta tags, but for assets, it behaves differently. It doesn't want to load new assets in fear of messing up file loading order (if I understand correctly). For assets whose content have changed, it triggers a full page reload if you tell it to ( data-turbo-track="reload" ). But for new assets, you are out of luck. They just won't load.
Apparently, this is because a best practice until recently was to bundle your assest into 1 file with versioning. In that scenario, data-turbo-track="reload" does the job.
But when we use import map without bundling assets, and use lazy controllers that not included on all pages, we run into this problem : if the first page hit by the user does not contain a lazy controller, it will not be loaded on any other pages that you navigate to !
This is what I see in my app.
Am I understanding this correctly ? Is there a workaround ? I assume the turbo team is aware of this ? I can't find much info on it.