ks-fwork 2.0.1 → 3.0.1

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/package.json CHANGED
@@ -1,16 +1,28 @@
1
1
  {
2
2
  "name": "ks-fwork",
3
- "version": "2.0.1",
3
+ "version": "3.0.1",
4
4
  "description": "Demo Frontend Framework",
5
5
  "license": "ISC",
6
6
  "author": "",
7
7
  "type": "module",
8
- "main": "src/index.js",
9
- "exports": "./src/index.js",
8
+ "main": "dist/ks-fwork.js",
9
+ "module": "dist/ks-fwork.js",
10
+ "exports": "./dist/ks-fwork.js",
10
11
  "files": [
11
12
  "src"
12
13
  ],
13
14
  "scripts": {
14
- "test": "echo \"Error: no test specified\" && exit 1"
15
+ "test": "echo \"Error: no test specified\" && exit 1",
16
+ "build": "rollup -c"
17
+ },
18
+ "dependencies": {
19
+ "fast-deep-equal": "^3.1.3"
20
+ },
21
+ "devDependencies": {
22
+ "@rollup/plugin-commonjs": "^29.0.3",
23
+ "@rollup/plugin-node-resolve": "^16.0.3",
24
+ "rollup": "^4.24.0",
25
+ "rollup-plugin-cleanup": "^3.2.1",
26
+ "rollup-plugin-filesize": "^10.0.0"
15
27
  }
16
28
  }
package/src/app.js CHANGED
@@ -2,8 +2,9 @@ import { destroyDOM } from "./destroy-dom.js";
2
2
  import { Dispatcher } from "./dispatcher.js";
3
3
  import { mountDOM } from "./mount-dom.js";
4
4
  import { patchDOM } from "./patch-dom.js";
5
+ import { h } from "./h.js";
5
6
 
6
- export function createApp({state, view, reducers = {} }) {
7
+ export function createApp(RootComponent, props = {}) {
7
8
  let parentEl = null;
8
9
  let rootNode = null;
9
10
  let isMounted = false;
@@ -11,25 +12,10 @@ export function createApp({state, view, reducers = {} }) {
11
12
  const dispatcher = new Dispatcher();
12
13
  const unsubscribers = [dispatcher.afterEveryCommand(renderApp)];
13
14
 
14
- // Wire each reducer to its action: when the action is emitted, replace state with the reducer's result.
15
- for (const actionName in reducers) {
16
- const reducer = reducers[actionName];
17
-
18
- const unsubscribe = dispatcher.subscribe(actionName, (payload) => {
19
- state = reducer(state, payload);
20
- })
21
- unsubscribers.push(unsubscribe);
22
- }
23
-
24
- // Passed into the view so it can trigger actions without touching the dispatcher directly.
25
- function emit(actionName, payload) {
26
- dispatcher.dispatch(actionName, payload);
27
- }
28
-
29
- function renderApp() {
30
- const newRootNode = view(state, emit);
31
-
32
- rootNode = patchDOM(rootNode, newRootNode, parentEl);
15
+ function reset() {
16
+ parentEl = null;
17
+ rootNode = null;
18
+ isMounted = false;
33
19
  }
34
20
 
35
21
  return {
@@ -38,19 +24,18 @@ export function createApp({state, view, reducers = {} }) {
38
24
  if (isMounted) throw new Error('The application is already mounted');
39
25
 
40
26
  parentEl = _parentEl;
41
- rootNode = view(state, emit);
27
+ rootNode = h(RootComponent, props);
42
28
  mountDOM(rootNode, parentEl);
29
+
30
+ isMounted = true;
43
31
  },
44
32
 
45
33
  // Detach the app: remove its DOM and cancel every subscription.
46
34
  unmount() {
47
- if (rootNode) {
48
- destroyDOM(rootNode);
49
- }
50
- rootNode = null;
51
- unsubscribers.forEach((unsubscribe) => unsubscribe());
35
+ if (!isMounted) throw new Error('The application is not mounted');
52
36
 
53
- isMounted = false;
37
+ destroyDOM(rootNode);
38
+ reset();
54
39
  },
55
40
  }
56
41
  }
@@ -0,0 +1,149 @@
1
+ import { mountDOM } from "./mount-dom.js";
2
+ import { destroyDOM } from "./destroy-dom.js";
3
+ import { patchDOM } from "./patch-dom.js";
4
+ import { DOM_TYPES, extractChildren } from "./h.js";
5
+ import { hasOwnProperty } from "./utils/objects.js";
6
+
7
+ import equal from 'fast-deep-equal';
8
+ import { Dispatcher } from "./dispatcher.js";
9
+
10
+ export function defineComponent({ render, state, ...methods }) {
11
+ class Component {
12
+ #isMounted = false;
13
+ #vNode = null;
14
+ #hostEl = null;
15
+ #eventHandlers = null;
16
+ #parentComponent = null;
17
+ #dispatcher = new Dispatcher();
18
+ #subscriptions = [];
19
+
20
+ constructor(props = {}, eventHandlers = {}, parentComponent = null) {
21
+ this.props = props;
22
+ this.state = state ? state(props) : {};
23
+ this.#eventHandlers = eventHandlers;
24
+ this.#parentComponent = parentComponent;
25
+ }
26
+
27
+ get elements() {
28
+ // If vNode is null, return an empty array
29
+ if (this.#vNode == null) {
30
+ return [];
31
+ }
32
+
33
+ // If vNode's top element is fragment, return the elements inside it
34
+ if (this.#vNode.type === DOM_TYPES.FRAGMENT) {
35
+ return extractChildren(this.#vNode).flatMap((child) => {
36
+ if (child.type === DOM_TYPES.COMPONENT) {
37
+ return child.component.elements;
38
+ }
39
+
40
+ return [child.el];
41
+ });
42
+ }
43
+
44
+ // If vNode's top element is a single node, return its element
45
+ return [this.#vNode.el];
46
+ }
47
+
48
+ // Return component's first element
49
+ get firstElement() {
50
+ return this.elements[0];
51
+ }
52
+
53
+ // Return component's first element offset inside the parent element. If is not fragment, return offset 0
54
+ get offset() {
55
+ if(this.#vNode.type === DOM_TYPES.FRAGMENT) {
56
+ return Array.from(this.#hostEl.children).indexOf(this.firstElement);
57
+ }
58
+
59
+ return 0;
60
+ }
61
+
62
+ updateProps(props) {
63
+ const newProps = { ...this.props, ...props };
64
+
65
+ if (equal(this.props, newProps)) {
66
+ return;
67
+ }
68
+
69
+ this.props = newProps;
70
+ this.#patch();
71
+ }
72
+
73
+ updateState(state) {
74
+ this.state = { ...this.state, ...state };
75
+ this.#patch();
76
+ }
77
+
78
+ render() {
79
+ return render.call(this);
80
+ }
81
+
82
+ emit(eventName, payload) {
83
+ this.#dispatcher.dispatch(eventName, payload);
84
+ }
85
+
86
+ mount(hostEl, index = null) {
87
+ if (this.#isMounted) {
88
+ throw new Error('Component is already mounted');
89
+ }
90
+
91
+ this.#vNode = this.render();
92
+ mountDOM(this.#vNode, hostEl, index, this);
93
+ this.#wireEventHandlers();
94
+
95
+ this.#hostEl = hostEl;
96
+ this.#isMounted = true;
97
+ }
98
+
99
+ unmount() {
100
+ if (!this.#isMounted) {
101
+ throw new Error('Component is not mounted');
102
+ }
103
+
104
+ destroyDOM(this.#vNode);
105
+ this.#subscriptions.forEach((unsubscribe) => unsubscribe());
106
+
107
+ this.#vNode = null;
108
+ this.#hostEl = null;
109
+ this.#isMounted = false;
110
+ this.#subscriptions = [];
111
+ }
112
+
113
+ #patch() {
114
+ if (!this.#isMounted) {
115
+ throw new Error('Component is not mounted');
116
+ }
117
+
118
+ const vNode = this.render();
119
+ this.#vNode = patchDOM(this.#vNode, vNode, this.#hostEl, this);
120
+ }
121
+
122
+ #wireEventHandlers() {
123
+ this.#subscriptions = Object.entries(this.#eventHandlers).map(
124
+ ([eventName, handler]) =>
125
+ this.#wireEventHandler(eventName, handler)
126
+ )
127
+ }
128
+
129
+ #wireEventHandler(eventName, handler) {
130
+ return this.#dispatcher.subscribe(eventName, (payload) => {
131
+ if (this.#parentComponent) {
132
+ handler.call(this.#parentComponent, payload)
133
+ } else {
134
+ handler(payload)
135
+ }
136
+ })
137
+ }
138
+ }
139
+
140
+ for (const methodName in methods) {
141
+ if (hasOwnProperty(Component, methodName)) {
142
+ throw new Error(`Method "${methodName}()" already exists in the component.`);
143
+ }
144
+
145
+ Component.prototype[methodName] = methods[methodName];
146
+ }
147
+
148
+ return Component;
149
+ }
@@ -21,6 +21,11 @@ export function destroyDOM(vNode) {
21
21
  break;
22
22
  }
23
23
 
24
+ case DOM_TYPES.COMPONENT: {
25
+ vNode.component.unmount();
26
+ break;
27
+ }
28
+
24
29
  default: {
25
30
  throw new Error(`Can't destroy virtual node of type: ${vNode.type}`);
26
31
  }
package/src/events.js CHANGED
@@ -1,20 +1,25 @@
1
- // Add an event listener to an element
2
- export function addEventListener(eventName, handler, el) {
3
- el.addEventListener(eventName, handler);
4
- return handler;
5
- }
6
-
7
1
  // Add an object of event listeners to an element
8
- export function addEventListeners(listeners = {}, el) {
2
+ export function addEventListeners(listeners = {}, el, hostComponent = null) {
9
3
  const addedListeners = {};
10
4
 
11
5
  Object.entries(listeners).forEach(([eventName, handler]) => {
12
- addedListeners[eventName] = addEventListener(eventName, handler, el);
6
+ addedListeners[eventName] = addEventListener(eventName, handler, el, hostComponent);
13
7
  });
14
8
 
15
9
  return addedListeners;
16
10
  }
17
11
 
12
+ // Add an event listener to an element
13
+ export function addEventListener(eventName, handler, el, hostComponent = null) {
14
+ function boundHandler() {
15
+ hostComponent ? handler.apply(hostComponent, arguments) : handler(...arguments);
16
+ }
17
+
18
+ el.addEventListener(eventName, boundHandler);
19
+
20
+ return boundHandler;
21
+ }
22
+
18
23
  // Remove an object of event listeners from an element
19
24
  export function removeEventListeners(listeners = {}, el) {
20
25
  Object.entries(listeners).forEach(([eventName, handler]) => {
package/src/h.js CHANGED
@@ -3,16 +3,19 @@ import { withoutNulls } from './utils/arrays.js';
3
3
  export const DOM_TYPES = {
4
4
  TEXT: 'text',
5
5
  ELEMENT: 'element',
6
- FRAGMENT: 'fragment'
6
+ FRAGMENT: 'fragment',
7
+ COMPONENT: 'component'
7
8
  }
8
9
 
9
10
  // Create a virtual DOM element
10
11
  export function h(tag, props = {}, children = []) {
12
+ const type = typeof tag === 'string' ? DOM_TYPES.ELEMENT : DOM_TYPES.COMPONENT;
13
+
11
14
  return {
12
15
  tag,
13
16
  props,
17
+ type,
14
18
  children: mapTextNodes(withoutNulls(children)),
15
- type: DOM_TYPES.ELEMENT,
16
19
  }
17
20
  }
18
21
 
@@ -53,6 +56,7 @@ export function lipsum(numberOfParagraphs) {
53
56
  );
54
57
  }
55
58
 
59
+ // Recursively extract a node's children into a flat list, dissolving any nested fragments into their child nodes
56
60
  export function extractChildren(vNode) {
57
61
  if (vNode.children == null) return [];
58
62
 
package/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createApp } from './app.js'
2
+ export { defineComponent } from './component.js'
2
3
  export { h, hFragment, hString } from './h.js'
package/src/mount-dom.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { DOM_TYPES } from "./h.js";
2
2
  import { setAttributes } from './attributes.js'
3
3
  import { addEventListeners } from "./events.js";
4
+ import { extractPropsAndEvents } from "./utils/props.js";
4
5
 
5
6
  // Recursively mount a virtual node (and its subtree) into a real parent DOM element.
6
- export function mountDOM(vNode, parentEl, index) {
7
+ export function mountDOM(vNode, parentEl, index, hostComponent = null) {
7
8
  switch (vNode.type) {
8
9
  case DOM_TYPES.TEXT: {
9
10
  createTextNode(vNode, parentEl, index);
@@ -11,15 +12,19 @@ export function mountDOM(vNode, parentEl, index) {
11
12
  }
12
13
 
13
14
  case DOM_TYPES.ELEMENT: {
14
- createElementNode(vNode, parentEl, index);
15
+ createElementNode(vNode, parentEl, index, hostComponent);
15
16
  break;
16
17
  }
17
18
 
18
19
  case DOM_TYPES.FRAGMENT: {
19
- createFragmentNode(vNode, parentEl, index);
20
+ createFragmentNode(vNode, parentEl, index, hostComponent);
20
21
  break;
21
22
  }
22
23
 
24
+ case DOM_TYPES.COMPONENT: {
25
+ createComponentNode(vNode, parentEl, index, hostComponent);
26
+ }
27
+
23
28
  default: {
24
29
  throw new Error(`Can't mount virtual node of type: ${vNode.type}`);
25
30
  }
@@ -36,38 +41,49 @@ function createTextNode(vNode, parentEl, index) {
36
41
  insert(textNode, parentEl, index);
37
42
  }
38
43
 
39
- // Create a real element, apply its props, recursively mount its children into it,
40
- // then append the finished element to the parent.
41
- function createElementNode(vNode, parentEl, index) {
42
- const { tag, props, children } = vNode;
44
+ // Create a real element, apply its props, recursively mount its children into it, then append the finished element to the parent.
45
+ function createElementNode(vNode, parentEl, index, hostComponent) {
46
+ const { tag, children } = vNode;
43
47
 
44
48
  const el = document.createElement(tag);
45
- addProps(el, props, vNode);
49
+ addProps(el, vNode, hostComponent);
46
50
  vNode.el = el;
47
51
 
48
- children.forEach((child) => mountDOM(child, el));
52
+ children.forEach((child) => mountDOM(child, el, null, hostComponent));
49
53
  insert(el, parentEl, index);
50
54
  }
51
55
 
52
56
  // Fragment has no DOM element. Mount its children directly into the parent.
53
- function createFragmentNode(vNode, parentEl, index) {
57
+ function createFragmentNode(vNode, parentEl, index, hostComponent) {
54
58
  const { children } = vNode;
55
59
  vNode.el = parentEl;
56
60
 
57
61
  children.forEach((child, i) => {
58
- mountDOM(child, parentEl, index ? index + i : null)
62
+ mountDOM(child, parentEl, index ? index + i : null, hostComponent)
59
63
  });
60
64
  }
61
65
 
66
+ function createComponentNode(vNode, parentEl, index, hostComponent) {
67
+ const Component = vNode.tag;
68
+ const { props, events } = extractPropsAndEvents(vNode);
69
+ const component = new Component(props, events, hostComponent);
70
+
71
+ component.mount(parentEl, index);
72
+ vNode.component = component;
73
+ vNode.el = component.firstElement;
74
+ }
75
+
62
76
  // Split props into event listeners ('on') and attributes and add them to DOM element.
63
- function addProps(el, props, vNode) {
64
- const { on: events, ...attrs} = props;
77
+ function addProps(el, vNode, hostComponent) {
78
+ const { props: attrs, events} = extractPropsAndEvents(vNode);
65
79
 
66
- vNode.listeners = addEventListeners(events, el);
80
+ vNode.listeners = addEventListeners(events, el, hostComponent);
67
81
  setAttributes(el, attrs);
68
82
  }
69
83
 
84
+ // Insert element into parent element at the specified index (or append if no index given)
70
85
  function insert(el, parentEl, index) {
86
+ // Append if no index specified (null or undefined)
71
87
  if (index == null) {
72
88
  parentEl.append(el);
73
89
  return;
@@ -80,8 +96,10 @@ function insert(el, parentEl, index) {
80
96
  const children = parentEl.childNodes;
81
97
 
82
98
  if (index >= children.length) {
99
+ // Append if index is greater than or equal to the parent element's length of children
83
100
  parentEl.append(el);
84
101
  } else {
102
+ // Insert before the element at the specified index
85
103
  parentEl.insertBefore(el, children[index]);
86
104
  }
87
105
  }
@@ -1,11 +1,23 @@
1
1
  import { DOM_TYPES } from "./h.js";
2
2
 
3
3
  export function areNodesEqual(nodeOne, nodeTwo) {
4
+ // Nodes are different type
4
5
  if (nodeOne.type !== nodeTwo.type) return false;
5
6
 
6
7
  if (nodeOne.type === DOM_TYPES.ELEMENT) {
7
- return nodeOne.tag === nodeTwo.tag;
8
+ const { tag: tagOne, props: { key: keyOne } } = nodeOne;
9
+ const { tag: tagTwo, props: { key: keyTwo } } = nodeTwo;
10
+
11
+ return tagOne === tagTwo && keyOne === keyTwo;
12
+ }
13
+
14
+ if (nodeOne.type === DOM_TYPES.COMPONENT) {
15
+ const { tag: componentOne, props: { key: keyOne } } = nodeOne;
16
+ const { tag: componentTwo, props: { key: keyTwo } } = nodeTwo;
17
+
18
+ return componentOne === componentTwo && keyOne === keyTwo;
8
19
  }
9
20
 
21
+ // Text and fragment nodes are always equal
10
22
  return true;
11
23
  }
package/src/patch-dom.js CHANGED
@@ -7,16 +7,24 @@ import { removeAttribute, removeStyle, setAttribute, setStyle } from "./attribut
7
7
  import { ARRAY_DIFF_OP, arraysDiff, arraysDiffSequence } from "./utils/arrays.js";
8
8
  import { isNotBlankOrEmptyString } from "./utils/strings.js";
9
9
  import { addEventListener } from "./events.js";
10
+ import { extractPropsAndEvents } from "./utils/props.js";
10
11
 
11
- export function patchDOM(oldVNode, newVNode, parentEl) {
12
+ export function patchDOM(oldVNode, newVNode, parentEl, hostComponent = null) {
13
+ // Nodes are too different to reuse/patch.
12
14
  if (!areNodesEqual(oldVNode, newVNode)) {
15
+ // Remember where the old node was located inside the parent so the replacement lands in the same spot.
13
16
  const index = findIndexInParent(parentEl, oldVNode.el);
17
+
18
+ // Destroy the old node and its whole subtree.
14
19
  destroyDOM(oldVNode);
15
- mountDOM(newVNode, parentEl, index);
20
+
21
+ // Mount the new node and its whole subtree at the index of the old node.
22
+ mountDOM(newVNode, parentEl, index, hostComponent);
16
23
 
17
24
  return newVNode;
18
25
  }
19
26
 
27
+ // Copy over the DOM element reference
20
28
  newVNode.el = oldVNode.el;
21
29
 
22
30
  switch (newVNode.type) {
@@ -26,16 +34,22 @@ export function patchDOM(oldVNode, newVNode, parentEl) {
26
34
  }
27
35
 
28
36
  case DOM_TYPES.ELEMENT: {
29
- patchElement(oldVNode, newVNode);
37
+ patchElement(oldVNode, newVNode, hostComponent);
38
+ break;
39
+ }
40
+
41
+ case DOM_TYPES.COMPONENT: {
42
+ patchComponent(oldVNode, newVNode);
30
43
  break;
31
44
  }
32
45
  }
33
46
 
34
- patchChildren(oldVNode, newVNode);
47
+ patchChildren(oldVNode, newVNode, hostComponent);
35
48
 
36
49
  return newVNode;
37
50
  }
38
51
 
52
+ // Get index of the element inside the parent element
39
53
  function findIndexInParent(parentEl, el) {
40
54
  const index = Array.from(parentEl.childNodes).indexOf(el);
41
55
  if (index < 0) return null;
@@ -53,7 +67,7 @@ function patchText(oldVNode, newVNode) {
53
67
  }
54
68
  }
55
69
 
56
- function patchElement(oldVNode, newVNode) {
70
+ function patchElement(oldVNode, newVNode, hostComponent) {
57
71
  const el = oldVNode.el;
58
72
 
59
73
  const {
@@ -75,7 +89,17 @@ function patchElement(oldVNode, newVNode) {
75
89
  patchAttrs(el, oldAttrs, newAttrs);
76
90
  patchClasses(el, oldClass, newClass);
77
91
  patchStyles(el, oldStyle, newStyle);
78
- newVNode.listeners = patchEvents(el, oldListeners, oldEvents, newEvents);
92
+ newVNode.listeners = patchEvents(el, oldListeners, oldEvents, newEvents, hostComponent);
93
+ }
94
+
95
+ function patchComponent(oldVNode, newVNode) {
96
+ const { component } = oldVNode;
97
+ const { props } = extractPropsAndEvents(newVNode);
98
+
99
+ component.updateProps(props);
100
+
101
+ newVNode.component = component;
102
+ newVNode.el = component.firstElement;
79
103
  }
80
104
 
81
105
  function patchAttrs(el, oldAttrs, newAttrs) {
@@ -124,7 +148,7 @@ function patchStyles(el, oldStyle = {}, newStyle = {}) {
124
148
  }
125
149
  }
126
150
 
127
- function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}) {
151
+ function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}, hostComponent) {
128
152
  const { removed, added, updated} = objectsDiff(oldEvents, newEvents);
129
153
 
130
154
  for (const eventName of removed.concat(updated)) {
@@ -134,46 +158,55 @@ function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}) {
134
158
  const addedListeners = {};
135
159
 
136
160
  for (const eventName of added.concat(updated)) {
137
- addedListeners[eventName] = addEventListener(eventName, newEvents[eventName], el);
161
+ addedListeners[eventName] = addEventListener(eventName, newEvents[eventName], el, hostComponent);
138
162
  }
139
163
 
140
164
  return addedListeners;
141
165
  }
142
166
 
143
- function patchChildren(oldVNode, newVNode) {
167
+ // Reconcile a node's child lists.
168
+ function patchChildren(oldVNode, newVNode, hostComponent) {
144
169
  const oldChildren = extractChildren(oldVNode);
145
170
  const newChildren = extractChildren(newVNode);
146
171
  const parentEl = oldVNode.el;
147
172
 
173
+ // Create the sequence of operations to match the note's child lists.
148
174
  const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
149
175
 
176
+ // Loop over the operations and perform the operations on the DOM.
150
177
  for (const operation of diffSeq) {
151
178
  const { originalIndex, from, index, item } = operation;
179
+ // Get the host component’s offset, if there is one; offset is 0 otherwise
180
+ const offset = hostComponent?.offset ?? 0;
152
181
 
153
182
  switch (operation.op) {
183
+ // New node with no old counterpart: build its subtree and insert it at `index`
154
184
  case ARRAY_DIFF_OP.ADD: {
155
- mountDOM(item, parentEl, index)
185
+ mountDOM(item, parentEl, index, hostComponent)
156
186
  break;
157
187
  }
158
188
 
189
+ // Old node gone from the new list: destroy it and its subtree
159
190
  case ARRAY_DIFF_OP.REMOVE: {
160
191
  destroyDOM(item);
161
192
  break;
162
193
  }
163
194
 
195
+ // Node still present but at a different position: move the existing node to its new spot, then patch it in place for any inner changes
164
196
  case ARRAY_DIFF_OP.MOVE: {
165
197
  const el = oldChildren[from].el;
166
- const elAtTargetIndex = parentEl.childNodes[index];
167
-
198
+ const elAtTargetIndex = parentEl.childNodes[index + offset];
168
199
  parentEl.insertBefore(el, elAtTargetIndex);
169
- patchDOM(oldChildren[from], newChildren[index], parentEl);
200
+
201
+ patchDOM(oldChildren[from], newChildren[index], parentEl, hostComponent);
170
202
  break;
171
203
  }
172
204
 
205
+ // Same node, same position, but its contents may still differ, so recurse
173
206
  case ARRAY_DIFF_OP.NOOP: {
174
- patchDOM(oldChildren[originalIndex], newChildren[index], parentEl)
207
+ patchDOM(oldChildren[originalIndex], newChildren[index], parentEl, hostComponent);
175
208
  break;
176
209
  }
177
210
  }
178
211
  }
179
- }
212
+ }