native-document 1.0.9 → 1.0.11
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/native-document.dev.js +2671 -0
- package/dist/native-document.min.js +1 -0
- package/docs/anchor.md +208 -0
- package/docs/conditional-rendering.md +628 -0
- package/docs/contributing.md +51 -0
- package/docs/core-concepts.md +513 -0
- package/docs/elements.md +383 -0
- package/docs/getting-started.md +403 -0
- package/docs/lifecycle-events.md +106 -0
- package/docs/memory-management.md +90 -0
- package/docs/observables.md +265 -0
- package/docs/routing.md +817 -0
- package/docs/state-management.md +423 -0
- package/docs/validation.md +193 -0
- package/elements.js +3 -1
- package/index.js +2 -0
- package/package.json +1 -1
- package/readme.md +189 -425
- package/router.js +2 -0
- package/src/data/MemoryManager.js +15 -5
- package/src/data/Observable.js +35 -2
- package/src/data/ObservableChecker.js +3 -0
- package/src/data/ObservableItem.js +4 -0
- package/src/data/Store.js +6 -6
- package/src/router/Router.js +13 -13
- package/src/router/link.js +8 -5
- package/src/utils/plugins-manager.js +12 -0
- package/src/utils/prototypes.js +13 -0
- package/src/utils/validator.js +23 -1
- package/src/wrappers/AttributesWrapper.js +11 -1
- package/src/wrappers/HtmlElementWrapper.js +10 -1
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
Welcome to NativeDocument! This guide will help you set up and create your first application with NativeDocument.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
NativeDocument offers multiple installation methods to fit your development workflow.
|
|
8
|
+
|
|
9
|
+
### Method 1: CDN (Recommended for beginners)
|
|
10
|
+
|
|
11
|
+
The fastest way to get started is using our CDN. Simply add this script tag to your HTML:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!DOCTYPE html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
<title>My NativeDocument App</title>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<script src="https://cdn.jsdelivr.net/gh/afrocodeur/native-document@latest/dist/native-document.min.js"></script>
|
|
23
|
+
<script>
|
|
24
|
+
const { Div, Button } = NativeDocument.elements;
|
|
25
|
+
const { Observable } = NativeDocument;
|
|
26
|
+
|
|
27
|
+
// Your code here
|
|
28
|
+
const count = Observable(0);
|
|
29
|
+
|
|
30
|
+
const App = Div({ class: 'app' }, [
|
|
31
|
+
Div(['Count: ', count]),
|
|
32
|
+
Button('Increment').nd.on.click(() => count.set(count.val() + 1))
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
document.body.appendChild(App);
|
|
36
|
+
</script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Method 2: Vite Template (Recommended for projects)
|
|
42
|
+
|
|
43
|
+
For a complete development setup with Vite, use our official template:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx degit afrocodeur/native-document-vite my-app
|
|
47
|
+
cd my-app
|
|
48
|
+
npm install
|
|
49
|
+
npm run dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This template includes:
|
|
53
|
+
- Pre-configured Vite setup
|
|
54
|
+
- Development server with auto reload
|
|
55
|
+
- Build optimization
|
|
56
|
+
- Example components
|
|
57
|
+
|
|
58
|
+
### Method 3: NPM/Yarn Package
|
|
59
|
+
|
|
60
|
+
Install NativeDocument as a dependency in your existing project:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install native-document
|
|
64
|
+
# or
|
|
65
|
+
yarn add native-document
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then import what you need:
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import { Div, Button } from 'native-document/src/elements'
|
|
72
|
+
import { Observable } from 'native-document'
|
|
73
|
+
|
|
74
|
+
const count = Observable(0);
|
|
75
|
+
|
|
76
|
+
const App = Div({ class: 'app' }, [
|
|
77
|
+
Div(['Count: ', count]),
|
|
78
|
+
Button('Increment').nd.on.click(() => count.set(count.val() + 1))
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
document.body.appendChild(App);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Your First Application
|
|
85
|
+
|
|
86
|
+
Let's build a simple counter application to understand NativeDocument basics.
|
|
87
|
+
|
|
88
|
+
### Step 1: Create the HTML Structure
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<!DOCTYPE html>
|
|
92
|
+
<html lang="en">
|
|
93
|
+
<head>
|
|
94
|
+
<meta charset="UTF-8">
|
|
95
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
96
|
+
<title>Counter App</title>
|
|
97
|
+
<style>
|
|
98
|
+
.counter-app {
|
|
99
|
+
max-width: 400px;
|
|
100
|
+
margin: 50px auto;
|
|
101
|
+
padding: 20px;
|
|
102
|
+
text-align: center;
|
|
103
|
+
font-family: Arial, sans-serif;
|
|
104
|
+
}
|
|
105
|
+
.count-display {
|
|
106
|
+
font-size: 2rem;
|
|
107
|
+
margin: 20px 0;
|
|
108
|
+
color: #333;
|
|
109
|
+
}
|
|
110
|
+
button {
|
|
111
|
+
margin: 0 10px;
|
|
112
|
+
padding: 10px 20px;
|
|
113
|
+
font-size: 1rem;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
117
|
+
</head>
|
|
118
|
+
<body>
|
|
119
|
+
<script src="https://cdn.jsdelivr.net/gh/afrocodeur/native-document@latest/dist/native-document.min.js"></script>
|
|
120
|
+
<script>
|
|
121
|
+
// Your JavaScript code will go here
|
|
122
|
+
</script>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Step 2: Add the JavaScript Logic
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
const { Div, Button, H1 } = NativeDocument.elements;
|
|
131
|
+
const { Observable } = NativeDocument;
|
|
132
|
+
|
|
133
|
+
// Create reactive state
|
|
134
|
+
const count = Observable(0);
|
|
135
|
+
|
|
136
|
+
// Create the application
|
|
137
|
+
const CounterApp = Div({ class: 'counter-app' }, [
|
|
138
|
+
H1('Counter Application'),
|
|
139
|
+
|
|
140
|
+
Div({ class: 'count-display' }, [
|
|
141
|
+
'Current count: ', count
|
|
142
|
+
]),
|
|
143
|
+
|
|
144
|
+
Div([
|
|
145
|
+
Button('Decrease').nd.on.click(() => {
|
|
146
|
+
count.set(count.val() - 1);
|
|
147
|
+
}),
|
|
148
|
+
|
|
149
|
+
Button('Reset').nd.on.click(() => {
|
|
150
|
+
count.set(0);
|
|
151
|
+
}),
|
|
152
|
+
|
|
153
|
+
Button('Increase').nd.on.click(() => {
|
|
154
|
+
count.set(count.val() + 1);
|
|
155
|
+
})
|
|
156
|
+
])
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
// Mount the application
|
|
160
|
+
document.body.appendChild(CounterApp);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Step 3: Understanding What Happened
|
|
164
|
+
|
|
165
|
+
1. **Imported Components**: We used `Div`, `Button`, and `H1` from `NativeDocument.elements`
|
|
166
|
+
2. **Created Reactive State**: `Observable(0)` creates a reactive value that starts at 0
|
|
167
|
+
3. **Built the UI**: Elements are created with attributes and children
|
|
168
|
+
4. **Added Event Handlers**: `.nd.on.click()` attaches click event listeners
|
|
169
|
+
5. **Automatic Updates**: When `count` changes, the UI updates automatically
|
|
170
|
+
|
|
171
|
+
## Todo List Application
|
|
172
|
+
|
|
173
|
+
Let's build something more complex - a todo list with add, delete, and filter functionality:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const { Div, Input, Button, ShowIf, ForEach } = NativeDocument.elements;
|
|
177
|
+
const { Observable } = NativeDocument;
|
|
178
|
+
|
|
179
|
+
// Reactive state
|
|
180
|
+
const todos = Observable.array([]);
|
|
181
|
+
const newTodo = Observable('');
|
|
182
|
+
const filter = Observable('all'); // 'all', 'active', 'completed'
|
|
183
|
+
|
|
184
|
+
// Computed values
|
|
185
|
+
const filteredTodos = Observable.computed(() => {
|
|
186
|
+
const allTodos = todos.val();
|
|
187
|
+
const currentFilter = filter.val();
|
|
188
|
+
|
|
189
|
+
if (currentFilter === 'active') {
|
|
190
|
+
return allTodos.filter(todo => !todo.done);
|
|
191
|
+
}
|
|
192
|
+
if (currentFilter === 'completed') {
|
|
193
|
+
return allTodos.filter(todo => todo.done);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return [...allTodos];
|
|
197
|
+
}, [todos, filter]);
|
|
198
|
+
|
|
199
|
+
const addTodo = () => {
|
|
200
|
+
if (!newTodo.val().trim()) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
todos.push({
|
|
204
|
+
id: Date.now(),
|
|
205
|
+
text: newTodo.val().trim(),
|
|
206
|
+
done: false
|
|
207
|
+
});
|
|
208
|
+
newTodo.set('');
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Todo application
|
|
212
|
+
const TodoApp = Div({ class: 'todo-app' }, [
|
|
213
|
+
// Header
|
|
214
|
+
Div({ class: 'header' }, [
|
|
215
|
+
Input({
|
|
216
|
+
placeholder: 'What needs to be done?',
|
|
217
|
+
value: newTodo
|
|
218
|
+
}),
|
|
219
|
+
Button('Add').nd.on.click(addTodo)
|
|
220
|
+
]),
|
|
221
|
+
|
|
222
|
+
// Todo list container
|
|
223
|
+
Div({ class: 'todos-list'}, [
|
|
224
|
+
ShowIf(todos.check(list => list.length === 0),
|
|
225
|
+
Div({ class: 'empty' }, 'No todos yet! Add one above.')), // Empty state
|
|
226
|
+
|
|
227
|
+
// List of todos
|
|
228
|
+
ForEach(filteredTodos, (todo, index) => // Todo list
|
|
229
|
+
Div({ class: 'todo-item' }, [
|
|
230
|
+
Input({
|
|
231
|
+
type: 'checkbox',
|
|
232
|
+
checked: Observable(todo.done)
|
|
233
|
+
}).nd.on.change((e) => {
|
|
234
|
+
const todoList = todos.val();
|
|
235
|
+
todoList[index.val()].done = e.target.checked;
|
|
236
|
+
todos.set([...todoList]);
|
|
237
|
+
}),
|
|
238
|
+
|
|
239
|
+
Div(['Task: ', todo.text]),
|
|
240
|
+
|
|
241
|
+
Button('Delete').nd.on.click(() => {
|
|
242
|
+
todos.splice(index.val(), 1);
|
|
243
|
+
})
|
|
244
|
+
]),
|
|
245
|
+
// Key function for efficient updates
|
|
246
|
+
(item) => item.id
|
|
247
|
+
)
|
|
248
|
+
]),
|
|
249
|
+
|
|
250
|
+
// Filters
|
|
251
|
+
Div({ class: 'filters' }, [
|
|
252
|
+
Button('All').nd.on.click(() => filter.set('all')),
|
|
253
|
+
Button('Active').nd.on.click(() => filter.set('active')),
|
|
254
|
+
Button('Completed').nd.on.click(() => filter.set('completed'))
|
|
255
|
+
])
|
|
256
|
+
|
|
257
|
+
]);
|
|
258
|
+
|
|
259
|
+
document.body.appendChild(TodoApp);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Key Concepts Demonstrated
|
|
263
|
+
|
|
264
|
+
### Observables
|
|
265
|
+
- `Observable(value)` - Creates reactive primitive values
|
|
266
|
+
- `Observable.array([])` - Creates reactive arrays with array methods
|
|
267
|
+
- `Observable.computed(() => {}, [deps])` - Creates computed values
|
|
268
|
+
|
|
269
|
+
### Elements
|
|
270
|
+
- Elements are functions that return DOM nodes
|
|
271
|
+
- First parameter is attributes object (optional)
|
|
272
|
+
- Second parameter is children array or single child
|
|
273
|
+
- Children can be strings, numbers, elements, or observables
|
|
274
|
+
|
|
275
|
+
### Event Handling
|
|
276
|
+
- `.nd.on.click()` - Add click event listener
|
|
277
|
+
- `.nd.on.change()` - Add change event listener
|
|
278
|
+
- Event handlers receive the native event object
|
|
279
|
+
|
|
280
|
+
### Conditional Rendering
|
|
281
|
+
- `ShowIf(condition, content)` - Show content when condition is true
|
|
282
|
+
- `ForEach(array, callback, propertyKey || keyFn)` - Render lists efficiently
|
|
283
|
+
|
|
284
|
+
## Project Structure
|
|
285
|
+
|
|
286
|
+
For larger applications, organize your code like this:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
my-app/
|
|
290
|
+
├── index.html
|
|
291
|
+
├── src/
|
|
292
|
+
│ ├── main.js # Application entry point
|
|
293
|
+
│ ├── components/ # Reusable components
|
|
294
|
+
│ │ ├── TodoItem.js
|
|
295
|
+
│ │ └── Header.js
|
|
296
|
+
│ ├── stores/ # Global state
|
|
297
|
+
│ │ └── TodoStore.js
|
|
298
|
+
│ └── utils/ # Helper functions
|
|
299
|
+
│ └── validators.js
|
|
300
|
+
├── styles/
|
|
301
|
+
│ └── main.css
|
|
302
|
+
└── package.json
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Example Component (components/TodoItem.js)
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
import { Div, Input, Button } from 'native-document/src/elements';
|
|
309
|
+
|
|
310
|
+
export function TodoItem(todo, onToggle, onDelete) {
|
|
311
|
+
return Div({ class: 'todo-item' }, [
|
|
312
|
+
Input({
|
|
313
|
+
type: 'checkbox',
|
|
314
|
+
checked: todo.done
|
|
315
|
+
}).nd.on.change(onToggle),
|
|
316
|
+
|
|
317
|
+
Div(['Task: ', todo.text]),
|
|
318
|
+
|
|
319
|
+
Button('Delete').nd.on.click(onDelete)
|
|
320
|
+
]);
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Development Workflow
|
|
325
|
+
|
|
326
|
+
### Auto Reload with Vite
|
|
327
|
+
|
|
328
|
+
When using the Vite template, your development server automatically reloads when you make changes:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
npm run dev # Start development server
|
|
332
|
+
npm run build # Build for production
|
|
333
|
+
npm run preview # Preview production build
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Browser Support
|
|
337
|
+
|
|
338
|
+
NativeDocument works in all modern browsers that support:
|
|
339
|
+
- ES6 Modules
|
|
340
|
+
- Proxy objects
|
|
341
|
+
- FinalizationRegistry (for automatic memory management)
|
|
342
|
+
|
|
343
|
+
**Supported browsers:**
|
|
344
|
+
- Chrome 84+
|
|
345
|
+
- Firefox 79+
|
|
346
|
+
- Safari 14.1+
|
|
347
|
+
- Edge 84+
|
|
348
|
+
|
|
349
|
+
## Next Steps
|
|
350
|
+
|
|
351
|
+
Now that you've built your first NativeDocument applications, explore these topics:
|
|
352
|
+
|
|
353
|
+
- **[Core Concepts](docs/core-concepts.md)** - Understanding the fundamentals
|
|
354
|
+
- **[Observables](docs/observables.md)** - Reactive state management
|
|
355
|
+
- **[Elements](docs/elements.md)** - Creating and composing UI
|
|
356
|
+
- **[Conditional Rendering](docs/conditional-rendering.md)** - Dynamic content
|
|
357
|
+
- **[Routing](docs/routing.md)** - Navigation and URL management
|
|
358
|
+
- **[State Management](docs/state-management.md)** - Global state patterns
|
|
359
|
+
- **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
|
|
360
|
+
- **[Memory Management](docs/memory-management.md)** - Memory management
|
|
361
|
+
- **[Anchor](docs/anchor.md)** - Anchor
|
|
362
|
+
|
|
363
|
+
## Common Issues
|
|
364
|
+
|
|
365
|
+
### Import Errors
|
|
366
|
+
**Problem**: `Cannot resolve module 'native-document'`
|
|
367
|
+
|
|
368
|
+
**Solution**: Make sure you're using the correct import path:
|
|
369
|
+
```javascript
|
|
370
|
+
// Correct
|
|
371
|
+
import { Div } from 'native-document/src/elements'
|
|
372
|
+
import { Observable } from 'native-document'
|
|
373
|
+
|
|
374
|
+
// CDN
|
|
375
|
+
const { Div } = NativeDocument.elements;
|
|
376
|
+
const { Observable } = NativeDocument;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Observable Not Updating
|
|
380
|
+
**Problem**: UI doesn't update when you change observable values
|
|
381
|
+
|
|
382
|
+
**Solution**: Make sure you're using `.set()` method:
|
|
383
|
+
```javascript
|
|
384
|
+
// Wrong
|
|
385
|
+
count = 5;
|
|
386
|
+
|
|
387
|
+
// Correct
|
|
388
|
+
count.set(5);
|
|
389
|
+
// Correct
|
|
390
|
+
count.$value = 5;
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Memory Leaks
|
|
394
|
+
**Problem**: Application slows down over time
|
|
395
|
+
|
|
396
|
+
**Solution**: NativeDocument has automatic memory management, but you can help by cleaning up manual subscriptions:
|
|
397
|
+
```javascript
|
|
398
|
+
const unsubscribe = observable.subscribe(callback);
|
|
399
|
+
// Later...
|
|
400
|
+
unsubscribe(); // Clean up manually if needed
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Ready to dive deeper? Continue with [Core Concepts](core-concepts.md)!
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Lifecycle Events
|
|
2
|
+
|
|
3
|
+
NativeDocument provides lifecycle hooks that let you execute code when elements are added to or removed from the DOM. This is essential for setup, cleanup, and managing resources.
|
|
4
|
+
|
|
5
|
+
## Basic Lifecycle Hooks
|
|
6
|
+
|
|
7
|
+
### mounted() - Element Added to DOM
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const myComponent = Div("Hello World")
|
|
11
|
+
.nd.mounted(element => {
|
|
12
|
+
console.log("Element is now in the DOM!", element);
|
|
13
|
+
// Setup code here
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
document.body.appendChild(myComponent); // Triggers mounted callback
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### unmounted() - Element Removed from DOM
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const myComponent = Div("Temporary content")
|
|
23
|
+
.nd.unmounted(element => {
|
|
24
|
+
console.log("Element removed from DOM!", element);
|
|
25
|
+
// Cleanup external resources only
|
|
26
|
+
// Element can be re-injected later
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Later, when element is removed
|
|
30
|
+
myComponent.remove(); // Triggers unmounted callback
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Combined Lifecycle Management
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
const timer = Div("Timer: 0")
|
|
37
|
+
.nd.lifecycle({
|
|
38
|
+
mounted(element) {
|
|
39
|
+
console.log("Timer started");
|
|
40
|
+
element.intervalId = setInterval(() => {
|
|
41
|
+
element.textContent = `Timer: ${Date.now()}`;
|
|
42
|
+
}, 1000);
|
|
43
|
+
},
|
|
44
|
+
unmounted(element) {
|
|
45
|
+
console.log("Timer stopped");
|
|
46
|
+
clearInterval(element.intervalId);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Practical Examples
|
|
52
|
+
|
|
53
|
+
### Auto-focus Input Field
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const focusInput = Input({ placeholder: "Auto-focused" })
|
|
57
|
+
.nd.mounted(input => {
|
|
58
|
+
input.focus();
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Event Listener Cleanup
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
|
|
66
|
+
const MyButton = function() {
|
|
67
|
+
const handler = () => console.log("Global click detected");
|
|
68
|
+
return Button("Click me")
|
|
69
|
+
.nd.mounted(button => {
|
|
70
|
+
document.addEventListener('click', handler);
|
|
71
|
+
})
|
|
72
|
+
.nd.unmounted(button => {
|
|
73
|
+
// Clean up external listeners, but keep observables intact
|
|
74
|
+
document.removeEventListener('click', handler);
|
|
75
|
+
// DON'T cleanup observables unless element won't be reused
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Observable Management Warning
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
const reusableComponent = Div()
|
|
84
|
+
.nd.unmounted(element => {
|
|
85
|
+
// AVOID: Don't cleanup observables if element might be re-injected
|
|
86
|
+
// myObservable.cleanup(); // Only do this if element is permanently destroyed
|
|
87
|
+
|
|
88
|
+
// GOOD: Only cleanup external resources
|
|
89
|
+
clearInterval(element.timerId);
|
|
90
|
+
element.websocket?.close();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Element can be safely re-appended later
|
|
94
|
+
document.body.appendChild(reusableComponent);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Next Steps
|
|
98
|
+
|
|
99
|
+
Now that you understand lifecycle events, explore these related topics:
|
|
100
|
+
|
|
101
|
+
- **[Memory Management](docs/memory-management.md)** - Memory management
|
|
102
|
+
- **[Anchor](docs/anchor.md)** - Anchor
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Memory Management
|
|
2
|
+
|
|
3
|
+
NativeDocument includes an advanced automatic memory management system that prevents memory leaks and optimizes performance using modern browser APIs like FinalizationRegistry.
|
|
4
|
+
|
|
5
|
+
## Automatic Observable Cleanup
|
|
6
|
+
|
|
7
|
+
### How It Works
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const data = Observable("test");
|
|
11
|
+
|
|
12
|
+
// When 'data' goes out of scope and is garbage collected,
|
|
13
|
+
// NativeDocument automatically cleans up its internal listeners
|
|
14
|
+
// No manual cleanup required!
|
|
15
|
+
|
|
16
|
+
function createComponent() {
|
|
17
|
+
const localObservable = Observable(42);
|
|
18
|
+
return Div(localObservable); // Observable auto-cleaned when component is GC'd
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Memory Registry System
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// Each observable is automatically registered for cleanup
|
|
26
|
+
const count = Observable(0);
|
|
27
|
+
console.log(count.toString()); // "{{#ObItem::(1)}}" - Shows internal ID
|
|
28
|
+
|
|
29
|
+
// When count becomes unreachable, cleanup happens automatically
|
|
30
|
+
// via FinalizationRegistry
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Manual Memory Management
|
|
34
|
+
|
|
35
|
+
### Force Cleanup
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
const obs = Observable("data");
|
|
39
|
+
const unsubscribe = obs.subscribe(console.log);
|
|
40
|
+
|
|
41
|
+
// Manual cleanup if needed
|
|
42
|
+
obs.cleanup(); // Removes all listeners immediately
|
|
43
|
+
// obs is now unusable - will warn on new subscriptions
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Global Cleanup
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// Clean all orphaned observables
|
|
50
|
+
Observable.cleanup(); // Force cleanup of all registered observables
|
|
51
|
+
|
|
52
|
+
// Auto-cleanup configuration
|
|
53
|
+
Observable.autoCleanup(true, {
|
|
54
|
+
interval: 60000, // Check every minute
|
|
55
|
+
threshold: 100 // Clean when 100+ orphaned observables
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Performance Monitoring
|
|
60
|
+
|
|
61
|
+
### Debug Mode
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// Enable memory debugging
|
|
65
|
+
NativeDocument.debug.enable();
|
|
66
|
+
|
|
67
|
+
// Monitor cleanup events in console
|
|
68
|
+
const obs = Observable("test");
|
|
69
|
+
obs = null; // Will log cleanup when GC runs
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Memory Leak Detection
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// Check for potential leaks
|
|
76
|
+
window.addEventListener('beforeunload', () => {
|
|
77
|
+
// Force cleanup on page unload
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Best Practices
|
|
82
|
+
|
|
83
|
+
1. **Trust the system** - Let automatic cleanup handle most cases
|
|
84
|
+
2. **Manual cleanup** only for critical resources or immediate needs
|
|
85
|
+
3. **Enable auto-cleanup** in long-running applications
|
|
86
|
+
4. **Use debug mode** during development to monitor memory usage
|
|
87
|
+
|
|
88
|
+
## Next Steps
|
|
89
|
+
|
|
90
|
+
- **[Anchor](docs/anchor.md)** - Anchor
|