@esportsplus/template 0.35.0 → 0.35.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,6 +1,6 @@
1
1
  import { Attributes, Element } from './types.js';
2
- declare const setClass: (element: Element, classlist: false | string | undefined, value: unknown) => void;
2
+ declare const setClass: (element: Element, classlist: false | string, value: unknown) => void;
3
3
  declare const setProperty: (element: Element, name: string, value: unknown) => void;
4
- declare const setProperties: (element: Element, value: Attributes | Attributes[]) => void;
5
- declare const setStyle: (element: Element, styles: false | string | undefined, value: unknown) => void;
4
+ declare const setProperties: (element: Element, value?: Attributes | Attributes[] | false | null | undefined) => void;
5
+ declare const setStyle: (element: Element, styles: false | string, value: unknown) => void;
6
6
  export { setClass, setProperty, setProperties, setStyle };
@@ -199,7 +199,10 @@ const setProperty = (element, name, value) => {
199
199
  }
200
200
  };
201
201
  const setProperties = function (element, value) {
202
- if (isObject(value)) {
202
+ if (!value) {
203
+ return;
204
+ }
205
+ else if (isObject(value)) {
203
206
  for (let name in value) {
204
207
  let v = value[name];
205
208
  if (v == null || v === false || v === '') {
@@ -4,8 +4,7 @@ type CodegenResult = {
4
4
  changed: boolean;
5
5
  code: string;
6
6
  };
7
- declare const generateCode: (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => CodegenResult;
8
- declare const generateReactiveInlining: (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile) => string;
9
- declare const needsArraySlotImport: (sourceFile: ts.SourceFile) => boolean;
10
- export { generateCode, generateReactiveInlining, needsArraySlotImport };
7
+ declare const generateCode: (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker, existingAliases?: Map<string, string>) => CodegenResult;
8
+ declare const generateReactiveInlining: (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile, arraySlotAlias: string) => string;
9
+ export { generateCode, generateReactiveInlining };
11
10
  export type { CodegenResult };
@@ -5,6 +5,14 @@ import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } f
5
5
  import parser from './parser.js';
6
6
  const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
7
7
  let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
8
+ function addImport(ctx, name) {
9
+ let alias = ctx.imports.get(name);
10
+ if (!alias) {
11
+ alias = uid(name);
12
+ ctx.imports.set(name, alias);
13
+ }
14
+ return alias;
15
+ }
8
16
  function collectNestedTemplateReplacements(ctx, node, exprStart, replacements) {
9
17
  if (isNestedHtmlTemplate(node)) {
10
18
  replacements.push({
@@ -35,8 +43,7 @@ function generateNestedTemplateCode(ctx, node) {
35
43
  }
36
44
  function generateNodeBinding(ctx, anchor, exprText, exprNode) {
37
45
  if (!exprNode) {
38
- ctx.neededImports.add('slot');
39
- return `slot(${anchor}, ${exprText});`;
46
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
40
47
  }
41
48
  if (isNestedHtmlTemplate(exprNode)) {
42
49
  return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
@@ -44,18 +51,15 @@ function generateNodeBinding(ctx, anchor, exprText, exprNode) {
44
51
  let slotType = analyzeExpression(exprNode, ctx.checker);
45
52
  switch (slotType) {
46
53
  case COMPILER_TYPES.Effect:
47
- ctx.neededImports.add('EffectSlot');
48
- return `new EffectSlot(${anchor}, ${exprText});`;
54
+ return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
49
55
  case COMPILER_TYPES.ArraySlot:
50
- ctx.neededImports.add('ArraySlot');
51
- return `new ArraySlot(${anchor}, ${exprText});`;
56
+ return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
52
57
  case COMPILER_TYPES.Static:
53
58
  return `${anchor}.textContent = ${exprText};`;
54
59
  case COMPILER_TYPES.DocumentFragment:
55
60
  return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
56
61
  default:
57
- ctx.neededImports.add('slot');
58
- return `slot(${anchor}, ${exprText});`;
62
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
59
63
  }
60
64
  }
61
65
  function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArrowBody) {
@@ -74,23 +78,25 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
74
78
  if (nodes.has(key)) {
75
79
  continue;
76
80
  }
77
- let ancestorVar = root, startIdx = 0;
81
+ let ancestor = root, start = 0;
78
82
  for (let j = path.length - 1; j >= 0; j--) {
79
83
  let prefix = path.slice(0, j).join('.');
80
84
  if (nodes.has(prefix)) {
81
- ancestorVar = nodes.get(prefix);
82
- startIdx = j;
85
+ ancestor = nodes.get(prefix);
86
+ start = j;
83
87
  break;
84
88
  }
85
89
  }
86
- let name = uid('element'), suffix = path.slice(startIdx).join('.');
87
- ctx.neededImports.add('Element');
88
- declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
90
+ let alias = addImport(ctx, 'Element'), name = uid('element'), segments = path.slice(start), value = `${ancestor}.${segments.join('!.')}`;
91
+ if (ancestor === root && segments[0] === 'firstChild') {
92
+ value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${alias})`);
93
+ }
94
+ declarations.push(`${name} = ${value} as ${alias}`);
89
95
  nodes.set(key, name);
90
96
  }
91
97
  code.push(isArrowBody ? '{' : `(() => {`, `let ${declarations.join(',\n')};`);
92
98
  for (let i = 0, n = slots.length; i < n; i++) {
93
- let elementVar = slots[i].path.length === 0
99
+ let element = slots[i].path.length === 0
94
100
  ? root
95
101
  : (nodes.get(slots[i].path.join('.')) || root), slot = slots[i];
96
102
  if (slot.type === COMPILER_TYPES.Attribute) {
@@ -98,19 +104,19 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
98
104
  for (let j = 0, m = names.length; j < m; j++) {
99
105
  let name = names[j];
100
106
  if (name === COMPILER_TYPES.Attributes) {
101
- let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker, ctx.neededImports);
107
+ let bindings = generateSpreadBindings(exprTexts[index] || 'undefined', element, n => addImport(ctx, n));
102
108
  for (let k = 0, o = bindings.length; k < o; k++) {
103
109
  code.push(bindings[k]);
104
110
  }
105
111
  index++;
106
112
  }
107
113
  else {
108
- code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', ctx.neededImports));
114
+ code.push(generateAttributeBinding(element, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', n => addImport(ctx, n)));
109
115
  }
110
116
  }
111
117
  }
112
118
  else {
113
- code.push(generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index]));
119
+ code.push(generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index]));
114
120
  index++;
115
121
  }
116
122
  }
@@ -119,32 +125,13 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
119
125
  return code.join('\n');
120
126
  }
121
127
  function getOrCreateTemplateId(ctx, html) {
122
- let id = ctx.htmlToTemplateId.get(html);
128
+ let id = ctx.templates.get(html);
123
129
  if (!id) {
124
- id = uid('tmpl');
125
- ctx.hoistedFactories.set(id, html);
126
- ctx.htmlToTemplateId.set(html, id);
130
+ id = uid('template');
131
+ ctx.templates.set(html, id);
127
132
  }
128
133
  return id;
129
134
  }
130
- function hasArraySlotImport(sourceFile) {
131
- for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
132
- let stmt = sourceFile.statements[i];
133
- if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
134
- continue;
135
- }
136
- let bindings = stmt.importClause.namedBindings;
137
- if (!ts.isNamedImports(bindings)) {
138
- continue;
139
- }
140
- for (let j = 0, m = bindings.elements.length; j < m; j++) {
141
- if (bindings.elements[j].name.text === 'ArraySlot') {
142
- return true;
143
- }
144
- }
145
- }
146
- return false;
147
- }
148
135
  function isNestedHtmlTemplate(expr) {
149
136
  return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
150
137
  }
@@ -159,7 +146,7 @@ function rewriteExpression(ctx, expr) {
159
146
  collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
160
147
  return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
161
148
  }
162
- const generateCode = (templates, originalCode, sourceFile, checker) => {
149
+ const generateCode = (templates, originalCode, sourceFile, checker, existingAliases) => {
163
150
  if (templates.length === 0) {
164
151
  return { changed: false, code: originalCode };
165
152
  }
@@ -176,12 +163,11 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
176
163
  }
177
164
  let ctx = {
178
165
  checker,
179
- hoistedFactories: new Map(),
180
- htmlToTemplateId: new Map(),
181
- neededImports: new Set(['template']),
166
+ templates: new Map(),
167
+ imports: existingAliases ?? new Map(),
182
168
  printer,
183
169
  sourceFile
184
- }, replacements = [];
170
+ }, replacements = [], templateAlias = addImport(ctx, 'template');
185
171
  for (let i = 0, n = rootTemplates.length; i < n; i++) {
186
172
  let exprTexts = [], template = rootTemplates[i];
187
173
  for (let j = 0, m = template.expressions.length; j < m; j++) {
@@ -206,19 +192,23 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
206
192
  });
207
193
  }
208
194
  let changed = replacements.length > 0, code = c.replaceReverse(originalCode, replacements);
209
- if (changed && ctx.hoistedFactories.size > 0) {
210
- let factories = [];
211
- for (let [id, html] of ctx.hoistedFactories) {
212
- factories.push(`const ${id} = template(\`${html}\`);`);
195
+ if (changed && ctx.templates.size > 0) {
196
+ let aliasedImports = [], factories = [];
197
+ for (let [name, alias] of ctx.imports) {
198
+ aliasedImports.push(`${name} as ${alias}`);
199
+ }
200
+ for (let [html, id] of ctx.templates) {
201
+ factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
213
202
  }
214
- code = imports.modify(factories.join('\n') + code, sourceFile, PACKAGE, {
215
- add: ctx.neededImports,
203
+ code = imports.modify(code, sourceFile, PACKAGE, {
204
+ add: new Set(aliasedImports),
216
205
  remove: [COMPILER_ENTRYPOINT]
217
206
  });
207
+ code = factories.join('\n') + '\n\n' + code;
218
208
  }
219
209
  return { changed, code };
220
210
  };
221
- const generateReactiveInlining = (calls, code, sourceFile) => {
211
+ const generateReactiveInlining = (calls, code, sourceFile, arraySlotAlias) => {
222
212
  if (calls.length === 0) {
223
213
  return code;
224
214
  }
@@ -227,7 +217,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
227
217
  let call = calls[i];
228
218
  replacements.push({
229
219
  end: call.end,
230
- newText: `new ArraySlot(
220
+ newText: `new ${arraySlotAlias}(
231
221
  ${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
232
222
  ${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
233
223
  )`,
@@ -236,9 +226,4 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
236
226
  }
237
227
  return c.replaceReverse(code, replacements);
238
228
  };
239
- const needsArraySlotImport = (sourceFile) => {
240
- return ast.hasMatch(sourceFile, n => ts.isNewExpression(n) &&
241
- ts.isIdentifier(n.expression) &&
242
- n.expression.text === 'ArraySlot') && !hasArraySlotImport(sourceFile);
243
- };
244
- export { generateCode, generateReactiveInlining, needsArraySlotImport };
229
+ export { generateCode, generateReactiveInlining };
@@ -1,7 +1,7 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
- import { code as c, imports } from '@esportsplus/typescript/compiler';
2
+ import { code as c, imports, uid } from '@esportsplus/typescript/compiler';
3
3
  import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '../constants.js';
4
- import { generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen.js';
4
+ import { generateCode, generateReactiveInlining } from './codegen.js';
5
5
  import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
6
6
  const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
7
7
  const REGEX_BACKSLASH = /\\/g;
@@ -18,24 +18,29 @@ const transform = (sourceFile, program) => {
18
18
  checker = program.getTypeChecker();
19
19
  sourceFile = programSourceFile;
20
20
  }
21
- let changed = false, codegenChanged = false, needsImport = false, reactiveCalls = findReactiveCalls(sourceFile, checker), result = code;
21
+ let changed = false, codegenChanged = false, existingAliases = new Map(), reactiveCalls = findReactiveCalls(sourceFile, checker), result = code;
22
22
  if (reactiveCalls.length > 0) {
23
+ let arraySlotAlias = uid('ArraySlot');
23
24
  changed = true;
24
- result = generateReactiveInlining(reactiveCalls, result, sourceFile);
25
+ existingAliases.set('ArraySlot', arraySlotAlias);
26
+ result = generateReactiveInlining(reactiveCalls, result, sourceFile, arraySlotAlias);
25
27
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
26
- needsImport = needsArraySlotImport(sourceFile);
27
28
  }
28
29
  let templates = findHtmlTemplates(sourceFile, checker);
29
30
  if (templates.length > 0) {
30
- let codegenResult = generateCode(templates, result, sourceFile, checker);
31
+ let codegenResult = generateCode(templates, result, sourceFile, checker, existingAliases);
31
32
  if (codegenResult.changed) {
32
33
  changed = true;
33
34
  codegenChanged = true;
34
35
  result = codegenResult.code;
35
36
  }
36
37
  }
37
- if (needsImport && !codegenChanged) {
38
- result = imports.modify(result, sourceFile, PACKAGE, { add: ['ArraySlot'] });
38
+ if (existingAliases.size > 0 && !codegenChanged) {
39
+ let aliasedImports = [];
40
+ for (let [name, alias] of existingAliases) {
41
+ aliasedImports.push(`${name} as ${alias}`);
42
+ }
43
+ result = imports.modify(result, sourceFile, PACKAGE, { add: aliasedImports });
39
44
  }
40
45
  if (changed) {
41
46
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
@@ -1,6 +1,6 @@
1
1
  import { COMPILER_TYPES } from '../constants.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
3
  declare const analyzeExpression: (expr: ts.Expression, checker?: ts.TypeChecker) => COMPILER_TYPES;
4
- declare const generateAttributeBinding: (elementVar: string, name: string, expr: string, staticValue: string, neededImports: Set<string>) => string;
5
- declare const generateSpreadBindings: (expr: ts.Expression, exprCode: string, elementVar: string, checker: ts.TypeChecker | undefined, neededImports: Set<string>) => string[];
4
+ declare const generateAttributeBinding: (elementVar: string, name: string, expr: string, staticValue: string, addImport: (name: string) => string) => string;
5
+ declare const generateSpreadBindings: (exprCode: string, elementVar: string, addImport: (name: string) => string) => string[];
6
6
  export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
@@ -1,45 +1,5 @@
1
1
  import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS } from '../constants.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
- function analyzeSpread(expr, checker) {
4
- while (ts.isParenthesizedExpression(expr)) {
5
- expr = expr.expression;
6
- }
7
- if (ts.isObjectLiteralExpression(expr)) {
8
- let keys = [];
9
- for (let i = 0, n = expr.properties.length; i < n; i++) {
10
- let prop = expr.properties[i];
11
- if (ts.isPropertyAssignment(prop)) {
12
- if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
13
- keys.push(prop.name.text);
14
- }
15
- }
16
- else if (ts.isShorthandPropertyAssignment(prop)) {
17
- keys.push(prop.name.text);
18
- }
19
- else if (ts.isSpreadAssignment(prop)) {
20
- return { canUnpack: false, keys: [] };
21
- }
22
- }
23
- return { canUnpack: true, keys };
24
- }
25
- if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
26
- try {
27
- let keys = [], props = checker.getTypeAtLocation(expr).getProperties();
28
- for (let i = 0, n = props.length; i < n; i++) {
29
- let name = props[i].getName();
30
- if (name.startsWith('__') || name.startsWith('[')) {
31
- continue;
32
- }
33
- keys.push(name);
34
- }
35
- if (keys.length > 0) {
36
- return { canUnpack: true, keys };
37
- }
38
- }
39
- catch { }
40
- }
41
- return { canUnpack: false, keys: [] };
42
- }
43
3
  function inferCOMPILER_TYPES(expr, checker) {
44
4
  while (ts.isParenthesizedExpression(expr)) {
45
5
  expr = expr.expression;
@@ -57,9 +17,6 @@ function inferCOMPILER_TYPES(expr, checker) {
57
17
  if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT) {
58
18
  return COMPILER_TYPES.DocumentFragment;
59
19
  }
60
- if (ts.isArrayLiteralExpression(expr)) {
61
- return COMPILER_TYPES.ArraySlot;
62
- }
63
20
  if (ts.isNumericLiteral(expr) ||
64
21
  ts.isStringLiteral(expr) ||
65
22
  ts.isNoSubstitutionTemplateLiteral(expr) ||
@@ -88,106 +45,53 @@ function inferCOMPILER_TYPES(expr, checker) {
88
45
  if (isTypeFunction(type, checker)) {
89
46
  return COMPILER_TYPES.Effect;
90
47
  }
91
- if (isTypeArray(type, checker)) {
92
- return COMPILER_TYPES.ArraySlot;
93
- }
94
48
  }
95
49
  catch {
96
50
  }
97
51
  }
98
52
  return COMPILER_TYPES.Unknown;
99
53
  }
100
- function isTypeArray(type, checker) {
101
- if (checker.isArrayType(type)) {
102
- return true;
103
- }
104
- return type.getSymbol()?.getName() === 'ReactiveArray';
105
- }
106
54
  function isTypeFunction(type, checker) {
107
- if (type.getCallSignatures().length > 0) {
108
- return true;
109
- }
110
55
  if (type.isUnion()) {
56
+ let allFunctions = true, hasFunction = false;
111
57
  for (let i = 0, n = type.types.length; i < n; i++) {
112
58
  if (isTypeFunction(type.types[i], checker)) {
113
- return true;
59
+ hasFunction = true;
60
+ }
61
+ else {
62
+ allFunctions = false;
114
63
  }
115
64
  }
65
+ return hasFunction && allFunctions;
116
66
  }
117
- return false;
67
+ return type.getCallSignatures().length > 0;
118
68
  }
119
69
  const analyzeExpression = (expr, checker) => {
120
70
  return inferCOMPILER_TYPES(expr, checker);
121
71
  };
122
- const generateAttributeBinding = (elementVar, name, expr, staticValue, neededImports) => {
72
+ const generateAttributeBinding = (elementVar, name, expr, staticValue, addImport) => {
123
73
  if (name.startsWith('on') && name.length > 2) {
124
74
  let event = name.slice(2).toLowerCase(), key = name.toLowerCase();
125
75
  if (LIFECYCLE_EVENTS.has(key)) {
126
- neededImports.add(key);
127
- return `${key}(${elementVar}, ${expr});`;
76
+ return `${addImport(key)}(${elementVar}, ${expr});`;
128
77
  }
129
78
  if (DIRECT_ATTACH_EVENTS.has(key)) {
130
- neededImports.add('on');
131
- return `on(${elementVar}, '${event}', ${expr});`;
79
+ return `${addImport('on')}(${elementVar}, '${event}', ${expr});`;
132
80
  }
133
- neededImports.add('delegate');
134
- return `delegate(${elementVar}, '${event}', ${expr});`;
81
+ return `${addImport('delegate')}(${elementVar}, '${event}', ${expr});`;
135
82
  }
136
83
  if (name === 'class') {
137
- neededImports.add('setClass');
138
- return `setClass(${elementVar}, '${staticValue}', ${expr});`;
84
+ return `${addImport('setClass')}(${elementVar}, '${staticValue}', ${expr});`;
139
85
  }
140
86
  if (name === COMPILER_TYPES.Attributes) {
141
- neededImports.add('setProperties');
142
- return `setProperties(${elementVar}, ${expr});`;
87
+ return `${addImport('setProperties')}(${elementVar}, ${expr});`;
143
88
  }
144
89
  if (name === 'style') {
145
- neededImports.add('setStyle');
146
- return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
90
+ return `${addImport('setStyle')}(${elementVar}, '${staticValue}', ${expr});`;
147
91
  }
148
- neededImports.add('setProperty');
149
- return `setProperty(${elementVar}, '${name}', ${expr});`;
92
+ return `${addImport('setProperty')}(${elementVar}, '${name}', ${expr});`;
150
93
  };
151
- const generateSpreadBindings = (expr, exprCode, elementVar, checker, neededImports) => {
152
- while (ts.isParenthesizedExpression(expr)) {
153
- expr = expr.expression;
154
- }
155
- let analysis = analyzeSpread(expr, checker);
156
- if (!analysis.canUnpack) {
157
- neededImports.add('setProperties');
158
- return [`setProperties(${elementVar}, ${exprCode});`];
159
- }
160
- let lines = [];
161
- if (ts.isObjectLiteralExpression(expr)) {
162
- for (let i = 0, n = analysis.keys.length; i < n; i++) {
163
- let key = analysis.keys[i], value = null;
164
- for (let j = 0, m = expr.properties.length; j < m; j++) {
165
- let prop = expr.properties[j];
166
- if (ts.isPropertyAssignment(prop)) {
167
- let text = ts.isIdentifier(prop.name)
168
- ? prop.name.text
169
- : ts.isStringLiteral(prop.name) ? prop.name.text : null;
170
- if (text === key) {
171
- value = prop.initializer.getText();
172
- break;
173
- }
174
- }
175
- else if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === key) {
176
- value = prop.name.text;
177
- break;
178
- }
179
- }
180
- if (value !== null) {
181
- lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
182
- }
183
- }
184
- }
185
- else {
186
- for (let i = 0, n = analysis.keys.length; i < n; i++) {
187
- let key = analysis.keys[i];
188
- lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
189
- }
190
- }
191
- return lines;
94
+ const generateSpreadBindings = (exprCode, elementVar, addImport) => {
95
+ return [`${addImport('setProperties')}(${elementVar}, ${exprCode});`];
192
96
  };
193
97
  export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
package/package.json CHANGED
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "type": "module",
37
37
  "types": "./build/index.d.ts",
38
- "version": "0.35.0",
38
+ "version": "0.35.1",
39
39
  "scripts": {
40
40
  "build": "tsc",
41
41
  "build:test": "vite build --config test/vite.config.ts",
package/src/attributes.ts CHANGED
@@ -61,7 +61,7 @@ function list(
61
61
  changed = false,
62
62
  delimiter = delimiters[name],
63
63
  store = (ctx ??= context(element)).store ??= {},
64
- dynamic = store[name] as Set<string> | undefined;
64
+ dynamic = store[name] as Set<string>;
65
65
 
66
66
  if (dynamic === undefined) {
67
67
  let value = (element.getAttribute(name) || '').trim();
@@ -105,7 +105,7 @@ function list(
105
105
  }
106
106
  }
107
107
 
108
- let cold = store[id] as Record<PropertyKey, true> | undefined;
108
+ let cold = store[id] as Record<PropertyKey, true>;
109
109
 
110
110
  if (cold !== undefined) {
111
111
  for (let part in cold) {
@@ -274,7 +274,7 @@ function task() {
274
274
  }
275
275
 
276
276
 
277
- const setClass = (element: Element, classlist: false | string | undefined, value: unknown) => {
277
+ const setClass = (element: Element, classlist: false | string, value: unknown) => {
278
278
  let ctx = context(element),
279
279
  store = ctx.store ??= {};
280
280
 
@@ -298,8 +298,11 @@ const setProperty = (element: Element, name: string, value: unknown) => {
298
298
  }
299
299
  };
300
300
 
301
- const setProperties = function (element: Element, value: Attributes | Attributes[]) {
302
- if (isObject(value)) {
301
+ const setProperties = function (element: Element, value?: Attributes | Attributes[] | false | null | undefined) {
302
+ if (!value) {
303
+ return;
304
+ }
305
+ else if (isObject(value)) {
303
306
  for (let name in value) {
304
307
  let v = value[name];
305
308
 
@@ -317,7 +320,7 @@ const setProperties = function (element: Element, value: Attributes | Attributes
317
320
  }
318
321
  };
319
322
 
320
- const setStyle = (element: Element, styles: false | string | undefined, value: unknown) => {
323
+ const setStyle = (element: Element, styles: false | string, value: unknown) => {
321
324
  let ctx = context(element),
322
325
  store = ctx.store ??= {};
323
326
 
@@ -17,11 +17,10 @@ type Attribute = {
17
17
 
18
18
  type CodegenContext = {
19
19
  checker?: ts.TypeChecker;
20
- hoistedFactories: Map<string, string>;
21
- htmlToTemplateId: Map<string, string>;
22
- neededImports: Set<string>;
20
+ imports: Map<string, string>;
23
21
  printer: ts.Printer;
24
22
  sourceFile: ts.SourceFile;
23
+ templates: Map<string, string>;
25
24
  };
26
25
 
27
26
  type CodegenResult = {
@@ -46,6 +45,17 @@ const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
46
45
  let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
47
46
 
48
47
 
48
+ function addImport(ctx: CodegenContext, name: string): string {
49
+ let alias = ctx.imports.get(name);
50
+
51
+ if (!alias) {
52
+ alias = uid(name);
53
+ ctx.imports.set(name, alias);
54
+ }
55
+
56
+ return alias;
57
+ }
58
+
49
59
  function collectNestedTemplateReplacements(
50
60
  ctx: CodegenContext,
51
61
  node: ts.Node,
@@ -96,8 +106,7 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
96
106
 
97
107
  function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
98
108
  if (!exprNode) {
99
- ctx.neededImports.add('slot');
100
- return `slot(${anchor}, ${exprText});`;
109
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
101
110
  }
102
111
 
103
112
  if (isNestedHtmlTemplate(exprNode)) {
@@ -108,12 +117,10 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
108
117
 
109
118
  switch (slotType) {
110
119
  case COMPILER_TYPES.Effect:
111
- ctx.neededImports.add('EffectSlot');
112
- return `new EffectSlot(${anchor}, ${exprText});`;
120
+ return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
113
121
 
114
122
  case COMPILER_TYPES.ArraySlot:
115
- ctx.neededImports.add('ArraySlot');
116
- return `new ArraySlot(${anchor}, ${exprText});`;
123
+ return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
117
124
 
118
125
  case COMPILER_TYPES.Static:
119
126
  return `${anchor}.textContent = ${exprText};`;
@@ -122,8 +129,7 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
122
129
  return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
123
130
 
124
131
  default:
125
- ctx.neededImports.add('slot');
126
- return `slot(${anchor}, ${exprText});`;
132
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
127
133
  }
128
134
  }
129
135
 
@@ -160,24 +166,30 @@ function generateTemplateCode(
160
166
  continue;
161
167
  }
162
168
 
163
- let ancestorVar = root,
164
- startIdx = 0;
169
+ let ancestor = root,
170
+ start = 0;
165
171
 
166
172
  for (let j = path.length - 1; j >= 0; j--) {
167
173
  let prefix = path.slice(0, j).join('.');
168
174
 
169
175
  if (nodes.has(prefix)) {
170
- ancestorVar = nodes.get(prefix)!;
171
- startIdx = j;
176
+ ancestor = nodes.get(prefix)!;
177
+ start = j;
172
178
  break;
173
179
  }
174
180
  }
175
181
 
176
- let name = uid('element'),
177
- suffix = path.slice(startIdx).join('.');
182
+ let alias = addImport(ctx, 'Element'),
183
+ name = uid('element'),
184
+ segments = path.slice(start),
185
+ value = `${ancestor}.${segments.join('!.')}`;
186
+
187
+ // Cast root.firstChild to Element since DocumentFragment.firstChild returns ChildNode
188
+ if (ancestor === root && segments[0] === 'firstChild') {
189
+ value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${alias})`);
190
+ }
178
191
 
179
- ctx.neededImports.add('Element');
180
- declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
192
+ declarations.push(`${name} = ${value} as ${alias}`);
181
193
  nodes.set(key, name);
182
194
  }
183
195
 
@@ -187,7 +199,7 @@ function generateTemplateCode(
187
199
  );
188
200
 
189
201
  for (let i = 0, n = slots.length; i < n; i++) {
190
- let elementVar = slots[i].path.length === 0
202
+ let element = slots[i].path.length === 0
191
203
  ? root
192
204
  : (nodes.get(slots[i].path.join('.')) || root),
193
205
  slot = slots[i];
@@ -200,11 +212,9 @@ function generateTemplateCode(
200
212
 
201
213
  if (name === COMPILER_TYPES.Attributes) {
202
214
  let bindings = generateSpreadBindings(
203
- exprNodes[index],
204
215
  exprTexts[index] || 'undefined',
205
- elementVar,
206
- ctx.checker,
207
- ctx.neededImports
216
+ element,
217
+ n => addImport(ctx, n)
208
218
  );
209
219
 
210
220
  for (let k = 0, o = bindings.length; k < o; k++) {
@@ -216,11 +226,11 @@ function generateTemplateCode(
216
226
  else {
217
227
  code.push(
218
228
  generateAttributeBinding(
219
- elementVar,
229
+ element,
220
230
  name,
221
231
  exprTexts[index++] || 'undefined',
222
232
  slot.attributes.statics[name] || '',
223
- ctx.neededImports
233
+ n => addImport(ctx, n)
224
234
  )
225
235
  );
226
236
  }
@@ -228,7 +238,7 @@ function generateTemplateCode(
228
238
  }
229
239
  else {
230
240
  code.push(
231
- generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index])
241
+ generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index])
232
242
  );
233
243
  index++;
234
244
  }
@@ -241,41 +251,16 @@ function generateTemplateCode(
241
251
  }
242
252
 
243
253
  function getOrCreateTemplateId(ctx: CodegenContext, html: string): string {
244
- let id = ctx.htmlToTemplateId.get(html);
254
+ let id = ctx.templates.get(html);
245
255
 
246
256
  if (!id) {
247
- id = uid('tmpl');
248
- ctx.hoistedFactories.set(id, html);
249
- ctx.htmlToTemplateId.set(html, id);
257
+ id = uid('template');
258
+ ctx.templates.set(html, id);
250
259
  }
251
260
 
252
261
  return id;
253
262
  }
254
263
 
255
- function hasArraySlotImport(sourceFile: ts.SourceFile): boolean {
256
- for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
257
- let stmt = sourceFile.statements[i];
258
-
259
- if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
260
- continue;
261
- }
262
-
263
- let bindings = stmt.importClause.namedBindings;
264
-
265
- if (!ts.isNamedImports(bindings)) {
266
- continue;
267
- }
268
-
269
- for (let j = 0, m = bindings.elements.length; j < m; j++) {
270
- if (bindings.elements[j].name.text === 'ArraySlot') {
271
- return true;
272
- }
273
- }
274
- }
275
-
276
- return false;
277
- }
278
-
279
264
  function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
280
265
  return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
281
266
  }
@@ -297,7 +282,7 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
297
282
  }
298
283
 
299
284
 
300
- const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
285
+ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker, existingAliases?: Map<string, string>): CodegenResult => {
301
286
  if (templates.length === 0) {
302
287
  return { changed: false, code: originalCode };
303
288
  }
@@ -321,13 +306,13 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
321
306
 
322
307
  let ctx: CodegenContext = {
323
308
  checker,
324
- hoistedFactories: new Map(),
325
- htmlToTemplateId: new Map(),
326
- neededImports: new Set(['template']),
309
+ templates: new Map(),
310
+ imports: existingAliases ?? new Map(),
327
311
  printer,
328
312
  sourceFile
329
313
  },
330
- replacements: Replacement[] = [];
314
+ replacements: Replacement[] = [],
315
+ templateAlias = addImport(ctx, 'template');
331
316
 
332
317
  for (let i = 0, n = rootTemplates.length; i < n; i++) {
333
318
  let exprTexts: string[] = [],
@@ -371,23 +356,30 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
371
356
  let changed = replacements.length > 0,
372
357
  code = c.replaceReverse(originalCode, replacements);
373
358
 
374
- if (changed && ctx.hoistedFactories.size > 0) {
375
- let factories: string[] = [];
359
+ if (changed && ctx.templates.size > 0) {
360
+ let aliasedImports: string[] = [],
361
+ factories: string[] = [];
362
+
363
+ for (let [name, alias] of ctx.imports) {
364
+ aliasedImports.push(`${name} as ${alias}`);
365
+ }
376
366
 
377
- for (let [id, html] of ctx.hoistedFactories) {
378
- factories.push(`const ${id} = template(\`${html}\`);`);
367
+ for (let [html, id] of ctx.templates) {
368
+ factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
379
369
  }
380
370
 
381
- code = imports.modify(factories.join('\n') + code, sourceFile, PACKAGE, {
382
- add: ctx.neededImports,
371
+ // Remove html entrypoint and add aliased imports
372
+ code = imports.modify(code, sourceFile, PACKAGE, {
373
+ add: new Set(aliasedImports),
383
374
  remove: [COMPILER_ENTRYPOINT]
384
375
  });
376
+ code = factories.join('\n') + '\n\n' + code;
385
377
  }
386
378
 
387
379
  return { changed, code };
388
380
  };
389
381
 
390
- const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile): string => {
382
+ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile, arraySlotAlias: string): string => {
391
383
  if (calls.length === 0) {
392
384
  return code;
393
385
  }
@@ -399,7 +391,7 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
399
391
 
400
392
  replacements.push({
401
393
  end: call.end,
402
- newText: `new ArraySlot(
394
+ newText: `new ${arraySlotAlias}(
403
395
  ${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
404
396
  ${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
405
397
  )`,
@@ -410,14 +402,5 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
410
402
  return c.replaceReverse(code, replacements);
411
403
  };
412
404
 
413
- const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
414
- return ast.hasMatch(sourceFile, n =>
415
- ts.isNewExpression(n) &&
416
- ts.isIdentifier(n.expression) &&
417
- n.expression.text === 'ArraySlot'
418
- ) && !hasArraySlotImport(sourceFile);
419
- };
420
-
421
-
422
- export { generateCode, generateReactiveInlining, needsArraySlotImport };
405
+ export { generateCode, generateReactiveInlining };
423
406
  export type { CodegenResult };
@@ -1,7 +1,7 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
- import { code as c, imports } from '@esportsplus/typescript/compiler';
2
+ import { code as c, imports, uid } from '@esportsplus/typescript/compiler';
3
3
  import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
4
- import { generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen';
4
+ import { generateCode, generateReactiveInlining } from './codegen';
5
5
  import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
6
6
 
7
7
 
@@ -39,21 +39,23 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
39
39
 
40
40
  let changed = false,
41
41
  codegenChanged = false,
42
- needsImport = false,
42
+ existingAliases = new Map<string, string>(),
43
43
  reactiveCalls = findReactiveCalls(sourceFile, checker),
44
44
  result = code;
45
45
 
46
46
  if (reactiveCalls.length > 0) {
47
+ let arraySlotAlias = uid('ArraySlot');
48
+
47
49
  changed = true;
48
- result = generateReactiveInlining(reactiveCalls, result, sourceFile);
50
+ existingAliases.set('ArraySlot', arraySlotAlias);
51
+ result = generateReactiveInlining(reactiveCalls, result, sourceFile, arraySlotAlias);
49
52
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
50
- needsImport = needsArraySlotImport(sourceFile);
51
53
  }
52
54
 
53
55
  let templates = findHtmlTemplates(sourceFile, checker);
54
56
 
55
57
  if (templates.length > 0) {
56
- let codegenResult = generateCode(templates, result, sourceFile, checker);
58
+ let codegenResult = generateCode(templates, result, sourceFile, checker, existingAliases);
57
59
 
58
60
  if (codegenResult.changed) {
59
61
  changed = true;
@@ -62,8 +64,15 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
62
64
  }
63
65
  }
64
66
 
65
- if (needsImport && !codegenChanged) {
66
- result = imports.modify(result, sourceFile, PACKAGE, { add: ['ArraySlot'] });
67
+ // Add aliased ArraySlot import if reactive calls were processed but codegen didn't run
68
+ if (existingAliases.size > 0 && !codegenChanged) {
69
+ let aliasedImports: string[] = [];
70
+
71
+ for (let [name, alias] of existingAliases) {
72
+ aliasedImports.push(`${name} as ${alias}`);
73
+ }
74
+
75
+ result = imports.modify(result, sourceFile, PACKAGE, { add: aliasedImports });
67
76
  }
68
77
 
69
78
  if (changed) {
@@ -8,64 +8,6 @@ import {
8
8
  import { ts } from '@esportsplus/typescript';
9
9
 
10
10
 
11
- type SpreadAnalysis = {
12
- canUnpack: boolean;
13
- keys: string[];
14
- };
15
-
16
-
17
- function analyzeSpread(expr: ts.Expression, checker?: ts.TypeChecker): SpreadAnalysis {
18
- while (ts.isParenthesizedExpression(expr)) {
19
- expr = expr.expression;
20
- }
21
-
22
- if (ts.isObjectLiteralExpression(expr)) {
23
- let keys: string[] = [];
24
-
25
- for (let i = 0, n = expr.properties.length; i < n; i++) {
26
- let prop = expr.properties[i];
27
-
28
- if (ts.isPropertyAssignment(prop)) {
29
- if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
30
- keys.push(prop.name.text);
31
- }
32
- }
33
- else if (ts.isShorthandPropertyAssignment(prop)) {
34
- keys.push(prop.name.text);
35
- }
36
- else if (ts.isSpreadAssignment(prop)) {
37
- return { canUnpack: false, keys: [] };
38
- }
39
- }
40
-
41
- return { canUnpack: true, keys };
42
- }
43
-
44
- if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
45
- try {
46
- let keys: string[] = [],
47
- props = checker.getTypeAtLocation(expr).getProperties();
48
-
49
- for (let i = 0, n = props.length; i < n; i++) {
50
- let name = props[i].getName();
51
-
52
- if (name.startsWith('__') || name.startsWith('[')) {
53
- continue;
54
- }
55
-
56
- keys.push(name);
57
- }
58
-
59
- if (keys.length > 0) {
60
- return { canUnpack: true, keys };
61
- }
62
- }
63
- catch { }
64
- }
65
-
66
- return { canUnpack: false, keys: [] };
67
- }
68
-
69
11
  function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES {
70
12
  while (ts.isParenthesizedExpression(expr)) {
71
13
  expr = expr.expression;
@@ -75,6 +17,7 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
75
17
  return COMPILER_TYPES.Effect;
76
18
  }
77
19
 
20
+ // Only html.reactive() calls become ArraySlot - handled by generateReactiveInlining
78
21
  if (
79
22
  ts.isCallExpression(expr) &&
80
23
  ts.isPropertyAccessExpression(expr.expression) &&
@@ -89,10 +32,6 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
89
32
  return COMPILER_TYPES.DocumentFragment;
90
33
  }
91
34
 
92
- if (ts.isArrayLiteralExpression(expr)) {
93
- return COMPILER_TYPES.ArraySlot;
94
- }
95
-
96
35
  if (
97
36
  ts.isNumericLiteral(expr) ||
98
37
  ts.isStringLiteral(expr) ||
@@ -131,10 +70,6 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
131
70
  if (isTypeFunction(type, checker)) {
132
71
  return COMPILER_TYPES.Effect;
133
72
  }
134
-
135
- if (isTypeArray(type, checker)) {
136
- return COMPILER_TYPES.ArraySlot;
137
- }
138
73
  }
139
74
  catch {
140
75
  }
@@ -143,28 +78,26 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
143
78
  return COMPILER_TYPES.Unknown;
144
79
  }
145
80
 
146
- function isTypeArray(type: ts.Type, checker: ts.TypeChecker): boolean {
147
- if (checker.isArrayType(type)) {
148
- return true;
149
- }
150
-
151
- return type.getSymbol()?.getName() === 'ReactiveArray';
152
- }
153
-
154
81
  function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
155
- if (type.getCallSignatures().length > 0) {
156
- return true;
157
- }
158
-
82
+ // Union types that mix functions with non-functions (e.g., Renderable)
83
+ // should fall through to runtime slot dispatch
159
84
  if (type.isUnion()) {
85
+ let allFunctions = true,
86
+ hasFunction = false;
87
+
160
88
  for (let i = 0, n = type.types.length; i < n; i++) {
161
89
  if (isTypeFunction(type.types[i], checker)) {
162
- return true;
90
+ hasFunction = true;
91
+ }
92
+ else {
93
+ allFunctions = false;
163
94
  }
164
95
  }
96
+
97
+ return hasFunction && allFunctions;
165
98
  }
166
99
 
167
- return false;
100
+ return type.getCallSignatures().length > 0;
168
101
  }
169
102
 
170
103
 
@@ -172,102 +105,43 @@ const analyzeExpression = (expr: ts.Expression, checker?: ts.TypeChecker): COMPI
172
105
  return inferCOMPILER_TYPES(expr, checker);
173
106
  };
174
107
 
175
- const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, neededImports: Set<string>): string => {
108
+ const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, addImport: (name: string) => string): string => {
176
109
  if (name.startsWith('on') && name.length > 2) {
177
110
  let event = name.slice(2).toLowerCase(),
178
111
  key = name.toLowerCase();
179
112
 
180
113
  if (LIFECYCLE_EVENTS.has(key)) {
181
- neededImports.add(key);
182
- return `${key}(${elementVar}, ${expr});`;
114
+ return `${addImport(key)}(${elementVar}, ${expr});`;
183
115
  }
184
116
 
185
117
  if (DIRECT_ATTACH_EVENTS.has(key)) {
186
- neededImports.add('on');
187
- return `on(${elementVar}, '${event}', ${expr});`;
118
+ return `${addImport('on')}(${elementVar}, '${event}', ${expr});`;
188
119
  }
189
120
 
190
- neededImports.add('delegate');
191
- return `delegate(${elementVar}, '${event}', ${expr});`;
121
+ return `${addImport('delegate')}(${elementVar}, '${event}', ${expr});`;
192
122
  }
193
123
 
194
124
  if (name === 'class') {
195
- neededImports.add('setClass');
196
- return `setClass(${elementVar}, '${staticValue}', ${expr});`;
125
+ return `${addImport('setClass')}(${elementVar}, '${staticValue}', ${expr});`;
197
126
  }
198
127
 
199
128
  if (name === COMPILER_TYPES.Attributes) {
200
- neededImports.add('setProperties');
201
- return `setProperties(${elementVar}, ${expr});`;
129
+ return `${addImport('setProperties')}(${elementVar}, ${expr});`;
202
130
  }
203
131
 
204
132
  if (name === 'style') {
205
- neededImports.add('setStyle');
206
- return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
133
+ return `${addImport('setStyle')}(${elementVar}, '${staticValue}', ${expr});`;
207
134
  }
208
135
 
209
- neededImports.add('setProperty');
210
- return `setProperty(${elementVar}, '${name}', ${expr});`;
136
+ return `${addImport('setProperty')}(${elementVar}, '${name}', ${expr});`;
211
137
  };
212
138
 
213
139
  const generateSpreadBindings = (
214
- expr: ts.Expression,
215
140
  exprCode: string,
216
141
  elementVar: string,
217
- checker: ts.TypeChecker | undefined,
218
- neededImports: Set<string>
142
+ addImport: (name: string) => string
219
143
  ): string[] => {
220
- while (ts.isParenthesizedExpression(expr)) {
221
- expr = expr.expression;
222
- }
223
-
224
- let analysis = analyzeSpread(expr, checker);
225
-
226
- if (!analysis.canUnpack) {
227
- neededImports.add('setProperties');
228
- return [`setProperties(${elementVar}, ${exprCode});`];
229
- }
230
-
231
- let lines: string[] = [];
232
-
233
- if (ts.isObjectLiteralExpression(expr)) {
234
- for (let i = 0, n = analysis.keys.length; i < n; i++) {
235
- let key = analysis.keys[i],
236
- value: string | null = null;
237
-
238
- for (let j = 0, m = expr.properties.length; j < m; j++) {
239
- let prop = expr.properties[j];
240
-
241
- if (ts.isPropertyAssignment(prop)) {
242
- let text = ts.isIdentifier(prop.name)
243
- ? prop.name.text
244
- : ts.isStringLiteral(prop.name) ? prop.name.text : null;
245
-
246
- if (text === key) {
247
- value = prop.initializer.getText();
248
- break;
249
- }
250
- }
251
- else if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === key) {
252
- value = prop.name.text;
253
- break;
254
- }
255
- }
256
-
257
- if (value !== null) {
258
- lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
259
- }
260
- }
261
- }
262
- else {
263
- for (let i = 0, n = analysis.keys.length; i < n; i++) {
264
- let key = analysis.keys[i];
265
-
266
- lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
267
- }
268
- }
269
-
270
- return lines;
144
+ return [`${addImport('setProperties')}(${elementVar}, ${exprCode});`];
271
145
  };
272
146
 
273
147