@esportsplus/template 0.16.0
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/.editorconfig +9 -0
- package/.gitattributes +2 -0
- package/.github/dependabot.yml +25 -0
- package/.github/workflows/bump.yml +9 -0
- package/.github/workflows/dependabot.yml +12 -0
- package/.github/workflows/publish.yml +16 -0
- package/README.md +385 -0
- package/build/attributes.d.ts +5 -0
- package/build/attributes.js +212 -0
- package/build/compiler/codegen.d.ts +21 -0
- package/build/compiler/codegen.js +303 -0
- package/build/compiler/constants.d.ts +16 -0
- package/build/compiler/constants.js +19 -0
- package/build/compiler/index.d.ts +14 -0
- package/build/compiler/index.js +61 -0
- package/build/compiler/parser.d.ts +19 -0
- package/build/compiler/parser.js +164 -0
- package/build/compiler/plugins/tsc.d.ts +3 -0
- package/build/compiler/plugins/tsc.js +4 -0
- package/build/compiler/plugins/vite.d.ts +13 -0
- package/build/compiler/plugins/vite.js +8 -0
- package/build/compiler/ts-analyzer.d.ts +4 -0
- package/build/compiler/ts-analyzer.js +63 -0
- package/build/compiler/ts-parser.d.ts +24 -0
- package/build/compiler/ts-parser.js +67 -0
- package/build/constants.d.ts +12 -0
- package/build/constants.js +25 -0
- package/build/event/index.d.ts +10 -0
- package/build/event/index.js +90 -0
- package/build/event/onconnect.d.ts +3 -0
- package/build/event/onconnect.js +15 -0
- package/build/event/onresize.d.ts +3 -0
- package/build/event/onresize.js +26 -0
- package/build/event/ontick.d.ts +6 -0
- package/build/event/ontick.js +41 -0
- package/build/html.d.ts +9 -0
- package/build/html.js +7 -0
- package/build/index.d.ts +8 -0
- package/build/index.js +12 -0
- package/build/render.d.ts +3 -0
- package/build/render.js +8 -0
- package/build/slot/array.d.ts +25 -0
- package/build/slot/array.js +189 -0
- package/build/slot/cleanup.d.ts +4 -0
- package/build/slot/cleanup.js +23 -0
- package/build/slot/effect.d.ts +12 -0
- package/build/slot/effect.js +85 -0
- package/build/slot/index.d.ts +7 -0
- package/build/slot/index.js +14 -0
- package/build/slot/render.d.ts +2 -0
- package/build/slot/render.js +44 -0
- package/build/svg.d.ts +5 -0
- package/build/svg.js +14 -0
- package/build/types.d.ts +23 -0
- package/build/types.js +1 -0
- package/build/utilities.d.ts +7 -0
- package/build/utilities.js +31 -0
- package/package.json +43 -0
- package/src/attributes.ts +313 -0
- package/src/compiler/codegen.ts +492 -0
- package/src/compiler/constants.ts +25 -0
- package/src/compiler/index.ts +87 -0
- package/src/compiler/parser.ts +242 -0
- package/src/compiler/plugins/tsc.ts +6 -0
- package/src/compiler/plugins/vite.ts +10 -0
- package/src/compiler/ts-analyzer.ts +89 -0
- package/src/compiler/ts-parser.ts +112 -0
- package/src/constants.ts +44 -0
- package/src/event/index.ts +130 -0
- package/src/event/onconnect.ts +22 -0
- package/src/event/onresize.ts +37 -0
- package/src/event/ontick.ts +59 -0
- package/src/html.ts +18 -0
- package/src/index.ts +19 -0
- package/src/llm.txt +403 -0
- package/src/render.ts +13 -0
- package/src/slot/array.ts +257 -0
- package/src/slot/cleanup.ts +37 -0
- package/src/slot/effect.ts +114 -0
- package/src/slot/index.ts +17 -0
- package/src/slot/render.ts +61 -0
- package/src/svg.ts +27 -0
- package/src/types.ts +40 -0
- package/src/utilities.ts +53 -0
- package/storage/compiler-architecture-2026-01-13.md +420 -0
- package/test/dist/test.js +1912 -0
- package/test/dist/test.js.map +1 -0
- package/test/index.ts +648 -0
- package/test/vite.config.ts +23 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Attributes, Element } from '../types';
|
|
2
|
+
import { raf } from '../utilities';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let tasks = Object.assign(new Set<VoidFunction>(), { running: false });
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function tick() {
|
|
9
|
+
if (tasks.size === 0) {
|
|
10
|
+
tasks.running = false;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for (let task of tasks) {
|
|
15
|
+
task();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
raf(tick);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const add = (task: VoidFunction) => {
|
|
23
|
+
tasks.add(task);
|
|
24
|
+
|
|
25
|
+
if (!tasks.running) {
|
|
26
|
+
tasks.running = true;
|
|
27
|
+
raf(tick);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const remove = (task: VoidFunction) => {
|
|
32
|
+
tasks.delete(task);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export default (element: Element, listener: NonNullable<Attributes['ontick']>) => {
|
|
37
|
+
let connected = false,
|
|
38
|
+
fn = () => {
|
|
39
|
+
if (connected === false) {
|
|
40
|
+
if (element.isConnected) {
|
|
41
|
+
connected = true;
|
|
42
|
+
}
|
|
43
|
+
else if (retry--) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!element.isConnected) {
|
|
49
|
+
remove(fn);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
listener(() => remove(fn), element);
|
|
54
|
+
},
|
|
55
|
+
retry = 60;
|
|
56
|
+
|
|
57
|
+
add(fn);
|
|
58
|
+
};
|
|
59
|
+
export { add, remove };
|
package/src/html.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Reactive } from '@esportsplus/reactivity';
|
|
2
|
+
import { Attribute, Attributes, Renderable } from './types';
|
|
3
|
+
import { ArraySlot } from './slot';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
type Values<T> = ArraySlot<T extends unknown[] ? T : never> | Attribute | Attributes<any> | Renderable<T>;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const html = <T>(_literals: TemplateStringsArray, ..._values: (Values<T> | Values<T>[])[]): DocumentFragment => {
|
|
10
|
+
throw new Error('html`` templates must be compiled. Ensure vite-plugin is configured.');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
html.reactive = <T>(_arr: Reactive<T[]>, _template: (value: T) => DocumentFragment): ArraySlot<T[]> => {
|
|
14
|
+
throw new Error('html.reactive() must be compiled. Ensure vite-plugin is configured.');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export default html;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CLEANUP, STORE } from './constants';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// Pre-allocate on Node prototype to optimize property access
|
|
5
|
+
if (typeof Node !== 'undefined') {
|
|
6
|
+
(Node.prototype as any)[CLEANUP] = null;
|
|
7
|
+
(Node.prototype as any)[STORE] = null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export * from './attributes';
|
|
12
|
+
export * from './event';
|
|
13
|
+
export * from './utilities';
|
|
14
|
+
|
|
15
|
+
export { default as html } from './html';
|
|
16
|
+
export { default as render } from './render';
|
|
17
|
+
export { default as slot, ArraySlot, EffectSlot } from './slot';
|
|
18
|
+
export { default as svg } from './svg';
|
|
19
|
+
export type { Attributes, Element, Renderable } from './types';
|
package/src/llm.txt
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Template Library - LLM Context Guide
|
|
2
|
+
|
|
3
|
+
This document provides comprehensive context for LLMs to understand, maintain, and refactor this template compilation and rendering library.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A compile-time template system that transforms `html` tagged template literals into optimized DOM construction code. The system consists of:
|
|
8
|
+
|
|
9
|
+
1. **Compiler** (`compiler/`) - TypeScript transformer that runs at build time
|
|
10
|
+
2. **Runtime** (`*.ts` in root) - Minimal runtime for DOM manipulation, slots, and events
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
16
|
+
│ BUILD TIME │
|
|
17
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
18
|
+
│ Source: html`<div>${expr}</div>` │
|
|
19
|
+
│ ↓ │
|
|
20
|
+
│ ts-parser.ts → Find html`` tags in AST │
|
|
21
|
+
│ ↓ │
|
|
22
|
+
│ parser.ts → Parse HTML, extract slots, generate paths │
|
|
23
|
+
│ ↓ │
|
|
24
|
+
│ ts-analyzer.ts → Analyze expression types (Effect, Static..) │
|
|
25
|
+
│ ↓ │
|
|
26
|
+
│ codegen.ts → Generate optimized JS code │
|
|
27
|
+
│ ↓ │
|
|
28
|
+
│ Output: template_xxx().firstChild.nextSibling... │
|
|
29
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
30
|
+
│ RUNTIME │
|
|
31
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
32
|
+
│ utilities.ts → template(), clone(), fragment() │
|
|
33
|
+
│ attributes.ts → setList(), setProperty(), reactive bindings │
|
|
34
|
+
│ slot/ → ArraySlot, EffectSlot, static rendering │
|
|
35
|
+
│ event/ → Event delegation, lifecycle hooks │
|
|
36
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Compiler Pipeline
|
|
40
|
+
|
|
41
|
+
### Entry Point: `compiler/index.ts`
|
|
42
|
+
|
|
43
|
+
The transform function:
|
|
44
|
+
1. Finds all `html` tagged templates via `findHtmlTemplates()`
|
|
45
|
+
2. Finds all `html.reactive()` calls via `findReactiveCalls()`
|
|
46
|
+
3. Generates replacement code via `generateCode()`
|
|
47
|
+
4. Outputs import statements and template factory definitions
|
|
48
|
+
|
|
49
|
+
### HTML Parser: `compiler/parser.ts`
|
|
50
|
+
|
|
51
|
+
**Purpose**: Parse HTML string, identify dynamic slots, generate DOM traversal paths.
|
|
52
|
+
|
|
53
|
+
**Key Data Structures**:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
type NodePath = ('firstChild' | 'firstElementChild' | 'nextElementSibling' | 'nextSibling')[];
|
|
57
|
+
|
|
58
|
+
// Level tracking for nested elements
|
|
59
|
+
type Level = {
|
|
60
|
+
children: number; // Text/comment node count (for firstChild/nextSibling)
|
|
61
|
+
elements: number; // Element count (for firstElementChild/nextElementSibling)
|
|
62
|
+
path: NodePath; // Path to reach this level from root
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Slot Marker**: `{{$}}` represents dynamic expression positions in the template.
|
|
67
|
+
|
|
68
|
+
**Node Types**:
|
|
69
|
+
- `NODE_ELEMENT` (3) - Regular HTML elements
|
|
70
|
+
- `NODE_VOID` (5) - Self-closing elements (img, input, br, etc.)
|
|
71
|
+
- `NODE_SLOT` (4) - Dynamic content position (`{{$}}`)
|
|
72
|
+
- `NODE_CLOSING` (1) - Closing tags (`</div>`)
|
|
73
|
+
- `NODE_COMMENT` (2) - Comments
|
|
74
|
+
|
|
75
|
+
**Path Generation Algorithm**:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// At root level (empty parent.path):
|
|
79
|
+
// Use firstChild/nextSibling with parent.children count
|
|
80
|
+
// At nested levels (non-empty parent.path):
|
|
81
|
+
// Use firstElementChild/nextElementSibling with parent.elements count
|
|
82
|
+
|
|
83
|
+
path = parent.path.length
|
|
84
|
+
? methods(parent.elements, parent.path, 'firstElementChild', 'nextElementSibling')
|
|
85
|
+
: methods(parent.children, [], 'firstChild', 'nextSibling');
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Critical Bug Fix (Line 217)**:
|
|
89
|
+
|
|
90
|
+
Self-closing tags like `<path/>`, `<circle/>` in SVG must decrement level:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// IMPORTANT: Check for self-closing syntax before NODE_CLOSING
|
|
94
|
+
if (match[0].at(-2) === '/' || type === NODE_CLOSING) {
|
|
95
|
+
level--;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Without this fix, self-closing tags increment `level` but never decrement, causing all subsequent paths to be offset incorrectly.
|
|
100
|
+
|
|
101
|
+
### Code Generator: `compiler/codegen.ts`
|
|
102
|
+
|
|
103
|
+
**Purpose**: Transform parsed template data into executable JavaScript.
|
|
104
|
+
|
|
105
|
+
**Key Functions**:
|
|
106
|
+
|
|
107
|
+
- `generateTemplateCode()` - Main code generation
|
|
108
|
+
- `generateAttributeBinding()` - Attribute/event binding code
|
|
109
|
+
- `generateNodeBinding()` - Slot content binding code
|
|
110
|
+
|
|
111
|
+
**Path Optimization**:
|
|
112
|
+
|
|
113
|
+
The codegen optimizes traversal by finding common ancestors:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// If element A has path [a, b, c, d]
|
|
117
|
+
// And element B has path [a, b, c, d, e, f]
|
|
118
|
+
// Then B can be declared as: A.e!.f
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This optimization relies on correct paths from the parser. If paths share an incorrect prefix due to level tracking bugs, the generated code will fail at runtime.
|
|
122
|
+
|
|
123
|
+
**Output Structure**:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
(() => {
|
|
127
|
+
let root_xxx = template_xxx(),
|
|
128
|
+
element_yyy = root_xxx.firstChild!.firstElementChild as Element,
|
|
129
|
+
element_zzz = element_yyy.nextElementSibling!.firstChild as Element;
|
|
130
|
+
|
|
131
|
+
// Attribute bindings
|
|
132
|
+
namespace.delegate(element_yyy, 'click', handler);
|
|
133
|
+
namespace.setList(element_yyy, 'class', value, attributes_xxx);
|
|
134
|
+
|
|
135
|
+
// Slot bindings
|
|
136
|
+
namespace.slot(element_zzz, content);
|
|
137
|
+
|
|
138
|
+
return root_xxx;
|
|
139
|
+
})()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Type Analyzer: `compiler/ts-analyzer.ts`
|
|
143
|
+
|
|
144
|
+
Analyzes expressions to determine optimal runtime binding:
|
|
145
|
+
|
|
146
|
+
| Expression Type | Result | Runtime Behavior |
|
|
147
|
+
|----------------|--------|------------------|
|
|
148
|
+
| Arrow/Function | `Effect` | Creates `EffectSlot` with reactivity |
|
|
149
|
+
| `html.reactive()` | `ArraySlot` | Creates `ArraySlot` for lists |
|
|
150
|
+
| Nested `html` tag | `DocumentFragment` | Direct insertion |
|
|
151
|
+
| Literal values | `Static` | Direct text assignment |
|
|
152
|
+
| Unknown | `Unknown` | Runtime `slot()` dispatch |
|
|
153
|
+
|
|
154
|
+
## Runtime Components
|
|
155
|
+
|
|
156
|
+
### Template Factory: `utilities.ts`
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const template = (html: string) => {
|
|
160
|
+
let cached: DocumentFragment | undefined;
|
|
161
|
+
return () => {
|
|
162
|
+
if (!cached) {
|
|
163
|
+
element.innerHTML = html;
|
|
164
|
+
cached = element.content;
|
|
165
|
+
}
|
|
166
|
+
return clone(cached, true);
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Caches parsed HTML, returns cloned fragments on each call.
|
|
172
|
+
|
|
173
|
+
### Slot System: `slot/`
|
|
174
|
+
|
|
175
|
+
**Three slot types**:
|
|
176
|
+
|
|
177
|
+
1. **Static Slot** (`slot/index.ts`)
|
|
178
|
+
- One-time content insertion
|
|
179
|
+
- No reactivity overhead
|
|
180
|
+
|
|
181
|
+
2. **EffectSlot** (`slot/effect.ts`)
|
|
182
|
+
- Wraps reactive function in `effect()`
|
|
183
|
+
- Updates content on dependency changes
|
|
184
|
+
- Batches updates via `requestAnimationFrame`
|
|
185
|
+
|
|
186
|
+
3. **ArraySlot** (`slot/array.ts`)
|
|
187
|
+
- Handles reactive arrays with fine-grained updates
|
|
188
|
+
- Listens to array mutation events (push, pop, splice, etc.)
|
|
189
|
+
- Maintains DOM node groups per array item
|
|
190
|
+
|
|
191
|
+
**SlotGroup Structure**:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
type SlotGroup = {
|
|
195
|
+
head: Element; // First node of rendered content
|
|
196
|
+
tail: Element; // Last node of rendered content
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Used to track multi-node slot content for efficient removal/replacement.
|
|
201
|
+
|
|
202
|
+
### Attribute System: `attributes.ts`
|
|
203
|
+
|
|
204
|
+
**Attribute Types**:
|
|
205
|
+
|
|
206
|
+
1. **List Attributes** (class, style)
|
|
207
|
+
- Merge static + dynamic values
|
|
208
|
+
- Track changes per-slot to minimize updates
|
|
209
|
+
- Use delimiters: class=' ', style=';'
|
|
210
|
+
|
|
211
|
+
2. **Property Attributes**
|
|
212
|
+
- Direct property assignment
|
|
213
|
+
- Uses `element.removeAttribute()` for null/false/empty
|
|
214
|
+
|
|
215
|
+
3. **Reactive Attributes**
|
|
216
|
+
- Wrapped in `effect()` for automatic updates
|
|
217
|
+
- Batched via RAF queue
|
|
218
|
+
|
|
219
|
+
**Update Batching**:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
let queue = q<Context>(64); // Circular buffer
|
|
223
|
+
let scheduled = false;
|
|
224
|
+
|
|
225
|
+
function schedule(ctx, element, name, state, value) {
|
|
226
|
+
(ctx.updates ??= {})[name] = value;
|
|
227
|
+
if (!ctx.updating) {
|
|
228
|
+
queue.add(ctx);
|
|
229
|
+
}
|
|
230
|
+
if (!scheduled) {
|
|
231
|
+
scheduled = true;
|
|
232
|
+
raf(task); // Apply all updates in next frame
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Event System: `event/`
|
|
238
|
+
|
|
239
|
+
**Event Delegation**:
|
|
240
|
+
|
|
241
|
+
Most events use document-level delegation:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Registration creates a symbol key for each event type
|
|
245
|
+
const delegate = (element, event, listener) => {
|
|
246
|
+
element[ keys[event] || register(element, event) ] = listener;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Document listener bubbles up, checking for handler
|
|
250
|
+
host.addEventListener(event, (e) => {
|
|
251
|
+
let node = e.target;
|
|
252
|
+
while (node) {
|
|
253
|
+
if (typeof node[key] === 'function') {
|
|
254
|
+
return node[key].call(node, e);
|
|
255
|
+
}
|
|
256
|
+
node = node.parentElement;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Direct Attachment Events**:
|
|
262
|
+
|
|
263
|
+
Events that don't bubble properly use `addEventListener` directly:
|
|
264
|
+
- `blur`, `focus`, `focusin`, `focusout`
|
|
265
|
+
- `load`, `error`
|
|
266
|
+
- Media events: `play`, `pause`, `ended`, `timeupdate`
|
|
267
|
+
- `scroll`, `submit`, `reset`
|
|
268
|
+
|
|
269
|
+
**Lifecycle Events**:
|
|
270
|
+
|
|
271
|
+
Custom events handled specially:
|
|
272
|
+
- `onconnect` - Called when element enters DOM
|
|
273
|
+
- `ondisconnect` - Called when element leaves DOM
|
|
274
|
+
- `onrender` - Called after initial render
|
|
275
|
+
- `onresize` - ResizeObserver-based
|
|
276
|
+
- `ontick` - RAF-based animation loop
|
|
277
|
+
|
|
278
|
+
### Cleanup System: `slot/cleanup.ts`
|
|
279
|
+
|
|
280
|
+
Nodes store cleanup functions via symbol key:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const ondisconnect = (element, fn) => {
|
|
284
|
+
((element as any)[CLEANUP] ??= []).push(fn);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const remove = (...groups) => {
|
|
288
|
+
// Walk backwards through node range
|
|
289
|
+
// Call all cleanup functions
|
|
290
|
+
// Remove nodes from DOM
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Constants Reference
|
|
295
|
+
|
|
296
|
+
### `constants.ts` (Runtime)
|
|
297
|
+
|
|
298
|
+
| Constant | Value | Purpose |
|
|
299
|
+
|----------|-------|---------|
|
|
300
|
+
| `CLEANUP` | Symbol | Key for cleanup function array on nodes |
|
|
301
|
+
| `STORE` | Symbol | Key for attribute context on nodes |
|
|
302
|
+
| `ARRAY_SLOT` | Symbol | Marker for ArraySlot instances |
|
|
303
|
+
| `SLOT_HTML` | `'<!--$-->'` | Comment marker for slot positions |
|
|
304
|
+
| `STATE_HYDRATING` | 0 | Initial render, apply immediately |
|
|
305
|
+
| `STATE_NONE` | 1 | Normal update, batch via RAF |
|
|
306
|
+
| `STATE_WAITING` | 2 | Array item, wait for batch |
|
|
307
|
+
|
|
308
|
+
### `compiler/constants.ts`
|
|
309
|
+
|
|
310
|
+
| Constant | Value | Purpose |
|
|
311
|
+
|----------|-------|---------|
|
|
312
|
+
| `ENTRYPOINT` | 'html' | Tag function name to transform |
|
|
313
|
+
| `ENTRYPOINT_REACTIVITY` | 'reactive' | Method for array slots |
|
|
314
|
+
| `NAMESPACE` | uid('template') | Runtime import namespace |
|
|
315
|
+
|
|
316
|
+
## Refactoring Guidelines
|
|
317
|
+
|
|
318
|
+
### Safe Changes
|
|
319
|
+
|
|
320
|
+
1. **Adding void elements to `NODE_WHITELIST`**
|
|
321
|
+
- Add tag name with `NODE_VOID` value
|
|
322
|
+
- No other changes needed
|
|
323
|
+
|
|
324
|
+
2. **Adding lifecycle events**
|
|
325
|
+
- Add to `LIFECYCLE_EVENTS` set in `constants.ts`
|
|
326
|
+
- Implement handler in `event/on<name>.ts`
|
|
327
|
+
- Export from `event/index.ts`
|
|
328
|
+
|
|
329
|
+
3. **Adding direct-attach events**
|
|
330
|
+
- Add to `DIRECT_ATTACH_EVENTS` set
|
|
331
|
+
- Uses existing `on()` function
|
|
332
|
+
|
|
333
|
+
### Dangerous Changes
|
|
334
|
+
|
|
335
|
+
1. **Modifying `parser.ts` level tracking**
|
|
336
|
+
- Level must increment for `NODE_ELEMENT` only
|
|
337
|
+
- Level must decrement for `NODE_CLOSING` AND self-closing tags
|
|
338
|
+
- Off-by-one errors cause path corruption across all templates
|
|
339
|
+
|
|
340
|
+
2. **Modifying `codegen.ts` path optimization**
|
|
341
|
+
- Ancestor matching relies on exact path prefixes
|
|
342
|
+
- Incorrect matches cause runtime null reference errors
|
|
343
|
+
|
|
344
|
+
3. **Modifying slot rendering order**
|
|
345
|
+
- Slots must be processed in document order
|
|
346
|
+
- Expression indices must match slot order
|
|
347
|
+
|
|
348
|
+
### Testing Recommendations
|
|
349
|
+
|
|
350
|
+
After modifying parser/codegen:
|
|
351
|
+
1. Test templates with SVG containing self-closing tags (`<path/>`, `<circle/>`)
|
|
352
|
+
2. Test deeply nested structures (3+ levels)
|
|
353
|
+
3. Test multiple sibling elements with event handlers
|
|
354
|
+
4. Test templates with mixed slot types (attributes + content)
|
|
355
|
+
|
|
356
|
+
### Common Failure Modes
|
|
357
|
+
|
|
358
|
+
1. **"Cannot read property 'nextElementSibling' of null"**
|
|
359
|
+
- Cause: Path goes deeper than actual DOM structure
|
|
360
|
+
- Debug: Check level tracking in parser for self-closing tags
|
|
361
|
+
|
|
362
|
+
2. **Wrong element receives event handler**
|
|
363
|
+
- Cause: Path offset, element N gets binding for element N+1
|
|
364
|
+
- Debug: Check `parent.elements` count at each level
|
|
365
|
+
|
|
366
|
+
3. **Slot content appears in wrong position**
|
|
367
|
+
- Cause: `parent.children` count incorrect
|
|
368
|
+
- Debug: Check text node handling between elements
|
|
369
|
+
|
|
370
|
+
## File Dependency Graph
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
index.ts (runtime entry)
|
|
374
|
+
├── constants.ts
|
|
375
|
+
├── attributes.ts ← constants, types, utilities, event
|
|
376
|
+
├── html.ts ← types, slot (stub, replaced at compile)
|
|
377
|
+
├── render.ts ← types, utilities, slot
|
|
378
|
+
├── svg.ts (same as html.ts)
|
|
379
|
+
├── utilities.ts ← constants
|
|
380
|
+
├── types.ts ← slot/array
|
|
381
|
+
├── slot/
|
|
382
|
+
│ ├── index.ts ← types, effect, render
|
|
383
|
+
│ ├── array.ts ← reactivity, constants, types, utilities, cleanup, html
|
|
384
|
+
│ ├── effect.ts ← reactivity, types, utilities, cleanup, render
|
|
385
|
+
│ ├── cleanup.ts ← constants, types
|
|
386
|
+
│ └── render.ts ← utilities, constants, types, array
|
|
387
|
+
└── event/
|
|
388
|
+
├── index.ts ← reactivity, utilities, constants, slot, types, onconnect/resize/tick
|
|
389
|
+
├── onconnect.ts ← types
|
|
390
|
+
├── onresize.ts ← types, cleanup
|
|
391
|
+
└── ontick.ts ← types, cleanup, utilities
|
|
392
|
+
|
|
393
|
+
compiler/
|
|
394
|
+
├── index.ts ← ts, ast, constants, codegen, ts-parser
|
|
395
|
+
├── constants.ts ← typescript/compiler
|
|
396
|
+
├── parser.ts ← constants (from ..)
|
|
397
|
+
├── codegen.ts ← typescript, utilities, ts-parser, ts-analyzer, constants, parser
|
|
398
|
+
├── ts-parser.ts ← typescript, typescript/compiler, constants
|
|
399
|
+
├── ts-analyzer.ts ← typescript, constants
|
|
400
|
+
└── plugins/
|
|
401
|
+
├── vite.ts ← typescript/compiler, constants, reactivity/compiler, ..
|
|
402
|
+
└── tsc.ts (similar)
|
|
403
|
+
```
|
package/src/render.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Element, Renderable } from './types';
|
|
2
|
+
import { marker } from './utilities';
|
|
3
|
+
import slot from './slot';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export default <T>(parent: HTMLElement, renderable: Renderable<T>) => {
|
|
7
|
+
let anchor = marker.cloneNode() as unknown as Element;
|
|
8
|
+
|
|
9
|
+
parent.nodeValue = '';
|
|
10
|
+
parent.append(anchor);
|
|
11
|
+
|
|
12
|
+
slot(anchor, renderable);
|
|
13
|
+
};
|