Problems importing loads-es from an Addon (possibly related to ember-auto-import)

Hi again! Back with another question about the best way to import / manage dependencies, after reading the docs and hitting a wall.

Ultimately, I am unclear about when (in an Embroider, Webpack, ember-auto-import world) I should add a package to:

  • an App;
  • an Addon’s dependencies; or
  • an Addon’s Blueprint for installation in the parent App.

Some background:

I have an Addon running on Ember 3.28. It is embroiderOptimized, and has ember-auto-import@2 in dependencies and webpack@5 in devDependencies.

I have an Embroider enabled App with my Addon installed. It’s on the same version of Ember, and has ember-auto-import@2 and webpack@5 installed in devDependencies.

My project uses Lodash, via lodash-es so I can take advantage of some tree-shaking magic. :crossed_fingers:t2:

Both Addon and App have their own, independent needs for Lodash.

Initially, I had the package installed in the Addon and the App dependencies, and just assumed that Embroider / Webpack would ensure only one copy ended up in the build. When I look at the Bundle Analyser I can see two copies of lodash-es in the same chunk. I also found that all the Lodash modules were included, not just the handful I have imported, via:

import { sortBy } from 'lodash-es';

My immediate priority was the duplicated packages. I moved Lodash into the Addon’s devDependencies and used a Blueprint to include the package in the App via addPackagesToProject. When I serve the app I get errors indicating that Lodash is missing:

"Could not find module `lodash-es` imported from `(require)`

The above should work, right? Either way, my Addon’s tests fail because they expect lodash-es to be in dependencies:

my-addon tried to import "lodash-es" in "my-addon/components/some-component.js" from addon code, but "lodash-es" is a devDependency. You may need to move it into dependencies.

I’m back to having duplicate copes of the package in the App build.

In this scenario (one package being shared by the App and Addon, for their own reasons) what is the correct way to manage the dependency?

Regarding tree-shaking the unused loads-es modules , what’s the correct way to import?

As always, thank you :pray:t3:

The addon will definitely need it in dependencies. The general rule of thumb is “if it’s needed by the addon code it must be in dependencies”, vs devDependencies are just needed to build the dummy app. This will all be much clearer with the v2 addon format (see below for further reading) because the dummy app will no longer be a part of an addon at all.

I think what you’ll want to do to deduplicate the lodash bundling is to have the addon add lodash as a peerDependency (IIRC this means it should be listed in both dependencies and peerDependencies). This signals that the addon expects lodash-es to be installed by the app that is using it, and it should use the app’s lodash version.

Anyway I think that’s the gist but ackages/bundling isn’t really my forté, someone like @ef4 could give you far more authoritative advice.

links: v2 addon format RFC: 0507-embroider-v2-package-format - article describing migrating to v2 addon format: Migrating an Ember addon to the next-gen v2 format

1 Like

Thanks @dknutsen, I did read Simon Ihmig’s post about migrating to the V2 format but that’s a project for 2022.

Thanks for the note about using peerDependencies, that’s good to know and something to try out. It still leaves me with a question about when I should use that approach, over using a blueprint to add it to the parent app directly.

I’d think you may want both, although the blueprint is more of an optional convenience thing. Using peerDependency in the addon is telling the package manager that the package is needed by the addon, but the addon should expect it to be present from the host app. The blueprint is just a convenient way of adding said package to the host app to help guarantee that it is there.

That makes sense, thanks. I’ll give that a shot :+1:t2:

An addon is really two different packages: the addon itself and the dummy app. The dummy app has access to devDependencies and dependencies. The addon has access to dependencies and peerDependencies.

The choice between dependencies and peerDependencies comes down to whether you want to guarantee that the app and your addon will definitely get the same copy of the dependency. When it doesn’t matter if they’re different or duplicated, you can use dependencies and that guarantees that your addon will always bring its own copy, and that might get optimized into the same copy the app is using but it might not. When you use peerDependencies you guarantee that they will share the same copy and the app must provide it.

When putting a library into peerDependencies, you will also typically put the same library into devDependencies in order to make the dummy app work. (The dummy app must provide the library to the addon, just as any other app would.)