lithent 1.21.3 → 1.21.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/dist/lithent-agent-addon.md +126 -0
- package/dist/skills/SKILL.md +382 -0
- package/dist/skills/constraints/mistakes.md +15 -0
- package/dist/skills/constraints/rules.md +16 -0
- package/dist/skills/constraints/troubleshooting.md +13 -0
- package/dist/skills/examples/quick-examples.md +195 -0
- package/dist/skills/reference/children-type.md +25 -0
- package/dist/skills/reference/lstore-inference.md +19 -0
- package/dist/skills/reference/props-types.md +28 -0
- package/package.json +3 -2
- package/ssr/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Lithent Agent Addon
|
|
2
|
+
|
|
3
|
+
> Version: 1.21.5
|
|
4
|
+
|
|
5
|
+
You are a coding agent with Lithent virtual DOM guidance enabled.
|
|
6
|
+
|
|
7
|
+
ACTIVATION CONDITIONS:
|
|
8
|
+
|
|
9
|
+
These guidelines apply ONLY when Lithent is installed in the current project.
|
|
10
|
+
|
|
11
|
+
Before suggesting Lithent patterns, verify Lithent availability:
|
|
12
|
+
1. Check if `package.json` contains lithent in dependencies or devDependencies
|
|
13
|
+
2. Check if `node_modules/lithent` directory exists
|
|
14
|
+
3. Check if lithent imports are present in existing code
|
|
15
|
+
|
|
16
|
+
If Lithent is NOT installed:
|
|
17
|
+
- Do not enforce these guidelines
|
|
18
|
+
- Use standard coding practices appropriate for the project
|
|
19
|
+
- Never suggest installing lithent unless explicitly requested
|
|
20
|
+
|
|
21
|
+
If Lithent IS installed:
|
|
22
|
+
- Apply all guidelines below
|
|
23
|
+
- Suggest Lithent patterns for UI component creation
|
|
24
|
+
- Prioritize closure-based state management patterns
|
|
25
|
+
|
|
26
|
+
CODING GUIDELINES:
|
|
27
|
+
|
|
28
|
+
1. COMPONENT MODE SELECTION
|
|
29
|
+
- Use `mount` for explicit render control with `renew()`
|
|
30
|
+
- Use `lmount` for automatic reactivity with `lstate`/`lstore`
|
|
31
|
+
- Don't mix modes: use `state`/`store` with `mount`, use `lstate`/`lstore` with `lmount`
|
|
32
|
+
- Suggest `lmount` for simpler components, `mount` for fine-grained control
|
|
33
|
+
|
|
34
|
+
2. COMPONENT STRUCTURE
|
|
35
|
+
- Outer function (mounter) runs once for initialization
|
|
36
|
+
- Inner function (updater) runs on every render
|
|
37
|
+
- Keep state setup in mounter, return JSX from updater
|
|
38
|
+
- Always return a function from mounter, never JSX directly
|
|
39
|
+
|
|
40
|
+
3. STATE MANAGEMENT
|
|
41
|
+
- Use `state(initialValue, renew)` with `mount` components
|
|
42
|
+
- Use `lstate(initialValue)` with `lmount` components
|
|
43
|
+
- Access values via `.value` property
|
|
44
|
+
- For global state, use `store`/`lstore` instead of prop drilling
|
|
45
|
+
- Use `computed` for derived values, `effect` for side effects
|
|
46
|
+
|
|
47
|
+
4. LIFECYCLE HOOKS
|
|
48
|
+
- Use `mountCallback` for setup after mount (return cleanup function)
|
|
49
|
+
- Use `updateCallback` for logic before each update
|
|
50
|
+
- Use `mountReadyCallback` for logic after initial render
|
|
51
|
+
- Place hooks in mounter function, not in updater
|
|
52
|
+
|
|
53
|
+
5. LIST RENDERING
|
|
54
|
+
- Always use `key` prop for list items
|
|
55
|
+
- Keys must be unique and stable (prefer IDs over indices)
|
|
56
|
+
- Suggest adding keys when iterating over arrays without them
|
|
57
|
+
|
|
58
|
+
6. PERFORMANCE OPTIMIZATION
|
|
59
|
+
- Suggest `cacheUpdate` when multiple state updates cause re-renders
|
|
60
|
+
- Suggest `nextTickRender` for deferring expensive renders
|
|
61
|
+
- Avoid creating new functions in updater when possible
|
|
62
|
+
- Move event handlers to mounter scope when they don't need updater closure
|
|
63
|
+
|
|
64
|
+
7. SSR AND HYDRATION
|
|
65
|
+
- Use `renderToString` for server-side rendering
|
|
66
|
+
- Use `hydration` flag with `render` for client-side hydration
|
|
67
|
+
- Ensure component output is deterministic for hydration to work
|
|
68
|
+
|
|
69
|
+
8. TEMPLATE ALTERNATIVES
|
|
70
|
+
- Prefer JSX for most cases
|
|
71
|
+
- Suggest `fTags` for no-build-step environments
|
|
72
|
+
- Suggest `lTag` (HTM) for template literal preference
|
|
73
|
+
- Don't mix template styles within a single component
|
|
74
|
+
|
|
75
|
+
9. CONTEXT USAGE
|
|
76
|
+
- Use `createContext` with `mount`, `createLContext` with `lmount`
|
|
77
|
+
- Pass `renew` to `useContext()` in manual mode
|
|
78
|
+
- Suggest context over deep prop drilling (3+ levels)
|
|
79
|
+
|
|
80
|
+
10. CHILDREN HANDLING
|
|
81
|
+
- Use `unwrapChildren` helper when manipulating children
|
|
82
|
+
- Access children as third parameter in mounter function
|
|
83
|
+
- Don't modify children directly, wrap or filter them
|
|
84
|
+
|
|
85
|
+
IMPORT PATHS:
|
|
86
|
+
- Core: `import { mount, lmount, render, h, Fragment, portal, ref, nextTick } from 'lithent'`
|
|
87
|
+
- Lifecycle: `import { mountCallback, updateCallback, mountReadyCallback } from 'lithent'`
|
|
88
|
+
- Helper (manual): `import { state, store, createContext, computed, effect } from 'lithent/helper'`
|
|
89
|
+
- Helper (light): `import { lstate, lstore, createLContext } from 'lithent/helper'`
|
|
90
|
+
- Helper (utils): `import { cacheUpdate, nextTickRender, unwrapChildren } from 'lithent/helper'`
|
|
91
|
+
- SSR: `import { renderToString, hydration } from 'lithent/ssr'`
|
|
92
|
+
- FTags: `import { fTags, fFragment, fMount } from 'lithent/ftags'`
|
|
93
|
+
- HTM: `import { lTag } from 'lithent/tag'`
|
|
94
|
+
|
|
95
|
+
GUIDANCE APPROACH:
|
|
96
|
+
|
|
97
|
+
When user creates UI components:
|
|
98
|
+
1. Suggest appropriate component mode (mount vs lmount)
|
|
99
|
+
2. Ensure proper mounter/updater structure
|
|
100
|
+
3. Add keys to list renderings if missing
|
|
101
|
+
|
|
102
|
+
When reviewing existing Lithent code:
|
|
103
|
+
1. Check for mode consistency (don't mix state/lstate)
|
|
104
|
+
2. Suggest lifecycle hooks for side effects
|
|
105
|
+
3. Recommend performance optimizations when applicable
|
|
106
|
+
|
|
107
|
+
When user requests could benefit from Lithent patterns:
|
|
108
|
+
1. Provide solution using Lithent idioms
|
|
109
|
+
2. Explain the closure-based component model
|
|
110
|
+
3. If user prefers different approach, respect their choice
|
|
111
|
+
|
|
112
|
+
COMMON MISTAKES TO CATCH:
|
|
113
|
+
- Returning JSX directly from mounter instead of returning a function
|
|
114
|
+
- Using `state` without passing `renew` parameter
|
|
115
|
+
- Using `lstate` with `mount` instead of `lmount`
|
|
116
|
+
- Missing `key` prop in list iterations
|
|
117
|
+
- Calling hooks inside updater instead of mounter
|
|
118
|
+
- Mutating state directly instead of using `.value`
|
|
119
|
+
|
|
120
|
+
REFERENCE MATERIALS (NOT PART OF BEHAVIORAL RULES):
|
|
121
|
+
|
|
122
|
+
For detailed code examples and API usage, refer to:
|
|
123
|
+
node_modules/lithent/dist/skills/SKILL.md
|
|
124
|
+
|
|
125
|
+
This reference material provides comprehensive examples, patterns,
|
|
126
|
+
and import references that complement the behavioral guidelines above.
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lithent
|
|
3
|
+
title: Lithent Agent Skills
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
description: AI agent skills for working with the Lithent library.
|
|
6
|
+
additional_materials:
|
|
7
|
+
- constraints/
|
|
8
|
+
- reference/
|
|
9
|
+
- examples/
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Lithent Agent Skills
|
|
13
|
+
|
|
14
|
+
> Version: 1.21.5
|
|
15
|
+
|
|
16
|
+
This file provides AI agent skills for working with the Lithent library.
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
Lithent is a lightweight (~4KB) JSX-based virtual DOM library with closure-based state management. It offers two component modes:
|
|
21
|
+
- **Manual Mode** (`mount`): Explicit control with `renew()` function
|
|
22
|
+
- **Light API Mode** (`lmount`): Auto-reactive with `lstate`/`lstore`
|
|
23
|
+
|
|
24
|
+
## Component Creation
|
|
25
|
+
|
|
26
|
+
### Manual Mode (mount)
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { mount, render } from 'lithent';
|
|
30
|
+
import { state } from 'lithent/helper';
|
|
31
|
+
|
|
32
|
+
const Counter = mount<{ initial?: number }>((renew, props) => {
|
|
33
|
+
const count = state(props.initial ?? 0, renew);
|
|
34
|
+
|
|
35
|
+
return () => (
|
|
36
|
+
<div>
|
|
37
|
+
<p>Count: {count.value}</p>
|
|
38
|
+
<button onClick={() => count.value++}>+1</button>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
render(<Counter initial={10} />, document.getElementById('app')!);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Light API Mode (lmount)
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { lmount, render } from 'lithent';
|
|
50
|
+
import { lstate } from 'lithent/helper';
|
|
51
|
+
|
|
52
|
+
const Counter = lmount<{ initial?: number }>((props) => {
|
|
53
|
+
const count = lstate(props.initial ?? 0);
|
|
54
|
+
|
|
55
|
+
return () => (
|
|
56
|
+
<div>
|
|
57
|
+
<p>Count: {count.value}</p>
|
|
58
|
+
<button onClick={() => count.value++}>+1</button>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
render(<Counter initial={10} />, document.getElementById('app')!);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## State Management
|
|
67
|
+
|
|
68
|
+
### State (Manual Mode)
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { mount } from 'lithent';
|
|
72
|
+
import { state, computed, effect } from 'lithent/helper';
|
|
73
|
+
|
|
74
|
+
const Component = mount((renew) => {
|
|
75
|
+
const firstName = state('John', renew);
|
|
76
|
+
const lastName = state('Doe', renew);
|
|
77
|
+
|
|
78
|
+
// Derived value
|
|
79
|
+
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
|
|
80
|
+
|
|
81
|
+
// Side effect
|
|
82
|
+
effect(() => {
|
|
83
|
+
console.log('Name changed:', fullName.value);
|
|
84
|
+
return () => console.log('Cleanup');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return () => <p>{fullName.value}</p>;
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### State (Light API Mode)
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { lmount } from 'lithent';
|
|
95
|
+
import { lstate, computed, effect } from 'lithent/helper';
|
|
96
|
+
|
|
97
|
+
const Component = lmount(() => {
|
|
98
|
+
const firstName = lstate('John');
|
|
99
|
+
const lastName = lstate('Doe');
|
|
100
|
+
|
|
101
|
+
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
|
|
102
|
+
|
|
103
|
+
effect(() => {
|
|
104
|
+
console.log('Name changed:', fullName.value);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return () => <p>{fullName.value}</p>;
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Global Store
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// Manual Mode
|
|
115
|
+
import { store } from 'lithent/helper';
|
|
116
|
+
const userStore = store({ name: '', loggedIn: false });
|
|
117
|
+
|
|
118
|
+
const Component = mount((renew) => {
|
|
119
|
+
const user = userStore(renew);
|
|
120
|
+
return () => <p>{user.name}</p>;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Light API Mode
|
|
124
|
+
import { lstore } from 'lithent/helper';
|
|
125
|
+
const userStore = lstore({ name: '', loggedIn: false });
|
|
126
|
+
|
|
127
|
+
const Component = lmount(() => {
|
|
128
|
+
const user = userStore();
|
|
129
|
+
return () => <p>{user.name}</p>;
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Lifecycle Hooks
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { mount, mountCallback, updateCallback, mountReadyCallback } from 'lithent';
|
|
137
|
+
|
|
138
|
+
const Component = mount((renew) => {
|
|
139
|
+
// After mount (return cleanup function for unmount)
|
|
140
|
+
mountCallback(() => {
|
|
141
|
+
console.log('Mounted');
|
|
142
|
+
return () => console.log('Unmounted');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Before each update
|
|
146
|
+
updateCallback(() => {
|
|
147
|
+
console.log('Will update');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// After initial render complete
|
|
151
|
+
mountReadyCallback(() => {
|
|
152
|
+
console.log('Initial render done');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return () => <div>Hello</div>;
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## DOM References
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
import { mount, ref } from 'lithent';
|
|
163
|
+
|
|
164
|
+
const Component = mount((renew) => {
|
|
165
|
+
const inputRef = ref<HTMLInputElement>(null);
|
|
166
|
+
|
|
167
|
+
const focusInput = () => inputRef.value?.focus();
|
|
168
|
+
|
|
169
|
+
return () => (
|
|
170
|
+
<div>
|
|
171
|
+
<input ref={inputRef} type="text" />
|
|
172
|
+
<button onClick={focusInput}>Focus</button>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Context API
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
// Manual Mode
|
|
182
|
+
import { mount } from 'lithent';
|
|
183
|
+
import { createContext } from 'lithent/helper';
|
|
184
|
+
|
|
185
|
+
const ThemeContext = createContext<{ theme: string }>();
|
|
186
|
+
|
|
187
|
+
const Provider = mount((renew, _props, children) => {
|
|
188
|
+
return () => (
|
|
189
|
+
<ThemeContext.Provider value={{ theme: 'dark' }}>
|
|
190
|
+
{children}
|
|
191
|
+
</ThemeContext.Provider>
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const Consumer = mount((renew) => {
|
|
196
|
+
const { theme } = ThemeContext.useContext(renew);
|
|
197
|
+
return () => <p>{theme}</p>;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Light API Mode
|
|
201
|
+
import { lmount } from 'lithent';
|
|
202
|
+
import { createLContext } from 'lithent/helper';
|
|
203
|
+
|
|
204
|
+
const ThemeContext = createLContext<{ theme: string }>();
|
|
205
|
+
|
|
206
|
+
const Consumer = lmount(() => {
|
|
207
|
+
const { theme } = ThemeContext.useContext();
|
|
208
|
+
return () => <p>{theme}</p>;
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Portal
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { mount, portal } from 'lithent';
|
|
216
|
+
|
|
217
|
+
const Modal = mount((renew, props: { onClose: () => void }) => {
|
|
218
|
+
return () => portal(
|
|
219
|
+
<div class="modal-overlay" onClick={props.onClose}>
|
|
220
|
+
<div class="modal-content" onClick={(e) => e.stopPropagation()}>
|
|
221
|
+
<h2>Modal</h2>
|
|
222
|
+
<button onClick={props.onClose}>Close</button>
|
|
223
|
+
</div>
|
|
224
|
+
</div>,
|
|
225
|
+
document.body
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## List Rendering
|
|
231
|
+
|
|
232
|
+
Always use `key` prop for lists:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { mount } from 'lithent';
|
|
236
|
+
import { state } from 'lithent/helper';
|
|
237
|
+
|
|
238
|
+
const TodoList = mount((renew) => {
|
|
239
|
+
const todos = state([
|
|
240
|
+
{ id: 1, text: 'Learn Lithent', done: false },
|
|
241
|
+
{ id: 2, text: 'Build app', done: false },
|
|
242
|
+
], renew);
|
|
243
|
+
|
|
244
|
+
const toggle = (id: number) => {
|
|
245
|
+
todos.value = todos.value.map(t =>
|
|
246
|
+
t.id === id ? { ...t, done: !t.done } : t
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return () => (
|
|
251
|
+
<ul>
|
|
252
|
+
{todos.value.map(todo => (
|
|
253
|
+
<li key={todo.id} onClick={() => toggle(todo.id)}>
|
|
254
|
+
{todo.text} {todo.done && '✓'}
|
|
255
|
+
</li>
|
|
256
|
+
))}
|
|
257
|
+
</ul>
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## SSR and Hydration
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// Server
|
|
266
|
+
import { renderToString } from 'lithent/ssr';
|
|
267
|
+
const html = renderToString(<App />);
|
|
268
|
+
|
|
269
|
+
// Client (hydrate existing DOM)
|
|
270
|
+
import { render } from 'lithent';
|
|
271
|
+
import { hydration } from 'lithent/ssr';
|
|
272
|
+
render(<App />, document.getElementById('app')!, hydration);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## FTags (No JSX)
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { fTags, fMount } from 'lithent/ftags';
|
|
279
|
+
import { state } from 'lithent/helper';
|
|
280
|
+
|
|
281
|
+
const { div, button, p } = fTags;
|
|
282
|
+
|
|
283
|
+
const Counter = fMount((renew) => {
|
|
284
|
+
const count = state(0, renew);
|
|
285
|
+
return () => div({},
|
|
286
|
+
p({}, `Count: ${count.value}`),
|
|
287
|
+
button({ onClick: () => count.value++ }, '+1')
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## HTM (Tagged Templates)
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { lTag } from 'lithent/tag';
|
|
296
|
+
import { mount } from 'lithent';
|
|
297
|
+
import { state } from 'lithent/helper';
|
|
298
|
+
|
|
299
|
+
const html = lTag;
|
|
300
|
+
|
|
301
|
+
const Counter = mount((renew) => {
|
|
302
|
+
const count = state(0, renew);
|
|
303
|
+
return () => html`
|
|
304
|
+
<div>
|
|
305
|
+
<p>Count: ${count.value}</p>
|
|
306
|
+
<button onClick=${() => count.value++}>+1</button>
|
|
307
|
+
</div>
|
|
308
|
+
`;
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Performance
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { cacheUpdate, nextTickRender } from 'lithent/helper';
|
|
316
|
+
|
|
317
|
+
// Batch multiple state updates into single render
|
|
318
|
+
cacheUpdate(() => {
|
|
319
|
+
a.value = 1;
|
|
320
|
+
b.value = 2;
|
|
321
|
+
c.value = 3;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Defer render to next tick
|
|
325
|
+
nextTickRender(() => {
|
|
326
|
+
items.value = [...items.value, ...newItems];
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Children Handling
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
import { mount } from 'lithent';
|
|
334
|
+
import { unwrapChildren } from 'lithent/helper';
|
|
335
|
+
|
|
336
|
+
const Card = mount((renew, props: { title: string }, children) => {
|
|
337
|
+
return () => (
|
|
338
|
+
<div class="card">
|
|
339
|
+
<h3>{props.title}</h3>
|
|
340
|
+
<div class="card-body">{unwrapChildren(children)}</div>
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Usage
|
|
346
|
+
<Card title="Title"><p>Content</p></Card>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Import Reference
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
// Core
|
|
353
|
+
import {
|
|
354
|
+
mount, lmount, render, h, Fragment, portal,
|
|
355
|
+
ref, nextTick, mountCallback, updateCallback, mountReadyCallback
|
|
356
|
+
} from 'lithent';
|
|
357
|
+
|
|
358
|
+
// Helper
|
|
359
|
+
import {
|
|
360
|
+
state, lstate, // Reactive state
|
|
361
|
+
computed, effect, // Derived values & side effects
|
|
362
|
+
store, lstore, // Global state
|
|
363
|
+
createContext, createLContext, // Context API
|
|
364
|
+
cacheUpdate, nextTickRender, // Performance
|
|
365
|
+
unwrapChildren // Utility
|
|
366
|
+
} from 'lithent/helper';
|
|
367
|
+
|
|
368
|
+
// SSR
|
|
369
|
+
import { renderToString, hydration } from 'lithent/ssr';
|
|
370
|
+
|
|
371
|
+
// FTags
|
|
372
|
+
import { fTags, fFragment, fMount } from 'lithent/ftags';
|
|
373
|
+
|
|
374
|
+
// HTM
|
|
375
|
+
import { lTag } from 'lithent/tag';
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Additional materials (optional)
|
|
379
|
+
|
|
380
|
+
- `constraints/` (rules, mistakes, troubleshooting)
|
|
381
|
+
- `reference/` (props types, children types, lstore inference)
|
|
382
|
+
- `examples/` (quick examples)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Common Mistakes
|
|
3
|
+
category: constraints
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Common Mistakes
|
|
8
|
+
|
|
9
|
+
- Destructuring props in the mounter and expecting primitive values to update.
|
|
10
|
+
- Using `props.children` instead of the `children` argument.
|
|
11
|
+
- Swapping argument order for `mount` or `lmount`.
|
|
12
|
+
- Calling `state`/`lstate` outside the mounter (e.g., in updaters/handlers).
|
|
13
|
+
- Using `state` with `lmount` or `lstate` with `mount`.
|
|
14
|
+
- Creating `store`/`lstore` inside components (new store per render).
|
|
15
|
+
- Mutating arrays or nested objects directly instead of replacing references.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Rules
|
|
3
|
+
category: constraints
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Rules
|
|
8
|
+
|
|
9
|
+
- Use `state` with `mount` and pass `renew` as the second argument.
|
|
10
|
+
- Use `lstate` with `lmount` (no `renew` argument is needed).
|
|
11
|
+
- Call `state`/`lstate` only inside the mounter (not in updaters or handlers).
|
|
12
|
+
- Use `store` with `mount`, `lstore` with `lmount`, and create stores outside components.
|
|
13
|
+
- `store` is shallow‑reactive: replace first‑level properties or arrays to update.
|
|
14
|
+
- Don’t rely on `props.children`; use the separate `children` argument.
|
|
15
|
+
- Argument order is `mount(renew, props, children)` and `lmount(props, children)`.
|
|
16
|
+
- Avoid destructuring props in the mounter if you need live updates.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Troubleshooting
|
|
3
|
+
category: constraints
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Troubleshooting
|
|
8
|
+
|
|
9
|
+
- **Props look stale:** avoid destructuring props in the mounter; read `props.x` directly or use the updater’s props argument.
|
|
10
|
+
- **Children missing:** use the `children` argument (not `props.children`); remember children are only passed to the mounter.
|
|
11
|
+
- **Updates not rendering:** don’t mutate arrays/objects in place—assign a new array or replace first‑level properties.
|
|
12
|
+
- **State doesn’t react:** `state` requires `mount` + `renew`; `lstate` requires `lmount` and is mounter‑only.
|
|
13
|
+
- **Stores reset unexpectedly:** define `store`/`lstore` outside components and reuse them.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Quick Examples
|
|
3
|
+
category: examples
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Quick Examples
|
|
8
|
+
|
|
9
|
+
## Manual Counter (mount + state)
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { mount } from 'lithent';
|
|
13
|
+
import { state } from 'lithent/helper';
|
|
14
|
+
|
|
15
|
+
const Counter = mount((renew) => {
|
|
16
|
+
const count = state(0, renew);
|
|
17
|
+
return () => <button onClick={() => count.value++}>{count.value}</button>;
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Light Counter (lmount + lstate)
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { lmount } from 'lithent';
|
|
25
|
+
import { lstate } from 'lithent/helper';
|
|
26
|
+
|
|
27
|
+
const Counter = lmount(() => {
|
|
28
|
+
const count = lstate(0);
|
|
29
|
+
return () => <button onClick={() => count.value++}>{count.value}</button>;
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Computed + Effect
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { lmount } from 'lithent';
|
|
37
|
+
import { lstate, computed, effect } from 'lithent/helper';
|
|
38
|
+
|
|
39
|
+
const Profile = lmount(() => {
|
|
40
|
+
const first = lstate('Ada');
|
|
41
|
+
const last = lstate('Lovelace');
|
|
42
|
+
const full = computed(() => `${first.value} ${last.value}`);
|
|
43
|
+
|
|
44
|
+
effect(() => {
|
|
45
|
+
console.log('Name changed:', full.value);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return () => <p>{full.value}</p>;
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Lifecycle Hooks (mount / update)
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { mount, mountCallback, updateCallback, mountReadyCallback } from 'lithent';
|
|
56
|
+
import { state } from 'lithent/helper';
|
|
57
|
+
|
|
58
|
+
const Widget = mount((renew) => {
|
|
59
|
+
const count = state(0, renew);
|
|
60
|
+
|
|
61
|
+
// Runs after DOM mount
|
|
62
|
+
mountCallback(() => {
|
|
63
|
+
console.log('mounted');
|
|
64
|
+
return () => console.log('unmounted');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Runs right after virtual DOM creation (before DOM mount)
|
|
68
|
+
mountReadyCallback(() => {
|
|
69
|
+
console.log('mountReady');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Runs before every update; returned function runs after DOM update
|
|
73
|
+
updateCallback(() => {
|
|
74
|
+
console.log('before update');
|
|
75
|
+
return () => console.log('after update');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return () => (
|
|
79
|
+
<button onClick={() => count.value++}>
|
|
80
|
+
Count: {count.value}
|
|
81
|
+
</button>
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Ref
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { mount, ref } from 'lithent';
|
|
90
|
+
|
|
91
|
+
const FocusInput = mount((renew) => {
|
|
92
|
+
const inputRef = ref<HTMLInputElement>(null);
|
|
93
|
+
return () => (
|
|
94
|
+
<div>
|
|
95
|
+
<input ref={inputRef} />
|
|
96
|
+
<button onClick={() => inputRef.value?.focus()}>Focus</button>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Portal
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { mount, portal } from 'lithent';
|
|
106
|
+
|
|
107
|
+
const Modal = mount((renew, props: { onClose: () => void }) => {
|
|
108
|
+
return () => portal(
|
|
109
|
+
<div class="modal" onClick={props.onClose}>Close</div>,
|
|
110
|
+
document.body
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Context (mount + lmount)
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { mount, lmount } from 'lithent';
|
|
119
|
+
import { createContext, createLContext } from 'lithent/helper';
|
|
120
|
+
|
|
121
|
+
const ThemeContext = createContext<{ theme: string }>();
|
|
122
|
+
const LThemeContext = createLContext<{ theme: string }>();
|
|
123
|
+
|
|
124
|
+
const ThemeProvider = mount((renew, _props, children) => {
|
|
125
|
+
return () => (
|
|
126
|
+
<ThemeContext.Provider value={{ theme: 'dark' }}>
|
|
127
|
+
{children}
|
|
128
|
+
</ThemeContext.Provider>
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const LThemeProvider = lmount((_props, children) => {
|
|
133
|
+
return () => (
|
|
134
|
+
<LThemeContext.Provider value={{ theme: 'dark' }}>
|
|
135
|
+
{children}
|
|
136
|
+
</LThemeContext.Provider>
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const Consumer = lmount(() => {
|
|
141
|
+
const { theme } = LThemeContext.useContext();
|
|
142
|
+
return () => <p>{theme}</p>;
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Store (store + lstore)
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { mount, lmount } from 'lithent';
|
|
150
|
+
import { store, lstore } from 'lithent/helper';
|
|
151
|
+
|
|
152
|
+
const userStore = store({ name: '', loggedIn: false });
|
|
153
|
+
const userLStore = lstore({ name: '', loggedIn: false });
|
|
154
|
+
|
|
155
|
+
const User = mount((renew) => {
|
|
156
|
+
const user = userStore(renew);
|
|
157
|
+
return () => <p>{user.name}</p>;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const UserLite = lmount(() => {
|
|
161
|
+
const user = userLStore();
|
|
162
|
+
return () => <p>{user.name}</p>;
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Performance (cacheUpdate + nextTickRender)
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { cacheUpdate, nextTickRender } from 'lithent/helper';
|
|
170
|
+
|
|
171
|
+
cacheUpdate(() => {
|
|
172
|
+
a.value = 1;
|
|
173
|
+
b.value = 2;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
nextTickRender(() => {
|
|
177
|
+
items.value = [...items.value, ...more];
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Children Helpers
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { mount } from 'lithent';
|
|
185
|
+
import { unwrapChildren } from 'lithent/helper';
|
|
186
|
+
|
|
187
|
+
const Card = mount((renew, props: { title: string }, children) => {
|
|
188
|
+
return () => (
|
|
189
|
+
<div class="card">
|
|
190
|
+
<h3>{props.title}</h3>
|
|
191
|
+
{unwrapChildren(children)}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Children Type
|
|
3
|
+
category: reference
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Children Type
|
|
8
|
+
|
|
9
|
+
- Lithent does **not** use `props.children`. Use the separate `children` argument.
|
|
10
|
+
- Argument order: `mount(renew, props, children)` and `lmount(props, children)`.
|
|
11
|
+
- Children are always `WDom[]`, even with a single child.
|
|
12
|
+
- Children are only provided to the mounter; use closure inside the updater.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { mount, WDom } from 'lithent';
|
|
16
|
+
|
|
17
|
+
const Panel = mount<{ title: string }>((renew, props, children: WDom[]) => {
|
|
18
|
+
return () => (
|
|
19
|
+
<section>
|
|
20
|
+
<h2>{props.title}</h2>
|
|
21
|
+
{children}
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: lstore Type Inference
|
|
3
|
+
category: reference
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# lstore Type Inference
|
|
8
|
+
|
|
9
|
+
- Defining stores as **objects** is usually better than primitives.
|
|
10
|
+
- Grouping related fields improves type inference and clarity.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { lstore } from 'lithent/helper';
|
|
14
|
+
|
|
15
|
+
const userStore = lstore({
|
|
16
|
+
name: '',
|
|
17
|
+
loggedIn: false,
|
|
18
|
+
});
|
|
19
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Props Types
|
|
3
|
+
category: reference
|
|
4
|
+
version: 1.21.5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Props Types
|
|
8
|
+
|
|
9
|
+
- Props are the **second** argument to the mounter and the **first** argument to the updater.
|
|
10
|
+
- The same props reference is preserved for the component’s lifetime.
|
|
11
|
+
- For TypeScript, define the props type via a `mount`/`lmount` generic.
|
|
12
|
+
- If you destructure props in the mounter, primitive values are copied and won’t update.
|
|
13
|
+
Prefer `props.property` or the updater’s props argument for fresh values.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { mount } from 'lithent';
|
|
17
|
+
|
|
18
|
+
type Props = { count: number };
|
|
19
|
+
|
|
20
|
+
const Counter = mount<Props>((renew, props) => {
|
|
21
|
+
return (updaterProps) => (
|
|
22
|
+
<div>
|
|
23
|
+
<p>props: {props.count}</p>
|
|
24
|
+
<p>updater: {updaterProps.count}</p>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "lithent",
|
|
4
|
-
"version": "1.21.
|
|
4
|
+
"version": "1.21.5",
|
|
5
5
|
"description": "An extensible virtual DOM library for lightweight use in a variety of environments.",
|
|
6
6
|
"main": "./dist/lithent.umd.js",
|
|
7
7
|
"module": "./dist/lithent.mjs",
|
|
@@ -113,7 +113,8 @@
|
|
|
113
113
|
"dev:devHelper": "pnpm watch:core & pnpm --filter lithent-dev-helper dev",
|
|
114
114
|
"dev:ssr": "pnpm watch:core & pnpm --filter lithent-ssr dev",
|
|
115
115
|
"dev:docs": "pnpm watch:core & pnpm watch:helper & pnpm --filter lithent-docs dev",
|
|
116
|
-
"build:core": "vite build",
|
|
116
|
+
"build:core": "vite build && node scripts/build-skills.js",
|
|
117
|
+
"build:skills": "node scripts/build-skills.js",
|
|
117
118
|
"build:helper": "pnpm --filter lithent-helper build",
|
|
118
119
|
"build:devHelper": "pnpm --filter lithent-dev-helper build",
|
|
119
120
|
"build:hmrParser": "pnpm --filter @lithent/hmr-parser build",
|