Ensuring treeForApp from Addon1 runs before Addon2

I’m working on AddonDocs, and it depends on Mirage. I want the treeForApp hook from AddonDocs to run before the treeForApp hook from Mirage (so that AddonDocs can create a file that Mirage expects, in case the host app doesn’t provide it).

In AddonDocs’ package.json I made sure to specify that it should run before Mirage:

"ember-addon": {
  "configPath": "tests/dummy/config",
  "before": [
    "ember-cli-mirage",
    "ember-cli-htmlbars",
    "ember-svg-jar"
  ],
  "after": [
    "ember-modal-dialog"
  ]
}

and defined treeForApp

treeForApp(app) {
  console.log('here in addon docs..');

  // snip
}

I also cracked open node_modules/ember-cli-mirage/index.js and added a log to Mirage’s hook:

treeForApp(appTree) {
  console.log('here in mirage');
  
  // snip
}

Here’s the output from terminal:

➜  ember-cli-addon-docs git:(fix-mirage) ✗ ember s
here in mirage
here in addon docs..
here in mirage

Any ideas what’s going on?

The treeForApp methods of addons cannot “see” the results of other addons treeForApp. Each is called independently, and expected to return its own content, then later in the build pipeline all of the addons’ app trees are merged together with the one from the actual app.

That should be okay for my use case… AddonDocs can look at the tests/dummy directory of the “host addon” and determine on its own if it wants to augment the appTree. I just need to run that augmentation code before Mirage has a chance to run.

Is this possible?

I can imagine how to make that work down in the broccoli layer, but I wonder if there isn’t an alternative design.

It sounds like you want AddonDocs to be able to create additional input to Mirage, and it’s doing so by augmenting the host application’s tree. But you could also establish an API directly between AddonDocs and Mirage that would convey the same information. A general API for an addon to augment the mirage config.

Also, one of the goals of module unification is to stop allowing addons to mess around with the app tree.

:+1: this is definitely the long-term solution and has been requested by others.

However in the short-term, and in the interest of shoring up my Ember CLI knowledge, I’d still like to understand why Mirage’s treeFor* hooks are getting run in the build before AddonDocs’.

Now that I notice this:

I think you may have an easy out, which is that the application takes precedence over all addons, so it’s safe to always provide your default file in AddonDocs’s treeForApp.

But if you want to go deeper…

One thing about broccoli is that nearly everything is pull based. The whole graph is constructed first, and then actual work happens as each tree is read. And the work can be asynchronous. The treeFor methods are called in plugin order, but that is just the graph construction phase.

You can’t really guarantee order between two broccoli transforms unless there is some dependency between them. This is a feature, not a bug, because it leaves the build system free to determine when/if to run any particular transform stage. Once you start to consider arbitrary incremental rebuilds, one can see that it needs to be this way.

So if you really find yourself caring about the relative order of two transforms, you should be looking to refactor so that one actually depends on the other explicitly.

But here is where your question gets murky. When an addon processes treeForApp, all the inputs to that function are things from the addon itself. It doesn’t get to see anything from the app anyway. The default behavior of treeForApp is just to return everything that was in the addon’s app folder (which is typically just re-exports as written by the component blueprint, etc).

To put it another way, I think you were imagining that each addon’s treeForApp composes something like:

treeForApp(treeForApp(treeForApp(originalApp)))

But in reality it is more like

mergeTrees([treeForApp(), treeForApp(), treeforApp(), originalApp])

So even if you manipulate the relative order of the addons, you are only changing the order of the merge, you’re not establishing any data dependency between the addons.

1 Like

Thanks so much for the explanation :+1:

I do understand your point about each treeForApp not having access to others. That makes sense.

Ultimately I think my problem is that currently, Mirage is hardcoded to read from disk instead of checking the build. I haven’t had time to circle back yet but that would obviously cause an issue.

I would love to keep learning how Ember CLI works its magic. Is there any particular part of the codebase that would be a good place to start reading, to better understand it?

One idea is to take a relatively small but comprehensive test case, drop a breakpoint in the start of the test, and step through the whole process in the debugger to see what calls what.

3 Likes