Understanding Ember's import system


#1

I’m getting really frustrated with Ember’s import system. I can’t tell exactly what’s going on behind the scenes and all of the ember-cli documentation just tells me how to do specific things and I can’t extrapolate based on those examples to my own use-cases. Can anyone shed some light on the steps the importer takes to load a module/addon (I’m interested in both) and what relevant properties are set where in Ember so I can figure out what I need to do?

My specific use cases are as follows:

The first situation I have is an integration test that requires a function from another file. I tried sticking the file in tests/helpers and using a relative import but this GitHub issue says I shouldn’t. I just don’t know where to put this file and due to the nature of the function, I’d rather not just copy it into the test itself because it is used across multiple tests.

The second situation I have is I have an add-on with a component that’s being included in multiple other consuming add-ons. I want to add a dependency to my add-on (specifically ember-lodash b/c I need lodash to merge some objects). When the consuming add-ons try to create an instance of my component, I get an import failure saying it can’t find ember-lodash. If I add ember-lodash as a dependency to the apps that consume my add-on, this all works. I feel like this GitHub issue impacts me somehow too but I’m really not sure what I’m supposed to do, especially because ember-lodash doesn’t implement include but implements treeForAddon in it’s index.js instead…


#2

To prevent any confusion, it’s not Ember’s import system, nor is it Ember’s dependency system. Ember is build on top of Node.js which itself uses several libraries to handle this. So your best bet would be to delve deeper in Node.js, there are many resources and tutorials around. Also you should understand the Broccoli build process. Too bad it’s poorly documented, but this great video helped me a lot:

Both the concept of Node.js and Broccoli were huge eye-openers for me and helped me out a lot organizing my projects using addon’s and writing custom precompilers. It’s worth the effort.

Personally I don’t mind using relative imports. Sometimes it can be annoying figuring out the correct path, but that happens with absolute paths as well. Some people recommend absolute paths, some people recommend believing the earth is flat. Don’t worry about it too much.

I think your second use case might have something to do with the difference between devDependencies and dependencies. The devDependencies won’t be added to the build of any consuming app or addon, so try moving it to dependencies and try again.


#3

Thanks so much for the help!

I had no idea that devDependencies were not being consumed by the build. Definitely explains an earlier problem I was having too.

That video was really enlightening! Really solidified my somewhat patchy understanding of Broccoli. I feel like now I might be able to ask the right questions instead of just kind of being frustrated.

Here’s my current understanding of the build (correct me if I’m wrong):

If I do a build, the build will see my dependencies (but not devDependencies) and use that to discover addons which it needs to load and then run its index.js. The build then calls each addon’s include() and other such functions through which the addon then adds its dependencies (through a treeFor* hook or app.import). This happens for each level of the build (so addons and addons of addons) such that the addons dependencies are read and that is used to discover the addons of addons. The final tree is output.

What I’m not sure of is:

  1. What is the format of the final tree? If the final tree includes all the code from addons, then does the build process rewrite all the requires to point to the correct places in this new tree? Because otherwise I see Node.js handling imports and requires such that they will try to load up files in the node_modules folder (which was the issue I think I was running into in #2).
  2. I understand treeFor* and how that works but what tree and folder does app.import loads its things into. Is it just a vendor file at that point and it gets copied to dist during the final step?
  3. In the video (@ 23:02, the graphic shown) she says that treeForAddon is consumed by the vendor files (.js and .css) but what about things like .png, are they just copied in?
  4. If I depend on an addon in my addon that the parent also depends on, will my app run it too or will it only run it in the parent application? I had an issue where I had an addon of an addon that wanted to run the plugin but couldnt add its options to app fast enough even with the “before” property set to run before the addon it was depending on because it was a doubly nested addon.
  5. Does this GitHub issue still apply? Has this been fixed (b/c it seems like its a thing you have to do manually from rwjblue’s comment). If it is still manual, do I have to do a similar invocation for all the hooks too and other such functions (even if I don’t implement them in the current addon).