subay 0.0.5 β 0.0.7
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 +302 -28
- package/README.zh.md +377 -0
- package/lib/h.d.ts.map +1 -1
- package/lib/h.js +32 -25
- package/lib/h.js.map +1 -1
- package/lib/index.js +2 -3
- package/lib/index.js.map +1 -1
- package/lib/s.d.ts.map +1 -1
- package/lib/s.js +2 -66
- package/lib/s.js.map +1 -1
- package/package.json +8 -3
- package/src/h.ts +569 -0
- package/src/index.ts +4 -0
- package/src/s.ts +193 -0
package/README.md
CHANGED
|
@@ -1,23 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# π£ Subay
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A < 700 LOC declarative, reactive UI library.
|
|
4
4
|
|
|
5
5
|
## β¨ Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
7
|
+
- **Lightweight**: Less than 700 lines of code, small size, fast loading
|
|
8
|
+
- **Declarative Rendering**: Use template literals for declarative UI construction
|
|
9
|
+
- **Reactive State**: Simple yet powerful reactive state management
|
|
10
|
+
- **Efficient Updates**: Optimized DOM updates with minimal DOM operations
|
|
11
|
+
- **TypeScript Support**: Complete TypeScript type definitions
|
|
12
|
+
- **Zero Dependencies**: No external library dependencies
|
|
11
13
|
|
|
12
14
|
## π¦ Installation
|
|
13
15
|
|
|
16
|
+
### NPM
|
|
17
|
+
|
|
14
18
|
```bash
|
|
15
19
|
npm install subay
|
|
16
20
|
```
|
|
17
21
|
|
|
22
|
+
### Yarn
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
yarn add subay
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Direct Import
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<script type="module">
|
|
32
|
+
import { html, o, S } from 'https://cdn.jsdelivr.net/npm/subay@latest/lib/index.js';
|
|
33
|
+
// Use Subay
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
|
|
18
37
|
## π§ Basic Usage
|
|
19
38
|
|
|
20
|
-
### π
|
|
39
|
+
### π State Management
|
|
21
40
|
|
|
22
41
|
```javascript
|
|
23
42
|
import { o, S } from 'subay';
|
|
@@ -28,12 +47,17 @@ const count = o(0);
|
|
|
28
47
|
// Create a computed property
|
|
29
48
|
const doubled = S(() => count() * 2);
|
|
30
49
|
|
|
31
|
-
//
|
|
50
|
+
// Read value
|
|
51
|
+
console.log(count()); // Output: 0
|
|
52
|
+
console.log(doubled()); // Output: 0
|
|
53
|
+
|
|
54
|
+
// Update value
|
|
32
55
|
count(5);
|
|
56
|
+
console.log(count()); // Output: 5
|
|
33
57
|
console.log(doubled()); // Output: 10
|
|
34
58
|
```
|
|
35
59
|
|
|
36
|
-
### π¨
|
|
60
|
+
### π¨ Rendering
|
|
37
61
|
|
|
38
62
|
```javascript
|
|
39
63
|
import { html, o } from 'subay';
|
|
@@ -44,16 +68,16 @@ const count = o(0);
|
|
|
44
68
|
document.body.append(
|
|
45
69
|
...html`
|
|
46
70
|
<div>
|
|
47
|
-
<h1>Hello ${name}!</h1>
|
|
71
|
+
<h1>Hello, ${name}!</h1>
|
|
48
72
|
<p>Count: ${count}</p>
|
|
49
73
|
<button onclick="${() => count(count() + 1)}">Increment</button>
|
|
50
|
-
<button onclick="${() => name('
|
|
74
|
+
<button onclick="${() => name('Subay')}">Change Name</button>
|
|
51
75
|
</div>
|
|
52
76
|
`,
|
|
53
77
|
);
|
|
54
78
|
```
|
|
55
79
|
|
|
56
|
-
### π List
|
|
80
|
+
### π List
|
|
57
81
|
|
|
58
82
|
```javascript
|
|
59
83
|
import { html, o, map } from 'subay';
|
|
@@ -73,30 +97,280 @@ document.body.append(
|
|
|
73
97
|
);
|
|
74
98
|
```
|
|
75
99
|
|
|
76
|
-
## π
|
|
100
|
+
## π API
|
|
101
|
+
|
|
102
|
+
### βοΈ State
|
|
103
|
+
|
|
104
|
+
#### `o(value)`
|
|
105
|
+
|
|
106
|
+
Create an observable value.
|
|
107
|
+
|
|
108
|
+
- **Parameters**: `value` - Initial value
|
|
109
|
+
- **Return**: A function that returns the current value when called, and updates the value when passed a parameter
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const count = o(0);
|
|
113
|
+
count(); // Read value
|
|
114
|
+
count(5); // Update value
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `S(fn, initialValue?)`
|
|
118
|
+
|
|
119
|
+
Create a computed property.
|
|
120
|
+
|
|
121
|
+
- **Parameters**:
|
|
122
|
+
- `fn` - Computation function, receives the previous value as a parameter
|
|
123
|
+
- `initialValue` - Optional initial value
|
|
124
|
+
- **Return**: A function that returns the computation result when called
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const doubled = S(() => count() * 2);
|
|
128
|
+
doubled(); // Read computation result
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### `root(fn)`
|
|
132
|
+
|
|
133
|
+
Create a root context for managing the lifecycle of computed properties.
|
|
134
|
+
|
|
135
|
+
- **Parameters**: `fn` - Function that receives a destroy function as a parameter
|
|
136
|
+
- **Return**: The result of the function execution
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
root((destroy) => {
|
|
140
|
+
const count = o(0);
|
|
141
|
+
const doubled = S(() => count() * 2);
|
|
142
|
+
|
|
143
|
+
// Use count and doubled
|
|
144
|
+
|
|
145
|
+
// Optional: Call destroy() to clean up resources when appropriate
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `cleanup(fn)`
|
|
150
|
+
|
|
151
|
+
Register a cleanup function that executes when the computed property is recomputed or destroyed.
|
|
152
|
+
|
|
153
|
+
- **Parameters**: `fn` - Cleanup function
|
|
154
|
+
- **Return**: The passed cleanup function
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
S(() => {
|
|
158
|
+
const timer = setInterval(() => {
|
|
159
|
+
console.log('tick');
|
|
160
|
+
}, 1000);
|
|
77
161
|
|
|
78
|
-
|
|
162
|
+
cleanup(() => clearInterval(timer));
|
|
79
163
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
164
|
+
return count();
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### `transaction(fn)`
|
|
169
|
+
|
|
170
|
+
Batch execute multiple updates to reduce unnecessary computations and rendering.
|
|
171
|
+
|
|
172
|
+
- **Parameters**: `fn` - Function containing update operations
|
|
173
|
+
- **Return**: The result of the function execution
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
transaction(() => {
|
|
177
|
+
count(1);
|
|
178
|
+
name('Subay');
|
|
179
|
+
items(['a', 'b', 'c']);
|
|
180
|
+
});
|
|
181
|
+
```
|
|
86
182
|
|
|
87
183
|
### πΌοΈ Rendering
|
|
88
184
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
185
|
+
#### `html`
|
|
186
|
+
|
|
187
|
+
Template literal tag for creating HTML elements.
|
|
188
|
+
|
|
189
|
+
- **Usage**: `html\`...\``
|
|
190
|
+
- **Return**: An array of DOM nodes
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
const element = html`
|
|
194
|
+
<div class="container">
|
|
195
|
+
<h1>Hello ${name}!</h1>
|
|
196
|
+
</div>
|
|
197
|
+
`;
|
|
198
|
+
document.body.append(...element);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `svg`
|
|
202
|
+
|
|
203
|
+
Template literal tag for creating SVG elements.
|
|
204
|
+
|
|
205
|
+
- **Usage**: `svg\`...\``
|
|
206
|
+
- **Return**: An array of DOM nodes
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
const icon = svg /*html*/ `
|
|
210
|
+
<svg width="24" height="24" viewBox="0 0 24 24">
|
|
211
|
+
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
212
|
+
</svg>
|
|
213
|
+
`;
|
|
214
|
+
document.body.append(...icon);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `map(arrayFn, itemFn)`
|
|
218
|
+
|
|
219
|
+
Function for rendering lists.
|
|
220
|
+
|
|
221
|
+
- **Parameters**:
|
|
222
|
+
- `arrayFn` - Function that returns an array
|
|
223
|
+
- `itemFn` - Function that creates DOM nodes for each array item
|
|
224
|
+
- **Return**: An array of DOM nodes
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
const list = map(
|
|
228
|
+
() => items(),
|
|
229
|
+
(item, index) => html` <li key="${index}">${item}</li> `,
|
|
230
|
+
);
|
|
231
|
+
```
|
|
92
232
|
|
|
93
233
|
### π οΈ Utilities
|
|
94
234
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
-
|
|
235
|
+
#### `isObservable(value)`
|
|
236
|
+
|
|
237
|
+
Check if a value is an observable.
|
|
238
|
+
|
|
239
|
+
- **Parameters**: `value` - Value to check
|
|
240
|
+
- **Return**: Boolean
|
|
241
|
+
|
|
242
|
+
#### `isComputation(value)`
|
|
243
|
+
|
|
244
|
+
Check if a value is a computed property.
|
|
245
|
+
|
|
246
|
+
- **Parameters**: `value` - Value to check
|
|
247
|
+
- **Return**: Boolean
|
|
248
|
+
|
|
249
|
+
#### `isReactive(value)`
|
|
250
|
+
|
|
251
|
+
Check if a value is reactive (observable or computed property).
|
|
252
|
+
|
|
253
|
+
- **Parameters**: `value` - Value to check
|
|
254
|
+
- **Return**: Boolean
|
|
255
|
+
|
|
256
|
+
#### `sample(fn)`
|
|
257
|
+
|
|
258
|
+
Run a function without tracking its dependencies.
|
|
259
|
+
|
|
260
|
+
- **Parameters**: `fn` - Function to run
|
|
261
|
+
- **Return**: The result of the function execution
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
const value = sample(() => count()); // Read count's value without establishing dependency
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## β οΈ Caveats
|
|
268
|
+
|
|
269
|
+
### 1. Tag Position Interpolation
|
|
270
|
+
|
|
271
|
+
In template strings, if interpolation is used in the tag position, the interpolation should be a string or a function that returns a string, where the returned string is the name of the HTML/SVG element. If you need to use a component, you need to call the method directly.
|
|
272
|
+
|
|
273
|
+
**Correct Usage**:
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
// Static tag
|
|
277
|
+
html`<div>Hello</div>`;
|
|
278
|
+
|
|
279
|
+
// Dynamic tag (using string)
|
|
280
|
+
const tagName = o('div');
|
|
281
|
+
html`<${tagName}>Hello</${tagName}>`;
|
|
282
|
+
|
|
283
|
+
// Dynamic tag (using function that returns string)
|
|
284
|
+
const getTagName = () => 'div';
|
|
285
|
+
html`<${getTagName}>Hello</${getTagName}>`;
|
|
286
|
+
|
|
287
|
+
// Using component (calling method directly)
|
|
288
|
+
const MyComponent = () => html`<div>Component</div>`;
|
|
289
|
+
html`<div>${MyComponent()}</div>`;
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Incorrect Usage**:
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// Error: Component not called directly
|
|
296
|
+
const MyComponent = () => html`<div>Component</div>`;
|
|
297
|
+
html`<${MyComponent}>Hello</${MyComponent}>`; // This will try to create an element named "function MyComponent() {...}"
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 2. Dynamic Attribute Values
|
|
301
|
+
|
|
302
|
+
If you need to dynamically set HTML/SVG element attribute values, you need to pass the return value of `S()` or `o()`. If you directly pass a regular function, this function will be treated as the attribute value, not the return value of the function.
|
|
303
|
+
|
|
304
|
+
**Correct Usage**:
|
|
305
|
+
|
|
306
|
+
```javascript
|
|
307
|
+
// Using observable
|
|
308
|
+
const disabled = o(false);
|
|
309
|
+
html`<button disabled="${disabled}">Click</button>`;
|
|
310
|
+
|
|
311
|
+
// Using computed property
|
|
312
|
+
const isDisabled = S(() => count() > 5);
|
|
313
|
+
html`<button disabled="${isDisabled}">Click</button>`;
|
|
314
|
+
|
|
315
|
+
// Event binding (passing function directly)
|
|
316
|
+
html`<button onclick="${() => count(count() + 1)}">Increment</button>`;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Incorrect Usage**:
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
// Error: Regular function treated as attribute value
|
|
323
|
+
const getDisabled = () => false;
|
|
324
|
+
html`<button disabled="${getDisabled}">Click</button>`; // This will set "function getDisabled() {...}" as the attribute value
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## π‘ Motivation
|
|
328
|
+
|
|
329
|
+
The motivation behind Subay is to provide a lightweight, easy-to-use reactive UI library that avoids the complexity and size of large frameworks. Its design philosophy is:
|
|
330
|
+
|
|
331
|
+
- **Simplicity First**: Keep the API simple and intuitive, easy to learn and use
|
|
332
|
+
- **Performance Priority**: Optimize DOM updates, reduce unnecessary computations
|
|
333
|
+
- **Minimal Dependencies**: No external library dependencies, keep size small
|
|
334
|
+
- **TypeScript Support**: Provide complete type definitions, improve development experience
|
|
335
|
+
|
|
336
|
+
### Relationship with Sinuous
|
|
337
|
+
|
|
338
|
+
Subay references the design ideas of the [Sinuous](https://github.com/luwes/sinuous) library. The `o` and `S` APIs are almost identical to Sinuous, but there are the following differences in implementation details:
|
|
339
|
+
|
|
340
|
+
1. **S Execution Timing**: In Subay, even if the result of an S function is not used, it will still be executed. In Sinuous, an S function is only executed when its result is actually used.
|
|
341
|
+
|
|
342
|
+
**Example**:
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
const userId = o('');
|
|
346
|
+
const defaultThemeId = o('rainbow');
|
|
347
|
+
|
|
348
|
+
const themeId = S(() => {
|
|
349
|
+
if (!userId()) {
|
|
350
|
+
return defaultThemeId();
|
|
351
|
+
}
|
|
352
|
+
return userTheme();
|
|
353
|
+
});
|
|
354
|
+
const userTheme = S(() => {
|
|
355
|
+
// if !userId(), sinuous won't execute here
|
|
356
|
+
// but subay will execute
|
|
357
|
+
console.log('side effect on ', userId());
|
|
358
|
+
return `${userId()}'s theme`;
|
|
359
|
+
});
|
|
360
|
+
S(() => {
|
|
361
|
+
console.log('now the theme is ', themeId());
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
userId('John');
|
|
365
|
+
userId('');
|
|
366
|
+
userId('Mary');
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
In this example, when `userId()` is an empty string, `themeId()` will return `defaultThemeId()` and not use the result of `userTheme()`. In Sinuous, the `userTheme()` function will not be executed; but in Subay, the `userTheme()` function will still be executed, producing a side effect.
|
|
370
|
+
|
|
371
|
+
2. **o's Reference Management for S**: When S is recomputed, the o's that S depended on during the last computation won't immediately forget S. Instead, they will filter out the "forgotten" S only when o is updated next time. Since o updates are usually less frequent than S recomputations, this approach slightly improves performance.
|
|
372
|
+
|
|
373
|
+
Subay is suitable for projects that need lightweight UI solutions, especially small applications, prototype development, or scenarios with high performance requirements.
|
|
100
374
|
|
|
101
375
|
## π License
|
|
102
376
|
|