Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Making your Custom Controllers Lazy

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Stimulus 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.

... lines 1 - 4
/* stimulusFetch: 'lazy' */
export default class extends Controller {
... 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.

Leave a comment!

5
Login or Register to join the conversation
Dang Avatar

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.

Reply
Jesse-Rushlow Avatar
Jesse-Rushlow Avatar Jesse-Rushlow | SFCASTS | Dang | posted 9 months ago

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!

Reply

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!

Reply
Default user avatar

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.

Reply

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!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works perfectly with Stimulus 3!

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.18.5
        "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
    }
}
userVoice