@open-wc/scoped-elements
Advanced tools
Comparing version 2.0.1 to 2.1.0
# Change Log | ||
## 2.1.0 | ||
### Minor Changes | ||
- 3bb2f979: BREAKING CHANGE: Work without polyfill if possible (and do not auto load polyfill) | ||
ScopedElementsMixin 2.x tried to be as convenient as possible by automatically loading the scoped custom elements registry polyfill. | ||
This however led to a fatal error whenever you registered any component before ScopedElementsMixin was used. | ||
The error especially happened when you would import a component applying ScopedElementsMixin into an existing application, fundamentally going against the "import and use" nature of web components. | ||
Therefore, we decided to not load the polyfill inside the mixin, but let the developer (optionally) load it on top of his/her app. | ||
This means that, depending on the app of the developer, the change can be breaking. After updating to this latest version, two scenarios will be possible: | ||
1. **everything is fixed and less js is loaded than before**: the app doesn't need scoping (in this case the app either continues to work as is, or the fatal error mentioned above will disappear) | ||
2. **the fatal error is replaced by a console error from ScopedElementsMixin**: the app needs scoping, because different versions of the same component are used (in this case, the fatal error mentioned above will disappear and a console error asking you to load the polyfill on top of your app will appear) | ||
Only the second case requires an action. The remaining error can be resolved via a small migration path, a breaking bugfix if you will, which should be treated as if it were a security breaking change: consumers do the breaking bug fix on their end, because releasing a new major version would not be beneficial, neither to us nor to our consumers. | ||
Not loading the polyfill by default will also allow sites to opt out of it altogether. This means until the browser ships scoped registries, the developer can choose to fall back to the global registry, by not loading the polyfill. This will save bandwidth & complexity since it doesn't need to be loaded by the client in that case. | ||
All previous 2.x versions will be deprecated and scoped element will behave as follows: | ||
1. If polyfill is not loaded it will use the global registry as a fallback | ||
2. Log error if actually scoping is needed and polyfill is not loaded | ||
3. If you manually create elements you will need to handle polyfilled and not polyfilled cases now | ||
```diff | ||
- const myButton = this.shadowRoot.createElement('my-button'); | ||
+ const myButton = this.createScopedElement('my-button'); | ||
``` | ||
This also removes `@webcomponents/scoped-custom-element-registry` as a production dependency. | ||
If you need scoping be sure to load the polyfill before any other web component gets registered. | ||
It may look something like this in your HTML | ||
```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 | ||
```js | ||
import '@webcomponents/scoped-custom-element-registry'; | ||
``` | ||
You need scoping if you want to | ||
- use 2 major versions of a web component (e.g. in an SPA pageA uses 1.x and pageB uses 2.x of color-picker) | ||
- or you want to use the same tag name with different implementations (use tag color-picker from foo here and from bar here) | ||
## 2.0.1 | ||
@@ -4,0 +57,0 @@ |
{ | ||
"name": "@open-wc/scoped-elements", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"publishConfig": { | ||
@@ -46,6 +46,8 @@ "access": "public" | ||
"@lit/reactive-element": "^1.0.0", | ||
"@open-wc/dedupe-mixin": "^1.3.0", | ||
"@webcomponents/scoped-custom-element-registry": "^0.0.3" | ||
"@open-wc/dedupe-mixin": "^1.3.0" | ||
}, | ||
"devDependencies": { | ||
"@webcomponents/scoped-custom-element-registry": "^0.0.5" | ||
}, | ||
"types": "types/index.d.ts" | ||
} |
@@ -73,9 +73,37 @@ # Development >> Scoped Elements ||40 | ||
<inline-notification type="tip"> | ||
5. (optional) load the polyfill if you need scoping | ||
ScopedElements loads a polyfill of the scoped registry for you . Why do we do this? The spec is quite mature but not yet implemented in a browser and the polyfill is pretty new so we want to control it to be able to patch things if needed to stay backward compatible. | ||
The polyfill we use is [@webcomponents/scoped-custom-element-registry](https://github.com/webcomponents/polyfills/tree/master/packages/scoped-custom-element-registry). | ||
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. | ||
</inline-notification> | ||
We recommend [@webcomponents/scoped-custom-element-registry](https://github.com/webcomponents/polyfills/tree/master/packages/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 | ||
```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 | ||
```js | ||
import '@webcomponents/scoped-custom-element-registry'; | ||
``` | ||
<inline-notification type="tip"> | ||
As long as you only use one version of a web component ScopeElementsMixin will work with 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/ | ||
``` | ||
</inline-notification> | ||
### Complete example | ||
@@ -122,2 +150,40 @@ | ||
### Creating elements | ||
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. | ||
```js | ||
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. | ||
```js | ||
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. | ||
```js | ||
initElements() { | ||
const myButton = this.createScopedElement('my-button'); | ||
this.appendChild(myButton); | ||
} | ||
``` | ||
### Lazy scoped components | ||
@@ -124,0 +190,0 @@ |
@@ -1,2 +0,1 @@ | ||
import '@webcomponents/scoped-custom-element-registry'; | ||
import { dedupeMixin } from '@open-wc/dedupe-mixin'; | ||
@@ -13,2 +12,5 @@ import { adoptStyles } from '@lit/reactive-element/css-tag.js'; | ||
// @ts-ignore | ||
const supportsScopedRegistry = !!ShadowRoot.prototype.createElement; | ||
/** | ||
@@ -90,3 +92,2 @@ * @template {import('./types').Constructor<HTMLElement>} T | ||
/** @override */ | ||
createRenderRoot() { | ||
@@ -100,7 +101,6 @@ const { | ||
if (!this.registry) { | ||
this.registry = new CustomElementRegistry(); | ||
Object.entries(scopedElements).forEach(([tagName, klass]) => | ||
this.registry.define(tagName, klass), | ||
); | ||
this.registry = supportsScopedRegistry ? new CustomElementRegistry() : customElements; | ||
for (const [tagName, klass] of Object.entries(scopedElements)) { | ||
this.defineScopedElement(tagName, klass); | ||
} | ||
} | ||
@@ -115,14 +115,21 @@ | ||
this.renderOptions.creationScope = this.attachShadow(options); | ||
const createdRoot = this.attachShadow(options); | ||
if (supportsScopedRegistry) { | ||
this.renderOptions.creationScope = createdRoot; | ||
} | ||
if (this.renderOptions.creationScope instanceof ShadowRoot) { | ||
adoptStyles(this.renderOptions.creationScope, elementStyles); | ||
this.renderOptions.renderBefore = | ||
this.renderOptions.renderBefore || this.renderOptions.creationScope.firstChild; | ||
if (createdRoot instanceof ShadowRoot) { | ||
adoptStyles(createdRoot, elementStyles); | ||
this.renderOptions.renderBefore = this.renderOptions.renderBefore || createdRoot.firstChild; | ||
} | ||
return this.renderOptions.creationScope; | ||
return createdRoot; | ||
} | ||
createScopedElement(tagName) { | ||
const root = supportsScopedRegistry ? this.shadowRoot : document; | ||
// @ts-ignore polyfill to support createElement on shadowRoot is loaded | ||
return root.createElement(tagName); | ||
} | ||
/** | ||
@@ -135,3 +142,20 @@ * Defines a scoped element. | ||
defineScopedElement(tagName, klass) { | ||
return this.registry.get(tagName) || this.registry.define(tagName, klass); | ||
const registeredClass = this.registry.get(tagName); | ||
if (registeredClass && supportsScopedRegistry === false && registeredClass !== klass) { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
[ | ||
`You are trying to re-register the "${tagName}" 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/', | ||
].join('\n'), | ||
); | ||
} | ||
if (!registeredClass) { | ||
return this.registry.define(tagName, klass); | ||
} | ||
return this.registry.get(tagName); | ||
} | ||
@@ -138,0 +162,0 @@ |
export type ScopedElementsMixin = <T extends import("@open-wc/dedupe-mixin").Constructor<import("@lit/reactive-element").ReactiveElement>>(superclass: T) => T & import("@open-wc/dedupe-mixin").Constructor<import("./types").ScopedElementsHost> & typeof import("./types").ScopedElementsHost; | ||
/** | ||
* @typedef {import('./types').RenderOptions} RenderOptions | ||
* @typedef {import('./types').ScopedElementsMixin} ScopedElementsMixin | ||
* @typedef {import('./types').ScopedElementsHost} ScopedElementsHost | ||
* @typedef {import('./types').ScopedElementsMap} ScopedElementsMap | ||
* @typedef {import('@lit/reactive-element').CSSResultOrNative} CSSResultOrNative | ||
*/ | ||
/** | ||
* @template {import('./types').Constructor<HTMLElement>} T | ||
@@ -11,0 +4,0 @@ * @param {T} superclass |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
60753
2
258
354
1
- Removed@webcomponents/scoped-custom-element-registry@^0.0.3
- Removed@webcomponents/scoped-custom-element-registry@0.0.3(transitive)