@esportsplus/template 0.28.1 → 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 (112) hide show
  1. package/.github/workflows/bump.yml +2 -2
  2. package/.github/workflows/dependabot.yml +1 -1
  3. package/.github/workflows/publish.yml +2 -2
  4. package/build/attributes.d.ts +7 -1
  5. package/build/attributes.js +86 -33
  6. package/build/constants.d.ts +3 -11
  7. package/build/constants.js +4 -32
  8. package/build/event/constants.d.ts +3 -0
  9. package/build/event/constants.js +13 -0
  10. package/build/event/index.d.ts +9 -1
  11. package/build/event/index.js +29 -35
  12. package/build/event/ontick.js +6 -9
  13. package/build/html.d.ts +9 -0
  14. package/build/html.js +7 -0
  15. package/build/index.d.ts +8 -2
  16. package/build/index.js +8 -1
  17. package/build/render.d.ts +2 -2
  18. package/build/render.js +2 -3
  19. package/build/runtime.d.ts +1 -0
  20. package/build/runtime.js +5 -0
  21. package/build/slot/array.d.ts +3 -3
  22. package/build/slot/array.js +11 -14
  23. package/build/slot/cleanup.d.ts +1 -1
  24. package/build/slot/cleanup.js +1 -2
  25. package/build/slot/effect.js +5 -7
  26. package/build/slot/index.js +1 -7
  27. package/build/slot/render.js +6 -8
  28. package/build/svg.d.ts +1 -1
  29. package/build/svg.js +1 -1
  30. package/build/transformer/codegen.d.ts +18 -0
  31. package/build/transformer/codegen.js +316 -0
  32. package/build/transformer/index.d.ts +12 -0
  33. package/build/transformer/index.js +62 -0
  34. package/build/transformer/parser.d.ts +18 -0
  35. package/build/transformer/parser.js +166 -0
  36. package/build/transformer/plugins/esbuild.d.ts +5 -0
  37. package/build/transformer/plugins/esbuild.js +35 -0
  38. package/build/transformer/plugins/tsc.d.ts +3 -0
  39. package/build/transformer/plugins/tsc.js +4 -0
  40. package/build/transformer/plugins/vite.d.ts +5 -0
  41. package/build/transformer/plugins/vite.js +37 -0
  42. package/build/transformer/ts-parser.d.ts +21 -0
  43. package/build/transformer/ts-parser.js +72 -0
  44. package/build/transformer/type-analyzer.d.ts +7 -0
  45. package/build/transformer/type-analyzer.js +230 -0
  46. package/build/types.d.ts +2 -3
  47. package/build/utilities.d.ts +7 -0
  48. package/build/utilities.js +31 -0
  49. package/package.json +11 -4
  50. package/src/attributes.ts +115 -51
  51. package/src/constants.ts +6 -53
  52. package/src/event/constants.ts +16 -0
  53. package/src/event/index.ts +36 -42
  54. package/src/event/onconnect.ts +1 -1
  55. package/src/event/onresize.ts +1 -1
  56. package/src/event/ontick.ts +7 -11
  57. package/src/html.ts +18 -0
  58. package/src/index.ts +8 -2
  59. package/src/render.ts +6 -7
  60. package/src/runtime.ts +8 -0
  61. package/src/slot/array.ts +18 -24
  62. package/src/slot/cleanup.ts +3 -4
  63. package/src/slot/effect.ts +6 -8
  64. package/src/slot/index.ts +2 -8
  65. package/src/slot/render.ts +7 -9
  66. package/src/svg.ts +1 -1
  67. package/src/transformer/codegen.ts +518 -0
  68. package/src/transformer/index.ts +98 -0
  69. package/src/transformer/parser.ts +239 -0
  70. package/src/transformer/plugins/esbuild.ts +46 -0
  71. package/src/transformer/plugins/tsc.ts +7 -0
  72. package/src/transformer/plugins/vite.ts +49 -0
  73. package/src/transformer/ts-parser.ts +123 -0
  74. package/src/transformer/type-analyzer.ts +334 -0
  75. package/src/types.ts +3 -4
  76. package/src/utilities.ts +52 -0
  77. package/storage/rewrite-analysis-2026-01-04.md +439 -0
  78. package/test/constants.ts +69 -0
  79. package/test/effects.ts +237 -0
  80. package/test/events.ts +318 -0
  81. package/test/imported-values.ts +253 -0
  82. package/test/nested.ts +298 -0
  83. package/test/slots.ts +259 -0
  84. package/test/spread.ts +290 -0
  85. package/test/static.ts +118 -0
  86. package/test/templates.ts +473 -0
  87. package/test/tsconfig.json +17 -0
  88. package/test/vite.config.ts +50 -0
  89. package/build/html/index.d.ts +0 -9
  90. package/build/html/index.js +0 -29
  91. package/build/html/parser.d.ts +0 -5
  92. package/build/html/parser.js +0 -165
  93. package/build/utilities/element.d.ts +0 -11
  94. package/build/utilities/element.js +0 -9
  95. package/build/utilities/fragment.d.ts +0 -3
  96. package/build/utilities/fragment.js +0 -10
  97. package/build/utilities/marker.d.ts +0 -2
  98. package/build/utilities/marker.js +0 -4
  99. package/build/utilities/node.d.ts +0 -9
  100. package/build/utilities/node.js +0 -10
  101. package/build/utilities/raf.d.ts +0 -2
  102. package/build/utilities/raf.js +0 -1
  103. package/build/utilities/text.d.ts +0 -2
  104. package/build/utilities/text.js +0 -9
  105. package/src/html/index.ts +0 -48
  106. package/src/html/parser.ts +0 -235
  107. package/src/utilities/element.ts +0 -28
  108. package/src/utilities/fragment.ts +0 -19
  109. package/src/utilities/marker.ts +0 -6
  110. package/src/utilities/node.ts +0 -29
  111. package/src/utilities/raf.ts +0 -1
  112. 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 };