-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1. Postpone more work until initial connect (closes #14 & closes #22). 2. Upgrade all properties at initialization time (closes #15). 3. Initialize properties before initial render (closes #17). 4. Improve handling of property effects (closes #19). 3. Robustify test suite.
- Loading branch information
1 parent
bbbb68a
commit 78e4b3d
Showing
23 changed files
with
1,211 additions
and
400 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# The x-element spec | ||
|
||
This describes the expected behavior of x-element. | ||
|
||
## Mixin hierarchy | ||
|
||
1. `element-mixin`: Provides base functionality. | ||
2. `properties-mixin`: Allows you to declare the `properties` block. | ||
3. `property-effects-mixin`: Allows you to declare effects that should happen | ||
when a property changes. | ||
4. `lit-html-mixin`: Chooses `lit-html` as the templating engine. | ||
|
||
It's currently the case that you can omit mixins from the tail, but things will | ||
break down if you choose to omit something from the middle/head. I.e., you | ||
cannot have `property-effects-mixin` without `properties-mixin`, but the other | ||
way around is OK. | ||
|
||
## Phases of `x-element` | ||
|
||
### Analysis | ||
|
||
Analysis takes place once per class and is cached. This allows all future | ||
instances to share common setup work. The result of the analysis phase is made | ||
available again during initialization. | ||
|
||
### Initialization | ||
|
||
The initialization phase takes place once per instance. This allows each class | ||
to leverage cached information in the analysis phase. | ||
|
||
## Lifecycle | ||
|
||
### Construction time | ||
|
||
- static analysis | ||
|
||
### On first connected callback | ||
|
||
- handle post-definition upgrade scenario | ||
- initialize render root | ||
- initialize property values | ||
- compute properties | ||
- render | ||
- enable property effects | ||
- reflect properties | ||
- observe properties | ||
|
||
### On subsequent property changes | ||
|
||
- reflect property if needed | ||
- observe property if needed | ||
- compute dependent properties if needed, causes subsequent property changes | ||
|
||
## Properties | ||
|
||
The properties block allows you to define the following: | ||
|
||
- `type` [Function]: type associated with the property | ||
- `value` [Funciton|Any Literal]: _initial_ value for the property or getter | ||
- `readOnly` [Boolean]: prevent property updates via normal setter? | ||
- `reflect` [Boolean]: reflect property to attribute? | ||
- `observer` [String]: DSL used to resolve an observer callback | ||
- `computed` [String]: DSL used to resolve computed callback and dependencies | ||
|
||
## References | ||
|
||
- [WHATWG Custom Elements Spec](https://html.spec.whatwg.org/multipage/custom-elements.html) | ||
|
||
|
||
## Computed properties and graphs | ||
|
||
Consider the following properties: | ||
|
||
``` | ||
{ | ||
a: { type: Boolean }, | ||
b: { type: Boolean, computed: 'computeB(a)' }, | ||
c: { type: Boolean, computed: 'computeC(a, b)' } | ||
} | ||
``` | ||
|
||
This properties block declares that `c` depends on `a` and `b` and that `b` | ||
depends on `a`. However, the _order_ in which we resolve `b` and `c` when `a` | ||
changes is important. In general, computed properties form a Directed, Acyclic | ||
Graph (DAG). The DAG looks like this: | ||
|
||
``` | ||
a | ||
↙ ↘ | ||
b → c | ||
``` | ||
|
||
DAGs can be solved using a topological sorting algorithm and this computation | ||
can be done at analysis-time to prevent repeating expensive work at runtime. | ||
|
||
Note that DAGs can have multiple solutions. For completeness, the solution for | ||
this DAG is `[a, b, c]`. This means that if `a` changes, you need to then update | ||
`b` and then update `c`--in that order. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
function makeGraphLoop(n, mapping, edges, nodes) { | ||
if (nodes.includes(n) === false) { | ||
// Don't traverse nodes we've already traversed. Prevents infinite loop. | ||
nodes.push(n); | ||
if (n in mapping) { | ||
for (const m of mapping[n]) { | ||
edges.push([n, m]); | ||
makeGraphLoop(m, mapping, edges, nodes); | ||
} | ||
} | ||
} | ||
} | ||
|
||
export function makeGraph(node, mapping) { | ||
const edges = []; | ||
const nodes = []; | ||
makeGraphLoop(node, mapping, edges, nodes); | ||
return { edges, nodes }; | ||
} | ||
|
||
export function topologicalSort(graph) { | ||
// Implements Kahn's algorithm for topological sorting: | ||
// | ||
// L ← Empty list that will contain the sorted elements | ||
// S ← Set of all nodes with no incoming edge | ||
// while S is non-empty do | ||
// remove a node n from S | ||
// add n to tail of L | ||
// for each node m with an edge e from n to m do | ||
// remove edge e from the graph | ||
// if m has no other incoming edges then | ||
// insert m into S | ||
// if graph has edges then | ||
// return error (graph has at least one cycle) | ||
// else | ||
// return L (a topologically sorted order) | ||
// | ||
// Assumptions: | ||
// | ||
// 1. Each node in each edge of "edges" is declared in "nodes". | ||
// 2. Each node in "nodes" is unique in the list. | ||
// 3. Each edge in "edges" is unique in the list. | ||
const nodes = [...graph.nodes]; | ||
const edges = [...graph.edges]; | ||
const L = []; | ||
const isInS = n => edges.every(e => e[1] !== n); | ||
const S = new Set(nodes.filter(isInS)); | ||
while (S.size) { | ||
const n = S.values().next().value; | ||
S.delete(n); | ||
L.push(n); | ||
// Loop over a copy of "edges" to prevent mutation while looping. | ||
for (const e of [...edges]) { | ||
if (e[0] === n) { | ||
const m = e[1]; | ||
edges.splice(edges.indexOf(e), 1); | ||
if (isInS(m)) { | ||
S.add(m); | ||
} | ||
} | ||
} | ||
} | ||
if (edges.length) { | ||
// Graph is cyclic. | ||
return; | ||
} | ||
return L; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.