Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Making your Custom Controllers Lazy
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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 SubscribeStimulus bridge has one more trick. On the analyzer tab, the 3 files that are loaded on every page are now these 2 dark blue files and this 1 light blue file. So the biggest JavaScript modules now are sweetalert2
, Stimulus itself and something called core-js
.
core-js
represents our Polyfills. For example, the web.url.js
thing is what gives us the URLSearchParams
object that we've used a few times.
So other than the Polyfills and Stimulus, the biggest item in here is definitely sweetalert2
. And, again, that's kind of unfortunate... because that's only used on the cart page. Could we do the same "lazy" trick with our own controllers?
The stimulusFetch: lazy Comment
Totally! Open assets/controllers/submit-confirm_controller.js
. This is the one file that imports SweetAlert. Above the controller class, add a special comment: stimulusFetch
colon then lazy
in quotes.
Show Lines
|
// ... lines 1 - 4 |
/* stimulusFetch: 'lazy' */ | |
export default class extends Controller { | |
Show Lines
|
// ... lines 7 - 52 |
} |
This is a special syntax and feature from stimulus-bridge
. The text inside the comments will be parsed as JSON, which is why we have the colon and quotes. The result of this is that... yea! Our controller will be lazy - just like the chartjs
controller!
Head back to our terminal. Let's re-run our commands again to do one last analyzer dump:
yarn run --silent dev --json > stats.json
When we run the analyzer:
yarn webpack-bundle-analyzer stats.json public/build
Yes! This time sweetalert
is in its own file! The only reason we don't also see our actual controller in this same file is that Webpack often splits vendor code and custom code into separate files for caching reasons. If we look around a bit... hmm... ah! Here it is! This new, tiny file contains our submit-confirm
controller code. This will also be loaded asynchronously as soon as it's needed.
Let's see this in action! Go refresh the homepage... I close out some old tabs... then view the page source. The 3 files being loaded now are runtime, this long vendors-stimulus-bridge and app.js
.
If you look at the analyzer... the 3 files are this app.js
, the tiny runtime.js
and... this vendors
file. These are now way smaller than when we started.
Click into a product, then go back to the Network tools filtered to JavaScript files. Add the item to the cart... then click to the cart page.
Woohoo! The SweetAlert code and our submit-confirm controller were both just downloaded asynchronously! And everything still works! This does mean that, in theory, someone could click "Remove" so fast that the JavaScript isn't loaded on the page. If that's a problem, you should either avoid using fetch lazy, or disable this button on page load and enable it in your Stimulus controller.
Okay, enough with that crazy laziness! Next, after chatting with some of you lovely people over the past few weeks, I thought it might be nice to do a full example of submitting a form via Ajax, complete with validation errors and reloading part of the page after success. Oh, and we'll do all of this inside a modal... to make it more interesting.
5 Comments
Howdy!
This is a very good question that I've often asked myself. Unfortunately, at least for now, no - each controller must be marked with /* stimulusFetch: 'lazy' */
to make them lazy.
I honestly don't know why the default behavior doesn't make everything lazy. I mean, that seems like a good idea on the surface! Ha, but I'm sure there is a reason why they're not lazy right off the bat. We'll have to wait for someone with bigger brains than me to answer that for us. Cough Cough Ryan...
I hope this helps a little bit for ya!
Hey!
Yea, it's a good question. There is a downside to lazy controllers, which is why they are not on by default. The downside is subtle, but it exists. Specifically, when a controller is lazy, there will be a short delay before it is initialized while the controller JavaScript code is downloaded via Ajax.
About making them lazy everywhere, I think it actually IS possible, but it's super technical, so probably not many people realize it. We talk about it, in a sense, in this section: https://github.com/symfony/stimulus-bridge#advanced-lazy-controllers
In theory, it should be this simple:
// assets/bootstrap.js
export const app = startStimulusApp(require.context(
'@symfony/stimulus-bridge/lazy-controller-loader?lazy=true!./controllers',
true,
/\.[jt]sx?$/
));
The only difference from the normal code is the extra ?lazy=true
in the long string line.
Cheers!
Is there any way to give a vertain order to the lazy loaded controllers? I'm extending a few and lazy they get added in the wrong order.
Hey @G D!
Sorry for the VERY slow reply - this message was waiting on me, and it's been a busy last couple of weeks!
So this is really interesting. Honestly, it sounds like a bug to me. Can you share your setup and the error you're getting? This was a fairly complex feature (that I developed) - so it's very possible there is an edge case here :)
Cheers!
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
"doctrine/annotations": "^1.0", // 1.11.1
"doctrine/doctrine-bundle": "^2.2", // 2.2.3
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
"doctrine/orm": "^2.8", // 2.8.1
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"sensio/framework-extra-bundle": "^5.6", // v5.6.1
"symfony/asset": "5.2.*", // v5.2.3
"symfony/console": "5.2.*", // v5.2.3
"symfony/dotenv": "5.2.*", // v5.2.3
"symfony/flex": "^1.3.1", // v1.21.6
"symfony/form": "5.2.*", // v5.2.3
"symfony/framework-bundle": "5.2.*", // v5.2.3
"symfony/property-access": "5.2.*", // v5.2.3
"symfony/property-info": "5.2.*", // v5.2.3
"symfony/proxy-manager-bridge": "5.2.*", // v5.2.3
"symfony/security-bundle": "5.2.*", // v5.2.3
"symfony/serializer": "5.2.*", // v5.2.3
"symfony/twig-bundle": "5.2.*", // v5.2.3
"symfony/ux-chartjs": "^1.1", // v1.2.0
"symfony/validator": "5.2.*", // v5.2.3
"symfony/webpack-encore-bundle": "^1.9", // v1.11.1
"symfony/yaml": "5.2.*", // v5.2.3
"twig/extra-bundle": "^2.12|^3.0", // v3.2.1
"twig/intl-extra": "^3.2", // v3.2.1
"twig/twig": "^2.12|^3.0" // v3.2.1
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
"symfony/debug-bundle": "^5.2", // v5.2.3
"symfony/maker-bundle": "^1.27", // v1.30.0
"symfony/monolog-bundle": "^3.0", // v3.6.0
"symfony/stopwatch": "^5.2", // v5.2.3
"symfony/var-dumper": "^5.2", // v5.2.3
"symfony/web-profiler-bundle": "^5.2" // v5.2.3
}
}
What JavaScript libraries does this tutorial use?
// package.json
{
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.12.13
"@popperjs/core": "^2.9.1", // 2.9.1
"@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
"@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
"@symfony/webpack-encore": "^1.0.0", // 1.0.4
"bootstrap": "^5.0.0-beta2", // 5.0.0-beta2
"core-js": "^3.0.0", // 3.8.3
"jquery": "^3.6.0", // 3.6.0
"react": "^17.0.1", // 17.0.1
"react-dom": "^17.0.1", // 17.0.1
"regenerator-runtime": "^0.13.2", // 0.13.7
"stimulus": "^2.0.0", // 2.0.0
"stimulus-autocomplete": "^2.0.1-phylor-6095f2a9", // 2.0.1-phylor-6095f2a9
"stimulus-use": "^0.24.0-1", // 0.24.0-1
"sweetalert2": "^10.13.0", // 10.14.0
"webpack-bundle-analyzer": "^4.4.0", // 4.4.0
"webpack-notifier": "^1.6.0" // 1.13.0
}
}
Hello guys,
I would like to know if there's a way to make all controllers and theirs dependencies are lazy load by default? (in a config file for example). I can have the
/* stimulusFetch: 'lazy' */
each time I have a new controller but still... I mean I don't understand why the default behavior is bundle all js file and download it even the page doesn't need it. In a real project, I could have hundreds files js and this could harm the performance if I forgot to do the lazy stuff.