Skip to content

Commit dca66e4

Browse files
author
Zaydek Michels-Gualtieri
committed
Scratch work on revised toReactTree algorithm
1 parent b1f2d34 commit dca66e4

7 files changed

Lines changed: 755 additions & 0 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Render precedence order.
2+
const SORT_ORDER = {
3+
"code": 0,
4+
"a": 1,
5+
"strike": 2,
6+
"strong": 3,
7+
"em": 4,
8+
}
9+
10+
// Sorts types based on render precedence.
11+
function sorter(t1, t2) {
12+
return SORT_ORDER[t1] - SORT_ORDER[t2]
13+
}
14+
15+
// Convert a type-map to a type-array.
16+
function convTypesToArray(types) {
17+
const keys = Object.keys(types).sort(sorter)
18+
return keys.reduce((acc, each) => {
19+
acc.push({ type: each, props: types[each] })
20+
return acc
21+
}, [])
22+
}
23+
24+
export default convTypesToArray
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import convTypesToArray from "./convTypesToArray"
2+
3+
test("convTypesToArray(...)", () => {
4+
const types = {
5+
em: {},
6+
strong: {},
7+
code: {},
8+
strike: {},
9+
a: { href: "foo" },
10+
}
11+
expect(convTypesToArray(types)).toEqual([
12+
{ type: "code", props: {} },
13+
{ type: "a", props: { href: "foo" } },
14+
{ type: "strike", props: {} },
15+
{ type: "strong", props: {} },
16+
{ type: "em", props: {} },
17+
])
18+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./toReactTree"
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// find the longest common element
2+
// if there are multiple candidates, select the based on render precedence
3+
// recurse?
4+
5+
// render the outside layer until we removed all the common types (based on order)
6+
// recurse on inner elements, that’s it
7+
8+
// <strong>Hello, <code>world</code>!</strong>
9+
10+
// for (let x = 0; x < children.length; x++) {
11+
// //
12+
// }
13+
14+
// for (let x = 0; x < children.length; x++) {
15+
// let x2 = x
16+
// for (; x2 < children.length; x2++) {
17+
// if (children[x].typeschildren[x2].types)
18+
// }
19+
// }
20+
21+
function doSomething(children) {
22+
for (let x = 0; x < children.length; x++) {
23+
let x2 = x + 1
24+
for (; x2 < children.length; x2++) {
25+
if (!typesInCommon(children[x], children[x2])) {
26+
// No-op
27+
break
28+
}
29+
}
30+
recurse(children.slice(x, x2))
31+
}
32+
}
33+
34+
// Returns a map of the types in common. Returns null if
35+
// there are no types in common.
36+
function typesInCommon(types1, types2) {
37+
const common = {}
38+
for (const each of Object.keys(types2)) {
39+
if (types1[each] && types2[each] && JSONEqual(types1[each], types2[each])) {
40+
common[each] = types1[each]
41+
}
42+
}
43+
if (!Object.keys(common).length) {
44+
return null
45+
}
46+
return common
47+
}
48+
49+
50+
// // Converts children to tree-shaped children.
51+
// function toTree(children) {
52+
// const tree = []
53+
// for (const each of children) {
54+
// const [parentElement, types] = queryNext(tree, each)
55+
// parentElement.push(createElement(each, types))
56+
// }
57+
// return tree
58+
// }
59+
60+
[
61+
{
62+
types: {
63+
code: {},
64+
},
65+
props: {
66+
children: "aaa"
67+
},
68+
},
69+
{
70+
types: {
71+
code: {},
72+
strong: {},
73+
},
74+
props: {
75+
children: "bbb",
76+
},
77+
},
78+
{
79+
types: {
80+
code: {},
81+
strong: {},
82+
strong: {},
83+
},
84+
props: {
85+
children: "ccc",
86+
},
87+
},
88+
{
89+
types: {
90+
code: {},
91+
strong: {},
92+
},
93+
props: {
94+
children: "ddd",
95+
},
96+
},
97+
{
98+
types: {
99+
code: {},
100+
},
101+
props: {
102+
children: "eee"
103+
},
104+
},
105+
]
106+
107+
[
108+
{
109+
type: "code",
110+
props: {
111+
children: [
112+
"aaa",
113+
{
114+
type: "strong",
115+
props: {
116+
children: [
117+
"bbb",
118+
{
119+
type: "em",
120+
props: {
121+
children: "ccc",
122+
},
123+
},
124+
"ddd",
125+
],
126+
},
127+
},
128+
"ddd",
129+
],
130+
},
131+
},
132+
]
133+
134+
aaa code
135+
bbb code + strong
136+
ccc code + strong + em
137+
ddd code + strong
138+
eee code
139+
140+
// [
141+
// {
142+
// types: {
143+
// strong: {},
144+
// },
145+
// props: {
146+
// children: "Hello, "
147+
// },
148+
// },
149+
// {
150+
// types: {
151+
// code: {},
152+
// strong: {},
153+
// },
154+
// props: {
155+
// children: "world",
156+
// },
157+
// },
158+
// {
159+
// types: {
160+
// strong: {},
161+
// },
162+
// props: {
163+
// children: "!",
164+
// },
165+
// },
166+
// ]
167+
//
168+
// [
169+
// {
170+
// type: "strong",
171+
// props: {
172+
// children: [
173+
// "Hello, ",
174+
// {
175+
// type: "code",
176+
// props: {
177+
// children: "world",
178+
// },
179+
// },
180+
// "!",
181+
// ],
182+
// },
183+
// },
184+
// ]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import componentMap from "../componentMap"
2+
import React from "react"
3+
import toArray from "lib/x/toArray"
4+
import toTree from "./toTree"
5+
6+
// Converts tree-shaped children to React elements.
7+
function toReactTreeImpl(tree) {
8+
const renderable = []
9+
for (const each of toArray(tree)) {
10+
if (typeof each === "string") {
11+
renderable.push(each)
12+
continue
13+
}
14+
const { type, props } = each
15+
renderable.push(React.createElement(componentMap[type], {
16+
...props,
17+
key: renderable.length,
18+
}, props.children && toReactTreeImpl(props.children)))
19+
}
20+
if (!renderable.length || (typeof renderable[0] === "string" && !renderable[0])) {
21+
return null
22+
} else if (renderable.length === 1) {
23+
return renderable[0]
24+
}
25+
return renderable
26+
}
27+
28+
// Converts children to React elements.
29+
function toReactTree(children) {
30+
return toReactTreeImpl(toTree(children))
31+
}
32+
33+
export default toReactTree
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import convTypesToArray from "./convTypesToArray"
2+
import JSONEqual from "lib/JSON/JSONEqual"
3+
import toArray from "lib/x/toArray"
4+
5+
// Compares whether props (rendered and non-rendered) are
6+
// deeply equal.
7+
function propsAreEqual(rendered, nonRendered) {
8+
const { children: _, ...props } = rendered // Obscures rendered.children
9+
return JSONEqual(props, nonRendered)
10+
}
11+
12+
// Queries the next parent element and non-nested types.
13+
function queryNext(tree, { types }) {
14+
const typeArr = convTypesToArray(types)
15+
16+
if (!tree.length || !typeArr.length) {
17+
return [tree, typeArr]
18+
}
19+
let lastRef = toArray(tree).slice(-1)[0]
20+
let ref = lastRef
21+
let x = 0
22+
for (; x < typeArr.length; x++) {
23+
const type = typeArr[x]
24+
25+
ref = toArray(ref).slice(-1)[0]
26+
if (typeof ref === "string" || ref.type !== type.type || !propsAreEqual(ref.props, type.props)) {
27+
// No-op
28+
break
29+
}
30+
lastRef = ref
31+
ref = ref.props.children
32+
}
33+
if (lastRef === ref) {
34+
return [tree, typeArr]
35+
}
36+
lastRef.props.children = toArray(lastRef.props.children)
37+
return [lastRef.props.children, typeArr.slice(x)]
38+
}
39+
40+
// Creates a tree-shaped React element.
41+
function createElement(child, types) {
42+
if (!types.length) {
43+
return child.props.children
44+
}
45+
const element = {}
46+
let ref = element
47+
for (const [x, { type, props }] of types.entries()) {
48+
Object.assign(ref, {
49+
type,
50+
props: {
51+
...props,
52+
children: x + 1 < types.length ? {}
53+
: child.props.children,
54+
},
55+
})
56+
ref = ref.props.children
57+
}
58+
return element
59+
}
60+
61+
// Converts children to tree-shaped children.
62+
function toTree(children) {
63+
const tree = []
64+
for (const each of children) {
65+
const [parentElement, types] = queryNext(tree, each)
66+
parentElement.push(createElement(each, types))
67+
}
68+
return tree
69+
}
70+
71+
export default toTree

0 commit comments

Comments
 (0)