mount-observer 0.0.27 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -20
- package/Synthesizer.js +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Author: Bruce B. Anderson (with valuable feedback from @doeixd )
|
|
|
11
11
|
|
|
12
12
|
Issues / pr's / polyfill: [mount-observer](https://github.com/bahrus/mount-observer)
|
|
13
13
|
|
|
14
|
-
Last Update: 2024-
|
|
14
|
+
Last Update: 2024-6-11
|
|
15
15
|
|
|
16
16
|
## Benefits of this API
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ What follows is a far more ambitious alternative to the [lazy custom element pro
|
|
|
19
19
|
|
|
20
20
|
["Binding from a distance"](https://github.com/WICG/webcomponents/issues/1035#issuecomment-1806393525) refers to empowering the developer to essentially manage their own "stylesheets" -- but rather than for purposes of styling, using these rules to attach behaviors, set property values, etc, to the HTML as it streams in. Libraries that take this approach include [Corset](https://corset.dev/) and [trans-render](https://github.com/bahrus/trans-render). The concept has been promoted by a [number](https://bkardell.com/blog/CSSLike.html) [of](https://www.w3.org/TR/NOTE-AS) [prominent](https://www.xanthir.com/blog/b4K_0) voices in the community.
|
|
21
21
|
|
|
22
|
-
The underlying theme is this api is meant to make it easy for the developer to do the right thing, by encouraging lazy loading and smaller footprints. It rolls up most all the other observer api's into one, including, potentially, [this one](https://github.com/whatwg/dom/issues/1285).
|
|
22
|
+
The underlying theme is this api is meant to make it easy for the developer to do the right thing, by encouraging lazy loading and smaller footprints. It rolls up most all the other observer api's into one, including, potentially, [this one](https://github.com/whatwg/dom/issues/1285), which may be a similar duplicate to [that one](https://github.com/whatwg/dom/issues/1225).
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
### Does this api make the impossible possible?
|
|
@@ -72,7 +72,7 @@ Invoking "disconnect" as shown above causes the observer to emit event "disconne
|
|
|
72
72
|
|
|
73
73
|
The argument can also be an array of objects that fit the pattern shown above.
|
|
74
74
|
|
|
75
|
-
In fact, as we will see, where it makes sense, where we see examples that are strings, we will also allow for arrays of such strings. For example, the "on" key can point to an array of CSS selectors (and in this case the mount/dismount callbacks would need to provide an index of which one matched). I only recommend adding this complexity if what I suspect is true -- providing this support can reduce "context switching" between threads / memory spaces (c++ vs JavaScript), and thus improve performance.
|
|
75
|
+
In fact, as we will see, where it makes sense, where we see examples that are strings, we will also allow for arrays of such strings. For example, the "on" key can point to an array of CSS selectors (and in this case the mount/dismount callbacks would need to provide an index of which one matched). I only recommend adding this complexity if what I suspect is true -- providing this support can reduce "context switching" between threads / memory spaces (c++ vs JavaScript), and thus improve performance. If multiple "on" selectors are provided, and multiple ones match, I think it makes sense to indicate the one with the highest specifier that matches. It would probably be helpful in this case to provide a special event that allows for knowing when the matching selector with the highest specificity changes for mounted elements.
|
|
76
76
|
|
|
77
77
|
If no imports are specified, it would go straight to do.* (if any such callbacks are specified), and it will also dispatch events as discussed below.
|
|
78
78
|
|
|
@@ -92,7 +92,7 @@ const observer = new MountObserver({
|
|
|
92
92
|
import: [
|
|
93
93
|
['./my-element-small.css', {type: 'css'}],
|
|
94
94
|
'./my-element.js',
|
|
95
|
-
]
|
|
95
|
+
],
|
|
96
96
|
do: {
|
|
97
97
|
mount: ({localName}, {modules, observer}) => {
|
|
98
98
|
if(!customElements.get(localName)) {
|
|
@@ -118,14 +118,14 @@ This proposal would also include support for JSON and HTML module imports.
|
|
|
118
118
|
Following an approach similar to the [speculation api](https://developer.chrome.com/blog/speculation-rules-improvements), we can add a script element anywhere in the DOM:
|
|
119
119
|
|
|
120
120
|
```html
|
|
121
|
-
<script type="mountobserver" id=myMountObserver onmount="
|
|
121
|
+
<script type="mountobserver" id=myMountObserver onmount="{
|
|
122
122
|
const {matchingElement} = event;
|
|
123
123
|
const {localName} = matchingElement;
|
|
124
124
|
if(!customElements.get(localName)) {
|
|
125
125
|
customElements.define(localName, modules[1].MyElement);
|
|
126
126
|
}
|
|
127
127
|
observer.disconnect();
|
|
128
|
-
">
|
|
128
|
+
}">
|
|
129
129
|
{
|
|
130
130
|
"on":"my-element",
|
|
131
131
|
"import": [
|
|
@@ -136,7 +136,7 @@ Following an approach similar to the [speculation api](https://developer.chrome.
|
|
|
136
136
|
</script>
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
The things that make this API work together, namely the "modules", "observer", and "mountedElements" (an array of an array of weak refs to elements that match all the criteria for the
|
|
139
|
+
The things that make this API work together, namely the "modules", "observer", and "mountedElements" (an array of an array of weak refs to elements that match all the criteria for the i<sup>th</sup> "on" selector) would be accessible as properties of the script element:
|
|
140
140
|
|
|
141
141
|
```JavaScript
|
|
142
142
|
const {modules, observer, mountedElements, mountInit} = myMountObserver;
|
|
@@ -144,7 +144,7 @@ const {modules, observer, mountedElements, mountInit} = myMountObserver;
|
|
|
144
144
|
|
|
145
145
|
The "scope" of the observer would be the ShadowRoot containing the script element (or the document outside Shadow if placed outside any shadow DOM, like in the head element).
|
|
146
146
|
|
|
147
|
-
No arrays of settings would be supported within a single tag (as this causes issues as far as supporting a single onmount, ondismount, etc event attributes), but remember that the on criteria can be an array of selectors.
|
|
147
|
+
No arrays of settings would be supported within a single tag (as this causes issues as far as supporting a single onmount, ondismount, etc event attributes), but remember that the "on" criteria can be an array of selectors.
|
|
148
148
|
|
|
149
149
|
## Shadow Root inheritance
|
|
150
150
|
|
|
@@ -230,7 +230,7 @@ const observer = new MountObserver({
|
|
|
230
230
|
exit: ...,
|
|
231
231
|
forget: ...,
|
|
232
232
|
}
|
|
233
|
-
})
|
|
233
|
+
});
|
|
234
234
|
```
|
|
235
235
|
|
|
236
236
|
Callbacks like we see above are useful for tight coupling, and probably are unmatched in terms of performance. The expression that the "do" field points to could also be a (stateful) user defined class instance.
|
|
@@ -337,11 +337,15 @@ We want to be alerted by the discovery of elements adorned by these attributes,
|
|
|
337
337
|
|
|
338
338
|
I think it is useful to divide [attributes](https://jakearchibald.com/2024/attributes-vs-properties/) that we would want to observe into two categories:
|
|
339
339
|
|
|
340
|
-
1. Invariably named, prefix-less, "top-level" attributes that serve as the "source of truth" for key features of the DOM element itself. We will refer to these attributes as "Source of Truth" attributes.
|
|
340
|
+
1. Invariably named, prefix-less, "top-level" attributes that serve as the "source of truth" for key features of the DOM element itself. We will refer to these attributes as "Source of Truth" attributes. Please don't read too much into the name. Whether the platform or custom element author developer chooses to make properties reflect to attributes, or attributes reflect to the properties, or some hybrid of some sort, is immaterial here.
|
|
341
|
+
|
|
342
|
+
By invariably named, I mean the name will be the same in all Shadow DOM realms.
|
|
341
343
|
|
|
342
|
-
Examples are many built-in global attributes, like lang, or contenteditable, or more specialized examples such as "content" for the meta tag.
|
|
344
|
+
Examples are many built-in global attributes, like lang, or contenteditable, or more specialized examples such as "content" for the meta tag. It could also include attributes of third party custom elements we want to enhance in a cross-cutting way.
|
|
343
345
|
|
|
344
|
-
|
|
346
|
+
I think in the vast majority of cases, setting the property values corresponding to these attributes results in directly reflecting those property values to the attributes (and vice versa). There are exceptions, especially for non-string attributes like the checked property of the input element / type=checkbox, and JSON based attributes for custom elements.
|
|
347
|
+
|
|
348
|
+
Usually, there are no events we can subscribe to in order to know when the property changes. Hijacking the property setter in order to observe changes may not always work or feel very resilient. So monitoring the attribute value associated with the property is often the most effective way of observing when the property/attribute state for these elements change. And some attributes (like the microdata attributes such as itemprop) don't even have properties that they pair with!
|
|
345
349
|
|
|
346
350
|
|
|
347
351
|
2. In contrast, there are scenarios where we want to support somewhat fluid, renamable attributes within different Shadow DOM scopes, which add behavior/enhancement capabilities on top of built-in or third party custom elements. We'll refer to these attributes as "Enhancement Attributes."
|
|
@@ -717,7 +721,7 @@ This proposal (and polyfill) also supports the option to utilize ShadowDOM / slo
|
|
|
717
721
|
<compose src="#productCard"></compose>
|
|
718
722
|
```
|
|
719
723
|
|
|
720
|
-
The discussion there leads to an open question whether a processing instruction would be better. I think the compose tag would make much more sense, vs a processing instruction, as it could then support slotted children (behaving similar to the Beatles' example above). Or maybe another tag should be introduced that is the equivalent of the slot, to avoid confusion.
|
|
724
|
+
The discussion there leads to an open question whether a processing instruction would be better. I think the compose tag would make much more sense, vs a processing instruction, as it could then support slotted children (behaving similar to the Beatles' example above). Or maybe another tag should be introduced that is the equivalent of the slot, to avoid confusion. But I strongly suspect that could significantly reduce the payload size of some documents, if we can reuse blocks of HTML, inserting sections of customized content for each instance.
|
|
721
725
|
|
|
722
726
|
## Creating "frameworks" that revolve around MOSEs.
|
|
723
727
|
|
|
@@ -725,28 +729,34 @@ Often, we will want to define a large number of "mount observer script elements
|
|
|
725
729
|
|
|
726
730
|
This is a problem space that [be-hive](https://github.com/bahrus/be-hive) is grappling with, and is used as an example for this section, to simply make things more concrete. But we can certainly envision other "frameworks" that could leverage this feature for a variety of purposes, including other families of behaviors/enhancements, or "binding from a distance" syntaxes.
|
|
727
731
|
|
|
728
|
-
In particular, *be-hive* supports publishing [enhancements](https://github.com/bahrus/be-enhanced) that take advantage of the DOM filtering ability that the MountObserver provides, that "ties the knot" based on CSS matches in the DOM to behaviors/enhancements that we want to attach directly onto the matching elements. *be-hive* seeks to take advantage of the inheritable infrastructure that MOSEs provide, but we don't want to burden the developer with having to manually list all these configurations, we want it to happen automatically, only expecting manual intervention when we need some special customizations within a specific ShadowDOM
|
|
732
|
+
In particular, *be-hive* supports publishing [enhancements](https://github.com/bahrus/be-enhanced) that take advantage of the DOM filtering ability that the MountObserver provides, that "ties the knot" based on CSS matches in the DOM to behaviors/enhancements that we want to attach directly onto the matching elements. *be-hive* seeks to take advantage of the inheritable infrastructure that MOSEs provide, but we don't want to burden the developer with having to manually list all these configurations, we want it to happen automatically, only expecting manual intervention when we need some special customizations within a specific ShadowDOM realm.
|
|
729
733
|
|
|
730
734
|
To support this, we propose these highlights:
|
|
731
735
|
|
|
732
736
|
1. Adding a static "synthesize" method to the MountObserver api. This would provide a kind of passage-way from the imperative api to the declarative one.
|
|
733
737
|
2. As the *synthesize* method is called repeatedly from different packages that work within that framework, it creates a cluster of MOSEs wrapped inside the "synthesizing" custom element ("be-hive") that the framework developer authors. It appends script elements with type="mountobserver" to the custom element instance sitting in the DOM, that dispatches events from the synthesizing custom element it gets appended to, so subscribers in child Shadow DOM's don't need to add a general mutation observer in order to know when parent shadow roots had a MOSE inserted that it needs to act on. This allows the child Shadow DOM's to inherit (in this case) behaviors/enhancements from the parent Shadow DOM.
|
|
734
738
|
|
|
735
|
-
So framework developers can develop a bespoke custom element that inherits from the class "*Synthesizer*" that is part of this package / proposal, that is used to group families of
|
|
739
|
+
So framework developers can develop a bespoke custom element that inherits from the "abstract" class "*Synthesizer*" that is part of this package / proposal, that is used to group families of MountObserver's together.
|
|
740
|
+
|
|
741
|
+
Some attributes that the base "Synthesizer" support. They are all tied to allow individual ShadowDOM realms to be able to easily opt in or opt out, depending on the level of control/trust that is exerted by a web component / Shadow Root, as far as the HTML it imports in.
|
|
742
|
+
|
|
743
|
+
1. passthrough. Allows for the inheritance of behaviors to flow through from above (or from the root document), while not actually activating any of them within the Shadow DOM realm itself.
|
|
744
|
+
2. exclude. List of specific MOSE id's to block. Allows them to flow through to child Shadow Roots.
|
|
745
|
+
3. include. List of specific MOSE id's to allow.
|
|
736
746
|
|
|
737
747
|
What functionality do these "synthesizing" custom elements provide, what value-add proposition do they fulfill over what is built into the MountObserver polyfill / package?
|
|
738
748
|
|
|
739
749
|
The sky is the limit, but focusing on the first example, be-hive, they are:
|
|
740
750
|
|
|
741
751
|
1. Managing, interpreting and parsing the attributes that add semantic enhancement vocabularies onto exiting elements.
|
|
742
|
-
2. Establishing the "handshake" that imports the enhancement package, instantiates the enhancement, and passes properties that
|
|
752
|
+
2. Establishing the "handshake" that imports the enhancement package, instantiates the enhancement, and passes properties that were previously assigned to the pre-enhanced element to the attached enhancement/behavior.
|
|
743
753
|
|
|
744
754
|
If one inspects the DOM, one will see grouped (already "parsed") MOSEs, like so:
|
|
745
755
|
|
|
746
756
|
```html
|
|
747
757
|
<be-hive>
|
|
748
|
-
<script type=mountobserver id=be-searching></script>
|
|
749
|
-
<script type=mountobserver id=be-counted></script>
|
|
758
|
+
<script type=mountobserver id=be-hive.be-searching></script>
|
|
759
|
+
<script type=mountobserver id=be-hive.be-counted></script>
|
|
750
760
|
</be-hive>
|
|
751
761
|
```
|
|
752
762
|
|
|
@@ -755,7 +765,7 @@ Without the help of the synthesize method / Synthesizer base class, the develope
|
|
|
755
765
|
The developer of each package defines their MOSE "template", and then syndicates it via the synthesize method:
|
|
756
766
|
|
|
757
767
|
```JavaScript
|
|
758
|
-
mountObserver.synthesize(
|
|
768
|
+
mountObserver.synthesize(root: document | shadowRootNode, ctr: ({new() => Synthesizer}), mose: MOSE)
|
|
759
769
|
```
|
|
760
770
|
|
|
761
771
|
What this method does is it:
|
|
@@ -775,7 +785,7 @@ And we can give each inheriting ShadowRoot a personality of its own by customizi
|
|
|
775
785
|
|
|
776
786
|
```html
|
|
777
787
|
<be-hive>
|
|
778
|
-
<script type=mountobserver id=be-searching>
|
|
788
|
+
<script type=mountobserver id=be-hive.be-searching>
|
|
779
789
|
{
|
|
780
790
|
...my custom settings
|
|
781
791
|
}
|
package/Synthesizer.js
CHANGED
|
@@ -31,7 +31,19 @@ export class Synthesizer extends HTMLElement {
|
|
|
31
31
|
this.inherit();
|
|
32
32
|
}
|
|
33
33
|
activate(mose) {
|
|
34
|
+
if (this.hasAttribute('passthrough'))
|
|
35
|
+
return;
|
|
34
36
|
const { init, do: d, id } = mose;
|
|
37
|
+
if (this.hasAttribute('include')) {
|
|
38
|
+
const split = this.getAttribute('include').split(' ');
|
|
39
|
+
if (!split.includes(id))
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (this.hasAttribute('exclude')) {
|
|
43
|
+
const split = this.getAttribute('exclude').split(' ');
|
|
44
|
+
if (split.includes(id))
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
35
47
|
const mi = {
|
|
36
48
|
do: d,
|
|
37
49
|
...init
|