└── readme.md /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Proposal: Unified Element Properties 3 | 4 | Today's UI frameworks exert considerable effort attempting to reconcile or "paper over" the many differences 5 | between Element properties and attributes. In React, this is manifested as the abstract "props" concept which, 6 | when applied to DOM Elements requires a [complex mapping](https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/DOMProperty.js) 7 | of abstract prop names to corresponding DOM properties or attributes. 8 | In Preact, an "in test" (prop in node) is used to infer whether a given abstract "prop" should be applied to the 9 | DOM as a property or an attribute. This approach handles Custom Element properties more naturally, but it comes at 10 | the cost of some performance and determinism. Other rendering libraries choose to expose the decision of whether to 11 | set a given value as a property or an attribute to the developer - Vue uses prefixes (:prop="value") to explicitly 12 | control this. 13 | 14 | It seems a set of new DOM methods designed specifically to address this case could be impactful. These methods would 15 | establish a consistent approach for setting properties/attributes based on the given "virtual property name". 16 | If widely adopted, this would lead to better interoperability between rendering libraries. It also opens the door for 17 | easier Custom Element upgrades through the use of overridden methods, and potentially provides a starting point for 18 | addressing the attributes → properties upgrade issue faced by today's components. 19 | 20 | ### How is this different from setting DOM properties? 21 | 22 | Setting a DOM (reflected) property immediately triggers any side effects associated with that property's setter. 23 | In the DOM, this can be surprisingly expensive - assigning to the src property of an `` element causes it 24 | to be loaded, even if the value assigned is identical to the current value. This is true for most DOM properties, 25 | including all properties governing Node contents (`.data`, `.textContent`, etc). This is mostly intuitive when working 26 | directly with the DOM's imperative API, but has resulted in all modern frameworks implementing what are effectively 27 | caches around DOM property access in order to achieve reasonable performance. Furthermore, both reading and writing 28 | DOM properties incurs the cost of a binding traversal, since element behaviors are not implemented in JavaScript. 29 | 30 | Ordering of property assignment can also be tricky to get right. Similar to the `` issue noted above, assigning 31 | to the `.src` property of an Image and then immediately assigning to the `.crossOrigin` property will cancel the 32 | already-started request and re-issue it with updated CORS constraints (note: this might be a browser bug). 33 | 34 | Similar ordering concerns are present for many DOM properties - setting an Node's contents via `.data` or `.textContent`, 35 | needs to happen prior to setting properties that refer to that content like `.datalist` and `.selected`. The behavior of 36 | attributes can sometimes be more manageable in this regard, since attribute changes are batched. Synchronous code that 37 | modifies attributes generally behaves the same regardless of the order in which they are set, even across multiple elements. 38 | 39 | ## API Sketch 40 | 41 | ```webidl 42 | interface Element { 43 | void setProperty(string propertyName, any value); 44 | any getProperty(string propertyName); 45 | } 46 | ``` 47 | 48 | #### Example usage: 49 | 50 | ```js 51 | const element = document.createElement('div'); 52 | element.setProperty('class', 'demo'); 53 | document.body.appendChild(element); 54 | element.getProperty('class'); // "demo" 55 | element.setProperty('class', 'demo'); // ignored - value is unchanged 56 | ``` 57 | 58 | # Additional Opportunities 59 | 60 | ## Referential Equality 61 | 62 | This would also open up an interesting possibility for simplifying how the DOM is manipulated both by developers 63 | and through popular libraries, by providing a method to apply a set of properties to an Element where properties 64 | with referentially-equal (or semantically equal?) values are skipped: 65 | 66 | ```webidl 67 | interface Element { 68 | void setProperty(string propertyName, any value); 69 | any getProperty(string propertyName); 70 | void setProperties(map; properties); 71 | } 72 | ``` 73 | 74 | #### Example usage: 75 | 76 | ```js 77 | const element = document.createElement('div'); 78 | element.setProperties({ 79 | class: 'demo', 80 | id: 'demo-1' 81 | }); 82 | document.body.appendChild(element); 83 | element.setProperties({ 84 | class: 'demo', // ignored - value is unchanged 85 | style: { backgroundColor: 'red' } 86 | }); 87 | ``` 88 | 89 | ## Observable Bindings 90 | 91 | Given the existence of a `setProperty()` method on DOM Nodes, it becomes possible to implement automatic one-way 92 | data binding simply by allowing developers to pass an [Observable](https://github.com/surma/observables-with-streams) 93 | as a value. When given an Observable, setProperty obtains the current value of the Observable and applies it as it 94 | would any static value, then subscribes to the observable. Future values propagated through the observable are 95 | reflected automatically as if setProperty had been invoked with the new value. 96 | 97 | ```js 98 | const element = document.createElement('h1'); 99 | const title = new Observable('initial title'); 100 | element.setProperty('title', title); // assigns h1.title to "initial title" 101 | document.body.appendChild(element); 102 | title.next('updated'); // assigns h1.title to "updated" 103 | ``` 104 | --------------------------------------------------------------------------------