@esportsplus/template 0.39.0 → 0.40.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.
@@ -1,5 +1,5 @@
1
- import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES } from '../constants.js';
2
1
  import { ts } from '@esportsplus/typescript';
2
+ import { ENTRYPOINT, ENTRYPOINT_REACTIVITY, TYPES } from './constants.js';
3
3
  function isTypeFunction(type, checker) {
4
4
  if (type.isUnion()) {
5
5
  for (let i = 0, n = type.types.length; i < n; i++) {
@@ -16,17 +16,17 @@ const analyze = (expr, checker) => {
16
16
  expr = expr.expression;
17
17
  }
18
18
  if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
19
- return COMPILER_TYPES.Effect;
19
+ return TYPES.Effect;
20
20
  }
21
21
  if (ts.isCallExpression(expr) &&
22
22
  ts.isPropertyAccessExpression(expr.expression) &&
23
23
  ts.isIdentifier(expr.expression.expression) &&
24
- expr.expression.expression.text === COMPILER_ENTRYPOINT &&
25
- expr.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY) {
26
- return COMPILER_TYPES.ArraySlot;
24
+ expr.expression.expression.text === ENTRYPOINT &&
25
+ expr.expression.name.text === ENTRYPOINT_REACTIVITY) {
26
+ return TYPES.ArraySlot;
27
27
  }
28
- if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT) {
29
- return COMPILER_TYPES.DocumentFragment;
28
+ if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === ENTRYPOINT) {
29
+ return TYPES.DocumentFragment;
30
30
  }
31
31
  if (ts.isNumericLiteral(expr) ||
32
32
  ts.isStringLiteral(expr) ||
@@ -35,30 +35,29 @@ const analyze = (expr, checker) => {
35
35
  expr.kind === ts.SyntaxKind.FalseKeyword ||
36
36
  expr.kind === ts.SyntaxKind.NullKeyword ||
37
37
  expr.kind === ts.SyntaxKind.UndefinedKeyword) {
38
- return COMPILER_TYPES.Static;
38
+ return TYPES.Static;
39
39
  }
40
40
  if (ts.isTemplateExpression(expr)) {
41
- return COMPILER_TYPES.Primitive;
41
+ return TYPES.Primitive;
42
42
  }
43
43
  if (ts.isConditionalExpression(expr)) {
44
44
  let whenFalse = analyze(expr.whenFalse, checker), whenTrue = analyze(expr.whenTrue, checker);
45
45
  if (whenTrue === whenFalse) {
46
46
  return whenTrue;
47
47
  }
48
- if (whenTrue === COMPILER_TYPES.Effect || whenFalse === COMPILER_TYPES.Effect) {
49
- return COMPILER_TYPES.Effect;
48
+ if (whenTrue === TYPES.Effect || whenFalse === TYPES.Effect) {
49
+ return TYPES.Effect;
50
50
  }
51
- return COMPILER_TYPES.Unknown;
51
+ return TYPES.Unknown;
52
52
  }
53
53
  if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
54
54
  try {
55
- let type = checker.getTypeAtLocation(expr);
56
- if (isTypeFunction(type, checker)) {
57
- return COMPILER_TYPES.Effect;
55
+ if (isTypeFunction(checker.getTypeAtLocation(expr), checker)) {
56
+ return TYPES.Effect;
58
57
  }
59
58
  }
60
59
  catch { }
61
60
  }
62
- return COMPILER_TYPES.Unknown;
61
+ return TYPES.Unknown;
63
62
  };
64
63
  export { analyze };
@@ -1,14 +1,14 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
2
  import { imports } from '@esportsplus/typescript/compiler';
3
- import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '../constants.js';
3
+ import { ENTRYPOINT, ENTRYPOINT_REACTIVITY, PACKAGE_NAME } from './constants.js';
4
4
  function visitReactiveCalls(node, calls, checker) {
5
5
  if (ts.isCallExpression(node) &&
6
6
  ts.isPropertyAccessExpression(node.expression) &&
7
7
  ts.isIdentifier(node.expression.expression) &&
8
- node.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY &&
8
+ node.expression.name.text === ENTRYPOINT_REACTIVITY &&
9
9
  node.arguments.length === 2 &&
10
- node.expression.expression.text === COMPILER_ENTRYPOINT &&
11
- (!checker || imports.inPackage(checker, node.expression.expression, PACKAGE, COMPILER_ENTRYPOINT))) {
10
+ node.expression.expression.text === ENTRYPOINT &&
11
+ (!checker || imports.includes(checker, node.expression.expression, PACKAGE_NAME, ENTRYPOINT))) {
12
12
  calls.push({
13
13
  arrayArg: node.arguments[0],
14
14
  callbackArg: node.arguments[1],
@@ -25,8 +25,8 @@ function visitTemplates(node, depth, templates, checker) {
25
25
  : depth;
26
26
  if (ts.isTaggedTemplateExpression(node) &&
27
27
  ts.isIdentifier(node.tag) &&
28
- node.tag.text === COMPILER_ENTRYPOINT &&
29
- (!checker || imports.inPackage(checker, node.tag, PACKAGE, COMPILER_ENTRYPOINT))) {
28
+ node.tag.text === ENTRYPOINT &&
29
+ (!checker || imports.includes(checker, node.tag, PACKAGE_NAME, ENTRYPOINT))) {
30
30
  let { expressions, literals } = extractTemplateParts(node.template);
31
31
  templates.push({
32
32
  depth,
@@ -1,25 +1,10 @@
1
1
  declare const ARRAY_SLOT: unique symbol;
2
2
  declare const CLEANUP: unique symbol;
3
- declare const COMPILER_ENTRYPOINT = "html";
4
- declare const COMPILER_ENTRYPOINT_REACTIVITY = "reactive";
5
- declare const COMPILER_NAMESPACE: string;
6
- declare const enum COMPILER_TYPES {
7
- ArraySlot = "array-slot",
8
- Attributes = "attributes",
9
- Attribute = "attribute",
10
- DocumentFragment = "document-fragment",
11
- Effect = "effect",
12
- Node = "node",
13
- Primitive = "primitive",
14
- Static = "static",
15
- Unknown = "unknown"
16
- }
17
3
  declare const DIRECT_ATTACH_EVENTS: Set<string>;
18
4
  declare const LIFECYCLE_EVENTS: Set<string>;
19
- declare const PACKAGE = "@esportsplus/template";
20
5
  declare const SLOT_HTML = "<!--$-->";
21
6
  declare const STATE_HYDRATING = 0;
22
7
  declare const STATE_NONE = 1;
23
8
  declare const STATE_WAITING = 2;
24
9
  declare const STORE: unique symbol;
25
- export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
10
+ export { ARRAY_SLOT, CLEANUP, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
@@ -1,22 +1,5 @@
1
- import { uid } from '@esportsplus/typescript/compiler';
2
1
  const ARRAY_SLOT = Symbol('template.array.slot');
3
2
  const CLEANUP = Symbol('template.cleanup');
4
- const COMPILER_ENTRYPOINT = 'html';
5
- const COMPILER_ENTRYPOINT_REACTIVITY = 'reactive';
6
- const COMPILER_NAMESPACE = uid('template');
7
- var COMPILER_TYPES;
8
- (function (COMPILER_TYPES) {
9
- COMPILER_TYPES["ArraySlot"] = "array-slot";
10
- COMPILER_TYPES["Attributes"] = "attributes";
11
- COMPILER_TYPES["Attribute"] = "attribute";
12
- COMPILER_TYPES["DocumentFragment"] = "document-fragment";
13
- COMPILER_TYPES["Effect"] = "effect";
14
- COMPILER_TYPES["Node"] = "node";
15
- COMPILER_TYPES["Primitive"] = "primitive";
16
- COMPILER_TYPES["Static"] = "static";
17
- COMPILER_TYPES["Unknown"] = "unknown";
18
- })(COMPILER_TYPES || (COMPILER_TYPES = {}));
19
- ;
20
3
  const DIRECT_ATTACH_EVENTS = new Set([
21
4
  'onblur',
22
5
  'onerror',
@@ -29,10 +12,9 @@ const DIRECT_ATTACH_EVENTS = new Set([
29
12
  const LIFECYCLE_EVENTS = new Set([
30
13
  'onconnect', 'ondisconnect', 'onrender', 'onresize', 'ontick'
31
14
  ]);
32
- const PACKAGE = '@esportsplus/template';
33
15
  const SLOT_HTML = '<!--$-->';
34
16
  const STATE_HYDRATING = 0;
35
17
  const STATE_NONE = 1;
36
18
  const STATE_WAITING = 2;
37
19
  const STORE = Symbol('template.store');
38
- export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
20
+ export { ARRAY_SLOT, CLEANUP, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/queue": "^0.2.0",
5
- "@esportsplus/reactivity": "^0.29.1",
6
- "@esportsplus/typescript": "^0.26.0",
5
+ "@esportsplus/reactivity": "^0.29.9",
6
+ "@esportsplus/typescript": "^0.27.0",
7
7
  "@esportsplus/utilities": "^0.27.2",
8
8
  "serve": "^14.2.5"
9
9
  },
@@ -17,6 +17,10 @@
17
17
  "types": "./build/index.d.ts",
18
18
  "default": "./build/index.js"
19
19
  },
20
+ "./compiler": {
21
+ "types": "./build/compiler/index.d.ts",
22
+ "default": "./build/compiler/index.js"
23
+ },
20
24
  "./compiler/tsc": {
21
25
  "types": "./build/compiler/plugins/tsc.d.ts",
22
26
  "default": "./build/compiler/plugins/tsc.js"
@@ -35,7 +39,7 @@
35
39
  },
36
40
  "type": "module",
37
41
  "types": "./build/index.d.ts",
38
- "version": "0.39.0",
42
+ "version": "0.40.1",
39
43
  "scripts": {
40
44
  "build": "tsc",
41
45
  "build:test": "vite build --config test/vite.config.ts",
@@ -1,10 +1,11 @@
1
1
  import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
2
- import { ts } from '@esportsplus/typescript';
3
- import { ast, uid } from '@esportsplus/typescript/compiler';
4
- import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS } from '~/constants';
5
- import { extractTemplateParts } from './ts-parser';
6
2
  import type { TemplateInfo } from './ts-parser';
7
3
  import { analyze } from './ts-analyzer';
4
+ import { ast, uid } from '@esportsplus/typescript/compiler';
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';
8
9
  import parser from './parser';
9
10
 
10
11
 
@@ -14,7 +15,7 @@ type Attribute = {
14
15
  statics: Record<string, string>;
15
16
  };
16
17
  path: string[];
17
- type: COMPILER_TYPES.Attribute;
18
+ type: TYPES.Attribute;
18
19
  };
19
20
 
20
21
  type CodegenContext = {
@@ -31,7 +32,7 @@ type CodegenResult = {
31
32
 
32
33
  type Node = {
33
34
  path: string[];
34
- type: COMPILER_TYPES.Node;
35
+ type: TYPES.Node;
35
36
  };
36
37
 
37
38
  type ParseResult = {
@@ -43,31 +44,74 @@ type ParseResult = {
43
44
  let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
44
45
 
45
46
 
47
+ function collectNestedReplacements(ctx: CodegenContext, node: ts.Node, replacements: { end: number; start: number; text: string }[]): void {
48
+ if (isNestedHtmlTemplate(node as ts.Expression)) {
49
+ replacements.push({
50
+ end: node.end,
51
+ start: node.getStart(ctx.sourceFile),
52
+ text: generateNestedTemplateCode(ctx, node as ts.TaggedTemplateExpression)
53
+ });
54
+
55
+ return;
56
+ }
57
+
58
+ if (isReactiveCall(node as ts.Expression)) {
59
+ let call = node as ts.CallExpression;
60
+
61
+ replacements.push({
62
+ end: node.end,
63
+ start: node.getStart(ctx.sourceFile),
64
+ text: `new ${NAMESPACE}.ArraySlot(${rewriteExpression(ctx, call.arguments[0] as ts.Expression)}, ${rewriteExpression(ctx, call.arguments[1] as ts.Expression)})`
65
+ });
66
+
67
+ return;
68
+ }
69
+
70
+ ts.forEachChild(node, child => collectNestedReplacements(ctx, child, replacements));
71
+ }
72
+
73
+ function discoverTemplatesInExpression(ctx: CodegenContext, node: ts.Node): void {
74
+ if (isNestedHtmlTemplate(node as ts.Expression)) {
75
+ let { expressions, literals } = extractTemplateParts((node as ts.TaggedTemplateExpression).template),
76
+ parsed = parser.parse(literals) as ParseResult;
77
+
78
+ getOrCreateTemplateId(ctx, parsed.html);
79
+
80
+ for (let i = 0, n = expressions.length; i < n; i++) {
81
+ discoverTemplatesInExpression(ctx, expressions[i]);
82
+ }
83
+
84
+ return;
85
+ }
86
+
87
+ ts.forEachChild(node, child => discoverTemplatesInExpression(ctx, child));
88
+ }
89
+
46
90
  function generateAttributeBinding(element: string, name: string, expr: string, staticValue: string): string {
47
91
  if (name.startsWith('on') && name.length > 2) {
48
92
  let event = name.slice(2).toLowerCase(),
49
93
  key = name.toLowerCase();
50
94
 
51
95
  if (LIFECYCLE_EVENTS.has(key)) {
52
- return `${COMPILER_NAMESPACE}.${key}(${element}, ${expr});`;
96
+ return `${NAMESPACE}.${key}(${element}, ${expr});`;
53
97
  }
54
98
 
55
99
  if (DIRECT_ATTACH_EVENTS.has(key)) {
56
- return `${COMPILER_NAMESPACE}.on(${element}, '${event}', ${expr});`;
100
+ return `${NAMESPACE}.on(${element}, '${event}', ${expr});`;
57
101
  }
58
102
 
59
- return `${COMPILER_NAMESPACE}.delegate(${element}, '${event}', ${expr});`;
103
+ return `${NAMESPACE}.delegate(${element}, '${event}', ${expr});`;
60
104
  }
61
105
 
62
106
  if (name === 'class') {
63
- return `${COMPILER_NAMESPACE}.setClass(${element}, '${staticValue}', ${expr});`;
107
+ return `${NAMESPACE}.setClass(${element}, '${staticValue}', ${expr});`;
64
108
  }
65
109
 
66
110
  if (name === 'style') {
67
- return `${COMPILER_NAMESPACE}.setStyle(${element}, '${staticValue}', ${expr});`;
111
+ return `${NAMESPACE}.setStyle(${element}, '${staticValue}', ${expr});`;
68
112
  }
69
113
 
70
- return `${COMPILER_NAMESPACE}.setProperty(${element}, '${name}', ${expr});`;
114
+ return `${NAMESPACE}.setProperty(${element}, '${name}', ${expr});`;
71
115
  }
72
116
 
73
117
  function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplateExpression): string {
@@ -89,7 +133,7 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
89
133
 
90
134
  function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
91
135
  if (!exprNode) {
92
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
136
+ return `${NAMESPACE}.slot(${anchor}, ${exprText});`;
93
137
  }
94
138
 
95
139
  if (isNestedHtmlTemplate(exprNode)) {
@@ -97,20 +141,20 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
97
141
  }
98
142
 
99
143
  switch (analyze(exprNode, ctx.checker)) {
100
- case COMPILER_TYPES.ArraySlot:
101
- return `${anchor}.parentNode!.insertBefore(new ${COMPILER_NAMESPACE}.ArraySlot(${exprText}).fragment, ${anchor});`;
144
+ case TYPES.ArraySlot:
145
+ return `${anchor}.parentNode!.insertBefore(new ${NAMESPACE}.ArraySlot(${exprText}).fragment, ${anchor});`;
102
146
 
103
- case COMPILER_TYPES.DocumentFragment:
147
+ case TYPES.DocumentFragment:
104
148
  return `${anchor}.parentNode!.insertBefore(${exprText}, ${anchor});`;
105
149
 
106
- case COMPILER_TYPES.Effect:
107
- return `new ${COMPILER_NAMESPACE}.EffectSlot(${anchor}, ${exprText});`;
150
+ case TYPES.Effect:
151
+ return `new ${NAMESPACE}.EffectSlot(${anchor}, ${exprText});`;
108
152
 
109
- case COMPILER_TYPES.Static:
153
+ case TYPES.Static:
110
154
  return `${anchor}.textContent = ${exprText};`;
111
155
 
112
156
  default:
113
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
157
+ return `${NAMESPACE}.slot(${anchor}, ${exprText});`;
114
158
  }
115
159
  }
116
160
 
@@ -166,17 +210,15 @@ function generateTemplateCode(
166
210
  value = `${ancestor}.${segments.join('!.')}`;
167
211
 
168
212
  if (ancestor === root && segments[0] === 'firstChild') {
169
- value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${COMPILER_NAMESPACE}.Element)`);
213
+ value = value.replace(`${ancestor}.firstChild!`, `(${root}.firstChild! as ${NAMESPACE}.Element)`);
170
214
  }
171
215
 
172
- declarations.push(`${name} = ${value} as ${COMPILER_NAMESPACE}.Element`);
216
+ declarations.push(`${name} = ${value} as ${NAMESPACE}.Element`);
173
217
  nodes.set(key, name);
174
218
  }
175
219
 
176
- code.push(
177
- isArrowBody ? '{' : `(() => {`,
178
- `let ${declarations.join(',\n')};`
179
- );
220
+ code.push(isArrowBody ? '{' : `(() => {`);
221
+ code.push(`let ${declarations.join(',\n')};`);
180
222
 
181
223
  for (let i = 0, n = slots.length; i < n; i++) {
182
224
  let element = slots[i].path.length === 0
@@ -184,15 +226,15 @@ function generateTemplateCode(
184
226
  : (nodes.get(slots[i].path.join('.')) || root),
185
227
  slot = slots[i];
186
228
 
187
- if (slot.type === COMPILER_TYPES.Attribute) {
229
+ if (slot.type === TYPES.Attribute) {
188
230
  let names = slot.attributes.names;
189
231
 
190
232
  for (let j = 0, m = names.length; j < m; j++) {
191
233
  let name = names[j];
192
234
 
193
- if (name === COMPILER_TYPES.Attributes) {
235
+ if (name === TYPES.Attributes) {
194
236
  code.push(
195
- `${COMPILER_NAMESPACE}.setProperties(${element}, ${exprTexts[index] || 'undefined'});`
237
+ `${NAMESPACE}.setProperties(${element}, ${exprTexts[index] || 'undefined'});`
196
238
  );
197
239
  index++;
198
240
  }
@@ -233,21 +275,21 @@ function getOrCreateTemplateId(ctx: CodegenContext, html: string): string {
233
275
  return id;
234
276
  }
235
277
 
236
- function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
237
- return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
238
- }
239
-
240
278
  function isArrowExpressionBody(node: ts.Node): boolean {
241
279
  return ts.isArrowFunction(node.parent) && (node.parent as ts.ArrowFunction).body === node;
242
280
  }
243
281
 
282
+ function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
283
+ return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === ENTRYPOINT;
284
+ }
285
+
244
286
  function isReactiveCall(expr: ts.Expression): expr is ts.CallExpression {
245
287
  return (
246
288
  ts.isCallExpression(expr) &&
247
289
  ts.isPropertyAccessExpression(expr.expression) &&
248
290
  ts.isIdentifier(expr.expression.expression) &&
249
- expr.expression.expression.text === COMPILER_ENTRYPOINT &&
250
- expr.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY
291
+ expr.expression.expression.text === ENTRYPOINT &&
292
+ expr.expression.name.text === ENTRYPOINT_REACTIVITY
251
293
  );
252
294
  }
253
295
 
@@ -256,138 +298,29 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
256
298
  return generateNestedTemplateCode(ctx, expr);
257
299
  }
258
300
 
259
- // Returns just args "array, callback" - for direct slot usage where generateNodeBinding wraps it
260
301
  if (isReactiveCall(expr)) {
261
- return `
262
- ${printer.printNode(ts.EmitHint.Expression, expr.arguments[0], ctx.sourceFile)},
263
- ${rewriteExpression(ctx, expr.arguments[1] as ts.Expression)}
264
- `;
302
+ return `${rewriteExpression(ctx, expr.arguments[0] as ts.Expression)}, ${rewriteExpression(ctx, expr.arguments[1] as ts.Expression)}`;
265
303
  }
266
304
 
267
- if (!ast.hasMatch(expr, n => isNestedHtmlTemplate(n as ts.Expression) || isReactiveCall(n as ts.Expression))) {
305
+ if (!ast.test(expr, n => isNestedHtmlTemplate(n as ts.Expression) || isReactiveCall(n as ts.Expression))) {
268
306
  return printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
269
307
  }
270
308
 
271
- return walkExpression(ctx, expr);
272
- }
309
+ let exprStart = expr.getStart(ctx.sourceFile),
310
+ replacements: { end: number; start: number; text: string }[] = [],
311
+ text = expr.getText(ctx.sourceFile);
273
312
 
274
- function walkExpression(ctx: CodegenContext, node: ts.Node): string {
275
- // Intercept special nodes
276
- if (isNestedHtmlTemplate(node as ts.Expression)) {
277
- return generateNestedTemplateCode(ctx, node as ts.TaggedTemplateExpression);
278
- }
313
+ ts.forEachChild(expr, child => collectNestedReplacements(ctx, child, replacements));
279
314
 
280
- if (isReactiveCall(node as ts.Expression)) {
281
- let call = node as ts.CallExpression;
282
-
283
- return `new ${COMPILER_NAMESPACE}.ArraySlot(${printer.printNode(ts.EmitHint.Expression, call.arguments[0], ctx.sourceFile)}, ${rewriteExpression(ctx, call.arguments[1] as ts.Expression)})`;
284
- }
285
-
286
- // No nested constructs - use printer
287
- if (!ast.hasMatch(node, n => isNestedHtmlTemplate(n as ts.Expression) || isReactiveCall(n as ts.Expression))) {
288
- return printer.printNode(ts.EmitHint.Expression, node as ts.Expression, ctx.sourceFile);
289
- }
315
+ replacements.sort((a, b) => b.start - a.start);
290
316
 
291
- // Recursive handling by node type
292
- if (ts.isConditionalExpression(node)) {
293
- return `${walkExpression(ctx, node.condition)} ? ${walkExpression(ctx, node.whenTrue)} : ${walkExpression(ctx, node.whenFalse)}`;
294
- }
295
-
296
- if (ts.isBinaryExpression(node)) {
297
- return `${walkExpression(ctx, node.left)} ${node.operatorToken.getText(ctx.sourceFile)} ${walkExpression(ctx, node.right)}`;
298
- }
299
-
300
- if (ts.isCallExpression(node)) {
301
- let args: string[] = [];
302
-
303
- for (let i = 0, n = node.arguments.length; i < n; i++) {
304
- args.push(walkExpression(ctx, node.arguments[i]));
305
- }
317
+ for (let i = 0, n = replacements.length; i < n; i++) {
318
+ let r = replacements[i];
306
319
 
307
- return `${walkExpression(ctx, node.expression)}(${args.join(', ')})`;
320
+ text = text.slice(0, r.start - exprStart) + r.text + text.slice(r.end - exprStart);
308
321
  }
309
322
 
310
- if (ts.isPropertyAccessExpression(node)) {
311
- return `${walkExpression(ctx, node.expression)}.${node.name.text}`;
312
- }
313
-
314
- if (ts.isElementAccessExpression(node)) {
315
- return `${walkExpression(ctx, node.expression)}[${walkExpression(ctx, node.argumentExpression)}]`;
316
- }
317
-
318
- if (ts.isParenthesizedExpression(node)) {
319
- return `(${walkExpression(ctx, node.expression)})`;
320
- }
321
-
322
- if (ts.isArrowFunction(node)) {
323
- return `(${node.parameters.map(p => p.getText(ctx.sourceFile)).join(', ')}) => ${
324
- ts.isBlock(node.body) ? node.body.getText(ctx.sourceFile) : walkExpression(ctx, node.body)
325
- }`;
326
- }
327
-
328
- if (ts.isArrayLiteralExpression(node)) {
329
- let elements: string[] = [];
330
-
331
- for (let i = 0, n = node.elements.length; i < n; i++) {
332
- elements.push( walkExpression(ctx, node.elements[i]) );
333
- }
334
-
335
- return `[${elements.join(', ')}]`;
336
- }
337
-
338
- if (ts.isObjectLiteralExpression(node)) {
339
- let properties: string[] = [];
340
-
341
- for (let i = 0, n = node.properties.length; i < n; i++) {
342
- let prop = node.properties[i];
343
-
344
- if (ts.isPropertyAssignment(prop)) {
345
- properties.push(`${prop.name.getText(ctx.sourceFile)}: ${walkExpression(ctx, prop.initializer)}`);
346
- }
347
- else if (ts.isShorthandPropertyAssignment(prop)) {
348
- properties.push(prop.name.text);
349
- }
350
- else if (ts.isSpreadAssignment(prop)) {
351
- properties.push(`...${walkExpression(ctx, prop.expression)}`);
352
- }
353
- else {
354
- properties.push(prop.getText(ctx.sourceFile));
355
- }
356
- }
357
-
358
- return `{ ${properties.join(', ')} }`;
359
- }
360
-
361
- if (ts.isPrefixUnaryExpression(node)) {
362
- return `${ts.tokenToString(node.operator)}${walkExpression(ctx, node.operand)}`;
363
- }
364
-
365
- if (ts.isPostfixUnaryExpression(node)) {
366
- return `${walkExpression(ctx, node.operand)}${ts.tokenToString(node.operator)}`;
367
- }
368
-
369
- if (ts.isTemplateExpression(node)) {
370
- let parts = [node.head.text];
371
-
372
- for (let i = 0, n = node.templateSpans.length; i < n; i++) {
373
- let span = node.templateSpans[i];
374
-
375
- parts.push('${' + walkExpression(ctx, span.expression) + '}' + span.literal.text);
376
- }
377
-
378
- return '`' + parts.join('') + '`';
379
- }
380
-
381
- if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
382
- return walkExpression(ctx, node.expression);
383
- }
384
-
385
- if (ts.isNonNullExpression(node)) {
386
- return `${walkExpression(ctx, node.expression)}!`;
387
- }
388
-
389
- // Fallback - use printer
390
- return printer.printNode(ts.EmitHint.Expression, node as ts.Expression, ctx.sourceFile);
323
+ return text;
391
324
  }
392
325
 
393
326
 
@@ -412,9 +345,9 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
412
345
  }
413
346
  }
414
347
 
415
- let rootTemplates = templates.filter(t => !ast.inRange(ranges, t.node.getStart(sourceFile), t.node.end));
348
+ let root = templates.filter(t => !ast.inRange(ranges, t.node.getStart(sourceFile), t.node.end));
416
349
 
417
- if (rootTemplates.length === 0) {
350
+ if (root.length === 0) {
418
351
  return result;
419
352
  }
420
353
 
@@ -424,46 +357,47 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
424
357
  templates: result.templates
425
358
  };
426
359
 
427
- for (let i = 0, n = rootTemplates.length; i < n; i++) {
428
- let template = rootTemplates[i];
429
-
430
- result.replacements.push({
431
- generate: (sf) => {
432
- let exprTexts: string[] = [],
433
- localCtx: CodegenContext = {
434
- checker,
435
- sourceFile: sf,
436
- templates: ctx.templates
437
- },
438
- parsed = parser.parse(template.literals) as ParseResult;
439
-
440
- for (let j = 0, m = template.expressions.length; j < m; j++) {
441
- exprTexts.push(rewriteExpression(localCtx, template.expressions[j]));
442
- }
360
+ for (let i = 0, n = root.length; i < n; i++) {
361
+ let exprTexts: string[] = [],
362
+ parsed = parser.parse(root[i].literals) as ParseResult,
363
+ template = root[i];
443
364
 
444
- // Zero-param arrow with no slots just return template ID
445
- if (
446
- isArrowExpressionBody(template.node) &&
447
- (template.node.parent as ts.ArrowFunction).parameters.length === 0 &&
448
- (!parsed.slots || parsed.slots.length === 0)
449
- ) {
450
- return getOrCreateTemplateId(localCtx, parsed.html);
451
- }
365
+ for (let j = 0, m = template.expressions.length; j < m; j++) {
366
+ exprTexts.push(rewriteExpression(ctx, template.expressions[j]));
367
+ }
452
368
 
453
- return generateTemplateCode(
454
- localCtx,
455
- parsed,
456
- exprTexts,
457
- template.expressions,
458
- template.node
459
- );
460
- },
461
- node: template.node
462
- });
369
+ if (
370
+ isArrowExpressionBody(template.node) &&
371
+ (template.node.parent as ts.ArrowFunction).parameters.length === 0 &&
372
+ (!parsed.slots || parsed.slots.length === 0)
373
+ ) {
374
+ let code = getOrCreateTemplateId(ctx, parsed.html);
375
+
376
+ result.replacements.push({
377
+ generate: () => code,
378
+ node: template.node
379
+ });
380
+ }
381
+ else {
382
+ let code = generateTemplateCode(ctx, parsed, exprTexts, template.expressions, template.node);
383
+
384
+ result.replacements.push({
385
+ generate: () => code,
386
+ node: template.node
387
+ });
388
+ }
389
+ }
390
+
391
+ for (let i = 0, n = templates.length; i < n; i++) {
392
+ getOrCreateTemplateId(ctx, parser.parse(templates[i].literals).html);
393
+
394
+ for (let j = 0, m = templates[i].expressions.length; j < m; j++) {
395
+ discoverTemplatesInExpression(ctx, templates[i].expressions[j]);
396
+ }
463
397
  }
464
398
 
465
399
  for (let [html, id] of ctx.templates) {
466
- result.prepend.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
400
+ result.prepend.push(`const ${id} = ${NAMESPACE}.template(\`${html}\`);`);
467
401
  }
468
402
 
469
403
  return result;