ajo 0.0.2 → 0.0.5
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/index.js +49 -37
- package/index.test.js +55 -0
- package/package.json +7 -3
package/index.js
CHANGED
|
@@ -1,52 +1,59 @@
|
|
|
1
|
+
const { isArray } = Array
|
|
2
|
+
const { hasOwn, keys } = Object
|
|
3
|
+
|
|
1
4
|
export const Skip = Symbol()
|
|
2
5
|
export const Fragment = ({ children }) => children
|
|
3
6
|
export const Portal = ({ host, children }) => render(host, children)
|
|
4
7
|
|
|
5
8
|
const Element = Symbol()
|
|
6
9
|
|
|
10
|
+
export const createTextNode = data => ({ [Element]: '#text', data, skip: true })
|
|
11
|
+
|
|
7
12
|
export const createElement = (name, props, ...children) => {
|
|
8
|
-
|
|
9
|
-
children =
|
|
10
|
-
|
|
13
|
+
props = { ...props }
|
|
14
|
+
children = props.children ?? children
|
|
15
|
+
if (children.length > 0) props.children = children.length === 1 ? children[0] : children
|
|
16
|
+
props[Element] = name
|
|
17
|
+
return props
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
function* g(vnode, host) {
|
|
14
|
-
const vnodes =
|
|
15
|
-
let
|
|
21
|
+
const vnodes = isArray(vnode) ? vnode : [vnode]
|
|
22
|
+
let data = ''
|
|
16
23
|
|
|
17
24
|
while (vnodes.length > 0) {
|
|
18
25
|
vnode = vnodes.shift()
|
|
19
26
|
|
|
20
|
-
if (vnode == null || typeof vnode
|
|
21
|
-
if (typeof vnode
|
|
22
|
-
else if (
|
|
27
|
+
if (vnode == null || typeof vnode === 'boolean') continue
|
|
28
|
+
if (typeof vnode === 'string') data += vnode
|
|
29
|
+
else if (hasOwn(vnode, Element)) {
|
|
23
30
|
const { [Element]: name } = vnode
|
|
24
31
|
|
|
25
|
-
if (typeof name
|
|
32
|
+
if (typeof name === 'function') {
|
|
26
33
|
vnodes.unshift(name(vnode, host))
|
|
27
34
|
continue
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
if (
|
|
31
|
-
yield
|
|
32
|
-
|
|
37
|
+
if (data) {
|
|
38
|
+
yield createTextNode(data)
|
|
39
|
+
data = ''
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
yield vnode
|
|
36
|
-
} else typeof vnode[Symbol.iterator]
|
|
43
|
+
} else typeof vnode[Symbol.iterator] === 'function'
|
|
37
44
|
? vnodes.unshift(...vnode)
|
|
38
|
-
:
|
|
45
|
+
: data += vnode
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
if (
|
|
48
|
+
if (data) yield createTextNode(data)
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export const render = (vnode, host) => {
|
|
45
52
|
let child = host.firstChild
|
|
46
53
|
|
|
47
|
-
for (const { [Element]: name, ref, children, ...props } of g(vnode, host)) {
|
|
54
|
+
for (const { [Element]: name, ref, skip, children, ...props } of g(vnode, host)) {
|
|
48
55
|
|
|
49
|
-
if (name
|
|
56
|
+
if (name === Skip) {
|
|
50
57
|
child = props.end ? null : child?.nextSibling ?? null
|
|
51
58
|
continue
|
|
52
59
|
}
|
|
@@ -54,26 +61,27 @@ export const render = (vnode, host) => {
|
|
|
54
61
|
let node = child
|
|
55
62
|
|
|
56
63
|
while (node != null) {
|
|
57
|
-
if (
|
|
64
|
+
if (node.nodeName.toLowerCase() === name && node.getAttribute?.('key') == props.key) break
|
|
58
65
|
node = node.nextSibling
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
if (node == null) node = name
|
|
68
|
+
if (node == null) node = name === '#text'
|
|
62
69
|
? document.createTextNode('')
|
|
63
70
|
: document.createElement(name)
|
|
64
71
|
|
|
65
72
|
patch(props, node)
|
|
66
73
|
|
|
67
|
-
if (typeof ref
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
if (typeof ref === 'function') ref(node)
|
|
75
|
+
else if (typeof ref === 'object' && ref !== null) ref.current = node
|
|
76
|
+
|
|
77
|
+
skip || render(children, node)
|
|
70
78
|
|
|
71
|
-
if (node
|
|
79
|
+
if (node === child) child = child.nextSibling
|
|
72
80
|
else if (node.contains?.(document.activeElement)) {
|
|
73
81
|
const { nextSibling } = node
|
|
74
82
|
let ref = child
|
|
75
83
|
|
|
76
|
-
while (ref != null && ref
|
|
84
|
+
while (ref != null && ref !== node) {
|
|
77
85
|
const next = ref.nextSibling
|
|
78
86
|
host.insertBefore(ref, nextSibling)
|
|
79
87
|
ref = next
|
|
@@ -82,18 +90,23 @@ export const render = (vnode, host) => {
|
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
while (child != null) {
|
|
85
|
-
const {
|
|
86
|
-
if (nodeType
|
|
93
|
+
const { nextSibling } = child
|
|
94
|
+
if (child.nodeType === Node.ELEMENT_NODE) dispose(child)
|
|
87
95
|
host.removeChild(child)
|
|
88
96
|
child = nextSibling
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
const patch = (props, node) => {
|
|
93
|
-
|
|
101
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
102
|
+
if (node.data !== props.data) node.data = props.data
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const name of new Set([...node.getAttributeNames(), ...keys(props)])) {
|
|
94
107
|
let value = props[name]
|
|
95
108
|
|
|
96
|
-
if (name in node && !(typeof value
|
|
109
|
+
if (name in node && !(typeof value === 'string' && typeof node[name] === 'boolean')) {
|
|
97
110
|
try {
|
|
98
111
|
if (node[name] !== value) node[name] = value
|
|
99
112
|
continue
|
|
@@ -110,16 +123,15 @@ const patch = (props, node) => {
|
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
125
|
|
|
113
|
-
const components = new WeakMap
|
|
126
|
+
const components = new WeakMap
|
|
114
127
|
|
|
115
|
-
export const createComponent = fn => ({ is, host, key,
|
|
128
|
+
export const createComponent = fn => ({ is, host, key, ...props }) =>
|
|
116
129
|
createElement(is ?? fn.is ?? 'div', {
|
|
117
|
-
...fn.host, ...host, key,
|
|
130
|
+
...fn.host, ...host, key, skip: true, ref: host => {
|
|
118
131
|
let cmp = components.get(host)
|
|
119
132
|
if (cmp == null) components.set(host, cmp = new Component(host, fn))
|
|
120
133
|
cmp.props = { ...fn.props, ...props }
|
|
121
134
|
cmp.update()
|
|
122
|
-
if (typeof ref == 'function') ref(host)
|
|
123
135
|
}
|
|
124
136
|
})
|
|
125
137
|
|
|
@@ -139,7 +151,7 @@ class Component {
|
|
|
139
151
|
try {
|
|
140
152
|
if (this.it == null) {
|
|
141
153
|
const init = this.fn.call(this, this.props, this.host)
|
|
142
|
-
this.it = typeof init?.next
|
|
154
|
+
this.it = typeof init?.next === 'function' ? init : generate.call(this, init)
|
|
143
155
|
}
|
|
144
156
|
|
|
145
157
|
const { value, done } = this.it[method](arg)
|
|
@@ -153,19 +165,19 @@ class Component {
|
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
*[Symbol.iterator]() {
|
|
156
|
-
while (
|
|
168
|
+
while (this.host) yield this.props
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
|
|
160
|
-
function* generate(init
|
|
172
|
+
function* generate(init) {
|
|
161
173
|
yield init
|
|
162
|
-
for (const props of
|
|
174
|
+
for (const props of this) yield this.fn.call(this, props, this.host)
|
|
163
175
|
}
|
|
164
176
|
|
|
165
177
|
const propagate = (el, err) => {
|
|
166
178
|
if (el == null) throw err
|
|
167
179
|
const cmp = components.get(el)
|
|
168
|
-
typeof cmp?.it?.throw
|
|
180
|
+
typeof cmp?.it?.throw === 'function'
|
|
169
181
|
? cmp.update({ method: 'throw', arg: err })
|
|
170
182
|
: propagate(el.parentNode, err)
|
|
171
183
|
}
|
package/index.test.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import 'backdom/register.js'
|
|
2
|
+
import { suite } from 'uvu'
|
|
3
|
+
import * as assert from 'uvu/assert'
|
|
4
|
+
import { createComponent, createElement, render, } from './index.js'
|
|
5
|
+
|
|
6
|
+
let it
|
|
7
|
+
|
|
8
|
+
// ----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
it = suite('createElement')
|
|
11
|
+
|
|
12
|
+
it('should create empty vnode', () => {
|
|
13
|
+
assert.equal(createElement('div'), {})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should create vnode with props', () => {
|
|
17
|
+
assert.equal(createElement('div', { id: 'app' }), { id: 'app' })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should create vnode with one string child', () => {
|
|
21
|
+
assert.equal(createElement('div', null, 'foo'), { children: 'foo' })
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should create vnode with one vnode child', () => {
|
|
25
|
+
assert.equal(createElement('div', null, createElement('span')), { children: {} })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it.run()
|
|
29
|
+
|
|
30
|
+
// ----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
it = suite('render')
|
|
33
|
+
|
|
34
|
+
it('should render a vnode', () => {
|
|
35
|
+
const host = document.createElement('div')
|
|
36
|
+
render(createElement('div', { foo: 'bar' }, 'foobar'), host)
|
|
37
|
+
assert.snapshot(host.innerHTML, '<div foo="bar">foobar</div>')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should render a stateless component', () => {
|
|
41
|
+
const host = document.createElement('div')
|
|
42
|
+
const Foo = ({ foo, children }) => createElement('div', { foo }, children)
|
|
43
|
+
debugger
|
|
44
|
+
render(createElement(Foo, { foo: 'bar' }, createElement('span', null, 'foobar')), host)
|
|
45
|
+
assert.snapshot(host.innerHTML, '<div foo="bar"><span>foobar</span></div>')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should render a stateful component', () => {
|
|
49
|
+
const host = document.createElement('div')
|
|
50
|
+
const Foo = createComponent(({ foo, children }) => createElement('div', { foo }, children))
|
|
51
|
+
render(createElement(Foo, { is: 'foo-component', foo: 'bar' }, createElement('span', null, 'foobar')), host)
|
|
52
|
+
assert.snapshot(host.innerHTML, '<foo-component><div foo="bar"><span>foobar</span></div></foo-component>')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it.run()
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "ajo is a JavaScript view library for building user interfaces",
|
|
5
|
-
"main": "index.js",
|
|
6
5
|
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/cristianfalcone/ajo.git"
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
"url": "https://github.com/cristianfalcone/ajo/issues"
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://github.com/cristianfalcone/ajo#readme",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"backdom": "^0.0.2",
|
|
19
|
+
"uvu": "^0.5.3"
|
|
20
|
+
},
|
|
17
21
|
"scripts": {
|
|
18
|
-
"test": "
|
|
22
|
+
"test": "uvu"
|
|
19
23
|
},
|
|
20
24
|
"readme": "# ajo\najo is a JavaScript view library for building user interfaces"
|
|
21
25
|
}
|