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.
Files changed (3) hide show
  1. package/index.js +49 -37
  2. package/index.test.js +55 -0
  3. 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
- const { length } = children
9
- children = length == 0 ? null : length == 1 ? children[0] : children
10
- return { children, ...props, [Element]: name }
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 = Array.isArray(vnode) ? vnode : [vnode]
15
- let textContent = ''
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 == 'boolean') continue
21
- if (typeof vnode == 'string') textContent += vnode
22
- else if (Object.hasOwn(vnode, Element)) {
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 == 'function') {
32
+ if (typeof name === 'function') {
26
33
  vnodes.unshift(name(vnode, host))
27
34
  continue
28
35
  }
29
36
 
30
- if (textContent) {
31
- yield { [Element]: '#text', textContent }
32
- textContent = ''
37
+ if (data) {
38
+ yield createTextNode(data)
39
+ data = ''
33
40
  }
34
41
 
35
42
  yield vnode
36
- } else typeof vnode[Symbol.iterator] == 'function'
43
+ } else typeof vnode[Symbol.iterator] === 'function'
37
44
  ? vnodes.unshift(...vnode)
38
- : textContent += vnode
45
+ : data += vnode
39
46
  }
40
47
 
41
- if (textContent) yield { [Element]: '#text', textContent }
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 == Skip) {
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 (String(node.nodeName).toLowerCase() === name && node.getAttribute?.('key') == props.key) break
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 == '#text'
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 == 'function') ref(node)
68
-
69
- if (children != null) render(children, node)
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 == child) child = child.nextSibling
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 != node) {
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 { nodeType, nextSibling } = child
86
- if (nodeType == 1) dispose(child)
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
- for (const name of new Set([...(node.getAttributeNames?.() ?? []), ...Object.keys(props)])) {
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 == 'string' && typeof node[name] == 'boolean')) {
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, ref, ...props }) =>
128
+ export const createComponent = fn => ({ is, host, key, ...props }) =>
116
129
  createElement(is ?? fn.is ?? 'div', {
117
- ...fn.host, ...host, key, ['ref']: host => {
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 == 'function' ? init : generate(init, this)
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 (true) yield this.props
168
+ while (this.host) yield this.props
157
169
  }
158
170
  }
159
171
 
160
- function* generate(init, ctx) {
172
+ function* generate(init) {
161
173
  yield init
162
- for (const props of ctx) yield ctx.fn.call(ctx, props, ctx.host)
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 == 'function'
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.2",
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": "echo \"Error: no test specified\" && exit 1"
22
+ "test": "uvu"
19
23
  },
20
24
  "readme": "# ajo\najo is a JavaScript view library for building user interfaces"
21
25
  }