Embroider - Rendering dynamic components using {{component}}

Hi, I am trying to use Embroider to perform lazy loading of my ember app(v3.28). Currently we have a lot of instances where we have used the {{component}} helper to render dynamic components. I have few queries related to this:

  1. I came across two options in Embroider - splitAtRoutes and staticComponents. Since, I am looking to just lazy load the ember app, is it fine if I only use splitAtRoutes without enabling staticComponents. Will I be able to use the {{component}} helper to render dynamic components if I don’t enable it? What will be the difference if I enable or don’t enable the staticComponents mode? Also is staticComponents dependent on allowUnsafeDynamicComponents?

  2. Consider my app to be structured as such:

route1 -> Uses parentComp -> Uses subComp1 -> Uses subComp2
route2 -> Uses parentComp -> Uses subComp1 -> Uses subComp2

And subComp2 uses {{component}} to render a dynamic component. Both route1 and route2 would know the possible set of components (both routes may have separate set of components that could be passed and each route would have no knowledge of the other route’s set of components)that could be passed to the dynamic component helper. Is this sufficient?

As per my knowledge of Embroider, since parentComp is used in two routes, it would be extracted to a common chunk(which will be loaded along with route1 or route2’s chunk based on whichever is loaded first), so should parentComp know regarding the set of all possible components, or is it sufficient for just the route to have that knowledge?

Since, I am looking to just lazy load the ember app

What do you mean by lazy load here?

As per my knowledge of Embroider, since parentComp is used in two routes…

What code actually “knows” about the dynamically rendered components? Is it the route? or parentComp? The only thing preventing you from using staticComponents (which I think is now called staticInvokables) would be not knowing at build time what the possible set of components to dynamically render are.

I’m not sure if this will answer your question how you wanted but I think the important thing with Embroider is that components are imported and passed “by value” rather than “by name”. So it doesn’t really matter where that happens as long as the place where you currently using the component “names” e.g. "some-component" could instead do something like this:

+  import SomeComponent from '...';

-  ... "some-component"
+  ... SomeComponent

If the spot where you’re using the {{component}} helper is the same place the component name is coming from then it’s easy:

+  import SomeComponent from '...';

-  {{component "some-component" ...}}
+  {{component SomeComponent ...}}

If you’re passing component names as values or args it’s slightly more complicated but not really any different:

+  import SomeComponent from '...';
+  import OtherComponent from '...';

-  <ParentComponent @dynamicChild={{if this.someCondition "some-component" "other-component"}} ... />
+  <ParentComponent @dynamicChild={{if this.someCondition SomeComponent OtherComponent}} ... />

If you were to import the dynamic components in your routes and pass them down to where they need to be invoked then that could theoretically optimize your code splitting. Otherwise I think whatever component does import them would bundle everything with it. Hope that makes sense and gives you something to go on but definitely drill in some more if not.

1 Like

Hello,

Thanks for the assistance!

I currently use broccoli for the build process, which builds the entire emberapp into a single js file (emberapp.js). This file is entirely loaded when accessing my web app. Instead, I want to split the emberapp.js into multiple chunks (ideally a chunk for each route), and then load each chunk as the route is accessed, and I figured using Embroider’s splitAtRoutes mode might help achieve my usecase.

My understanding of how Embroider and Webpack does this, is that for each route, it would try to get the list of dependencies (components, services, helpers) and then try to bundle the route with its dependencies into a single chunk. Dependencies, that are used in more than one route will be bundled into a common chunk.

Let’s say, if I use {{component}} helper in a route, without the route “knowing” what the possible set of components may be passed as the dynamic component:

  1. If staticInvokables is enabled, it would cause an issue at buildtime
  2. If staticInvokables is disabled, it would split and build the route as a chunk with all its resolvable dependencies and components bundled with it, and the dynamic component would be resolved at runtime. If the chunk that contains this dynamic component has already loaded, this wouldn’t cause an issue, but if it had not been loaded it would cause an issue at runtime.

Please feel free to correct my understanding!

Overall that sounds right and makes sense although I can’t say I’m a deep expert on Embroider or bundlers in general.

The part that I’m not sure about here is exactly how Embroider would treat these components when bundling. I know with some of the compatibility flags turned on it does some magic to try to bridge the “old ways” with the “new ways”, if you will. Not 100% how it would decide to bundle the dynamically invoked components.

How many possible components are we talking here? And where do the component names come from? Are they hardcoded in the FE somewhere or are they coming from the backend or something? In the long run I still think you’ll want/need to move towards a statically analyzable way of rendering these, but certainly don’t have to do that all at once.

1 Like

Unfortunately, there isn’t a fixed number for the possible components. Overall, it would be a fairly large number.

In my current design, the component names are loaded from the backend. And yes, I am planning on moving the logic to frontend, so that I can have some sort of registry containing the list of possible components that could be passed to the helper, so that it could be statically analyzable. But this would require some heavy design changes in my app.

So for now, I was exploring whether I could ship an initial version using Embroider’s route splitting, with the dynamic components being resolved at runtime, and then in a later version I could transition the logic for the components to be statically resolvable at buildtime.

Anyway, thanks for the help!

1 Like

Ah yeah that all makes sense. I think that’s a good strategy. Like I said I’m not sure exactly how Embroider would handle bundling without static components turned on but with splitAtRoutes turned on. Maybe @real_ate or @NullVoxPopuli could give you a better idea there. Good luck!

2 Likes

splitAtRoutes requires all static flags to be enabled <3

so that I can have some sort of registry containing the list of possible components that could be passed to the helper, so that it could be statically analyzable. But this would require some heavy design changes in my app.

this is the exact correct thing to do :tada:

2 Likes

that’s kinda what i was thinking but I didn’t see anything in the docs confirming (although I didn’t look very hard :sweat_smile:)

2 Likes

Thanks for the clarification! Could you also please confirm if Embroider with the splitAtRoutes and all static flags enabled work with pod structure of the Ember app?