@esportsplus/template 0.29.1 → 0.29.2

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.
Files changed (2) hide show
  1. package/README.md +431 -0
  2. package/package.json +23 -1
package/README.md ADDED
@@ -0,0 +1,431 @@
1
+ # @esportsplus/template
2
+
3
+ High-performance, compiler-optimized HTML templating library for JavaScript/TypeScript. Templates are transformed at build time into optimized DOM manipulation code with zero runtime parsing overhead.
4
+
5
+ ## Features
6
+
7
+ - **Compile-time transformation** - Templates converted to optimized code during build
8
+ - **Zero runtime parsing** - No template parsing at runtime
9
+ - **Reactive integration** - Works with `@esportsplus/reactivity` for dynamic updates
10
+ - **Event delegation** - Efficient event handling with automatic delegation
11
+ - **Lifecycle events** - `onconnect`, `ondisconnect`, `onrender`, `onresize`, `ontick`
12
+ - **Type-safe** - Full TypeScript support
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add @esportsplus/template
18
+ ```
19
+
20
+ ## Transformer Plugins
21
+
22
+ The library requires a build-time transformer to convert template literals into optimized code. Choose the plugin for your build tool:
23
+
24
+ ### Vite
25
+
26
+ ```typescript
27
+ // vite.config.ts
28
+ import { defineConfig } from 'vite';
29
+ import templatePlugin from '@esportsplus/template/plugins/vite';
30
+
31
+ export default defineConfig({
32
+ plugins: [
33
+ templatePlugin()
34
+ ]
35
+ });
36
+ ```
37
+
38
+ **Options:**
39
+
40
+ ```typescript
41
+ templatePlugin({
42
+ root: string // Optional: project root (defaults to config.root)
43
+ })
44
+ ```
45
+
46
+ ### ESBuild
47
+
48
+ ```typescript
49
+ // esbuild.config.ts
50
+ import * as esbuild from 'esbuild';
51
+ import templatePlugin from '@esportsplus/template/plugins/esbuild';
52
+
53
+ await esbuild.build({
54
+ entryPoints: ['src/index.ts'],
55
+ bundle: true,
56
+ outfile: 'dist/bundle.js',
57
+ plugins: [
58
+ templatePlugin()
59
+ ]
60
+ });
61
+ ```
62
+
63
+ **Options:**
64
+
65
+ ```typescript
66
+ templatePlugin({
67
+ root: string // Optional: project root (defaults to process.cwd())
68
+ })
69
+ ```
70
+
71
+ ### TypeScript Compiler (tsc)
72
+
73
+ For direct `tsc` compilation, use the transformer factory:
74
+
75
+ ```typescript
76
+ // tsconfig.json (with ts-patch or ttypescript)
77
+ {
78
+ "compilerOptions": {
79
+ "plugins": [
80
+ { "transform": "@esportsplus/template/plugins/tsc" }
81
+ ]
82
+ }
83
+ }
84
+ ```
85
+
86
+ Or programmatically:
87
+
88
+ ```typescript
89
+ import ts from 'typescript';
90
+ import templateTransformer from '@esportsplus/template/plugins/tsc';
91
+
92
+ const program = ts.createProgram(['src/index.ts'], {});
93
+ const result = ts.emit(program, undefined, undefined, false, {
94
+ before: [templateTransformer(program)]
95
+ });
96
+ ```
97
+
98
+ ## Basic Usage
99
+
100
+ ```typescript
101
+ import { html, render } from '@esportsplus/template';
102
+
103
+ // Static template
104
+ const greeting = html`<div class="greeting">Hello World</div>`;
105
+
106
+ // Dynamic text
107
+ const message = (text: string) => html`<div>${text}</div>`;
108
+
109
+ // Dynamic attributes
110
+ const button = (cls: string) => html`<button class="${cls}">Click</button>`;
111
+
112
+ // Render to DOM
113
+ render(document.body, greeting);
114
+ ```
115
+
116
+ ## Template Syntax
117
+
118
+ ### Text Slots
119
+
120
+ ```typescript
121
+ // Basic text interpolation
122
+ const text = (value: string) => html`<span>${value}</span>`;
123
+
124
+ // Multiple slots
125
+ const multi = (a: string, b: string) => html`<p>${a} and ${b}</p>`;
126
+
127
+ // Adjacent slots
128
+ const adjacent = (a: string, b: string, c: string) => html`<div>${a}${b}${c}</div>`;
129
+ ```
130
+
131
+ ### Attribute Slots
132
+
133
+ ```typescript
134
+ // Class
135
+ const dynamic = (cls: string) => html`<div class="${cls}"></div>`;
136
+
137
+ // Mixed static and dynamic
138
+ const mixed = (dynamic: string) => html`<div class="base ${dynamic}"></div>`;
139
+
140
+ // Multiple attributes
141
+ const attrs = (id: string, cls: string, style: string) =>
142
+ html`<div id="${id}" class="${cls}" style="${style}"></div>`;
143
+
144
+ // Data attributes
145
+ const data = (value: string) => html`<div data-value="${value}"></div>`;
146
+ ```
147
+
148
+ ### Spread Attributes
149
+
150
+ ```typescript
151
+ // Spread object as attributes
152
+ const spread = (attrs: Record<string, unknown>) => html`<div ${attrs}></div>`;
153
+
154
+ // With static attributes
155
+ const spreadMixed = (attrs: Record<string, unknown>) =>
156
+ html`<div class="base" ${attrs}></div>`;
157
+ ```
158
+
159
+ ### Nested Templates
160
+
161
+ ```typescript
162
+ // Map to nested templates
163
+ const list = (items: string[]) =>
164
+ html`<ul>${items.map(item => html`<li>${item}</li>`)}</ul>`;
165
+
166
+ // Deeply nested
167
+ const nested = (sections: { title: string; items: string[] }[]) =>
168
+ html`<div>
169
+ ${sections.map(section => html`
170
+ <section>
171
+ <h2>${section.title}</h2>
172
+ <ul>${section.items.map(item => html`<li>${item}</li>`)}</ul>
173
+ </section>
174
+ `)}
175
+ </div>`;
176
+ ```
177
+
178
+ ### Conditional Rendering
179
+
180
+ ```typescript
181
+ // Conditional class
182
+ const toggle = (active: boolean) =>
183
+ html`<div class="${active ? 'active' : 'inactive'}"></div>`;
184
+
185
+ // Conditional content
186
+ const conditional = (show: boolean, content: string) =>
187
+ html`<div>${show ? content : ''}</div>`;
188
+
189
+ // Conditional template
190
+ const conditionalTemplate = (items: string[] | null) =>
191
+ html`<div>
192
+ ${items ? html`<ul>${items.map(i => html`<li>${i}</li>`)}</ul>` : html`<p>No items</p>`}
193
+ </div>`;
194
+ ```
195
+
196
+ ## Reactive Bindings
197
+
198
+ ### Effect Slots
199
+
200
+ Function expressions automatically become reactive effect slots:
201
+
202
+ ```typescript
203
+ import { html } from '@esportsplus/template';
204
+
205
+ // Arrow function = reactive
206
+ const counter = (state: { count: number }) =>
207
+ html`<div>${() => state.count}</div>`;
208
+
209
+ // Reactive class
210
+ const toggle = (state: { active: boolean }) =>
211
+ html`<div class="${() => state.active ? 'on' : 'off'}"></div>`;
212
+
213
+ // Reactive style
214
+ const progress = (state: { width: number }) =>
215
+ html`<div style="${() => `width: ${state.width}px`}"></div>`;
216
+ ```
217
+
218
+ ### Array Slots
219
+
220
+ For reactive arrays, use `html.reactive`:
221
+
222
+ ```typescript
223
+ import { html } from '@esportsplus/template';
224
+
225
+ const todoList = (todos: string[]) =>
226
+ html`<ul>${html.reactive(todos, todo => html`<li>${todo}</li>`)}</ul>`;
227
+ ```
228
+
229
+ ## Events
230
+
231
+ ### Standard DOM Events
232
+
233
+ Events use delegation by default for efficiency:
234
+
235
+ ```typescript
236
+ // Click
237
+ const button = (handler: () => void) =>
238
+ html`<button onclick="${handler}">Click</button>`;
239
+
240
+ // Keyboard
241
+ const input = (handler: (e: KeyboardEvent) => void) =>
242
+ html`<input onkeydown="${handler}">`;
243
+
244
+ // Input
245
+ const textbox = (handler: (e: Event) => void) =>
246
+ html`<input oninput="${handler}">`;
247
+
248
+ // Multiple events
249
+ const interactive = (click: () => void, hover: () => void) =>
250
+ html`<div onclick="${click}" onmouseenter="${hover}">Interact</div>`;
251
+ ```
252
+
253
+ ### Lifecycle Events
254
+
255
+ Custom lifecycle events for DOM attachment:
256
+
257
+ ```typescript
258
+ // Called when element is added to DOM
259
+ const connect = (handler: (el: HTMLElement) => void) =>
260
+ html`<div onconnect="${handler}">Connected</div>`;
261
+
262
+ // Called when element is removed from DOM
263
+ const disconnect = (handler: (el: HTMLElement) => void) =>
264
+ html`<div ondisconnect="${handler}">Will disconnect</div>`;
265
+
266
+ // Called after template renders
267
+ const render = (handler: (el: HTMLElement) => void) =>
268
+ html`<div onrender="${handler}">Rendered</div>`;
269
+
270
+ // Called on element resize
271
+ const resize = (handler: (el: HTMLElement) => void) =>
272
+ html`<div onresize="${handler}">Resizable</div>`;
273
+
274
+ // Called on animation frame (with dispose function)
275
+ const tick = (handler: (dispose: () => void, el: HTMLElement) => void) =>
276
+ html`<div ontick="${handler}">Animating</div>`;
277
+ ```
278
+
279
+ ### Direct Attach Events
280
+
281
+ Some events don't bubble and are attached directly:
282
+
283
+ - `onfocus`, `onblur`, `onfocusin`, `onfocusout`
284
+ - `onload`, `onerror`
285
+ - `onplay`, `onpause`, `onended`, `ontimeupdate`
286
+ - `onscroll`
287
+
288
+ ```typescript
289
+ const input = (focus: () => void, blur: () => void) =>
290
+ html`<input onfocus="${focus}" onblur="${blur}">`;
291
+ ```
292
+
293
+ ## SVG Support
294
+
295
+ Use `svg` for SVG templates:
296
+
297
+ ```typescript
298
+ import { svg } from '@esportsplus/template';
299
+
300
+ const circle = (fill: string) =>
301
+ svg`<svg width="100" height="100">
302
+ <circle cx="50" cy="50" r="40" fill="${fill}"/>
303
+ </svg>`;
304
+ ```
305
+
306
+ ## API Reference
307
+
308
+ ### Exports
309
+
310
+ | Export | Description |
311
+ |--------|-------------|
312
+ | `html` | Template literal tag for HTML |
313
+ | `svg` | Template literal tag for SVG |
314
+ | `render` | Mount renderable to DOM element |
315
+ | `attributes` | Attribute manipulation utilities |
316
+ | `event` | Event system |
317
+ | `slot` | Slot rendering |
318
+ | `ArraySlot` | Reactive array rendering |
319
+ | `EffectSlot` | Reactive effect rendering |
320
+
321
+ ### Types
322
+
323
+ ```typescript
324
+ type Renderable = DocumentFragment | Node | string | number | null | undefined;
325
+ type Element = HTMLElement & { [STORE]?: Record<string, unknown> };
326
+ type Attributes = Record<string, unknown>;
327
+ ```
328
+
329
+ ### render(parent, renderable)
330
+
331
+ Mounts a renderable to a parent element.
332
+
333
+ ```typescript
334
+ import { html, render } from '@esportsplus/template';
335
+
336
+ const app = html`<div>App</div>`;
337
+ render(document.getElementById('root'), app);
338
+ ```
339
+
340
+ ## Complete Example
341
+
342
+ ```typescript
343
+ import { html, render } from '@esportsplus/template';
344
+
345
+ type Todo = { id: number; text: string; done: boolean };
346
+
347
+ const TodoItem = (todo: Todo, onToggle: () => void, onDelete: () => void) => html`
348
+ <li class="${() => todo.done ? 'completed' : ''}">
349
+ <input type="checkbox"
350
+ checked="${() => todo.done}"
351
+ onchange="${onToggle}">
352
+ <span>${todo.text}</span>
353
+ <button onclick="${onDelete}">Delete</button>
354
+ </li>
355
+ `;
356
+
357
+ const TodoApp = (state: {
358
+ todos: Todo[];
359
+ input: string;
360
+ addTodo: () => void;
361
+ toggleTodo: (id: number) => void;
362
+ deleteTodo: (id: number) => void;
363
+ setInput: (value: string) => void;
364
+ }) => html`
365
+ <div class="todo-app">
366
+ <h1>Todos</h1>
367
+ <form onsubmit="${(e: Event) => { e.preventDefault(); state.addTodo(); }}">
368
+ <input type="text"
369
+ value="${() => state.input}"
370
+ oninput="${(e: Event) => state.setInput((e.target as HTMLInputElement).value)}"
371
+ placeholder="Add todo...">
372
+ <button type="submit">Add</button>
373
+ </form>
374
+ <ul>
375
+ ${html.reactive(state.todos, todo =>
376
+ TodoItem(
377
+ todo,
378
+ () => state.toggleTodo(todo.id),
379
+ () => state.deleteTodo(todo.id)
380
+ )
381
+ )}
382
+ </ul>
383
+ </div>
384
+ `;
385
+
386
+ // Mount
387
+ render(document.body, TodoApp(/* state */));
388
+ ```
389
+
390
+ ## How Transformation Works
391
+
392
+ The transformer converts template literals into optimized code at build time:
393
+
394
+ **Input:**
395
+ ```typescript
396
+ const greeting = (name: string) => html`<div class="hello">${name}</div>`;
397
+ ```
398
+
399
+ **Output (simplified):**
400
+ ```typescript
401
+ const $template = (() => {
402
+ let cached;
403
+ return () => {
404
+ if (!cached) cached = template('<div class="hello"><!--$--></div>');
405
+ return clone(cached);
406
+ };
407
+ })();
408
+
409
+ const greeting = (name: string) => {
410
+ const fragment = $template();
411
+ const $0 = firstChild(fragment);
412
+ slot($0.firstChild, name);
413
+ return fragment;
414
+ };
415
+ ```
416
+
417
+ Key optimizations:
418
+ - Template HTML is parsed once and cached
419
+ - Slot positions are computed at compile time
420
+ - Type analysis determines optimal slot binding
421
+ - No runtime template parsing
422
+
423
+ ## Dependencies
424
+
425
+ - `@esportsplus/reactivity` - Reactive primitives
426
+ - `@esportsplus/queue` - Object pooling
427
+ - `@esportsplus/utilities` - Utility functions
428
+
429
+ ## License
430
+
431
+ MIT
package/package.json CHANGED
@@ -14,6 +14,28 @@
14
14
  "vite": "^7.3.0",
15
15
  "vite-tsconfig-paths": "^6.0.3"
16
16
  },
17
+ "exports": {
18
+ ".": {
19
+ "import": "./build/index.js",
20
+ "types": "./build/index.d.ts"
21
+ },
22
+ "./constants": {
23
+ "import": "./build/constants.js",
24
+ "types": "./build/constants.d.ts"
25
+ },
26
+ "./plugins/esbuild": {
27
+ "import": "./build/transformer/plugins/esbuild.js",
28
+ "types": "./build/transformer/plugins/esbuild.d.ts"
29
+ },
30
+ "./plugins/tsc": {
31
+ "import": "./build/transformer/plugins/tsc.js",
32
+ "types": "./build/transformer/plugins/tsc.d.ts"
33
+ },
34
+ "./plugins/vite": {
35
+ "import": "./build/transformer/plugins/vite.js",
36
+ "types": "./build/transformer/plugins/vite.d.ts"
37
+ }
38
+ },
17
39
  "main": "./build/index.js",
18
40
  "name": "@esportsplus/template",
19
41
  "private": false,
@@ -23,7 +45,7 @@
23
45
  },
24
46
  "type": "module",
25
47
  "types": "./build/index.d.ts",
26
- "version": "0.29.1",
48
+ "version": "0.29.2",
27
49
  "scripts": {
28
50
  "build": "tsc && tsc-alias",
29
51
  "compile:test": "vite build --config test/vite.config.ts",