ember-cli-addon-guard

Protect your app against common addon dependency problems.


Keywords
ember-addon
License
MIT
Install
npm install ember-cli-addon-guard@0.2.1

Documentation

ember-cli-addon-guard Build Status

This addon guards against addon interop issues that can arise in ember-cli projects.

Motivation

Suppose you're happily building an application using ember-modal-dialog, which in turn relies on ember-wormhole at 0.3.x. You then go add ember-power-select, which relies also relies on ember-wormhole via ember-basic-dropdown, but at 0.5.x. Your dependencies might now look like this:

my-app
├─┬ ember-modal-dialog
│ └── ember-wormhole@0.3.6
└─┬ ember-power-select
  └─┬ ember-basic-dropdown
    └── ember-wormhole@0.5.1

Your package manager notices the conflicting version requirements for ember-wormhole and helpfully makes sure each addon gets the version it's asking for. But your final built application will only have one copy of ember-wormhole — which version will it be?

ember-cli's current build process will merge both versions together, with files from one version clobbering files from the other whenever they have the same name. This also means either ember-modal-dialog or ember-power-select will wind up attempting to use a version of ember-wormhole that it's not expecting, which can lead to anything from hard exceptions to subtle behavioral bugs.

In the scenario described above, the version conflict arose because of adding a new dependency, but it can also happen when you update an existing one. Regardless of how it happens, it may or may not immediately be obvious that something is wrong. The things that break may be subtle, or in untested edges cases in your application.

Solution

As with any problem, the first step towards a solution is identifying the problem. By default, ember-cli-addon-guard runs prior to every build and checks the full dependency tree for duplicate addons of different versions. Any cases of addon duplication will be examined to see if the addons introduce browser run-time code. If a run-time duplication conflict is found, the problem will be logged in detail and a hard error will prevent the build from proceeding until conflicts have been resolved.

Once a build has been blocked, what's the best way to resolve dependency conflicts? Let's say that multiple versions of ember-wormhole have been identified. To avoid these problems "manually", you could take one of the following steps:

  • pin your app's ember-power-select dependency to an older version that uses ember-wormhole 0.3 (if one exists) until ember-modal-dialog is updated
  • fork ember-modal-dialog and make whatever changes are necessary for it to work with ember-wormhole 0.5, then use your fork until those changes are accepted upstream

You can also take a more automated approach to remove duplicate packages across your entire project. If you're using yarn as your package manager, consider using yarn-deduplicate.

ember-cli-addon-guard also offers its own (experimental!) solution to this problem: namespacing. Namespacing allows multiple versions of a single addon to co-exist in the same project without clobbering each other. You must explicitly opt-in to namespacing via the namespaceAddons array in your config file (see below). Namespacing should be used with caution and tested thoroughly.

As a last resort, if duplication of a specific addon is truly not an issue for your application, you can choose to explicitly ignore addons via the ignoreAddons array in your config file (see below).

Browser-run-time vs. Build-time-only Addons

Some addons don't actually add files to your application tree, so they don't have the conflict problem described above. In fact, for some addons (like preprocessors such as ember-cli-babel), insisting on a single version is undesirable. Different addons your app uses should be able to compile using whatever tooling they like without conflicting with one another.

For this reason, ember-cli-addon-guard is only concerned with preventing multiple versions of browser-run-time dependencies from co-existing in a project. In order to determine whether an addon contains browser-run-time code, it's checked to see if it contains addon, app, or src directories.

Cache-keys vs. Versions

So far, we've discussed addon duplication in terms of addon names and versions. ember-cli currently takes a dynamic and customizable approach to identifying unique instances of addons: cache-keys. By default, an addon's cache-key is calculated from a hash of its package.json (minus any private keys). An addon may also customize its cache-key, via a custom implementation of cacheKeyForTree, which can be narrower or broader than the default cache-key.

For consistency with ember-cli and its ecosystem, ember-cli-addon-guard also relies upon each addon's cache-key to determine uniqueness if that addon supports cacheKeyForTree. When cache-keys are used, they will be displayed together with version in dependency trees.

Usage of cache-keys will go away with the introduction of embroider v2 addons, which take a static approach to identifying package uniqueness that is simpler and more robust. ember-cli-addon-guard will remain compatible with these embroider packages by reverting to version keys when addons don't support cacheKeyForTree.

Note: The addon that calculates cache-keys by default, calculate-cache-key-for-tree, was just recently updated to ignore private, package-manager-specific keys that may be added to package.json files. Prior to v1.2.3, this may have made cache-keys much more unique and created a lot of false-positives when checking for multiple versions of an addon. For this reason, ember-cli-addon-guard includes a validation check to ensure that any instances of calculate-cache-key-for-tree that are encountered are version >= 1.2.3.

Usage

When ember-cli-addon-guard is added to your project, it will automatically attempt to identify and circumvent conflicts prior to every build.

You can also manually run ember addon-guard to get a detailed report of any problems encountered in a project without performing a build.

Configuration

Configuration for this addon is specified in a dedicated file in your project's config folder. For apps, this will be config/addon-guard.js, and for addons, this will be the dummy app's tests/dummy/config/addon-guard.js.

This configuration file allows you to safelist any addons you'd like to ignore with the ignoreAddons member:

// config/addon-guard.js
module.exports = {
  ignoreAddons: [
    'ember-yolo',
    'ember-wat'
  ]
};

Other configuration options include:

  • skipBuildChecks: true - specify if you only want to run ember-cli-addon-guard via its CLI, and not during every build.

  • skipCacheKeyDependencyChecks: true - skip the cache-key dependency checks mentioned above.

EXPERIMENTAL: Namespacing

IMPORTANT: The namespacing feature should be considered experimental, used with caution, and thoroughly tested.

In order to try the experimental namespacing feature of ember-cli-addon-guard, list any addons you'd like namespaced in the namespaceAddons member of your configuration file:

// config/addon-guard.js
module.exports = {
  namespaceAddons: [
    'ember-yolo',
    'ember-wat'
  ]
};

How does namespacing work? As described above, every unique instance of an addon can identified via its cache-key. Namespacing inserts this cache-key in the path of every module provided by the namespaced addon, and all imports from those modules are adjusted accordingly. Furthermore, namespacing must also be applied to resolvable elements in templates, such as helpers and components. This last portion feels like a clear boundary violation that, thankfully, should be unnecssary once template imports are introduced in the next edition of Ember. For now, please be aware that there are some edge cases that simply don't work yet with namespacing - such as using the component helper to dynamically invoke a namespaced component.

History and Attribution

This project is based upon the excellent ember-cli-dependency-lint addon. It was forked from the original project for the following reasons:

  • ember-cli-addon-guard does not attempt to be backward compatible with ember-cli-dependency-lint, and it's anticipated that both will evolve independently. Both addons have unique configuration files and approaches that should not conflict.

  • By default, ember-cli-addon-guard runs prior to every build and strictly prevents addon dependency conflicts that have not been explicitly ignored.

  • ember-cli-addon-guard is only concerned with addons that introduce run-time modules. This is inferred from the directories and files present in each addon. Build-time-only addons don't need to be explicitly safelisted, as they do in ember-cli-dependency-lint.

  • ember-cli-addon-guard identifies addons uniquely by name and cache-key, if available, for compatibility with the current approach taken by ember-cli. If cache-keys are not supported by a particular addon, version will be used, which should be forward-compatible with embroider (as explained above).

Note from @dgeb: I'll be glad to contribute this fork back to Salsify or to the ember-cli org, wherever it can be most useful. Hopefully, a single, shared solution will be adopted by default for ember-cli projects, as is being discussed in Ember RFC 464.

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.