@esportsplus/template 0.28.3 → 0.29.1

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 (109) hide show
  1. package/build/attributes.d.ts +7 -1
  2. package/build/attributes.js +86 -33
  3. package/build/constants.d.ts +3 -11
  4. package/build/constants.js +4 -32
  5. package/build/event/constants.d.ts +3 -0
  6. package/build/event/constants.js +13 -0
  7. package/build/event/index.d.ts +9 -1
  8. package/build/event/index.js +29 -35
  9. package/build/event/ontick.js +6 -9
  10. package/build/html.d.ts +9 -0
  11. package/build/html.js +7 -0
  12. package/build/index.d.ts +8 -2
  13. package/build/index.js +8 -1
  14. package/build/render.d.ts +2 -2
  15. package/build/render.js +2 -3
  16. package/build/runtime.d.ts +1 -0
  17. package/build/runtime.js +5 -0
  18. package/build/slot/array.d.ts +3 -3
  19. package/build/slot/array.js +11 -14
  20. package/build/slot/cleanup.d.ts +1 -1
  21. package/build/slot/cleanup.js +1 -2
  22. package/build/slot/effect.js +5 -7
  23. package/build/slot/index.js +1 -7
  24. package/build/slot/render.js +6 -8
  25. package/build/svg.d.ts +1 -1
  26. package/build/svg.js +1 -1
  27. package/build/transformer/codegen.d.ts +18 -0
  28. package/build/transformer/codegen.js +316 -0
  29. package/build/transformer/index.d.ts +12 -0
  30. package/build/transformer/index.js +62 -0
  31. package/build/transformer/parser.d.ts +18 -0
  32. package/build/transformer/parser.js +166 -0
  33. package/build/transformer/plugins/esbuild.d.ts +5 -0
  34. package/build/transformer/plugins/esbuild.js +35 -0
  35. package/build/transformer/plugins/tsc.d.ts +3 -0
  36. package/build/transformer/plugins/tsc.js +4 -0
  37. package/build/transformer/plugins/vite.d.ts +5 -0
  38. package/build/transformer/plugins/vite.js +37 -0
  39. package/build/transformer/ts-parser.d.ts +21 -0
  40. package/build/transformer/ts-parser.js +72 -0
  41. package/build/transformer/type-analyzer.d.ts +7 -0
  42. package/build/transformer/type-analyzer.js +230 -0
  43. package/build/types.d.ts +2 -3
  44. package/build/utilities.d.ts +7 -0
  45. package/build/utilities.js +31 -0
  46. package/package.json +11 -4
  47. package/src/attributes.ts +115 -51
  48. package/src/constants.ts +6 -53
  49. package/src/event/constants.ts +16 -0
  50. package/src/event/index.ts +36 -42
  51. package/src/event/onconnect.ts +1 -1
  52. package/src/event/onresize.ts +1 -1
  53. package/src/event/ontick.ts +7 -11
  54. package/src/html.ts +18 -0
  55. package/src/index.ts +8 -2
  56. package/src/render.ts +6 -7
  57. package/src/runtime.ts +8 -0
  58. package/src/slot/array.ts +18 -24
  59. package/src/slot/cleanup.ts +3 -4
  60. package/src/slot/effect.ts +6 -8
  61. package/src/slot/index.ts +2 -8
  62. package/src/slot/render.ts +7 -9
  63. package/src/svg.ts +1 -1
  64. package/src/transformer/codegen.ts +518 -0
  65. package/src/transformer/index.ts +98 -0
  66. package/src/transformer/parser.ts +239 -0
  67. package/src/transformer/plugins/esbuild.ts +46 -0
  68. package/src/transformer/plugins/tsc.ts +7 -0
  69. package/src/transformer/plugins/vite.ts +49 -0
  70. package/src/transformer/ts-parser.ts +123 -0
  71. package/src/transformer/type-analyzer.ts +334 -0
  72. package/src/types.ts +3 -4
  73. package/src/utilities.ts +52 -0
  74. package/storage/rewrite-analysis-2026-01-04.md +439 -0
  75. package/test/constants.ts +69 -0
  76. package/test/effects.ts +237 -0
  77. package/test/events.ts +318 -0
  78. package/test/imported-values.ts +253 -0
  79. package/test/nested.ts +298 -0
  80. package/test/slots.ts +259 -0
  81. package/test/spread.ts +290 -0
  82. package/test/static.ts +118 -0
  83. package/test/templates.ts +473 -0
  84. package/test/tsconfig.json +17 -0
  85. package/test/vite.config.ts +50 -0
  86. package/build/html/index.d.ts +0 -9
  87. package/build/html/index.js +0 -29
  88. package/build/html/parser.d.ts +0 -5
  89. package/build/html/parser.js +0 -165
  90. package/build/utilities/element.d.ts +0 -11
  91. package/build/utilities/element.js +0 -9
  92. package/build/utilities/fragment.d.ts +0 -3
  93. package/build/utilities/fragment.js +0 -10
  94. package/build/utilities/marker.d.ts +0 -2
  95. package/build/utilities/marker.js +0 -4
  96. package/build/utilities/node.d.ts +0 -9
  97. package/build/utilities/node.js +0 -10
  98. package/build/utilities/raf.d.ts +0 -2
  99. package/build/utilities/raf.js +0 -1
  100. package/build/utilities/text.d.ts +0 -2
  101. package/build/utilities/text.js +0 -9
  102. package/src/html/index.ts +0 -48
  103. package/src/html/parser.ts +0 -235
  104. package/src/utilities/element.ts +0 -28
  105. package/src/utilities/fragment.ts +0 -19
  106. package/src/utilities/marker.ts +0 -6
  107. package/src/utilities/node.ts +0 -29
  108. package/src/utilities/raf.ts +0 -1
  109. package/src/utilities/text.ts +0 -15
@@ -0,0 +1,334 @@
1
+ import { getNames } from './codegen';
2
+ import { DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS } from '../event/constants';
3
+ import ts from 'typescript';
4
+
5
+
6
+ type AnalyzerContext = {
7
+ checker?: ts.TypeChecker;
8
+ };
9
+
10
+ type SlotType =
11
+ | 'array-slot'
12
+ | 'document-fragment'
13
+ | 'effect'
14
+ | 'node'
15
+ | 'primitive'
16
+ | 'static'
17
+ | 'unknown';
18
+
19
+ type SpreadAnalysis = {
20
+ canUnpack: boolean;
21
+ keys: string[];
22
+ };
23
+
24
+
25
+ function analyzeSpread(expr: ts.Expression, checker?: ts.TypeChecker): SpreadAnalysis {
26
+ while (ts.isParenthesizedExpression(expr)) {
27
+ expr = expr.expression;
28
+ }
29
+
30
+ if (ts.isObjectLiteralExpression(expr)) {
31
+ let keys: string[] = [];
32
+
33
+ for (let i = 0, n = expr.properties.length; i < n; i++) {
34
+ let prop = expr.properties[i];
35
+
36
+ if (ts.isPropertyAssignment(prop)) {
37
+ if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
38
+ keys.push(prop.name.text);
39
+ }
40
+ }
41
+ else if (ts.isShorthandPropertyAssignment(prop)) {
42
+ keys.push(prop.name.text);
43
+ }
44
+ else if (ts.isSpreadAssignment(prop)) {
45
+ return { canUnpack: false, keys: [] };
46
+ }
47
+ }
48
+
49
+ return { canUnpack: true, keys };
50
+ }
51
+
52
+ if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
53
+ try {
54
+ let keys = extractTypePropertyKeys(checker.getTypeAtLocation(expr));
55
+
56
+ if (keys.length > 0) {
57
+ return { canUnpack: true, keys };
58
+ }
59
+ }
60
+ catch {
61
+ }
62
+ }
63
+
64
+ return { canUnpack: false, keys: [] };
65
+ }
66
+
67
+ function extractTypePropertyKeys(type: ts.Type): string[] {
68
+ let keys: string[] = [],
69
+ props = type.getProperties();
70
+
71
+ for (let i = 0, n = props.length; i < n; i++) {
72
+ let name = props[i].getName();
73
+
74
+ if (name.startsWith('__') || name.startsWith('[')) {
75
+ continue;
76
+ }
77
+
78
+ keys.push(name);
79
+ }
80
+
81
+ return keys;
82
+ }
83
+
84
+ function getObjectPropertyValue(expr: ts.ObjectLiteralExpression, key: string, sourceFile: ts.SourceFile): string | null {
85
+ for (let i = 0, n = expr.properties.length; i < n; i++) {
86
+ let prop = expr.properties[i];
87
+
88
+ if (ts.isPropertyAssignment(prop)) {
89
+ let name = ts.isIdentifier(prop.name)
90
+ ? prop.name.text
91
+ : ts.isStringLiteral(prop.name) ? prop.name.text : null;
92
+
93
+ if (name === key) {
94
+ return prop.initializer.getText(sourceFile);
95
+ }
96
+ }
97
+ else if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === key) {
98
+ return prop.name.text;
99
+ }
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ function inferSlotType(expr: ts.Expression, ctx?: AnalyzerContext): SlotType {
106
+ while (ts.isParenthesizedExpression(expr)) {
107
+ expr = expr.expression;
108
+ }
109
+
110
+ if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
111
+ return 'effect';
112
+ }
113
+
114
+ if (
115
+ ts.isCallExpression(expr) &&
116
+ ts.isPropertyAccessExpression(expr.expression) &&
117
+ ts.isIdentifier(expr.expression.expression) &&
118
+ expr.expression.expression.text === 'html' &&
119
+ expr.expression.name.text === 'reactive'
120
+ ) {
121
+ return 'array-slot';
122
+ }
123
+
124
+ if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === 'html') {
125
+ return 'document-fragment';
126
+ }
127
+
128
+ if (ts.isArrayLiteralExpression(expr)) {
129
+ return 'array-slot';
130
+ }
131
+
132
+ if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) {
133
+ return 'static';
134
+ }
135
+
136
+ if (ts.isNumericLiteral(expr)) {
137
+ return 'static';
138
+ }
139
+
140
+ if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword) {
141
+ return 'static';
142
+ }
143
+
144
+ if (expr.kind === ts.SyntaxKind.NullKeyword || expr.kind === ts.SyntaxKind.UndefinedKeyword) {
145
+ return 'static';
146
+ }
147
+
148
+ if (ts.isTemplateExpression(expr)) {
149
+ return 'primitive';
150
+ }
151
+
152
+ if (ts.isConditionalExpression(expr)) {
153
+ let whenFalse = inferSlotType(expr.whenFalse, ctx),
154
+ whenTrue = inferSlotType(expr.whenTrue, ctx);
155
+
156
+ if (whenTrue === whenFalse) {
157
+ return whenTrue;
158
+ }
159
+
160
+ if (whenTrue === 'effect' || whenFalse === 'effect') {
161
+ return 'effect';
162
+ }
163
+
164
+ return 'unknown';
165
+ }
166
+
167
+ if (ctx?.checker) {
168
+ let checker = ctx.checker;
169
+
170
+ if (ts.isIdentifier(expr)) {
171
+ try {
172
+ let type = checker.getTypeAtLocation(expr);
173
+
174
+ if (isTypeFunction(type, checker)) {
175
+ return 'effect';
176
+ }
177
+
178
+ if (isTypeArray(type, checker)) {
179
+ return 'array-slot';
180
+ }
181
+ }
182
+ catch {
183
+ }
184
+ }
185
+
186
+ if (ts.isPropertyAccessExpression(expr)) {
187
+ try {
188
+ let type = checker.getTypeAtLocation(expr);
189
+
190
+ if (isTypeFunction(type, checker)) {
191
+ return 'effect';
192
+ }
193
+
194
+ if (isTypeArray(type, checker)) {
195
+ return 'array-slot';
196
+ }
197
+ }
198
+ catch {
199
+ }
200
+ }
201
+
202
+ if (ts.isCallExpression(expr)) {
203
+ try {
204
+ let type = checker.getTypeAtLocation(expr);
205
+
206
+ if (isTypeFunction(type, checker)) {
207
+ return 'effect';
208
+ }
209
+
210
+ if (isTypeArray(type, checker)) {
211
+ return 'array-slot';
212
+ }
213
+ }
214
+ catch {
215
+ }
216
+ }
217
+ }
218
+
219
+ return 'unknown';
220
+ }
221
+
222
+ function isTypeArray(type: ts.Type, checker: ts.TypeChecker): boolean {
223
+ let typeStr = checker.typeToString(type);
224
+
225
+ if (typeStr.endsWith('[]') || typeStr.startsWith('Array<') || typeStr.startsWith('ReactiveArray<')) {
226
+ return true;
227
+ }
228
+
229
+ let symbol = type.getSymbol();
230
+
231
+ if (symbol && (symbol.getName() === 'Array' || symbol.getName() === 'ReactiveArray')) {
232
+ return true;
233
+ }
234
+
235
+ return false;
236
+ }
237
+
238
+ function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
239
+ if (type.getCallSignatures().length > 0) {
240
+ return true;
241
+ }
242
+
243
+ if (type.isUnion()) {
244
+ for (let i = 0, n = type.types.length; i < n; i++) {
245
+ if (isTypeFunction(type.types[i], checker)) {
246
+ return true;
247
+ }
248
+ }
249
+ }
250
+
251
+ return false;
252
+ }
253
+
254
+
255
+ const analyzeExpression = (expr: ts.Expression, checker?: ts.TypeChecker): SlotType => {
256
+ return inferSlotType(expr, checker ? { checker } : undefined);
257
+ };
258
+
259
+ const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string): string => {
260
+ let n = getNames();
261
+
262
+ if (name.startsWith('on') && name.length > 2) {
263
+ let event = name.slice(2).toLowerCase(),
264
+ key = name.toLowerCase();
265
+
266
+ if (LIFECYCLE_EVENTS.has(key)) {
267
+ return `${n.event}.${key}(${elementVar}, ${expr});`;
268
+ }
269
+
270
+ if (DIRECT_ATTACH_EVENTS.has(key)) {
271
+ return `${n.event}.direct(${elementVar}, '${event}', ${expr});`;
272
+ }
273
+
274
+ return `${n.event}.delegate(${elementVar}, '${event}', ${expr});`;
275
+ }
276
+
277
+ if (name === 'class') {
278
+ return `${n.attr}.setClass(${elementVar}, '${staticValue}', ${expr});`;
279
+ }
280
+
281
+ if (name === 'spread') {
282
+ return `${n.attr}.spread(${elementVar}, ${expr});`;
283
+ }
284
+
285
+ if (name === 'style') {
286
+ return `${n.attr}.setStyle(${elementVar}, '${staticValue}', ${expr});`;
287
+ }
288
+
289
+ return `${n.attr}.setProperty(${elementVar}, '${name}', ${expr});`;
290
+ };
291
+
292
+ const generateSpreadBindings = (
293
+ expr: ts.Expression,
294
+ exprCode: string,
295
+ elementVar: string,
296
+ sourceFile: ts.SourceFile,
297
+ checker?: ts.TypeChecker
298
+ ): string[] => {
299
+ while (ts.isParenthesizedExpression(expr)) {
300
+ expr = expr.expression;
301
+ }
302
+
303
+ let analysis = analyzeSpread(expr, checker);
304
+
305
+ if (!analysis.canUnpack) {
306
+ return [`${getNames().attr}.spread(${elementVar}, ${exprCode});`];
307
+ }
308
+
309
+ let lines: string[] = [];
310
+
311
+ if (ts.isObjectLiteralExpression(expr)) {
312
+ for (let i = 0, n = analysis.keys.length; i < n; i++) {
313
+ let key = analysis.keys[i],
314
+ value = getObjectPropertyValue(expr, key, sourceFile);
315
+
316
+ if (value !== null) {
317
+ lines.push(generateAttributeBinding(elementVar, key, value, ''));
318
+ }
319
+ }
320
+ }
321
+ else {
322
+ for (let i = 0, n = analysis.keys.length; i < n; i++) {
323
+ let key = analysis.keys[i];
324
+
325
+ lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, ''));
326
+ }
327
+ }
328
+
329
+ return lines;
330
+ };
331
+
332
+
333
+ export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
334
+ export type { SlotType };
package/src/types.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { firstChild } from './utilities/node';
2
1
  import { ArraySlot } from './slot/array';
3
2
  import attributes from './attributes';
4
3
  import slot from './slot';
@@ -39,9 +38,9 @@ type Template = {
39
38
  html: string;
40
39
  literals: TemplateStringsArray;
41
40
  slots: {
42
- fn: typeof attributes.set | typeof attributes.spread | typeof slot;
41
+ fn: typeof attributes.spread | typeof slot;
43
42
  name: string | null;
44
- path: typeof firstChild[];
43
+ path: (() => ChildNode | null)[];
45
44
  }[] | null;
46
45
  };
47
46
 
@@ -52,4 +51,4 @@ export type {
52
51
  Renderable,
53
52
  SlotGroup,
54
53
  Template
55
- };
54
+ };
@@ -0,0 +1,52 @@
1
+ import { SLOT_HTML } from './constants';
2
+
3
+
4
+ let clonableTemplate = document.createElement('template'),
5
+ clonableText = document.createTextNode('');
6
+
7
+
8
+ // Firefox's importNode outperforms cloneNode in certain scenarios
9
+ const clone = typeof navigator !== 'undefined' && navigator.userAgent.includes('Firefox')
10
+ ? document.importNode.bind(document)
11
+ : <T extends DocumentFragment | Node>(node: T, deep: boolean = true) => node.cloneNode(deep) as T;
12
+
13
+ // Create a fragment from HTML string
14
+ const fragment = (html: string): DocumentFragment => {
15
+ let element = clonableTemplate.cloneNode() as HTMLTemplateElement;
16
+
17
+ element.innerHTML = html;
18
+
19
+ return element.content;
20
+ };
21
+
22
+ const marker = fragment(SLOT_HTML).firstChild!;
23
+
24
+ const raf = globalThis?.requestAnimationFrame;
25
+
26
+ // Factory that caches the fragment for repeated cloning
27
+ const template = (html: string) => {
28
+ let cached: DocumentFragment | undefined;
29
+
30
+ return () => {
31
+ if (!cached) {
32
+ let element = clonableTemplate.cloneNode() as HTMLTemplateElement;
33
+ element.innerHTML = html;
34
+ cached = element.content;
35
+ }
36
+
37
+ return clone(cached, true) as DocumentFragment;
38
+ };
39
+ };
40
+
41
+ const text = (value: string) => {
42
+ let element = clonableText.cloneNode();
43
+
44
+ if (value !== '') {
45
+ element.nodeValue = value;
46
+ }
47
+
48
+ return element;
49
+ };
50
+
51
+
52
+ export { clone, fragment, template, marker, raf, text };