@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.
Files changed (90) hide show
  1. package/.editorconfig +9 -0
  2. package/.gitattributes +2 -0
  3. package/.github/dependabot.yml +25 -0
  4. package/.github/workflows/bump.yml +9 -0
  5. package/.github/workflows/dependabot.yml +12 -0
  6. package/.github/workflows/publish.yml +16 -0
  7. package/README.md +385 -0
  8. package/build/attributes.d.ts +5 -0
  9. package/build/attributes.js +212 -0
  10. package/build/compiler/codegen.d.ts +21 -0
  11. package/build/compiler/codegen.js +303 -0
  12. package/build/compiler/constants.d.ts +16 -0
  13. package/build/compiler/constants.js +19 -0
  14. package/build/compiler/index.d.ts +14 -0
  15. package/build/compiler/index.js +61 -0
  16. package/build/compiler/parser.d.ts +19 -0
  17. package/build/compiler/parser.js +164 -0
  18. package/build/compiler/plugins/tsc.d.ts +3 -0
  19. package/build/compiler/plugins/tsc.js +4 -0
  20. package/build/compiler/plugins/vite.d.ts +13 -0
  21. package/build/compiler/plugins/vite.js +8 -0
  22. package/build/compiler/ts-analyzer.d.ts +4 -0
  23. package/build/compiler/ts-analyzer.js +63 -0
  24. package/build/compiler/ts-parser.d.ts +24 -0
  25. package/build/compiler/ts-parser.js +67 -0
  26. package/build/constants.d.ts +12 -0
  27. package/build/constants.js +25 -0
  28. package/build/event/index.d.ts +10 -0
  29. package/build/event/index.js +90 -0
  30. package/build/event/onconnect.d.ts +3 -0
  31. package/build/event/onconnect.js +15 -0
  32. package/build/event/onresize.d.ts +3 -0
  33. package/build/event/onresize.js +26 -0
  34. package/build/event/ontick.d.ts +6 -0
  35. package/build/event/ontick.js +41 -0
  36. package/build/html.d.ts +9 -0
  37. package/build/html.js +7 -0
  38. package/build/index.d.ts +8 -0
  39. package/build/index.js +12 -0
  40. package/build/render.d.ts +3 -0
  41. package/build/render.js +8 -0
  42. package/build/slot/array.d.ts +25 -0
  43. package/build/slot/array.js +189 -0
  44. package/build/slot/cleanup.d.ts +4 -0
  45. package/build/slot/cleanup.js +23 -0
  46. package/build/slot/effect.d.ts +12 -0
  47. package/build/slot/effect.js +85 -0
  48. package/build/slot/index.d.ts +7 -0
  49. package/build/slot/index.js +14 -0
  50. package/build/slot/render.d.ts +2 -0
  51. package/build/slot/render.js +44 -0
  52. package/build/svg.d.ts +5 -0
  53. package/build/svg.js +14 -0
  54. package/build/types.d.ts +23 -0
  55. package/build/types.js +1 -0
  56. package/build/utilities.d.ts +7 -0
  57. package/build/utilities.js +31 -0
  58. package/package.json +43 -0
  59. package/src/attributes.ts +313 -0
  60. package/src/compiler/codegen.ts +492 -0
  61. package/src/compiler/constants.ts +25 -0
  62. package/src/compiler/index.ts +87 -0
  63. package/src/compiler/parser.ts +242 -0
  64. package/src/compiler/plugins/tsc.ts +6 -0
  65. package/src/compiler/plugins/vite.ts +10 -0
  66. package/src/compiler/ts-analyzer.ts +89 -0
  67. package/src/compiler/ts-parser.ts +112 -0
  68. package/src/constants.ts +44 -0
  69. package/src/event/index.ts +130 -0
  70. package/src/event/onconnect.ts +22 -0
  71. package/src/event/onresize.ts +37 -0
  72. package/src/event/ontick.ts +59 -0
  73. package/src/html.ts +18 -0
  74. package/src/index.ts +19 -0
  75. package/src/llm.txt +403 -0
  76. package/src/render.ts +13 -0
  77. package/src/slot/array.ts +257 -0
  78. package/src/slot/cleanup.ts +37 -0
  79. package/src/slot/effect.ts +114 -0
  80. package/src/slot/index.ts +17 -0
  81. package/src/slot/render.ts +61 -0
  82. package/src/svg.ts +27 -0
  83. package/src/types.ts +40 -0
  84. package/src/utilities.ts +53 -0
  85. package/storage/compiler-architecture-2026-01-13.md +420 -0
  86. package/test/dist/test.js +1912 -0
  87. package/test/dist/test.js.map +1 -0
  88. package/test/index.ts +648 -0
  89. package/test/vite.config.ts +23 -0
  90. package/tsconfig.json +8 -0
@@ -0,0 +1,492 @@
1
+ import { ast, uid, type ReplacementIntent } from '@esportsplus/typescript/compiler';
2
+ import { pick } from '@esportsplus/utilities';
3
+ import type { TemplateInfo } from './ts-parser';
4
+ import { analyze } from './ts-analyzer';
5
+ import { DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS } from '../constants';
6
+ import { ENTRYPOINT, ENTRYPOINT_REACTIVITY, NAMESPACE, TYPES } from './constants';
7
+ import { extractTemplateParts } from './ts-parser';
8
+ import { ts } from '@esportsplus/typescript';
9
+ import parser from './parser';
10
+
11
+
12
+ type CodegenContext = {
13
+ checker?: ts.TypeChecker;
14
+ sourceFile: ts.SourceFile;
15
+ templates: Map<string, string>;
16
+ };
17
+
18
+ type CodegenResult = {
19
+ prepend: string[];
20
+ replacements: ReplacementIntent[];
21
+ templates: Map<string, string>;
22
+ };
23
+
24
+ type ParseResult = ReturnType<typeof parser.parse>;
25
+
26
+ type ParseResultAttributes = Extract<NonNullable<ParseResult['slots']>[number], { type: TYPES.Attribute }>;
27
+
28
+
29
+ let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
30
+
31
+
32
+ function collectNestedReplacements(ctx: CodegenContext, node: ts.Node, replacements: { end: number; start: number; text: string }[]): void {
33
+ if (isNestedHtmlTemplate(node as ts.Expression)) {
34
+ replacements.push({
35
+ end: node.end,
36
+ start: node.getStart(ctx.sourceFile),
37
+ text: generateNestedTemplateCode(ctx, node as ts.TaggedTemplateExpression)
38
+ });
39
+
40
+ return;
41
+ }
42
+
43
+ if (isReactiveCall(node as ts.Expression)) {
44
+ let call = node as ts.CallExpression;
45
+
46
+ replacements.push({
47
+ end: node.end,
48
+ start: node.getStart(ctx.sourceFile),
49
+ text: `new ${NAMESPACE}.ArraySlot(
50
+ ${rewriteExpression(ctx, call.arguments[0] as ts.Expression)},
51
+ ${rewriteExpression(ctx, call.arguments[1] as ts.Expression)}
52
+ )`
53
+ });
54
+
55
+ return;
56
+ }
57
+
58
+ ts.forEachChild(node, child => collectNestedReplacements(ctx, child, replacements));
59
+ }
60
+
61
+ function discoverTemplatesInExpression(ctx: CodegenContext, node: ts.Node): void {
62
+ if (isNestedHtmlTemplate(node as ts.Expression)) {
63
+ let { expressions, literals } = extractTemplateParts((node as ts.TaggedTemplateExpression).template),
64
+ parsed = parser.parse(literals) as ParseResult;
65
+
66
+ getTemplateID(ctx, parsed.html);
67
+
68
+ for (let i = 0, n = expressions.length; i < n; i++) {
69
+ discoverTemplatesInExpression(ctx, expressions[i]);
70
+ }
71
+
72
+ return;
73
+ }
74
+
75
+ ts.forEachChild(node, child => discoverTemplatesInExpression(ctx, child));
76
+ }
77
+
78
+ function generateAttributeBinding(element: string, name: string, expr: string, attributes?: string): string {
79
+ if (name.startsWith('on') && name.length > 2) {
80
+ let event = name.slice(2).toLowerCase(),
81
+ key = name.toLowerCase();
82
+
83
+ if (LIFECYCLE_EVENTS.has(key)) {
84
+ return `${NAMESPACE}.${key}(${element}, ${expr});`;
85
+ }
86
+
87
+ if (DIRECT_ATTACH_EVENTS.has(key)) {
88
+ return `${NAMESPACE}.on(${element}, '${event}', ${expr});`;
89
+ }
90
+
91
+ return `${NAMESPACE}.delegate(${element}, '${event}', ${expr});`;
92
+ }
93
+
94
+ if (name === 'class' || name === 'style') {
95
+ return `${NAMESPACE}.setList(${element}, '${name}', ${expr}, ${attributes});`;
96
+ }
97
+
98
+ return `${NAMESPACE}.setProperty(${element}, '${name}', ${expr});`;
99
+ }
100
+
101
+ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplateExpression): string {
102
+ let { expressions, literals } = extractTemplateParts(node.template),
103
+ exprTexts: string[] = [];
104
+
105
+ for (let i = 0, n = expressions.length; i < n; i++) {
106
+ exprTexts.push(rewriteExpression(ctx, expressions[i]));
107
+ }
108
+
109
+ return generateTemplateCode(
110
+ ctx,
111
+ parser.parse(literals) as ParseResult,
112
+ exprTexts,
113
+ expressions,
114
+ node
115
+ );
116
+ }
117
+
118
+ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
119
+ if (!exprNode) {
120
+ return `${NAMESPACE}.slot(${anchor}, ${exprText});`;
121
+ }
122
+
123
+ if (isNestedHtmlTemplate(exprNode)) {
124
+ return `${anchor}.parentNode!.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
125
+ }
126
+
127
+ switch (analyze(exprNode, ctx.checker)) {
128
+ case TYPES.ArraySlot:
129
+ return `${anchor}.parentNode!.insertBefore(new ${NAMESPACE}.ArraySlot(${exprText}).fragment, ${anchor});`;
130
+
131
+ case TYPES.DocumentFragment:
132
+ return `${anchor}.parentNode!.insertBefore(${exprText}, ${anchor});`;
133
+
134
+ case TYPES.Effect:
135
+ return `new ${NAMESPACE}.EffectSlot(${anchor}, ${exprText});`;
136
+
137
+ case TYPES.Static:
138
+ return `${anchor}.textContent = ${exprText};`;
139
+
140
+ default:
141
+ return `${NAMESPACE}.slot(${anchor}, ${exprText});`;
142
+ }
143
+ }
144
+
145
+ function generateTemplateCode(
146
+ ctx: CodegenContext,
147
+ { html, slots }: ParseResult,
148
+ exprTexts: string[],
149
+ exprNodes: ts.Expression[],
150
+ templateNode: ts.Node
151
+ ): string {
152
+ if (!slots || slots.length === 0) {
153
+ return `${getTemplateID(ctx, html)}()`;
154
+ }
155
+
156
+ let attributes = new Map<number, string>(),
157
+ code: string[] = [],
158
+ declarations: string[] = [],
159
+ index = 0,
160
+ isArrowBody = isArrowExpressionBody(templateNode),
161
+ nodes = new Map<string, string>(),
162
+ root = uid('root');
163
+
164
+ declarations.push(`${root} = ${getTemplateID(ctx, html)}()`);
165
+ nodes.set('', root);
166
+
167
+ for (let i = 0, n = slots.length; i < n; i++) {
168
+ let path = slots[i].path;
169
+
170
+ if (path.length === 0) {
171
+ continue;
172
+ }
173
+
174
+ let key = path.join('.');
175
+
176
+ if (nodes.has(key)) {
177
+ continue;
178
+ }
179
+
180
+ let ancestor = root,
181
+ start = 0;
182
+
183
+ for (let j = path.length - 1; j >= 0; j--) {
184
+ let prefix = path.slice(0, j).join('.');
185
+
186
+ if (nodes.has(prefix)) {
187
+ ancestor = nodes.get(prefix)!;
188
+ start = j;
189
+ break;
190
+ }
191
+ }
192
+
193
+ let name = uid('element'),
194
+ segments = path.slice(start),
195
+ value = `${ancestor}.${segments.join('!.')}`;
196
+
197
+ if (ancestor === root && segments[0] === 'firstChild') {
198
+ value = value.replace(`${ancestor}.firstChild!`, `(${root}.firstChild! as ${NAMESPACE}.Element)`);
199
+ }
200
+
201
+ declarations.push(`${name} = ${value} as ${NAMESPACE}.Element`);
202
+ nodes.set(key, name);
203
+ }
204
+
205
+ code.push(isArrowBody ? '{' : `(() => {`);
206
+
207
+ for (let i = 0, n = slots.length; i < n; i++) {
208
+ let element = slots[i].path.length === 0
209
+ ? root
210
+ : (nodes.get(slots[i].path.join('.')) || root),
211
+ slot = slots[i];
212
+
213
+ if (slot.type === TYPES.Attribute) {
214
+ let names = slot.attributes.names;
215
+
216
+ for (let j = 0, m = names.length; j < m; j++) {
217
+ let name = names[j];
218
+
219
+ if (name === TYPES.Attributes) {
220
+ let exprNode = exprNodes[index];
221
+
222
+ // Object literals can be expanded at compile time
223
+ if (exprNode && ts.isObjectLiteralExpression(exprNode)) {
224
+ let canExpand = true,
225
+ props = exprNode.properties;
226
+
227
+ // Check if all properties can be statically analyzed
228
+ for (let k = 0, p = props.length; k < p; k++) {
229
+ let prop = props[k];
230
+
231
+ if (
232
+ ts.isSpreadAssignment(prop) ||
233
+ (ts.isPropertyAssignment(prop) && ts.isComputedPropertyName(prop.name)) ||
234
+ (ts.isShorthandPropertyAssignment(prop) && prop.objectAssignmentInitializer)
235
+ ) {
236
+ canExpand = false;
237
+ break;
238
+ }
239
+ }
240
+
241
+ if (canExpand) {
242
+ for (let k = 0, p = props.length; k < p; k++) {
243
+ let prop = props[k];
244
+
245
+ if (ts.isPropertyAssignment(prop)) {
246
+ let propName = ts.isIdentifier(prop.name)
247
+ ? prop.name.text
248
+ : ts.isStringLiteral(prop.name)
249
+ ? prop.name.text
250
+ : null;
251
+
252
+ if (propName) {
253
+ code.push(
254
+ generateAttributeBinding(
255
+ element,
256
+ propName,
257
+ rewriteExpression(ctx, prop.initializer),
258
+ getAttributes(declarations, i, propName, slot, attributes)
259
+ )
260
+ );
261
+ }
262
+ }
263
+ else if (ts.isShorthandPropertyAssignment(prop)) {
264
+ let propName = prop.name.text;
265
+
266
+ code.push(
267
+ generateAttributeBinding(
268
+ element,
269
+ propName,
270
+ propName,
271
+ getAttributes(declarations, i, propName, slot, attributes)
272
+ )
273
+ );
274
+ }
275
+ else if (ts.isMethodDeclaration(prop) && ts.isIdentifier(prop.name)) {
276
+ let propName = prop.name.text;
277
+
278
+ code.push(
279
+ generateAttributeBinding(
280
+ element,
281
+ propName,
282
+ printer.printNode(ts.EmitHint.Expression, prop, ctx.sourceFile),
283
+ getAttributes(declarations, i, propName, slot, attributes)
284
+ )
285
+ );
286
+ }
287
+ }
288
+ }
289
+ else {
290
+ code.push(
291
+ `${NAMESPACE}.setProperties(
292
+ ${element}, ${exprTexts[index] || 'undefined'},
293
+ ${getAttributes(declarations, i, 'attributes', slot, attributes)}
294
+ );`
295
+ );
296
+ }
297
+ }
298
+ else {
299
+ code.push(
300
+ `${NAMESPACE}.setProperties(
301
+ ${element}, ${exprTexts[index] || 'undefined'},
302
+ ${getAttributes(declarations, i, 'attributes', slot, attributes)}
303
+ );`
304
+ );
305
+ }
306
+
307
+ index++;
308
+ }
309
+ else {
310
+ code.push(
311
+ generateAttributeBinding(
312
+ element,
313
+ name,
314
+ exprTexts[index++] || 'undefined',
315
+ getAttributes(declarations, i, name, slot, attributes)
316
+ )
317
+ );
318
+ }
319
+ }
320
+ }
321
+ else {
322
+ code.push(
323
+ generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index])
324
+ );
325
+ index++;
326
+ }
327
+ }
328
+
329
+ code.splice(1, 0, `let ${declarations.join(',\n')};`);
330
+ code.push(`return ${root};`);
331
+ code.push(isArrowBody ? `}` : `})()`);
332
+
333
+ return code.join('\n');
334
+ }
335
+
336
+ function getAttributes(declarations: string[], i: number, name: string, slot: ParseResultAttributes, attributes: Map<number, string>): string | undefined {
337
+ if (name !== 'class' && name !== 'attributes' && name !== 'style') {
338
+ return undefined;
339
+ }
340
+
341
+ let attribute = attributes.get(i);
342
+
343
+ if (!attribute) {
344
+ declarations.push(`${attribute = uid('attributes')} = ${JSON.stringify(pick(slot.attributes.static, ['class', 'style']))}`);
345
+ attributes.set(i, attribute);
346
+ }
347
+
348
+ return attribute;
349
+ }
350
+
351
+ function getTemplateID(ctx: CodegenContext, html: string): string {
352
+ let id = ctx.templates.get(html);
353
+
354
+ if (!id) {
355
+ id = uid('template');
356
+ ctx.templates.set(html, id);
357
+ }
358
+
359
+ return id;
360
+ }
361
+
362
+ function isArrowExpressionBody(node: ts.Node): boolean {
363
+ return ts.isArrowFunction(node.parent) && (node.parent as ts.ArrowFunction).body === node;
364
+ }
365
+
366
+ function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
367
+ return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === ENTRYPOINT;
368
+ }
369
+
370
+ function isReactiveCall(expr: ts.Expression): expr is ts.CallExpression {
371
+ return (
372
+ ts.isCallExpression(expr) &&
373
+ ts.isPropertyAccessExpression(expr.expression) &&
374
+ ts.isIdentifier(expr.expression.expression) &&
375
+ expr.expression.expression.text === ENTRYPOINT &&
376
+ expr.expression.name.text === ENTRYPOINT_REACTIVITY
377
+ );
378
+ }
379
+
380
+
381
+ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, checker?: ts.TypeChecker, callRanges: { end: number; start: number }[] = []): CodegenResult => {
382
+ let result: CodegenResult = {
383
+ prepend: [],
384
+ replacements: [],
385
+ templates: new Map()
386
+ };
387
+
388
+ if (templates.length === 0) {
389
+ return result;
390
+ }
391
+
392
+ let ranges: { end: number; start: number }[] = [...callRanges];
393
+
394
+ for (let i = 0, n = templates.length; i < n; i++) {
395
+ let exprs = templates[i].expressions;
396
+
397
+ for (let j = 0, m = exprs.length; j < m; j++) {
398
+ ranges.push({ end: exprs[j].end, start: exprs[j].getStart(sourceFile) });
399
+ }
400
+ }
401
+
402
+ let root = templates.filter(t => !ast.inRange(ranges, t.node.getStart(sourceFile), t.node.end));
403
+
404
+ if (root.length === 0) {
405
+ return result;
406
+ }
407
+
408
+ let ctx: CodegenContext = {
409
+ checker,
410
+ sourceFile,
411
+ templates: result.templates
412
+ };
413
+
414
+ for (let i = 0, n = root.length; i < n; i++) {
415
+ let exprTexts: string[] = [],
416
+ parsed = parser.parse(root[i].literals) as ParseResult,
417
+ template = root[i];
418
+
419
+ for (let j = 0, m = template.expressions.length; j < m; j++) {
420
+ exprTexts.push(rewriteExpression(ctx, template.expressions[j]));
421
+ }
422
+
423
+ if (
424
+ isArrowExpressionBody(template.node) &&
425
+ (template.node.parent as ts.ArrowFunction).parameters.length === 0 &&
426
+ (!parsed.slots || parsed.slots.length === 0)
427
+ ) {
428
+ let code = getTemplateID(ctx, parsed.html);
429
+
430
+ result.replacements.push({
431
+ generate: () => code,
432
+ node: template.node
433
+ });
434
+ }
435
+ else {
436
+ let code = generateTemplateCode(ctx, parsed, exprTexts, template.expressions, template.node);
437
+
438
+ result.replacements.push({
439
+ generate: () => code,
440
+ node: template.node
441
+ });
442
+ }
443
+ }
444
+
445
+ for (let i = 0, n = templates.length; i < n; i++) {
446
+ getTemplateID(ctx, parser.parse(templates[i].literals).html);
447
+
448
+ for (let j = 0, m = templates[i].expressions.length; j < m; j++) {
449
+ discoverTemplatesInExpression(ctx, templates[i].expressions[j]);
450
+ }
451
+ }
452
+
453
+ for (let [html, id] of ctx.templates) {
454
+ result.prepend.push(`const ${id} = ${NAMESPACE}.template(\`${html}\`);`);
455
+ }
456
+
457
+ return result;
458
+ };
459
+
460
+ const rewriteExpression = (ctx: CodegenContext, expr: ts.Expression): string => {
461
+ if (isNestedHtmlTemplate(expr)) {
462
+ return generateNestedTemplateCode(ctx, expr);
463
+ }
464
+
465
+ if (isReactiveCall(expr)) {
466
+ return `${rewriteExpression(ctx, expr.arguments[0] as ts.Expression)}, ${rewriteExpression(ctx, expr.arguments[1] as ts.Expression)}`;
467
+ }
468
+
469
+ if (!ast.test(expr, n => isNestedHtmlTemplate(n as ts.Expression) || isReactiveCall(n as ts.Expression))) {
470
+ return printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
471
+ }
472
+
473
+ let replacements: { end: number; start: number; text: string }[] = [],
474
+ start = expr.getStart(ctx.sourceFile),
475
+ text = expr.getText(ctx.sourceFile);
476
+
477
+ ts.forEachChild(expr, child => collectNestedReplacements(ctx, child, replacements));
478
+
479
+ replacements.sort((a, b) => b.start - a.start);
480
+
481
+ for (let i = 0, n = replacements.length; i < n; i++) {
482
+ let r = replacements[i];
483
+
484
+ text = text.slice(0, r.start - start) + r.text + text.slice(r.end - start);
485
+ }
486
+
487
+ return text;
488
+ }
489
+
490
+
491
+ export { generateCode, printer, rewriteExpression };
492
+ export type { CodegenResult };
@@ -0,0 +1,25 @@
1
+ import { uid } from '@esportsplus/typescript/compiler';
2
+
3
+
4
+ const ENTRYPOINT = 'html';
5
+
6
+ const ENTRYPOINT_REACTIVITY = 'reactive';
7
+
8
+ const NAMESPACE = uid('template');
9
+
10
+
11
+ const enum TYPES {
12
+ ArraySlot = 'array-slot',
13
+ Attributes = 'attributes',
14
+ Attribute = 'attribute',
15
+ DocumentFragment = 'document-fragment',
16
+ Effect = 'effect',
17
+ Node = 'node',
18
+ Primitive = 'primitive',
19
+ Static = 'static',
20
+ Unknown = 'unknown'
21
+ };
22
+
23
+
24
+ export { ENTRYPOINT, ENTRYPOINT_REACTIVITY, NAMESPACE, TYPES };
25
+ export { PACKAGE_NAME } from '~/constants';
@@ -0,0 +1,87 @@
1
+ import { ts } from '@esportsplus/typescript';
2
+ import { ast } from '@esportsplus/typescript/compiler';
3
+ import type { ImportIntent, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
4
+ import { ENTRYPOINT, ENTRYPOINT_REACTIVITY, NAMESPACE, PACKAGE_NAME } from './constants';
5
+ import { generateCode, printer, rewriteExpression } from './codegen';
6
+ import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
7
+
8
+
9
+ export default {
10
+ patterns: [
11
+ `${ENTRYPOINT}\``,
12
+ `${ENTRYPOINT}.${ENTRYPOINT_REACTIVITY}`
13
+ ],
14
+ transform: (ctx: TransformContext) => {
15
+ let callRanges: { end: number; start: number }[] = [],
16
+ callTemplates = new Map<string, string>(),
17
+ imports: ImportIntent[] = [],
18
+ prepend: string[] = [],
19
+ ranges: { end: number; start: number }[] = [],
20
+ remove: string[] = [],
21
+ replacements: ReplacementIntent[] = [],
22
+ templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
23
+
24
+ for (let i = 0, n = templates.length; i < n; i++) {
25
+ ranges.push({
26
+ end: templates[i].end,
27
+ start: templates[i].start
28
+ });
29
+ }
30
+
31
+ let calls = findReactiveCalls(ctx.sourceFile, ctx.checker);
32
+
33
+ for (let i = 0, n = calls.length; i < n; i++) {
34
+ let call = calls[i];
35
+
36
+ if (ast.inRange(ranges, call.start, call.end)) {
37
+ continue;
38
+ }
39
+
40
+ // Add callback range so nested templates inside it are excluded from separate processing
41
+ callRanges.push({
42
+ end: call.callbackArg.end,
43
+ start: call.callbackArg.getStart(ctx.sourceFile)
44
+ });
45
+
46
+ // Pre-compute the rewritten callback to capture templates
47
+ let rewrittenCallback = rewriteExpression({
48
+ checker: ctx.checker,
49
+ sourceFile: ctx.sourceFile,
50
+ templates: callTemplates
51
+ }, call.callbackArg);
52
+
53
+ replacements.push({
54
+ generate: (sourceFile) => `new ${NAMESPACE}.ArraySlot(
55
+ ${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
56
+ ${rewrittenCallback}
57
+ )`,
58
+ node: call.node
59
+ });
60
+ }
61
+
62
+ // Add template definitions from reactive call callbacks
63
+ for (let [html, id] of callTemplates) {
64
+ prepend.push(`const ${id} = ${NAMESPACE}.template(\`${html}\`);`);
65
+ }
66
+
67
+ if (templates.length > 0) {
68
+ let result = generateCode(templates, ctx.sourceFile, ctx.checker, callRanges);
69
+
70
+ prepend.push(...result.prepend);
71
+ replacements.push(...result.replacements);
72
+ remove.push(ENTRYPOINT);
73
+ }
74
+
75
+ if (replacements.length === 0 && prepend.length === 0) {
76
+ return {};
77
+ }
78
+
79
+ imports.push({
80
+ namespace: NAMESPACE,
81
+ package: PACKAGE_NAME,
82
+ remove: remove
83
+ });
84
+
85
+ return { imports, prepend, replacements };
86
+ }
87
+ };