-
Notifications
You must be signed in to change notification settings - Fork 10
/
diff.js
210 lines (187 loc) · 6.06 KB
/
diff.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import { isPrimitiveElement, getVDOMElement } from './vdom-helpers';
// This is a map with all the diff types we will support
export const diffType = {
nodeAdded: 'nodeAdded',
nodeRemoved: 'nodeRemoved',
nodeReplaced: 'nodeReplaced',
primitiveNodeUpdate: 'primitiveNodeUpdate',
props: 'props',
};
// The props diff has two variations for updated and removed
export const propsDiffType = {
updated: 'updated',
removed: 'removed',
};
// The following are helper functions to create diff items
const createNodeAddedPayload = (
currentRenderableVDOMElement,
parentPointer,
) => ({
VDOMPointer: currentRenderableVDOMElement.VDOMPointer,
type: diffType.nodeAdded,
payload: { node: currentRenderableVDOMElement, parentPointer },
});
const createNodeRemoved = currentRenderableVDOMElement => ({
VDOMPointer: currentRenderableVDOMElement.VDOMPointer,
type: diffType.nodeRemoved,
payload: {},
});
const createNodeReplaced = (
currentRenderableVDOMElement,
parentPointer,
) => ({
VDOMPointer: currentRenderableVDOMElement.VDOMPointer,
type: diffType.nodeReplaced,
payload: {
newNode: currentRenderableVDOMElement,
parentPointer,
},
});
const createPrimitiveNodeUpdate = (
currentRenderableVDOMElement,
newElement,
) => ({
VDOMPointer: currentRenderableVDOMElement.VDOMPointer,
type: diffType.primitiveNodeUpdate,
payload: { newElement },
});
const createPropsDiff = (
currentRenderableVDOMElement,
changedProps
) => ({
VDOMPointer: currentRenderableVDOMElement.VDOMPointer,
type: diffType.props,
payload: changedProps,
});
const createPropsDiffTypeRemoved = (
oldValue
) => ([
propsDiffType.removed,
{ oldValue },
]);
const createPropsDiffTypeUpdated = (
newValue,
oldValue
) => ([
propsDiffType.updated,
{ newValue, oldValue },
]);
// Diff will need to be applied in a specific order to work correctly
export const diffApplicationOrder = [
diffType.nodeRemoved,
diffType.nodeAdded,
diffType.nodeReplaced,
diffType.primitiveNodeUpdate,
diffType.props,
];
/*
This function will calculate a diff for the renderable VDOM
Input
- currentRenderableVDOMElement the root element of the renderableVDOM (or the root element of a subtree for recursion)
- vdom the VDOMWithHistory structure with the current and previous VDOM
- parentPointer the VDOMPointer to the parent of the current element (convenience as in the renderableVDOM, parents might not be part of the structure in case they are components)
Output
- Diff between currentRenderableVDOMElement and the vdom.previous, an array of diff items created with the helper functions above
*/
export const getRenderableVDOMDiff = (
currentRenderableVDOMElement,
vdom,
parentPointer,
) => {
// Retrieve the previous element in the VDOM for comparison
const prev = getVDOMElement(
currentRenderableVDOMElement.VDOMPointer,
vdom.previous,
);
// If there is no previous element at the position for which there is a current element
if (!prev) {
// START HERE
// What type of diff is this?
return [];
}
// Access the actual elements to compare them
const prevElement = prev.element;
// renderableVDOM element don't have renderedChildren, so they are already the element
const currElement = currentRenderableVDOMElement;
if (prevElement.type !== currElement.type) {
return [
createNodeReplaced(
currentRenderableVDOMElement,
parentPointer,
),
];
}
// Both same type, let's continue calculting diff 👇
// If both primitive
if (isPrimitiveElement(prevElement) && isPrimitiveElement(currElement)) {
// DON'T FORGET
// Under which condition is a primitive node updated?
// A primitive element has the following structure
// { type: 'primitive', value: number | string | boolean | undefined }
const REPLACE_THIS_CONDITION = false;
if (REPLACE_THIS_CONDITION) {
return [
createPrimitiveNodeUpdate(currentRenderableVDOMElement, currElement),
];
}
// no change
return [];
}
// Prepare props diff item
const changedProps = {};
// Collect all props keys from the previous and current element
const keys = Array.from(
new Set([
...Object.keys(prevElement.props),
...Object.keys(currElement.props),
]),
);
// Loop through the props keys
for (const key of keys) {
// Children is a special prop as it requires us to go recursive so we ignore it here
if (key === 'children') {
continue;
}
const currentPropValue = currElement.props[key];
const previousPropValue = prevElement.props[key];
// If the current props have no reference to the prop we evaluate
if (typeof currentPropValue === 'undefined') {
changedProps[key] = createPropsDiffTypeRemoved(previousPropValue);
continue;
}
// If the current and previous prop value aren't the same
if (currentPropValue !== previousPropValue) {
changedProps[key] = createPropsDiffTypeUpdated(currentPropValue, previousPropValue);
}
}
let diff = [];
// conditional case to keep output clean
if (Object.keys(changedProps).length > 0) {
diff.push(createPropsDiff(currentRenderableVDOMElement, changedProps));
}
// Recursive into children
const prevChildren = prev.renderedChildren || [];
const currChildren = currentRenderableVDOMElement.props.children || [];
// We need to loop through all children to compute the diff correctly
// so we pick the length of the element with the most children
const maxLength = Math.max(prevChildren.length, currChildren.length);
for (let index = 0; index < maxLength; index++) {
const currChild = currChildren[index];
if (!currChild) {
// DON'T FORGET
// What does it mean if we don't have a child
// at the current position?
// Replace the error with the appropriate diff
throw new Error(
'We need to handle this case as otherwise the recursion will crash',
);
}
const subTreeDiff = getRenderableVDOMDiff(
currChild,
vdom,
currentRenderableVDOMElement.VDOMPointer,
);
diff = [...diff, ...subTreeDiff];
}
return diff;
};