Why do addons need an app and addon directory?

I haven’t seen this question answered definitively anywhere, so I was hoping to have a canonical reference. Why do Addons require both an addon/ and app/ directory? I realize that final app directories are merged together for the resolver to be able to find components from addons, for example, but I don’t understand why there couldn’t be a single top level?

This probably isn’t the canonical example you were hoping for but the way I understand it it’s all about namespacing and controlling what gets put into the app namespace vs remaining in the addon namespace. IIRC /addon isn’t actually required, you can just put everything in /app and it will work but you will usually end up polluting the app namespace with stuff that you don’t want.

For example components, helpers, initializers, etc are things that you want to re-export in app so they are picked up by the app resolver. Otherwise I think you’d have to add re-exports in your app for everything you wanted to use from an addon which would be a pain and kinda clutter up your codebase.

But things like utils, mixins, computed property macros, etc you generally don’t want to export because you’ll just pollute the app namespace with things that you will import from the addon anyway.

And of course you can also cause issues by re-exporting some things, like application adapters. For example the ember-django-adapter addon re-exports the drf adapter as application adapter which (not to call the addon author out or anything, it’s just an addon i’ve noticed this in) is an “aggressive” assumption and imho not good practice. Contrast this with emberfire which does not re-export adapters and makes the user determine which adapter to use and how (which imho is more responsible and flexible).

Anyway all that to say I think the primary reason is that you need some mechanism of controlling what gets re-exported into the app namespace and what stays in the addon namespace and must be imported. But i’m not a core team member or anyone with an understanding of the history of the addon format so this certainly isn’t a “canonical” answer, there might be a lot more to it.

That’s basically correct. It helps to talk about two separate layers here. The first layer is Javascript modules, and how files on disk get names that can be imported at runtime.

If you make an addon named example and you use it inside an app named the-app:

  • a file named addon/one.js in the addon can be accessed like import One from "example/one".
  • a file named app/two.js in the addon can be accessed like import Two from "the-app/two".

Notice that the addon’s app tree gets imported out of the app’s name, not the addon’s name! That is… not a great thing for libraries to do. So why is that how it works?

That brings us to the second layer, which is Ember’s resolver. When you try to access a component, or helper, or service, or ember-data adapter, Ember needs to resolve a name like <MyComponent/> into a an actual module it can import. Glossing over some unnecessary detail, <MyComponent/> gets translated into import MyComponent from "the-app/components/my-component".

So this is the explanation for why addons put stuff in the app tree. If you want people to be able to say <MyComponent/> for a component that comes from your addon, it needs to get into the app’s module namespace, not the addon’s.

This isn’t great overall. Human readers can’t see at a glance where the component is coming from. Tooling like VSCode, Typescript, and Javascript bundlers also can’t easily see where it’s coming from to give you a better experience or more optimization. The intention is to migrate people away from needing this by introducing a new way to explicitly import things instead of always resolving them.

This part makes sense. I was wondering why the design isn’t the inverse though. Everything is in app/ and things that you don’t want to pollute the app namespace go into any other directory in the repo?

I think it’s really nice (albeit under-documented) that this happens! (And I realize that namespace collisions is something actively being considered / worked on with template imports, etc).

Based on these answers, I’m still not quite sure why an addon structure couldn’t look like this:

app
  components
  utils
  etc
-private
  whatever

Anything in an addon that need to import something “private” from itself could use the addon’s namespace or relative imports, and apps would not be able to reference those things through the resolver (but I assume JS imports would still work).

The reason addon isn’t named “private” is that it’s still the public place for things you want people to import out of your addon.

The default blueprints assume that every component needs to be both resolvable (so it must be in the app folder) and also importable (so it must be in the addon folder) in case people want to import and extend it. That’s why they have a file in both places for every component.

2 Likes