@esportsplus/template 0.35.0 → 0.37.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.
@@ -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 === '') {
@@ -0,0 +1,4 @@
1
+ import { COMPILER_TYPES } from '../constants.js';
2
+ import { ts } from '@esportsplus/typescript';
3
+ declare const analyze: (expr: ts.Expression, checker?: ts.TypeChecker) => COMPILER_TYPES;
4
+ export { analyze };
@@ -0,0 +1,65 @@
1
+ import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES } from '../constants.js';
2
+ import { ts } from '@esportsplus/typescript';
3
+ function isTypeFunction(type, checker) {
4
+ if (type.isUnion()) {
5
+ for (let i = 0, n = type.types.length; i < n; i++) {
6
+ if (!isTypeFunction(type.types[i], checker)) {
7
+ return false;
8
+ }
9
+ }
10
+ return type.types.length > 0;
11
+ }
12
+ return type.getCallSignatures().length > 0;
13
+ }
14
+ const analyze = (expr, checker) => {
15
+ while (ts.isParenthesizedExpression(expr)) {
16
+ expr = expr.expression;
17
+ }
18
+ if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
19
+ return COMPILER_TYPES.Effect;
20
+ }
21
+ if (ts.isCallExpression(expr) &&
22
+ ts.isPropertyAccessExpression(expr.expression) &&
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;
27
+ }
28
+ if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT) {
29
+ return COMPILER_TYPES.DocumentFragment;
30
+ }
31
+ if (ts.isNumericLiteral(expr) ||
32
+ ts.isStringLiteral(expr) ||
33
+ ts.isNoSubstitutionTemplateLiteral(expr) ||
34
+ expr.kind === ts.SyntaxKind.TrueKeyword ||
35
+ expr.kind === ts.SyntaxKind.FalseKeyword ||
36
+ expr.kind === ts.SyntaxKind.NullKeyword ||
37
+ expr.kind === ts.SyntaxKind.UndefinedKeyword) {
38
+ return COMPILER_TYPES.Static;
39
+ }
40
+ if (ts.isTemplateExpression(expr)) {
41
+ return COMPILER_TYPES.Primitive;
42
+ }
43
+ if (ts.isConditionalExpression(expr)) {
44
+ let whenFalse = analyze(expr.whenFalse, checker), whenTrue = analyze(expr.whenTrue, checker);
45
+ if (whenTrue === whenFalse) {
46
+ return whenTrue;
47
+ }
48
+ if (whenTrue === COMPILER_TYPES.Effect || whenFalse === COMPILER_TYPES.Effect) {
49
+ return COMPILER_TYPES.Effect;
50
+ }
51
+ return COMPILER_TYPES.Unknown;
52
+ }
53
+ if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
54
+ try {
55
+ let type = checker.getTypeAtLocation(expr);
56
+ if (isTypeFunction(type, checker)) {
57
+ return COMPILER_TYPES.Effect;
58
+ }
59
+ }
60
+ catch {
61
+ }
62
+ }
63
+ return COMPILER_TYPES.Unknown;
64
+ };
65
+ export { analyze };
@@ -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 };
@@ -1,10 +1,37 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
2
  import { ast, code as c, imports, uid } from '@esportsplus/typescript/compiler';
3
- import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '../constants.js';
4
- import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer.js';
3
+ import { COMPILER_ENTRYPOINT, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE } from '../constants.js';
4
+ import { analyze } from './analyzer.js';
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
+ }
16
+ function generateAttributeBinding(ctx, element, name, expr, staticValue) {
17
+ if (name.startsWith('on') && name.length > 2) {
18
+ let event = name.slice(2).toLowerCase(), key = name.toLowerCase();
19
+ if (LIFECYCLE_EVENTS.has(key)) {
20
+ return `${addImport(ctx, key)}(${element}, ${expr});`;
21
+ }
22
+ if (DIRECT_ATTACH_EVENTS.has(key)) {
23
+ return `${addImport(ctx, 'on')}(${element}, '${event}', ${expr});`;
24
+ }
25
+ return `${addImport(ctx, 'delegate')}(${element}, '${event}', ${expr});`;
26
+ }
27
+ if (name === 'class') {
28
+ return `${addImport(ctx, 'setClass')}(${element}, '${staticValue}', ${expr});`;
29
+ }
30
+ if (name === 'style') {
31
+ return `${addImport(ctx, 'setStyle')}(${element}, '${staticValue}', ${expr});`;
32
+ }
33
+ return `${addImport(ctx, 'setProperty')}(${element}, '${name}', ${expr});`;
34
+ }
8
35
  function collectNestedTemplateReplacements(ctx, node, exprStart, replacements) {
9
36
  if (isNestedHtmlTemplate(node)) {
10
37
  replacements.push({
@@ -35,27 +62,23 @@ function generateNestedTemplateCode(ctx, node) {
35
62
  }
36
63
  function generateNodeBinding(ctx, anchor, exprText, exprNode) {
37
64
  if (!exprNode) {
38
- ctx.neededImports.add('slot');
39
- return `slot(${anchor}, ${exprText});`;
65
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
40
66
  }
41
67
  if (isNestedHtmlTemplate(exprNode)) {
42
68
  return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
43
69
  }
44
- let slotType = analyzeExpression(exprNode, ctx.checker);
70
+ let slotType = analyze(exprNode, ctx.checker);
45
71
  switch (slotType) {
46
72
  case COMPILER_TYPES.Effect:
47
- ctx.neededImports.add('EffectSlot');
48
- return `new EffectSlot(${anchor}, ${exprText});`;
73
+ return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
49
74
  case COMPILER_TYPES.ArraySlot:
50
- ctx.neededImports.add('ArraySlot');
51
- return `new ArraySlot(${anchor}, ${exprText});`;
75
+ return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
52
76
  case COMPILER_TYPES.Static:
53
77
  return `${anchor}.textContent = ${exprText};`;
54
78
  case COMPILER_TYPES.DocumentFragment:
55
79
  return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
56
80
  default:
57
- ctx.neededImports.add('slot');
58
- return `slot(${anchor}, ${exprText});`;
81
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
59
82
  }
60
83
  }
61
84
  function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArrowBody) {
@@ -74,23 +97,25 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
74
97
  if (nodes.has(key)) {
75
98
  continue;
76
99
  }
77
- let ancestorVar = root, startIdx = 0;
100
+ let ancestor = root, start = 0;
78
101
  for (let j = path.length - 1; j >= 0; j--) {
79
102
  let prefix = path.slice(0, j).join('.');
80
103
  if (nodes.has(prefix)) {
81
- ancestorVar = nodes.get(prefix);
82
- startIdx = j;
104
+ ancestor = nodes.get(prefix);
105
+ start = j;
83
106
  break;
84
107
  }
85
108
  }
86
- let name = uid('element'), suffix = path.slice(startIdx).join('.');
87
- ctx.neededImports.add('Element');
88
- declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
109
+ let alias = addImport(ctx, 'Element'), name = uid('element'), segments = path.slice(start), value = `${ancestor}.${segments.join('!.')}`;
110
+ if (ancestor === root && segments[0] === 'firstChild') {
111
+ value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${alias})`);
112
+ }
113
+ declarations.push(`${name} = ${value} as ${alias}`);
89
114
  nodes.set(key, name);
90
115
  }
91
116
  code.push(isArrowBody ? '{' : `(() => {`, `let ${declarations.join(',\n')};`);
92
117
  for (let i = 0, n = slots.length; i < n; i++) {
93
- let elementVar = slots[i].path.length === 0
118
+ let element = slots[i].path.length === 0
94
119
  ? root
95
120
  : (nodes.get(slots[i].path.join('.')) || root), slot = slots[i];
96
121
  if (slot.type === COMPILER_TYPES.Attribute) {
@@ -98,19 +123,16 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
98
123
  for (let j = 0, m = names.length; j < m; j++) {
99
124
  let name = names[j];
100
125
  if (name === COMPILER_TYPES.Attributes) {
101
- let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker, ctx.neededImports);
102
- for (let k = 0, o = bindings.length; k < o; k++) {
103
- code.push(bindings[k]);
104
- }
126
+ code.push(`${addImport(ctx, 'setProperties')}(${element}, ${exprTexts[index] || 'undefined'});`);
105
127
  index++;
106
128
  }
107
129
  else {
108
- code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', ctx.neededImports));
130
+ code.push(generateAttributeBinding(ctx, element, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || ''));
109
131
  }
110
132
  }
111
133
  }
112
134
  else {
113
- code.push(generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index]));
135
+ code.push(generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index]));
114
136
  index++;
115
137
  }
116
138
  }
@@ -119,32 +141,13 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
119
141
  return code.join('\n');
120
142
  }
121
143
  function getOrCreateTemplateId(ctx, html) {
122
- let id = ctx.htmlToTemplateId.get(html);
144
+ let id = ctx.templates.get(html);
123
145
  if (!id) {
124
- id = uid('tmpl');
125
- ctx.hoistedFactories.set(id, html);
126
- ctx.htmlToTemplateId.set(html, id);
146
+ id = uid('template');
147
+ ctx.templates.set(html, id);
127
148
  }
128
149
  return id;
129
150
  }
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
151
  function isNestedHtmlTemplate(expr) {
149
152
  return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
150
153
  }
@@ -159,7 +162,7 @@ function rewriteExpression(ctx, expr) {
159
162
  collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
160
163
  return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
161
164
  }
162
- const generateCode = (templates, originalCode, sourceFile, checker) => {
165
+ const generateCode = (templates, originalCode, sourceFile, checker, existingAliases) => {
163
166
  if (templates.length === 0) {
164
167
  return { changed: false, code: originalCode };
165
168
  }
@@ -176,12 +179,11 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
176
179
  }
177
180
  let ctx = {
178
181
  checker,
179
- hoistedFactories: new Map(),
180
- htmlToTemplateId: new Map(),
181
- neededImports: new Set(['template']),
182
+ imports: existingAliases ?? new Map(),
182
183
  printer,
183
- sourceFile
184
- }, replacements = [];
184
+ sourceFile,
185
+ templates: new Map(),
186
+ }, replacements = [], templateAlias = addImport(ctx, 'template');
185
187
  for (let i = 0, n = rootTemplates.length; i < n; i++) {
186
188
  let exprTexts = [], template = rootTemplates[i];
187
189
  for (let j = 0, m = template.expressions.length; j < m; j++) {
@@ -206,19 +208,23 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
206
208
  });
207
209
  }
208
210
  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}\`);`);
211
+ if (changed && ctx.templates.size > 0) {
212
+ let aliasedImports = [], factories = [], updatedSourceFile = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
213
+ for (let [name, alias] of ctx.imports) {
214
+ aliasedImports.push(`${name} as ${alias}`);
213
215
  }
214
- code = imports.modify(factories.join('\n') + code, sourceFile, PACKAGE, {
215
- add: ctx.neededImports,
216
+ for (let [html, id] of ctx.templates) {
217
+ factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
218
+ }
219
+ code = imports.modify(code, updatedSourceFile, PACKAGE, {
220
+ add: new Set(aliasedImports),
216
221
  remove: [COMPILER_ENTRYPOINT]
217
222
  });
223
+ code = factories.join('\n') + '\n\n' + code;
218
224
  }
219
225
  return { changed, code };
220
226
  };
221
- const generateReactiveInlining = (calls, code, sourceFile) => {
227
+ const generateReactiveInlining = (calls, code, sourceFile, arraySlotAlias) => {
222
228
  if (calls.length === 0) {
223
229
  return code;
224
230
  }
@@ -227,7 +233,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
227
233
  let call = calls[i];
228
234
  replacements.push({
229
235
  end: call.end,
230
- newText: `new ArraySlot(
236
+ newText: `new ${arraySlotAlias}(
231
237
  ${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
232
238
  ${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
233
239
  )`,
@@ -236,9 +242,4 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
236
242
  }
237
243
  return c.replaceReverse(code, replacements);
238
244
  };
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 };
245
+ export { generateCode, generateReactiveInlining };
@@ -1,8 +1,10 @@
1
+ import type { PluginContext } from '@esportsplus/typescript/compiler';
1
2
  import { ts } from '@esportsplus/typescript';
2
3
  type TransformResult = {
3
4
  changed: boolean;
4
5
  code: string;
5
6
  sourceFile: ts.SourceFile;
6
7
  };
7
- declare const transform: (sourceFile: ts.SourceFile, program: ts.Program) => TransformResult;
8
- export { transform };
8
+ declare const analyze: (sourceFile: ts.SourceFile, program: ts.Program, context: PluginContext) => void;
9
+ declare const transform: (sourceFile: ts.SourceFile, program: ts.Program, context?: PluginContext) => TransformResult;
10
+ export { analyze, transform };
@@ -1,45 +1,82 @@
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
+ const CONTEXT_KEY = 'template:analyzed';
6
7
  const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
7
8
  const REGEX_BACKSLASH = /\\/g;
8
9
  const REGEX_FORWARD_SLASH = /\//g;
9
- const transform = (sourceFile, program) => {
10
+ function getAnalyzedFile(context, filename) {
11
+ return context?.get(CONTEXT_KEY)?.get(filename);
12
+ }
13
+ const analyze = (sourceFile, program, context) => {
10
14
  let code = sourceFile.getFullText();
11
15
  if (!c.contains(code, { patterns: PATTERNS })) {
12
- return { changed: false, code, sourceFile };
16
+ return;
13
17
  }
14
- let checker, fileName = sourceFile.fileName, programSourceFile = program.getSourceFile(fileName)
15
- || program.getSourceFile(fileName.replace(REGEX_BACKSLASH, '/'))
16
- || program.getSourceFile(fileName.replace(REGEX_FORWARD_SLASH, '\\'));
18
+ let checker = program.getTypeChecker(), filename = sourceFile.fileName, files = context.get(CONTEXT_KEY), programSourceFile = program.getSourceFile(filename)
19
+ || program.getSourceFile(filename.replace(REGEX_BACKSLASH, '/'))
20
+ || program.getSourceFile(filename.replace(REGEX_FORWARD_SLASH, '\\'));
17
21
  if (programSourceFile) {
18
- checker = program.getTypeChecker();
19
22
  sourceFile = programSourceFile;
20
23
  }
21
- let changed = false, codegenChanged = false, needsImport = false, reactiveCalls = findReactiveCalls(sourceFile, checker), result = code;
22
- if (reactiveCalls.length > 0) {
24
+ if (!files) {
25
+ files = new Map();
26
+ context.set(CONTEXT_KEY, files);
27
+ }
28
+ files.set(filename, {
29
+ reactiveCalls: findReactiveCalls(sourceFile, checker),
30
+ templates: findHtmlTemplates(sourceFile, checker)
31
+ });
32
+ };
33
+ const transform = (sourceFile, program, context) => {
34
+ let code = sourceFile.getFullText(), filename = sourceFile.fileName;
35
+ let analyzed = getAnalyzedFile(context, filename);
36
+ if (!analyzed) {
37
+ if (!c.contains(code, { patterns: PATTERNS })) {
38
+ return { changed: false, code, sourceFile };
39
+ }
40
+ let checker = program.getTypeChecker(), programSourceFile = program.getSourceFile(filename)
41
+ || program.getSourceFile(filename.replace(REGEX_BACKSLASH, '/'))
42
+ || program.getSourceFile(filename.replace(REGEX_FORWARD_SLASH, '\\'));
43
+ if (programSourceFile) {
44
+ sourceFile = programSourceFile;
45
+ }
46
+ analyzed = {
47
+ reactiveCalls: findReactiveCalls(sourceFile, checker),
48
+ templates: findHtmlTemplates(sourceFile, checker)
49
+ };
50
+ }
51
+ let changed = false, codegenChanged = false, existingAliases = new Map(), result = code;
52
+ if (analyzed.reactiveCalls.length > 0) {
23
53
  changed = true;
24
- result = generateReactiveInlining(reactiveCalls, result, sourceFile);
54
+ existingAliases.set('ArraySlot', uid('ArraySlot'));
55
+ result = generateReactiveInlining(analyzed.reactiveCalls, result, sourceFile, existingAliases.get('ArraySlot'));
25
56
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
26
- needsImport = needsArraySlotImport(sourceFile);
57
+ analyzed = {
58
+ reactiveCalls: [],
59
+ templates: findHtmlTemplates(sourceFile, program.getTypeChecker())
60
+ };
27
61
  }
28
- let templates = findHtmlTemplates(sourceFile, checker);
29
- if (templates.length > 0) {
30
- let codegenResult = generateCode(templates, result, sourceFile, checker);
62
+ if (analyzed.templates.length > 0) {
63
+ let codegenResult = generateCode(analyzed.templates, result, sourceFile, program.getTypeChecker(), existingAliases);
31
64
  if (codegenResult.changed) {
32
65
  changed = true;
33
66
  codegenChanged = true;
34
67
  result = codegenResult.code;
35
68
  }
36
69
  }
37
- if (needsImport && !codegenChanged) {
38
- result = imports.modify(result, sourceFile, PACKAGE, { add: ['ArraySlot'] });
70
+ if (existingAliases.size > 0 && !codegenChanged) {
71
+ let aliasedImports = [];
72
+ for (let [name, alias] of existingAliases) {
73
+ aliasedImports.push(`${name} as ${alias}`);
74
+ }
75
+ result = imports.modify(result, sourceFile, PACKAGE, { add: aliasedImports });
39
76
  }
40
77
  if (changed) {
41
78
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
42
79
  }
43
80
  return { changed, code: result, sourceFile };
44
81
  };
45
- export { transform };
82
+ export { analyze, transform };
@@ -1,3 +1,3 @@
1
1
  import { plugin } from '@esportsplus/typescript/compiler';
2
- import { transform } from '../index.js';
3
- export default plugin.tsc(transform);
2
+ import { analyze, transform } from '../index.js';
3
+ export default plugin.tsc({ analyze, transform });
@@ -1,13 +1,13 @@
1
1
  declare const _default: ({ root }?: {
2
2
  root?: string;
3
3
  }) => {
4
- configResolved(config: import("vite").ResolvedConfig): void;
5
- enforce: string;
4
+ configResolved: (config: unknown) => void;
5
+ enforce: "pre";
6
6
  name: string;
7
- transform(code: string, id: string): {
7
+ transform: (code: string, id: string) => {
8
8
  code: string;
9
9
  map: null;
10
10
  } | null;
11
- watchChange(id: string): void;
11
+ watchChange: (id: string) => void;
12
12
  };
13
13
  export default _default;
@@ -1,7 +1,8 @@
1
1
  import { plugin } from '@esportsplus/typescript/compiler';
2
2
  import { PACKAGE } from '../../constants.js';
3
- import { transform } from '../index.js';
3
+ import { analyze, transform } from '../index.js';
4
4
  export default plugin.vite({
5
+ analyze,
5
6
  name: PACKAGE,
6
7
  transform
7
8
  });
@@ -19,8 +19,8 @@ function isHtmlFromPackage(node, checker) {
19
19
  return true;
20
20
  }
21
21
  for (let i = 0, n = declarations.length; i < n; i++) {
22
- let fileName = declarations[i].getSourceFile().fileName;
23
- if (fileName.includes(PACKAGE) || fileName.includes('@esportsplus/template')) {
22
+ let filename = declarations[i].getSourceFile().fileName;
23
+ if (filename.includes(PACKAGE) || filename.includes('@esportsplus/template')) {
24
24
  return true;
25
25
  }
26
26
  }
package/package.json CHANGED
@@ -2,14 +2,14 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/queue": "^0.2.0",
5
- "@esportsplus/reactivity": "^0.27.0",
5
+ "@esportsplus/reactivity": "^0.27.3",
6
+ "@esportsplus/typescript": "^0.24.2",
6
7
  "@esportsplus/utilities": "^0.27.2",
7
8
  "serve": "^14.2.5"
8
9
  },
9
10
  "devDependencies": {
10
- "@esportsplus/typescript": "^0.22.0",
11
11
  "@types/node": "^25.0.3",
12
- "vite": "^7.3.0",
12
+ "vite": "^7.3.1",
13
13
  "vite-tsconfig-paths": "^6.0.3"
14
14
  },
15
15
  "exports": {
@@ -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.37.0",
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