New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Managing common dependancies between the host app and engines #502

Open
gitmil opened this Issue Nov 16, 2017 · 10 comments

Comments

Projects
None yet
8 participants
@gitmil
Copy link

gitmil commented Nov 16, 2017

We have a host app and multiple routable engines they all share some common lib (addon). For any small change to common lib we have to do a release and update host app and all the engines' package.json. So we would have to also create releases of all the engines as well. We are using semantic versioning but The reason of doing it this way is, we have moved tests related to engines into engines and when we run engine's test in dummy app we need the latest version of those common libs in the engines. Is there a better solution to solve this process.

Also it will be nice if someone shares their processes regarding engines. Its been bit difficult to manage engines in terms of dependancies.

@MelSumner

This comment has been minimized.

Copy link

MelSumner commented Dec 5, 2017

We are having the same issue, and would benefit from some extra documentation/clarification. (+@dgeb)

@villander

This comment has been minimized.

Copy link
Contributor

villander commented Feb 28, 2018

@dgeb Is there a solution for this? or does a feature need to be created for it?

@villander

This comment has been minimized.

Copy link
Contributor

villander commented Apr 8, 2018

@MelSumner you found any solutions to this?

@Leooo

This comment has been minimized.

Copy link

Leooo commented Jul 6, 2018

We are thinking on our side to use ^ versions of our common addon in the package.json of both the parent app and every engine. That way new builds of the parent app will always automatically get the last common addon version both for the app and the engines.

Problem we have left with that is that we would still need to run every engine's tests together with the parent app tests, to make sure the new common addon version doesn't actually break the engines. Anyone knows how to do that?

@mydea

This comment has been minimized.

Copy link
Contributor

mydea commented Jul 9, 2018

This might not work for everyone, but we've recently moved our app + engines to a yarn workspace, roughly following this guide: https://medium.com/square-corner-blog/ember-and-yarn-workspaces-fca69dc5d44a

Now we run the tests for the host app & all engines in CI whenever we make a change to any of the engines.

@deepan83

This comment has been minimized.

Copy link

deepan83 commented Jul 9, 2018

So there seems to be some unpredictable behaviour with common addon dependencies in lazy loaded engines with host version modules (of the same addon). We've seen the host version modules being overridden with the engine's version causing bugs. Would the solution be for engines to build its dependencies into it's own namespace? /engine-name/addon-name/module-name for example so they don't conflict with host app modules?

@nmcclay

This comment has been minimized.

Copy link

nmcclay commented Jan 2, 2019

I wanted to leave a note that this issue has caused my company a lot of confusion as well. What is happening is that if you have multiple lazy-loading engines, and each engine has a shared dependency on a common addon whichever engine loads first will get its version of the addon defined for every other engine and even the HOST APP if it invokes the same component name. This is an issue if you are using engines to spread domain concerns down to individual teams because they all have to be in lock step with what version of this shared addon they use or else risk errors when component APIs change and a user doesn't load a particular engine first.

Most interestingly the code for all versions of the shared addon does exists within the engine-vendor.js source, however the namespace for its define is in conflict with the other engine's and host app's name so it will never be able to be imported. Personally this is a huge pain to deal with, and I'm currently exploring what @deepan83 suggests in name-spacing the modules somehow that you can manually import the correct version of your addon in each engine.

@deepan83

This comment has been minimized.

Copy link

deepan83 commented Jan 2, 2019

@nmcclay happy to help with this as well. i'll do some digging.

@nmcclay

This comment has been minimized.

Copy link

nmcclay commented Jan 2, 2019

So I played around with this and I have what seems to be a solution. Basically its a two part solution, first the shared addon needs a namespace configuration in its index.js:

const addonName = require('./package').name;

module.exports = {
  name: addonName,

  included(app, parentAddon) {
    // Quick fix for add-on nesting
    // https://github.com/aexmachina/ember-cli-sass/blob/v5.3.0/index.js#L73-L75
    // see: https://github.com/ember-cli/ember-cli/issues/3718
    while (typeof app.import !== 'function' && (app.app || app.parent)) {
      app = app.app || app.parent;
    }

    // if app.import and parentAddon are blank, we're probably being consumed by an in-repo-addon
    // or engine, for which the "bust through" technique above does not work.
    if (typeof app.import !== 'function' && !parentAddon) {
      if (app.registry && app.registry.app) {
        app = app.registry.app;
      }
    }

    // Per the ember-cli documentation
    // http://ember-cli.com/extending/#broccoli-build-options-for-in-repo-addons
    let target = (parentAddon || app);
    this.options = target.options || {};

    // only allows namespace to be set once per nested addon
    if (!this.namespace && this.options['namespace']) {
      this.namespace = `${this.options['namespace']}-${this.name}`;
      this.name = this.namespace;
    }
    this._super.included.apply(this, arguments);
  }
};

Then you can provide this namespace to your addon from your engine's index.js:

const EngineAddon = require('ember-engines/lib/engine-addon');
module.exports = EngineAddon.extend({
  name: 'engine-alpha',

  lazyLoading: Object.freeze({
    enabled: true
  }),

  namespace: 'alpha',

  isDevelopingAddon() {
    return true;
  }
});

Finally, in your engine's addon directory, you can declare a uniquely named component that will export this namespaced version of the addon declared in your package.json:

// addon/components/alpha-foo-bar
export { default } from 'alpha-addon-test/components/foo-bar';

(edit: cleaned up index.js implementation)

@dgeb

This comment has been minimized.

Copy link
Member

dgeb commented Jan 15, 2019

Thanks for the discussion so far, and apologies for taking so long to get involved myself.

I'd like to share a helpful repo from @CodeOfficer that explores dependency conflicts between apps, engines, and addons: https://github.com/CodeOfficer/demo-engine-addon-dependency-conflicts The README clearly lays out several types of conflicts that can be reproduced.

Some conflicts are very difficult to solve in a general case, but I believe we need to start with clear warnings as part of the build process. @CodeOfficer's suggestion to use ember-cli-dependency-lint is a good first step.

I just wanted to check in for now, but hope to be back soon after exploring some possibilities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment