Skip to content

Commit 40aea01

Browse files
marcoscaceresjyasskin
authored andcommitted
Add 'Choose the Appropriate WebIDL Construct for Data and Behavior'
1 parent 2dacfea commit 40aea01

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

index.bs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,132 @@ the interaction with garbage collection.
15251525

15261526
<h2 id="api-surface">JavaScript API Surface Concerns</h2>
15271527

1528+
<h3>Choose the Appropriate WebIDL Construct for Data and Behavior (Dictionaries, Interfaces, and Namespaces)</h3>
1529+
1530+
Web APIs commonly pass around data and functionality using WebIDL. As a specification author, decide carefully whether
1531+
to use a dictionary, an interface, or in rare cases, a namespace.
1532+
1533+
The goal is to ensure ergonomic, consistent APIs that feel natural to Web developers while avoiding pitfalls like "fake classes" or classes that provide no functionality.
1534+
1535+
Each construct has its own pros and cons, discussed below.
1536+
1537+
<h4>Use Dictionaries for “Configuration” or “Input-Only” Data</h4>
1538+
1539+
Choose a dictionary when the part of the API represents data that is transient or you need a configuration-style object or "options bag".
1540+
1541+
Dictionaries are ideal for when the data doesn't get stored or mutated; it's just used it at the time of the call.
1542+
1543+
For example, the `ShareData` member from Web Share:
1544+
1545+
```WebIDL
1546+
dictionary ShareData {
1547+
USVString title;
1548+
USVString text;
1549+
USVString url;
1550+
};
1551+
```
1552+
1553+
And how it's commonly used:
1554+
1555+
<pre class="highlight">
1556+
const data = { "text": "Text being shared" };
1557+
await navigator.share(data);
1558+
</pre>
1559+
1560+
Dictionaries are easily extensible and makes it easy to add optional fields later as needed.
1561+
Members of a dictionary are optional by default, but can be marked as `required` if needed.
1562+
1563+
Dictionaries are also highly idiomatic (i.e., natural to use in JavaScript). Passing `{ ... }` inline is the most natural way to supply configuration in JavaScript.
1564+
1565+
Dictionaries, because of how they are treated by user agents, are also relatively future-proof; accommodating new members gracefully, without breaking older code.
1566+
1567+
Dictionaries are best used for objects that don't need to be distigusied by type in their lifecycle (i.e., `instanceof` checks are mostly meaningless because it's always `Object`).
1568+
1569+
A key thing to know about dictionries is that they are "passed by value" to methods (i.e., they are copied) and that browsers engines strip unknown members when converting from JavaScript objects to a WebIDL reprensentation.
1570+
This means that if a developer changing the value after it is passed into an API has no effect.
1571+
1572+
Again, taking the `ShareData` dictinary as an example:
1573+
1574+
```JS
1575+
const data = {
1576+
"text": "Text being shared",
1577+
// Not in the dictionary, so removed by the browser
1578+
"whatever": 123,
1579+
};
1580+
navigator.share(data);
1581+
1582+
// Changing this after calling .share() has no effect
1583+
data.text = "New text";
1584+
```
1585+
1586+
<h4>Choose an Interface for Functionality, State, and Identity</h4>
1587+
1588+
Intefaces are roughly equivalent to classes in JavaScript. Choose and an interface when a specificaiton need object with data that might also have — or eventually gain — methods, computed or readonly properties, or internal state or "slots".
1589+
1590+
Unlike dictrionaries, interfaces:
1591+
1592+
* provide the ability to check object's identity (i.e., one can check if it is an `instanceof` a particular class on the global scope),
1593+
* can have need read-only properties,
1594+
* can have state,
1595+
* can exhibit side-effects on assignment.
1596+
1597+
Defining an interface also exposes it on the global scope, allowing for the specification of static methods. For example, the `canParse()` static method of the URL interface.
1598+
1599+
```JS
1600+
if (URL.canParse(someURL)) {
1601+
// Do stuff...
1602+
}
1603+
```
1604+
1605+
If the interface can holds data that is useful when serialized (e.g., `GeolocationPosition`), consider adding a `toJSON()` default method. Alternatively, if it holds binary data, one can add .toBlob() an so on.
1606+
1607+
This makes object natural to use with APIs. For example:
1608+
1609+
```JS
1610+
const position = await new Promise((resolve, reject) => {
1611+
navigator.geolocation.getCurrentPosition(resolve, reject);
1612+
});
1613+
1614+
// Prepare the fetch request options
1615+
const options = {
1616+
method: 'POST',
1617+
headers: {
1618+
'Content-Type': 'application/json'
1619+
},
1620+
// .stringify() calls .toJSON() automatically
1621+
position
1622+
};
1623+
```
1624+
1625+
If warrented by the use cases, give the interface a constructor. Be mindful to not just add a constructor if a class has no state. Doing so is considered by practice by effectively creaeting a fake class.
1626+
(see `DOMParser` or `DOMImplementation` as bad examples).
1627+
1628+
In such cases, prefer a static method on an existing object or, if absolutely necessary, mint a new namespace.
1629+
1630+
<h4>Choose a namespace to Avoid “Fake Classes” for Behavior-Only Utilities</h4>
1631+
1632+
A namespace is correct if everything is purely static and lacks a prototype (like the `Math`, `Intl`, `Atomics`, `Console` objects in JS).
1633+
1634+
If you only have one or two small static functions, a whole new namespace may be overkill. Attaching them to an existing object might be more idiomatic.
1635+
1636+
Conversely, a namespace that grows too large or too broad may need better organization or separate logical partitions.
1637+
1638+
<h4>"Pseudo-namespaces"</h4>
1639+
1640+
As WebIDL interfaces can have attributes, it is possible to create "pseudo-namespaces" by leveraging non-contructable interface as attributes.
1641+
1642+
A common example are all the attributes attaches to the navigator object. The navigator object has an interface definition (`Navigator`), which iteself contains other
1643+
atttributes that gives access to further functionality through interface instances.
1644+
1645+
For example:
1646+
1647+
* `navigator.credentials` - The `CredentialsContainer` interface of the Credentials Management API.
1648+
* `navigator.geolocation` - The `Geolocation` interface of the Geolocation specification.
1649+
* `navigator.permissions` - The `Permissions` interface of the Permissions specification.
1650+
1651+
Psudo-namespaces are useful for when, as a spec author, you need access to "this" particular instance
1652+
(e.g., where the properties of one instance may different from those of another browsing context).
1653+
15281654
<h3 id="attributes-like-data">Attributes should behave like data properties</h3>
15291655

15301656
[[!WEBIDL]] attributes should act like simple JavaScript object properties.

0 commit comments

Comments
 (0)