Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@open-wc/scoped-elements
Advanced tools
Scope element tag names avoiding naming collision and allowing to use different versions of the same web component in your code.
npm i --save @open-wc/scoped-elements
Version 2 of Scoped Elements only supports lit with lit-element 3.x
. If you need to support lit-element 2.x
be sure to use version 1 of Scoped Elements.
Import ScopedElementsMixin
from @open-wc/scoped-elements
.
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
Import the classes of the components you want to use.
import { MyButton } from './MyButton.js';
import { MyPanel } from './MyPanel.js';
Apply ScopedElementsMixin
and define the tags you want to use for your components.
class MyElement extends ScopedElementsMixin(LitElement) {
static get scopedElements() {
return {
'my-button': MyButton,
'my-panel': MyPanel,
};
}
}
If you are going to use elements that are globally defined you have to declare them in scopedElements
as well. This is required because we are trying to work as close as possible to the future Scoped Custom Element Registries feature and, by the moment, there is not going to be inheritance between registries.
You can declare them like in the following example:
static get scopedElements() {
return {
'old-button': customElements.get('old-button'),
'my-panel': MyPanel,
};
}
Use your components in your html.
render() {
return html`
<my-panel class="panel">
<my-button>${this.text}</my-button>
</my-panel>
`;
}
(optional) load the polyfill if you need scoping
Defining sub elements via scopedElements
is very useful on its own as it makes it clear what your element requires. However, if you need the actual scoping feature for example to use two major version or two different classes with the same tag name ,then you will need to load a polyfill.
We recommend @webcomponents/scoped-custom-element-registry.
You need to load the polyfill before any other web component gets registered.
It may look something like this in your HTML
<script src="/node_modules/@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min.js"></script>
or if you have an SPA you can load it at the top of your app shell code
import '@webcomponents/scoped-custom-element-registry';
As long as you only use one version of a web component ScopeElementsMixin will work without the polyfill. So start of without the polyfill. Once you need it, ScopeElementsMixin will log an error.
You are trying to re-register the "feature-a" custom element with a different class via ScopedElementsMixin.
This is only possible with a CustomElementRegistry.
Your browser does not support this feature so you will need to load a polyfill for it.
Load "@webcomponents/scoped-custom-element-registry" before you register ANY web component to the global customElements registry.
e.g. add "<script src="/node_modules/@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min.js"></script>" as your first script tag.
For more details you can visit https://open-wc.org/docs/development/scoped-elements/
import { css, LitElement } from 'lit';
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
import { MyButton } from './MyButton.js';
import { MyPanel } from './MyPanel.js';
export class MyElement extends ScopedElementsMixin(LitElement) {
static get scopedElements() {
return {
'my-button': MyButton,
'my-panel': MyPanel,
};
}
static get styles() {
return css`
.panel {
padding: 10px;
background-color: grey;
}
`;
}
static get properties() {
return {
text: String,
};
}
render() {
return html`
<my-panel class="panel">
<my-button>${this.text}</my-button>
</my-panel}>
`;
}
}
If you use the render
function of your lit component then it will automatically use the scoped registry as it will be defined within its shadow root.
render() {
return html`
<my-button>${this.text}</my-button>
`;
}
When creating components outside of the render function you will need to be explicit in which scope you want to define it.
initElements() {
// creating a `my-button` element in the global scope
const myButton = document.createElement('my-button');
// 👆 may fail if my-button is only registered in the scoped registry
// create a `my-button` element in the local scope
const myButton = this.shadowRoot.createElement('my-button');
// 👆 this only works with the polyfill
this.appendChild(myButton);
}
Additionally when using the ScopedElementMixin it may use the global or local scope depending on if the polyfill is loaded.
We added a helper createScopedElement
you can use to create scoped elements.
initElements() {
const myButton = this.createScopedElement('my-button');
this.appendChild(myButton);
}
In some situations may happen that you want to use a component in your templates that is not already loaded at the moment of defining the scoped elements map. The ScopedElementsMixin
provides the defineScopedElement
method to define scoped elements at any time.
import { LitElement } from 'lit';
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
import { MyPanel } from './MyPanel.js';
export class MyElement extends ScopedElementsMixin(LitElement) {
static get scopedElements() {
return {
'my-panel': MyPanel,
};
}
constructor() {
super();
import('./MyButton.js').then(({ MyButton }) => this.defineScopedElement('my-button', MyButton));
}
render() {
return html`
<my-panel class="panel">
<my-button>${this.text}</my-button>
</my-panel>
`;
}
}
By default, ScopedElementsMixin
shares the same CustomElementsRegistry
instance between all the instances of the same component class. There are some use cases in which you need to have just one registry instance per component instance. For those cases, you can override the get
and set
methods for the registry assigning and retrieving it from the component instance.
import { LitElement } from 'lit';
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
import { MyPanel } from './MyPanel.js';
export class MyElement extends ScopedElementsMixin(LitElement) {
static get scopedElements() {
return {
'my-panel': MyPanel,
};
}
get registry() {
return this.__registry;
}
set registry(registry) {
this.__registry = registry;
}
constructor() {
super();
import('./MyButton.js').then(({ MyButton }) => this.defineScopedElement('my-button', MyButton));
}
render() {
return html`
<my-panel class="panel">
<my-button>${this.text}</my-button>
</my-panel>
`;
}
}
Complex Web Component applications are often developed by several teams across organizations. In that scenario it is common that shared component libraries are used by teams to create a homogeneous look and feel or just to avoid creating the same components multiple times, but as those libraries evolve problems between different versions of the same library may appear, as teams may not be able to evolve and update their code at the same velocity. This causes bottlenecks in software delivery that should be managed by the teams and complex build systems, to try to alleviate the problem.
Scoped Custom Element Registries is a proposal that will solve this problem, but until it is ready, or a polyfill becomes available, we have to scope custom element tag names if we want to use different versions of those custom elements in our code. This package allows you to forget about how custom elements are defined, registered and scopes their tag names if it is necessary, and avoids the name collision problem.
Consider the following setup
Two possible solutions come to mind:
@open-wc/scoped-elements
; see the "fixed" example with-scope [code] running with nested dependencies.The simplified app has the following dependencies
which leads to the following node_modules tree
├── node_modules
│ ├── feature-a
│ ├── feature-b
│ ├── page-a
│ └── page-b
│ └── node_modules
│ └── feature-a
├── demo-app.js
└── index.html
To demonstrate, we made three demos:
before-nesting [code] In this demo, everything works fine as Page A and B both are using the same version of Feature A
no-scope [code] Feature A version 1.x and 2.x are imported via self registering entry points which leads to the following error message, because the feature-a
component tries to register multiple times:
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "feature-a" has already been used with this registry
at [...]/node_modules/page-b/node_modules/feature-a/feature-a.js:3:16
with-scope [code] This example successfully fixes the problem by using ScopedElementsMixin
on both Page A and Page B.
Components imported via npm SHOULD NOT be self registering components. If a shared component (installed from npm) does not offer an export to the class alone, without the registration side effect, then this component may not be used. E.g. every component that calls customElement.define
export class MyEl { ... }
customElement.define('my-el', MyEl);
Or uses the customElement
typescript decorator
@customElement('my-el')
export class MyEl { ... }
Only side effects free class exports may be used
export class MyEl { ... }
Every component that uses sub components should use scoped-elements
. Any import to a self registering component can potentially result in a browser exception - completely breaking the whole application
Imported elements should be fully side effect free (not only element registration)
Using the scoped registry polyfill
may result in a small performance degradation
Loading of duplicate/similar source code (most breaking releases are not a total rewrite) should always be a temporary solution
Often, temporary solutions tend to become more permanent. Be sure to focus on keeping the lifecycle of nested dependencies short
import '@rocket/launch/inline-notification/inline-notification.js';
FAQs
Allows to auto define custom elements scoping them
The npm package @open-wc/scoped-elements receives a total of 58,350 weekly downloads. As such, @open-wc/scoped-elements popularity was classified as popular.
We found that @open-wc/scoped-elements demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.