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

Templating creates unwanted custom elements #549

Open
jods4 opened this issue May 3, 2017 · 15 comments
Open

Templating creates unwanted custom elements #549

jods4 opened this issue May 3, 2017 · 15 comments

Comments

@jods4
Copy link
Contributor

jods4 commented May 3, 2017

I am editing this issue, the original text is below.

I originally thought that the root of the problem was Aurelia creating custom elements from exports without convention nor decorator. But from comments below it was clarified that this is actually supported in some scenarios.

With this in mind, I now think that the root of the problem is that Aurelia should not register a local custom element when instantiating a dialog like so:

import { MyDialog } from 'dialogs/whatever';
dialogService.open({ viewModel: MyDialog });

This code should not result in the registration of a <my-dialog> custom element.

It is mostly harmless, but if your exported name clashes with other tags it's breaking the app.


From aurelia/dialog#283:
When loading a component (e.g. a view model for a dialog, a page to be loaded in the router, etc.), aurelia-templating falls back to creating a custom element based on the exported component name.

I think the logic for that is here:

resourceTypeMeta.elementName = _hyphenate(key);

Now consider what happens if I choose to name my ViewModel export class A { }. When used by a dialog or router, it will create a <a> custom element, which is of course unwanted and will trigger bugs if there are links in the template.

Expected behavior:
Only classes decorated with @customElement or following a convention (by default ending in CustomElement) should create custom elements.

@jods4
Copy link
Contributor Author

jods4 commented May 3, 2017

CC: @StrahilKazlachev, @EisenbergEffect

@EisenbergEffect
Copy link
Contributor

We aren't changing this. This convention-based approach is fundamental to Aurelia and is one of the reasons why people choose Aurelia.

@EisenbergEffect
Copy link
Contributor

If you want to create a feature to configure Aurelia to opt-out of this, that's an option. But we can't change this all up. That would be devastatingly destructive.

@jods4
Copy link
Contributor Author

jods4 commented May 3, 2017

@EisenbergEffect
I am not sure you understood the bug correctly, or maybe I was unaware of an Aurelia option for a long time!

From the official documentation

Aurelia will take the JavaScript class name, strip CustomElement from the end, and convert it from InitCaps to dash-case for the custom element's name.
[...]
It is also possible to explicitly name your custom element by using the customElement decorator

I don't see a mention that classes with random names are converted to custom elements.

This is what happened in aurelia/dialog#283: a class that was not meant to be a custom element, named just "a" at runtime, was interpreted as a <a> custom element resource.

@eamodio
Copy link

eamodio commented Jun 7, 2017

Re-posting here from aurelia/dialog#283 (comment)

Since it seems that this would be quite a breaking change, since the behavior is a "supported" (even if slightly discouraged) scenario -- is there a much easier (low risk) fix here?

Without breaking things, it doesn't seem like you can reasonably stop people from exporting a module that matches a dom thing -- but honestly if you do that -- you are on your own. But because in this case it isn't the user naming it, it is webpack changing the names, could the template stuff just be protected from using single-letter names? Which I would think would cover the majority (if not all) of these issues caused by webpack name shortening. OR is there way to to control the webpack name shortening to include some prefix (I have no idea of the allowable characters in a module name -- but say $) so that they would never conflict with a dom element?

@jods4
Copy link
Contributor Author

jods4 commented Jun 7, 2017

User @eamodio made some good points in the original aurelia-dialog issue.

Apparently, using custom elements with no decorator and no naming convention is a thing.
It was promoted in several places, including in the current docs:

This means that, if you wish, you may ignore the Aurelia naming convention for your custom elements. In the example above, we could have simply named the class SecretMessage. The custom element would still be named secret-message.
Given this capability, it might be considered wise to utilize Aurelia's naming convention for custom elements or use the customElement decorator to be explicit when creating a component that is only meant to be used as a custom element and not as a standalone page.

So it's discouraged, but supported. I'm afraid making the CustomElement mandatory will break too much code to be doable.

Yet I still believe we have an important bug that more and more users of aurelia-dialog will hit when switching to Webpack. It's just that the fix might be more complicated.

As a reminder, the bug arises from the fact that aurelia-dialog instantiate a ViewModel without going through our plugin special dependencies, like so:

import { MyDialog } from 'dialogs/whatever';
dialogService.open({ viewModel: MyDialog });

Somehow, MyDialog -- or whatever the name -- ends up being a custom element, which is wrong.

Maybe an alternative fix is to only allow convention-less custom elements when going through <require>?

@eamodio
Copy link

eamodio commented Jun 7, 2017

@jods4 but isn't it worse than that?

Somehow, MyDialog -- or whatever the name -- ends up being a custom element, which is wrong.

Because webpack shortens the name of MyDialog to be something like a? Or am I getting that wrong?

@jods4
Copy link
Contributor Author

jods4 commented Jun 7, 2017

@eamodio
For me the core bug that has been there for a long time is that I don't expect a my-dialog custom element to be created in this situation.

The situation is indeed worse now that tools like Webpack rename the imports, which can create custom elements for <a>, <b>, <dd>...

But fundamentally, I don't think renaming is the cause of the issue. It just makes it worse/more visible.

@eamodio
Copy link

eamodio commented Jun 7, 2017

Yeah, for me that makes it dramatically worse -- because it isn't self inflicted. And doubly so, because the errors it causes are so far removed from the actual problem -- we spun for the better part of a day trying to figure out what was going on (before we found the github issue about it) during our (ongoing) conversion from systemjs/jspm to webpack.

@Scapal
Copy link

Scapal commented Jun 20, 2018

This seems quite an urgent issue since WebPack is now the default in Aurelia/Cli.
I ran into the same problem twice this week and lost a couple of hours :-(

@Scapal
Copy link

Scapal commented Jun 21, 2018

Ok, thanks to @bigopon, the problem goes away by using PLATFORM.moduleName in the DialogService.open() viewModel parameter.

@sflanker
Copy link

sflanker commented Jun 19, 2021

This workaround for DialogService.open(), while it does work and I'm grateful for that, is outright horrible. I have to make a massive app wide refactor that can't be easily automated with sed/awk and throws away type safety, forcing me to validate that every single dialog in my app still works. All thanks to a 4 year old bug that has had no effort put in. Sigh 💩

@bigopon
Copy link
Member

bigopon commented Jun 19, 2021

@sflanker tou can refactor eitber in the user of the view model, or the view model itself by giving it an explicit name via a @customElement()

@sflanker
Copy link

@bigopon So if I simply explicitly decorate every dialog viewModel type, I can safely continue to references these view models using the type name imported using an ES style import? That is good news 🙏

Just to clarify to be sure I understand the workaround:

// view that uses a dialog
import MyDialog from './my-dialog';

export default class MyPage {
  // ...

  public void showMyDialog() {
    this._dialogService.open({
      viewModel: MyDialog
    });
  }
}

// my-dialog.ts

@customElement('my-dailog') // <<--- this is the critical thing that prevents the stack overflow, need even though I never reference this with <my-dialog></my-dialog>
export default class MyDialog {
}

@bigopon
Copy link
Member

bigopon commented Jun 22, 2021

@sflanker yes thats exactly it

@EisenbergEffect EisenbergEffect removed their assignment Dec 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants