Ravenstine / ember-custom-elements

The easiest way to render parts of your Ember app using custom elements.
MIT License
15 stars 4 forks source link

Usage with Ember Addon Components #6

Closed alexlafroscia closed 4 years ago

alexlafroscia commented 4 years ago

Hey! I'm trying to use this addon to embed components from a one of my Ember add ons into a Rails application. We currently use Ember Islands for this, but I like the approach taken here and am hoping that it can solve some problems we're having with Ember Islands some some of the more modern Ember features that that library does not support.

It seems that using this addon with components within an addon doesn't work. Applying the decorator to the add-on's component does not appear to do anything, since the instance initializer is looking directly at the code for the the modules that the resolver knows about

https://github.com/Ravenstine/ember-custom-elements/blob/f20ca9be973628f38e01230dc44fb23688062bc0/addon/instance-initializers/ember-custom-elements.js#L18

For an addon component, that code will essentially just be a re-export of the add-on's module, and thus won't have the ~~EMBER~CUSTOM~ELEMENT~~ within it.

I tried wrapping the component re-export from the app tree in the customElement function like this:

// app/components/some-component.js
import SomeComponent from 'my-addon/components/some-component';
import { customElement } from 'ember-custom-elements';

export default customElement(SomeComponent, 'some-component');

but that didn't seem to work either.

Any thoughts on how we might be able to get this use-case to work?

alexlafroscia commented 4 years ago

I was able to at least get the custom elements registered by doing this instead:

// app/components/some-component.js
import SomeComponent from 'my-addon/components/some-component';
import { customElement } from 'ember-custom-elements';

@customElement('some-component')
export default class extends SomeComponent {}

When done this way, the following returns a constructor as expected

window.customElements.get('some-component');

But actually rendering <some-component></some-component> does not do anything; the tag exists, but there's none of the expected DOM below it.

alexlafroscia commented 4 years ago

It seems that the code that sets the parsedName on the component instance by registering an initializer function

https://github.com/Ravenstine/ember-custom-elements/blob/f20ca9be973628f38e01230dc44fb23688062bc0/addon/index.js#L203-L207

is running after the constructor that attempts to invoke those initializers

https://github.com/Ravenstine/ember-custom-elements/blob/f20ca9be973628f38e01230dc44fb23688062bc0/addon/lib/custom-element.js#L64-L65

I'm still working on why this is happening, but that's the order that they are being executed in my situation.

Based on the call stack to the constructor, it appears that called window.customElements.define actually calls the Custom Element's constructor, which is what's causing that to happen before the initializer helper functions are ready.

That seems... odd.

alexlafroscia commented 4 years ago

Actually, I think that does make sense — the custom elements are already in the DOM, so the moment that the element becomes defined, the constructor is run!

I think that running that initialization code will need to be deferred until Ember has had a chance to run the instance initializers.

alexlafroscia commented 4 years ago

I think the resolution to my issues would be two things:

  1. The PR that I opened (#7) fixes the problem around the initialization ordering. With that used in my app, things work!
  2. Adding documentation (to the README or elsewhere) about the fact that you have to decorator the addon component in the app tree in order to render addon components to a custom element
Ravenstine commented 4 years ago

@alexlafroscia Thanks for pointing out this issue. Admittedly, I haven't tested this on modules from add-ons(i.e. outside the app tree). I'll investigate this today and see if I can resolve this. Use of this decorator shouldn't require much extra forethought, ideally.

I just saw that you made a PR that closes this. I'll take a look right away! Thanks for contributing!

For an addon component, that code will essentially just be a re-export of the add-on's module, and thus won't have the EMBER~CUSTOM~ELEMENT within it.

If you encounter any problems with modules using the customElement decorator, you might be able to work around them with an option I didn't document called deoptimizeModuleEval that bypasses the need for the custom element sigil to be added via Babel.

module.exports = function(environment) {
  let ENV = {
    ...
    emberCustomElements: {
      deoptimizeModuleEval: true
    }
    ...
  }
};

This isn't really a solution, but more of a stopgap. I needed it for the examples in Ember Twiddle because, for some reason, it doesn't seem to run external Babel plugins. Even then, I don't know if it would work for your use case because it could be a matter of the initializer not even finding modules outside of app/.