veles 0.0.1 → 0.0.3
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/README.md +130 -151
- package/dist/{chunk-EVX3ZDYT.js → chunk-V3EV7UG6.js} +27 -16
- package/dist/index.cjs +53 -41
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +29 -27
- package/dist/{jsx-runtime-vDysOz5d.d.cts → jsx-runtime-C2XLvZ55.d.cts} +9 -2
- package/dist/{jsx-runtime-vDysOz5d.d.ts → jsx-runtime-C2XLvZ55.d.ts} +9 -2
- package/dist/jsx-runtime.cjs +18 -18
- package/dist/jsx-runtime.d.cts +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +4 -3
- package/package.json +3 -1
- package/dist/chunk-EVX3ZDYT.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/jsx-runtime.cjs.map +0 -1
- package/dist/jsx-runtime.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Veles
|
|
2
2
|
|
|
3
3
|

|
|
4
|
+
[](https://bundlephobia.com/result?p=veles)
|
|
5
|
+
[](https://www.npmjs.com/package/veles)
|
|
4
6
|
|
|
5
|
-
> The library is in very early stages and
|
|
7
|
+
> The library is in very early stages and some features, like proper TypeScript support, are not fully implemented yet
|
|
6
8
|
|
|
7
9
|
`Veles` is a component-based performance-focused UI library. The main goal of this library is to provide a composable way to build highly interactive interfaces, which should be performant out of the box, as long as you follow the recommendations.
|
|
8
10
|
|
|
@@ -22,30 +24,26 @@ Attach Veles tree to a regular DOM Node.
|
|
|
22
24
|
> As of right now, this method will wrap the component's HTML into one additional `div`. This will probably go away in the future, but for now it simplifies some things significantly.
|
|
23
25
|
|
|
24
26
|
```js
|
|
25
|
-
import {
|
|
27
|
+
import { attachComponent } from "veles";
|
|
28
|
+
|
|
29
|
+
const App () => <div>App</div>
|
|
26
30
|
|
|
27
31
|
const appContainer = document.getElementById("app");
|
|
28
|
-
attachComponent({ htmlElement: appContainer, component:
|
|
32
|
+
attachComponent({ htmlElement: appContainer, component: <App /> });
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
###
|
|
32
|
-
|
|
33
|
-
Create Veles tree. Accepts strings for regular valid HTML elements (like `div`, `span`, etc) and functions which are expected to return another Veles tree from `createElement`.
|
|
35
|
+
### JSX support
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
> JSX should be almost fully supported as long as you specify `Veles.createElement` pragma
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
import { createElement } from "veles";
|
|
37
|
+
Veles supports JSX transformation, so as long as you specify `importSource: "veles"` (this is for Babel, the other JS transpilers should have similar options) it will work as expected.
|
|
40
38
|
|
|
39
|
+
```jsx
|
|
41
40
|
function App() {
|
|
42
|
-
return
|
|
43
|
-
class
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
41
|
+
return (
|
|
42
|
+
<div class="app-container">
|
|
43
|
+
<h1>Veles App</h1>
|
|
44
|
+
<p>Random description</p>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
49
47
|
}
|
|
50
48
|
```
|
|
51
49
|
|
|
@@ -57,29 +55,27 @@ function App() {
|
|
|
57
55
|
|
|
58
56
|
The simplest way to react to state changes in the UI is to use `useValue` method from the state object. Let's build a simple counter to demonstrate:
|
|
59
57
|
|
|
60
|
-
```
|
|
61
|
-
import {
|
|
58
|
+
```jsx
|
|
59
|
+
import { createState } from "veles";
|
|
62
60
|
|
|
63
61
|
function Counter() {
|
|
64
62
|
const counterState = createState(0);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
const increment = () =>
|
|
64
|
+
counterState.setValue((currentValue) => currentValue + 1);
|
|
65
|
+
const decrement = () =>
|
|
66
|
+
counterState.setValue((currentValue) => currentValue - 1);
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<h1>Counter</h1>
|
|
70
|
+
<div>
|
|
71
|
+
{counterState.useValue(
|
|
70
72
|
(counterValue) => `counter value is: ${counterValue}`
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
78
|
-
},
|
|
79
|
-
children: "+",
|
|
80
|
-
}),
|
|
81
|
-
],
|
|
82
|
-
});
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
<button onClick={increment}>+</button>
|
|
76
|
+
<button onClick={decrement}>-</button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
83
79
|
}
|
|
84
80
|
```
|
|
85
81
|
|
|
@@ -87,8 +83,8 @@ function Counter() {
|
|
|
87
83
|
|
|
88
84
|
If you have an object in your store, even atomic updates will be wasteful. Let's say you have an object with several fields, but you are only interested in the `title` property. If you use `useValue`, it will do unnecessary work. To help with that, there is a `useValueSelector` state method, which accepts a selector function as the first parameter. Here is an example:
|
|
89
85
|
|
|
90
|
-
```
|
|
91
|
-
import {
|
|
86
|
+
```jsx
|
|
87
|
+
import { createState } from "veles";
|
|
92
88
|
|
|
93
89
|
function App() {
|
|
94
90
|
const taskState = createState({
|
|
@@ -96,17 +92,17 @@ function App() {
|
|
|
96
92
|
title: "title",
|
|
97
93
|
description: "long description",
|
|
98
94
|
});
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
return (
|
|
96
|
+
<div>
|
|
97
|
+
<h1>App</h1>
|
|
98
|
+
<div>
|
|
99
|
+
{taskState.useSelectorValue(
|
|
104
100
|
(task) => task.title,
|
|
105
101
|
(title) => `task title: ${title}`
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
110
106
|
}
|
|
111
107
|
```
|
|
112
108
|
|
|
@@ -121,8 +117,8 @@ Lists performance is one of the cornerstones of this library, and to help with t
|
|
|
121
117
|
|
|
122
118
|
Let's build a simple list component:
|
|
123
119
|
|
|
124
|
-
```
|
|
125
|
-
import { createState
|
|
120
|
+
```jsx
|
|
121
|
+
import { createState } from "veles";
|
|
126
122
|
|
|
127
123
|
function List() {
|
|
128
124
|
const listState = createState([
|
|
@@ -131,23 +127,18 @@ function List() {
|
|
|
131
127
|
{ id: 3, name: "third task" },
|
|
132
128
|
]);
|
|
133
129
|
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
listState.useValueIterator<{ id: number; name: string }>(
|
|
130
|
+
return <div>
|
|
131
|
+
<h1>List</h1>
|
|
132
|
+
{listState.useValueIterator<{ id: number; name: string }>(
|
|
138
133
|
{ key: "id" },
|
|
139
|
-
({ elementState }) =>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
),
|
|
149
|
-
],
|
|
150
|
-
});
|
|
134
|
+
({ elementState }) => <div>
|
|
135
|
+
{elementState.useValueSelector(
|
|
136
|
+
(element) => element.name,
|
|
137
|
+
(name) => <div>{name}</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
151
142
|
}
|
|
152
143
|
```
|
|
153
144
|
|
|
@@ -155,41 +146,38 @@ function List() {
|
|
|
155
146
|
|
|
156
147
|
In order to avoid unnecessary re-renders when you to change properties on a wrapper element/component, there is a special state method `useAttribute`. It allows to recalculate value for an attribute every time the state changes, but instead of re-rendering, it calls the `htmlNode.setAttribute` method. Here is an example:
|
|
157
148
|
|
|
158
|
-
```
|
|
159
|
-
import {
|
|
149
|
+
```jsx
|
|
150
|
+
import { createState } from "veles";
|
|
160
151
|
|
|
161
152
|
function Counter() {
|
|
162
153
|
const counterState = createState(0);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
onClick
|
|
171
|
-
|
|
172
|
-
(currentCounterValue) => currentCounterValue + 1
|
|
173
|
-
);
|
|
174
|
-
},
|
|
175
|
-
style: counterState.useAttribute(
|
|
154
|
+
const increment = () =>
|
|
155
|
+
counterState.setValue((currentValue) => currentValue + 1);
|
|
156
|
+
return (
|
|
157
|
+
<div>
|
|
158
|
+
<h1>Counter</h1>
|
|
159
|
+
<div>{counterState.useValue((value) => `counter value is ${value}`)}</div>
|
|
160
|
+
<button
|
|
161
|
+
onClick={increment}
|
|
162
|
+
style={counterState.useAttribute(
|
|
176
163
|
(currentValue) => `width: ${50 + currentValue}px;`
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
+
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
182
170
|
}
|
|
183
171
|
```
|
|
184
172
|
|
|
185
|
-
You can see that we dynamically change the width of the button with every press.
|
|
173
|
+
You can see that we dynamically change the width of the button with every press. This will be the only change, the rest of the application will not be re-rendered or any code will not be executed. You still need to be mindful what are you changing, as with some CSS changes you can force reflows/repaints and that can still cause some performance issues.
|
|
186
174
|
|
|
187
175
|
### createState and subscribing to updates
|
|
188
176
|
|
|
189
177
|
What if you don't want to render anything when the value changes, but you want to call your code? The state provides `trackValue` method, which does exactly that. Here is an example:
|
|
190
178
|
|
|
191
|
-
```
|
|
192
|
-
import {
|
|
179
|
+
```jsx
|
|
180
|
+
import { createState } from "veles";
|
|
193
181
|
|
|
194
182
|
function Counter() {
|
|
195
183
|
const counterState = createState(0);
|
|
@@ -197,23 +185,23 @@ function Counter() {
|
|
|
197
185
|
counterState.trackValue((counterValue) => {
|
|
198
186
|
console.log(`new counter value is ${counterValue}`);
|
|
199
187
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
188
|
+
const increment = () =>
|
|
189
|
+
counterState.setValue((currentValue) => currentValue + 1);
|
|
190
|
+
const decrement = () =>
|
|
191
|
+
counterState.setValue((currentValue) => currentValue - 1);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div>
|
|
195
|
+
<h1>Counter</h1>
|
|
196
|
+
<div>
|
|
197
|
+
{counterState.useValue(
|
|
198
|
+
(counterValue) => `counter value is: ${counterValue}`
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
<button onClick={increment}>+</button>
|
|
202
|
+
<button onClick={decrement}>-</button>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
217
205
|
}
|
|
218
206
|
```
|
|
219
207
|
|
|
@@ -227,8 +215,8 @@ In case you don't want to subscribe to the whole state, you have 2 options. You
|
|
|
227
215
|
|
|
228
216
|
Since `createState` is the only way to add dynamic behaviour to the application, sooner or later you'll need to build UI which depends on several states. To do so, you can use `combineState` function which accepts any amount of state objects, and returns an array with all combined values in it.
|
|
229
217
|
|
|
230
|
-
```
|
|
231
|
-
import {
|
|
218
|
+
```jsx
|
|
219
|
+
import { createState } from "veles";
|
|
232
220
|
|
|
233
221
|
function Counter() {
|
|
234
222
|
const firstcounterState = createState(0);
|
|
@@ -237,44 +225,37 @@ function Counter() {
|
|
|
237
225
|
firstcounterState,
|
|
238
226
|
secondCounterState
|
|
239
227
|
);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
(currentCounterValue) => currentCounterValue + 1
|
|
272
|
-
);
|
|
273
|
-
},
|
|
274
|
-
children: "add to the second counter",
|
|
275
|
-
}),
|
|
276
|
-
],
|
|
277
|
-
});
|
|
228
|
+
const incrementFirstCounter = () =>
|
|
229
|
+
firstcounterState.setValue(
|
|
230
|
+
(currentCounterValue) => currentCounterValue + 1
|
|
231
|
+
);
|
|
232
|
+
const incrementSecondCounter = () =>
|
|
233
|
+
secondCounterState.setValue(
|
|
234
|
+
(currentCounterValue) => currentCounterValue + 1
|
|
235
|
+
);
|
|
236
|
+
return (
|
|
237
|
+
<div>
|
|
238
|
+
<h1>Counters</h1>
|
|
239
|
+
<div>
|
|
240
|
+
{firstcounterState.useValue(
|
|
241
|
+
(value) => `first counter value is: ${value}`
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
<div>
|
|
245
|
+
{secondCounterState.useValue(
|
|
246
|
+
(value) => `second counter value is: ${value}`
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
<div>
|
|
250
|
+
{combinedCounterState.useValueSelector(
|
|
251
|
+
([firstValue, secondValue]) => firstValue + secondValue,
|
|
252
|
+
(counterValue) => `combined counter value is: ${counterValue}`
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
<button onClick={incrementFirstCounter}>Increment first counter</button>
|
|
256
|
+
<button onClick={incrementSecondCounter}>Increment second counter</button>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
278
259
|
}
|
|
279
260
|
```
|
|
280
261
|
|
|
@@ -285,8 +266,8 @@ Right now there are `onMount` and `onUnmount` lifecycle hooks. In your component
|
|
|
285
266
|
> [!NOTE]
|
|
286
267
|
> You can only use them during the original initialization of the component. If you want to add some callbacks later, use the same version passed as a second argument to your component
|
|
287
268
|
|
|
288
|
-
```
|
|
289
|
-
import {
|
|
269
|
+
```jsx
|
|
270
|
+
import { onMount, onUnmount } from "veles";
|
|
290
271
|
|
|
291
272
|
function App(_props, componentAPI) {
|
|
292
273
|
// could be used as `componentAPI.onMount()`
|
|
@@ -297,8 +278,6 @@ function App(_props, componentAPI) {
|
|
|
297
278
|
onUnmount(() => {
|
|
298
279
|
console.log("called when the component unmounts");
|
|
299
280
|
});
|
|
300
|
-
return
|
|
301
|
-
children: "Application",
|
|
302
|
-
});
|
|
281
|
+
return <div>Application<div>
|
|
303
282
|
}
|
|
304
283
|
```
|
|
@@ -47,6 +47,20 @@ function getComponentVelesNode(component) {
|
|
|
47
47
|
}
|
|
48
48
|
return { velesElementNode: childNode, componentsTree };
|
|
49
49
|
}
|
|
50
|
+
function callMountHandlers(component) {
|
|
51
|
+
if ("velesStringElement" in component) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if ("velesComponent" in component) {
|
|
55
|
+
component._privateMethods._callMountHandlers();
|
|
56
|
+
callMountHandlers(component.tree);
|
|
57
|
+
}
|
|
58
|
+
if ("velesNode" in component) {
|
|
59
|
+
component.childComponents.forEach(
|
|
60
|
+
(childComponent) => callMountHandlers(childComponent)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
50
64
|
function identity(value1, value2) {
|
|
51
65
|
return value1 === value2;
|
|
52
66
|
}
|
|
@@ -66,6 +80,9 @@ function parseChildren({
|
|
|
66
80
|
if (typeof childComponent === "string") {
|
|
67
81
|
const text = document.createTextNode(childComponent);
|
|
68
82
|
htmlElement.append(text);
|
|
83
|
+
} else if (typeof childComponent === "number") {
|
|
84
|
+
const text = document.createTextNode(String(childComponent));
|
|
85
|
+
htmlElement.append(text);
|
|
69
86
|
} else if (typeof childComponent === "object" && childComponent && "velesNode" in childComponent && (childComponent == null ? void 0 : childComponent.velesNode)) {
|
|
70
87
|
if (childComponent.phantom) {
|
|
71
88
|
childComponent.childComponents.forEach((childComponentofPhantom) => {
|
|
@@ -73,14 +90,13 @@ function parseChildren({
|
|
|
73
90
|
htmlElement.append(childComponentofPhantom.html);
|
|
74
91
|
childComponentofPhantom.parentVelesElement = velesNode;
|
|
75
92
|
} else {
|
|
76
|
-
const {
|
|
93
|
+
const { velesElementNode } = getComponentVelesNode(
|
|
94
|
+
childComponentofPhantom
|
|
95
|
+
);
|
|
77
96
|
if (!velesElementNode) {
|
|
78
97
|
console.error("can't find HTML tree in a component chain");
|
|
79
98
|
} else {
|
|
80
99
|
htmlElement.append(velesElementNode.html);
|
|
81
|
-
componentsTree.forEach((component) => {
|
|
82
|
-
component._privateMethods._callMountHandlers();
|
|
83
|
-
});
|
|
84
100
|
velesElementNode.parentVelesElement = velesNode;
|
|
85
101
|
}
|
|
86
102
|
}
|
|
@@ -109,11 +125,6 @@ function parseChildren({
|
|
|
109
125
|
console.error("can't find HTML tree in a component chain");
|
|
110
126
|
} else {
|
|
111
127
|
htmlElement.append(velesElementNode2.html);
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
componentsTree2.forEach((component) => {
|
|
114
|
-
component._privateMethods._callMountHandlers();
|
|
115
|
-
});
|
|
116
|
-
}, 0);
|
|
117
128
|
velesElementNode2.parentVelesElement = velesNode;
|
|
118
129
|
}
|
|
119
130
|
}
|
|
@@ -122,11 +133,6 @@ function parseChildren({
|
|
|
122
133
|
} else {
|
|
123
134
|
htmlElement.append(velesElementNode.html);
|
|
124
135
|
}
|
|
125
|
-
setTimeout(() => {
|
|
126
|
-
componentsTree.forEach((component) => {
|
|
127
|
-
component._privateMethods._callMountHandlers();
|
|
128
|
-
});
|
|
129
|
-
}, 0);
|
|
130
136
|
velesElementNode.parentVelesElement = velesNode;
|
|
131
137
|
childComponents.push(childComponent);
|
|
132
138
|
}
|
|
@@ -197,7 +203,12 @@ function parseComponent({
|
|
|
197
203
|
componentAPI.onUnmount(cb);
|
|
198
204
|
},
|
|
199
205
|
_callMountHandlers: () => {
|
|
200
|
-
componentMountCbs.forEach((cb) =>
|
|
206
|
+
componentMountCbs.forEach((cb) => {
|
|
207
|
+
const mountCbResult = cb();
|
|
208
|
+
if (typeof mountCbResult === "function") {
|
|
209
|
+
componentAPI.onUnmount(mountCbResult);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
201
212
|
},
|
|
202
213
|
_callUnmountHandlers: () => {
|
|
203
214
|
componentUnmountCbs.forEach((cb) => cb());
|
|
@@ -262,10 +273,10 @@ function Fragment({ children }) {
|
|
|
262
273
|
|
|
263
274
|
export {
|
|
264
275
|
getComponentVelesNode,
|
|
276
|
+
callMountHandlers,
|
|
265
277
|
identity,
|
|
266
278
|
onMount,
|
|
267
279
|
onUnmount,
|
|
268
280
|
createElement,
|
|
269
281
|
Fragment
|
|
270
282
|
};
|
|
271
|
-
//# sourceMappingURL=chunk-EVX3ZDYT.js.map
|