Stimulus Discourse

Advice Request: Stimulus + Webpack - Best practices bundle-splitting for multi-page, SSR site


#1

Hi!

Love Stimulus and have been working on a high-traffic Stimulus-powered site for some time.

The site is multi-page SSR ( not a rails project, we use Stimulus with Shopify ).

Looking to optimize things a bit with a refactor and was curious about best practices here.

For example, we have some controllers that load on every page ( like header, cart, etc ) and others that only run on a few or single pages ( like lookbooks, contact pages, etc ).

To-date, we have been using the webpack loader helper and all our controllers get bundled up to one javascript file. This is fine, but that single file has grown with the site and our initial page loads are getting slower.

What we are trying to do, is have some global controllers and some entrypoint-specific controllers, but run into an issue across multiple ES6 imports when it comes time to .start();

We could import each controller needed on each page entry point, but that leaves so much room for error ( and lots of repetition with our global controllers ).

Would love to hear if there are some ways to say have a main.js that starts stimulus then register other controllers to it based on the entrypoint ( Dynamic import and register? ).

I went through through all the github issues and posts here, but nothing really fit the bill. Closest was:


and

We have about 100 or so controllers, so wanted to kinda talk it out a bit before going in too deep.

Thanks in advance for the help!


#2

In simpler terms, I’d like to do something like this:

// global_controller.js
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";

const application = Application.start();
const context = require.context("../controllers/global", true, /\.js$/);
application.load(definitionsFromContext(context));

And then at a page / template level do something like this:

// template_controllers.js
**import 'global_controller'**
import HelloController from "./controllers/hello_controller"
import ClipboardController from "./controllers/clipboard_controller"

application.register("hello", HelloController)
application.register("clipboard", ClipboardController)

Looking for help on how to create a ‘global’ set of controllers that can be added to each entrypoint.

Just curious what best practice bundle-splitting practices are.

Thank you in advance for your time and help :slight_smile:


#3

If I understand correctly, I think I would generate different packs, a global pack and optionals packs (optional1, optional2, …)

My global controller entry point

// app/javascript/packs/global_controller.js
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";

const application = Application.start();
const context = require.context("../controllers/global", true, /\.js$/);
application.load(definitionsFromContext(context));

My optional1 entry point

// app/javascript/packs/optional1_controller.js
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";

const application = Application.start();
const context = require.context("../controllers/optional1", true, /\.js$/);
application.load(definitionsFromContext(context));

and then in my layout

  ...
  <%= javascript_pack_tag 'global', defer: true %>
  <%= javascript_pack_tag 'optional1', defer: true %>
  ...

Of course the layout would have some logic to toggle the optional packs. Maybe with a content_for

I have not tested it nor implemented it like this in the past… but I suppose it should work, not sure if it is exactly what you are looking for


#4

Thanks @adrienpoly this is a good start.

I guess my next question is ( one I maybe should have asked first :slight_smile: ) is - is it ok to have 2 Application.start() calls?

I don’t use Rails for this project, using Shopify ( and BTW - Stimulus works AMAZING with SSR Shopify! ) but I can split out my bundles and load them dynamically in liquid just like the erb sample you posted.

Will test and see how it goes - but if you have knowledge of Stimulus under the hood and know if its cool or not to have the 2 start()'s please enlighten me!

Thanks so much for taking the time to comment back!


#5

I’ve been following the pattern @adrienpoly describes, expect I called the global namespace
application, trying to follow rails conventions.

Then I load specific packs on a per-view basis, only when needed, nested in a content_for helper call.
It’s been working perfectly fine so far! :slight_smile:

My javascript folders & files organisation :point_down:


#6

Very interesting ideas presented here.

I’m wondering how to deal with controllers that are named the same thing using this arrangement?

For example, say you have the Webpack config as described:

// app/javascript/packs/global_controller.js
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";

const application = Application.start();
const context = require.context("../controllers/global", true, /\.js$/);
application.load(definitionsFromContext(context));

// app/javascript/packs/optional1_controller.js
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";

const application = Application.start();
const context = require.context("../controllers/optional1", true, /\.js$/);
application.load(definitionsFromContext(context));

and let’s say you have a folder structure such as

controllers/
  global/
    test_controller.js
  optional1
    test_controller.js

Now you have some HTML which resembles

<div data-controller="test">

How does stimulus know in this case which controller js file to use?


#7

Interesting question indeed.
I haven’t run into such a collision situation for now, as I’m using specific packs on each page.

Pretty sure you could still namespace then in separate folders, thus effectively being able to attach them both on the same page, following the baked-in conventions you can find here.

What do you think?


#8

How about using app/javascript/packs/application.js for storing global code ?


#9

This ( I think ) would work well in a rails project but I am using Stimulus with Shopify ( and I can’t reiterate how well Shopify components and sections work with Stimulus )

Needless to say I’m a happy camper. Always looking to improve.

What I am in the middle of doing now is namespacing with folders. Think is naming things is hard. Because I was dealing with what @fizznol posted really quick.

I think a another week or two of practical implementation and then I’d love to create an abstracted repro of the design pattern so we can discuss something more tangible.

I can’t thank you all enough for your feedback. It led to many thoughts that I solved my initial issue - but I’d also like to share the pattern for a broader look.