web-components
web-components copied to clipboard
Support renaming elements and using multiple tags on the same page without conflicts
Describe your motivation
In copilot we would like to use the Vaadin components but cannot directly import e.g. button for a couple of reasons:
- We do not know if we should import the bare version, the Lumo version or the Material version. If we import the wrong version and the application later imports another version, the theme from the application version will not be applied and a warning printed to the browser console that the element was finalized before a style module was registered.
- Application theme rules, both injected into the component shadow root and
::partstyle rules are applied also to components used for copilot. The themes should be independent.
Describe the solution you'd like
We would like to use e.g. text field and call it copilot-text-field. This would avoid most problems but currently cannot be done because
- There is no side effect free import that does not register
vaadin-text-fieldso we will always hit problem 1 above - Even if we had a side effect free import, text field internally registers and uses sub elements like
vaadin-input-containerwhich must also be renamed to e.g.copilot-input-containerso these internal elements will have application theming applied to them
Describe alternatives you've considered
We could fork the whole component set and do find and replace but this is sounds like the wrong way to go for many reasons
Additional context
No response
Depends on #6999 (which also suggests introducing side effects free imports, but for other use case).
As discussed in Slack, we could consider a wrapped html function for rendering children with custom tag names. The wrapper would replace strings from the raw template expression before passing it to the original.
const html = htmlReplace(originalHtml, {'vaadin-button': 'copilot-button'});
return html`<vaadin-button>Click me</vaadin-button>`;
Actually, in case of Polymer we can create a custom html tag function that returns <template> element. Example:
import { PolymerElement } from '@polymer/polymer';
const htmlReplace = function htmlReplace(replacements) {
// Create a custom html literal function
return function (strings, ...values) {
const replaced = strings.map((string) => {
Object.entries(replacements).forEach(([from, to]) => {
string = string.replaceAll(from, to);
});
return string;
});
// Return HTMLTemplateElement to be cloned
const template = document.createElement('template');
template.innerHTML = replaced.join('');
return template;
};
};
class MyElement extends PolymerElement {
static get template() {
const html = htmlReplace({ 'vaadin-button': 'copilot-button' });
return html`<vaadin-button>Button</vaadin-button>`;
}
static get properties() {
return {
disabled: {
type: Boolean,
},
};
}
}
customElements.define('my-test', MyElement);
For Lit based versions, we would need to use unsafeStatic directive to make tag names customizable.
One more thing to address in scope of this issue would be to replace usage of registerStyles in src folders e.g.
https://github.com/vaadin/web-components/blob/08563b9e009eb7f90921c172ab7982d64651aea2/packages/context-menu/src/vaadin-context-menu-overlay.js#L15-L17
These currently have hardcoded HTML tag names. Instead, we should be able to move them into static get template() by using the custom html function (e.g. the one suggested above) that wouldn't prevent interpolating like Polymer does.
I've had some similar interest for some time.
Within my organization, we've vended some components that utilize vaadin (unthemed) as building blocks for my-org-themed components. This looks similar to what was outlined by original commenter: vaadin-button => myorg-button... not dissimilar from the premise of "lion" or "microsoft fast (foundation)"; I've been using vaadin + custom theming long before either of those packages existed and have a lot of respect for you all, so using vaadin for a functional base was my choice.
Here are some lessons I learned along the way.
- The components inevitably would wind up - either by direct or transitive dependency - in packages that had their own direct or transitive dependency on another version of vaadin
- Naively changed apps to monkey-patch
customElements.defineto at least try to render something rather than being broken - Had "unwanted styles" show up because some package that depended upon vaadin and myorg-vaadin had imported the vaadin-lumo variant.
- This caused me to introduce a
strict-themable-mixinthat blocked "inherited styles" from being attached tomyorg-vaadin-*
- This caused me to introduce a
- Ultimately, I did vend a hacky workaround for not having sideEffectless-exports here:
- https://github.com/robrez/scoped-vaadin
- https://stackblitz.com/edit/typescript-gau6r3?file=index.ts
- Notes: that this has been working out well internally for some time now.. but I have not done a good job of keeping things up-to-date. This enabled the
myorg-button, etc, to be on the same page asvaadin-button(different version). I made a decision to not try to use scoped-registrations and a mixin for polymerelements because I didn't feel confident about some of the details there; Instead, registrations contain major-version. Note also that I generally did not wish for the underlying classes to be registered - I wanted to extend them and register my subclass. I think the readme.md and docs/decisions.md captured some finer points... - https://github.com/robrez/scoped-vaadin/blob/main/docs/decisions.md
Kind regards