Skip to content
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

Multiple Ember apps need to be namespaced. #17

Open
rajasegar opened this issue Dec 18, 2020 · 6 comments
Open

Multiple Ember apps need to be namespaced. #17

rajasegar opened this issue Dec 18, 2020 · 6 comments

Comments

@rajasegar
Copy link

Continuing the discussion with #3, I am able to somewhat bypass that with this hacky solution. But needed a better approach or refined one with the official library itself:

function mount(opts) {
  return Promise
    .resolve()
    .then(() => {
      window.dcx.desk = window.dcx.desk || {};
      if(window.dcx.desk.Ember) {
        const {Ember, entries} = window.dcx.desk;
        window.Ember = Ember;
        window.require.entries = entries;
      }
      opts.applicationInstance = opts.App.create(opts.createOpts);
    })
}

function unmount(opts) {
  return Promise
    .resolve()
    .then(() => {
      opts.applicationInstance.destroy();
      opts.applicationInstance = null;

      window.dcx.desk = window.dcx.desk || {};
      window.dcx.desk =  {
        Ember: window.Ember,
        entries: window.require.entries
      }
      delete window.Ember;
    });
}

Still I am getting error when switching back to different Ember apps something like:

Uncaught Error: Could not find module `desk/router` imported from `(require)`
    at vendor.js:12
    at l (vendor.js:12)
    at requireModule (vendor.js:6)
    at r._extractDefaultExport (vendor.js:4410)
    at resolveOther (vendor.js:4378)
    at resolve (vendor.js:4388)
    at vendor.js:1673
    at e.resolve (vendor.js:1676)
    at e.resolve (vendor.js:1677)
    at p (vendor.js:1652)

@joeldenning Please let me know your thoughts on this.

@joeldenning
Copy link
Member

I don't understand the description of this issue. Could you re-explain it to me, or provide a demonstration? I don't see how it's related to #3 and also don't understand what you changed in the single-spa-ember library or why.

@rajasegar
Copy link
Author

rajasegar commented Dec 19, 2020

@joeldenning Sorry about the issue description, I should have explained it clearly.
The problem is two fold:

  • How do I efficiently load multiple Ember Micro Front-ends (MFEs) based on different Ember versions?
  • How do I navigate back to different Ember MFEs without any errors?

Example reproduction here
Code is here

Ember.js namespacing

When we load an Ember app in the browser there is a global window object Ember which holds the reference to a particular version of Ember.js framework. In case of MFEs , the recently loaded Ember.js version will be overriding the previous ones.
Say for example, I have two mfes people and planets, created using Ember 3.4 and Ember 3.19 respectively. So when I first load the people mfe, my ember version will 3.4

window.Ember.VERSION => 3.4

When I load the planets mfe, my existing Ember version will be overridden with the new one, so now my ember version will be

window.Ember.VERSION => 3.19

we need to come up with a namespacing mechanism to solve the above issue.

require.entries

Similar to the window.Ember object there is another one called require.entries which holds the module references for each mfe. This will also get overridden when we switch to different mfes.

window.require.entries

The problem here is even if I restore the require.entries correctly back to the right mfe, my Ember app won't load, throwing an error that a particular module is not found. This is how I am doing the restore part with the mount and unmount hooks.

Storing a reference in a namespace for Ember and require.entries

function unmount(opts) {
  return Promise
    .resolve()
    .then(() => {
      opts.applicationInstance.destroy();
      opts.applicationInstance = null;

      // Create a namespace in window object for the desk mfe
      window.dcx.desk = window.dcx.desk || {};
      // Store the Ember and require.entries into the same
      window.dcx.desk =  {
        Ember: window.Ember,
        entries: window.require.entries
      }
      // delete the current Ember object (may be not required I am not sure)
      delete window.Ember;
    });
}

And during the mount phase , I am restoring the correct Ember version for the mfe

function mount(opts) {
  return Promise
    .resolve()
    .then(() => {
      // First time there will be no namespace or find the existing name space
     // dcx is the org and desk is the mfe
      window.dcx.desk = window.dcx.desk || {};
      // If there is an Ember object reference already stored, use that instead of the current window.Ember
     // which may not be the right one
      if(window.dcx.desk.Ember) {
        const {Ember, entries} = window.dcx.desk;
        window.Ember = Ember;
        window.require.entries = entries;
      }
      opts.applicationInstance = opts.App.create(opts.createOpts);
    })
}

@joeldenning
Copy link
Member

Thanks for the explanation. The single-spa-leaked-globals helper is designed to help with situations like these. A limitation of it is that the applications with global variable collisions cannot be mounted simultaneously.

Is single-spa-leaked-globals enough to solve your issues here? Or is it more complex?

@rajasegar
Copy link
Author

single-spa-leaked-globals is really helpful to solve the global Ember object problem, but still I think there are some more complexity involved. I think the only option we now has to refresh the page for every Ember micro-frontend to load the app properly. With client side navigation it is not working at the moment. I have reached out to the Ember community for more help. Thanks a lot @joeldenning

@joeldenning
Copy link
Member

What are the other complexities? From your message it seems that storing the Ember and require.entries global variables would be enough?

@rajasegar
Copy link
Author

Even if we have populated the require.entries list with the respective modules for the current mfe, Ember is throwing error on

Uncaught TypeError: can't redefine non-configurable property "Inflector"

When it tries to redefine the Inflector property in Ember. This happens when you switch from one mfe to the other. You can simulate the error by first going to the people's page and then to the planets page here
http://multi-ember-mfe.surge.sh/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants