subay 0.0.6 → 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 CHANGED
@@ -1,23 +1,42 @@
1
- # Subay
1
+ # 🐣 Subay
2
2
 
3
- A lightweight reactive UI library with state management and template rendering.
3
+ A < 700 LOC declarative, reactive UI library.
4
4
 
5
5
  ## ✨ Features
6
6
 
7
- - **Reactive State Management**: Create observable values and computed properties
8
- - **Template Rendering**: Use tagged template literals for HTML and SVG
9
- - **Efficient DOM Updates**: Optimized rendering with minimal DOM operations
10
- - **TypeScript Support**: Full TypeScript integration
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
- ### 🔄 Reactive State
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
- // Update the value
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
- ### 🎨 Template Rendering
60
+ ### 🎨 Rendering
37
61
 
38
62
  ```javascript
39
63
  import { html, o } from 'subay';
@@ -44,7 +68,7 @@ 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
74
  <button onclick="${() => name('Subay')}">Change Name</button>
@@ -53,7 +77,7 @@ document.body.append(
53
77
  );
54
78
  ```
55
79
 
56
- ### 📋 List Rendering
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
- ## 📚 Core API
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
- ### ⚙️ State Management
162
+ cleanup(() => clearInterval(timer));
79
163
 
80
- - **`o(value)`**: Create an observable value
81
- - **`S(fn, initialValue?)`**: Create a computed property
82
- - **`root(fn)`**: Create a root context for reactive computations
83
- - **`cleanup(fn)`**: Register a cleanup function
84
- - **`transaction(fn)`**: Batch multiple updates
85
- - **`sample(fn)`**: Run a function without tracking dependencies
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
- - **`html`**: Tagged template literal for HTML
90
- - **`svg`**: Tagged template literal for SVG
91
- - **`map(arrayFn, itemFn)`**: Render a list of items
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
- - **`isObservable(value)`**: Check if a value is an observable
96
- - **`isComputed(value)`**: Check if a value is a computed property
97
- - **`isReactive(value)`**: Check if a value is reactive
98
- - **`subscribe(fn)`**: Subscribe to changes
99
- - **`unsubscribe(fn)`**: Unsubscribe from changes
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