@esportsplus/template 0.34.1 → 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.
package/build/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
- import './runtime.js';
2
1
  export { default as html } from './html.js';
3
2
  export { default as render } from './render.js';
4
3
  export { default as svg } from './svg.js';
4
+ export * from './attributes.js';
5
+ export * from './event/index.js';
6
+ export { ArraySlot } from './slot/array.js';
7
+ export { EffectSlot } from './slot/effect.js';
8
+ export { default as slot } from './slot/index.js';
5
9
  export type { Attributes, Element, Renderable } from './types.js';
10
+ export * from './utilities.js';
package/build/index.js CHANGED
@@ -1,4 +1,14 @@
1
- import './runtime.js';
1
+ import { CLEANUP, STORE } from './constants.js';
2
+ if (typeof Node !== 'undefined') {
3
+ Node.prototype[CLEANUP] = null;
4
+ Node.prototype[STORE] = null;
5
+ }
2
6
  export { default as html } from './html.js';
3
7
  export { default as render } from './render.js';
4
8
  export { default as svg } from './svg.js';
9
+ export * from './attributes.js';
10
+ export * from './event/index.js';
11
+ export { ArraySlot } from './slot/array.js';
12
+ export { EffectSlot } from './slot/effect.js';
13
+ export { default as slot } from './slot/index.js';
14
+ export * from './utilities.js';
@@ -1,5 +1,5 @@
1
1
  import { SLOT_HTML } from './constants.js';
2
- let tmpl = document?.createElement('template'), txt = document?.createTextNode('');
2
+ let tmpl = document.createElement('template'), txt = document.createTextNode('');
3
3
  const clone = typeof navigator !== 'undefined' && navigator.userAgent.includes('Firefox')
4
4
  ? document.importNode.bind(document)
5
5
  : (node, deep = true) => node.cloneNode(deep);
package/package.json CHANGED
@@ -2,34 +2,28 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/queue": "^0.2.0",
5
- "@esportsplus/reactivity": "^0.26.0",
5
+ "@esportsplus/reactivity": "^0.27.0",
6
6
  "@esportsplus/utilities": "^0.27.2",
7
7
  "serve": "^14.2.5"
8
8
  },
9
9
  "devDependencies": {
10
- "@esportsplus/typescript": "^0.21.0",
10
+ "@esportsplus/typescript": "^0.22.0",
11
11
  "@types/node": "^25.0.3",
12
12
  "vite": "^7.3.0",
13
13
  "vite-tsconfig-paths": "^6.0.3"
14
14
  },
15
15
  "exports": {
16
16
  ".": {
17
- "import": "./build/index.js",
18
- "types": "./build/index.d.ts"
19
- },
20
- "./compiler": {
21
- "import": "./build/compiler/index.js",
22
- "types": "./build/compiler/index.d.ts"
17
+ "types": "./build/index.d.ts",
18
+ "default": "./build/index.js"
23
19
  },
24
20
  "./compiler/tsc": {
25
21
  "types": "./build/compiler/plugins/tsc.d.ts",
26
- "import": "./build/compiler/plugins/tsc.js",
27
- "require": "./build/compiler/plugins/tsc.js"
22
+ "default": "./build/compiler/plugins/tsc.js"
28
23
  },
29
24
  "./compiler/vite": {
30
25
  "types": "./build/compiler/plugins/vite.d.ts",
31
- "import": "./build/compiler/plugins/vite.js",
32
- "require": "./build/compiler/plugins/vite.js"
26
+ "default": "./build/compiler/plugins/vite.js"
33
27
  }
34
28
  },
35
29
  "main": "./build/index.js",
@@ -41,7 +35,7 @@
41
35
  },
42
36
  "type": "module",
43
37
  "types": "./build/index.d.ts",
44
- "version": "0.34.1",
38
+ "version": "0.35.1",
45
39
  "scripts": {
46
40
  "build": "tsc",
47
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
 
@@ -1,11 +1,8 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
- import { ast, code as c, uid, type Replacement } from '@esportsplus/typescript/compiler';
2
+ import { ast, code as c, imports, uid, type Replacement } from '@esportsplus/typescript/compiler';
3
+ import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '~/constants';
3
4
  import type { ReactiveCallInfo, TemplateInfo } from './ts-parser';
4
5
  import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer';
5
- import {
6
- COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES,
7
- PACKAGE, PACKAGE_COMPILER
8
- } from '~/constants';
9
6
  import parser from './parser';
10
7
 
11
8
 
@@ -20,10 +17,10 @@ type Attribute = {
20
17
 
21
18
  type CodegenContext = {
22
19
  checker?: ts.TypeChecker;
23
- hoistedFactories: Map<string, string>;
24
- htmlToTemplateId: Map<string, string>;
20
+ imports: Map<string, string>;
25
21
  printer: ts.Printer;
26
22
  sourceFile: ts.SourceFile;
23
+ templates: Map<string, string>;
27
24
  };
28
25
 
29
26
  type CodegenResult = {
@@ -44,15 +41,21 @@ type ParseResult = {
44
41
 
45
42
  const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
46
43
 
47
- const REGEX_PACKAGE_IMPORT = new RegExp(
48
- `import\\s*\\{[^}]*\\}\\s*from\\s*['"]${PACKAGE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"];?\\n?`,
49
- 'g'
50
- );
51
-
52
44
 
53
45
  let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
54
46
 
55
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
+
56
59
  function collectNestedTemplateReplacements(
57
60
  ctx: CodegenContext,
58
61
  node: ts.Node,
@@ -103,7 +106,7 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
103
106
 
104
107
  function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
105
108
  if (!exprNode) {
106
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
109
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
107
110
  }
108
111
 
109
112
  if (isNestedHtmlTemplate(exprNode)) {
@@ -114,10 +117,10 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
114
117
 
115
118
  switch (slotType) {
116
119
  case COMPILER_TYPES.Effect:
117
- return `new ${COMPILER_NAMESPACE}.EffectSlot(${anchor}, ${exprText});`;
120
+ return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
118
121
 
119
122
  case COMPILER_TYPES.ArraySlot:
120
- return `new ${COMPILER_NAMESPACE}.ArraySlot(${anchor}, ${exprText});`;
123
+ return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
121
124
 
122
125
  case COMPILER_TYPES.Static:
123
126
  return `${anchor}.textContent = ${exprText};`;
@@ -126,7 +129,7 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
126
129
  return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
127
130
 
128
131
  default:
129
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
132
+ return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
130
133
  }
131
134
  }
132
135
 
@@ -163,23 +166,30 @@ function generateTemplateCode(
163
166
  continue;
164
167
  }
165
168
 
166
- let ancestorVar = root,
167
- startIdx = 0;
169
+ let ancestor = root,
170
+ start = 0;
168
171
 
169
172
  for (let j = path.length - 1; j >= 0; j--) {
170
173
  let prefix = path.slice(0, j).join('.');
171
174
 
172
175
  if (nodes.has(prefix)) {
173
- ancestorVar = nodes.get(prefix)!;
174
- startIdx = j;
176
+ ancestor = nodes.get(prefix)!;
177
+ start = j;
175
178
  break;
176
179
  }
177
180
  }
178
181
 
179
- let name = uid('element'),
180
- 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
+ }
181
191
 
182
- declarations.push(`${name} = ${ancestorVar}.${suffix} as ${COMPILER_NAMESPACE}.Element`);
192
+ declarations.push(`${name} = ${value} as ${alias}`);
183
193
  nodes.set(key, name);
184
194
  }
185
195
 
@@ -189,7 +199,7 @@ function generateTemplateCode(
189
199
  );
190
200
 
191
201
  for (let i = 0, n = slots.length; i < n; i++) {
192
- let elementVar = slots[i].path.length === 0
202
+ let element = slots[i].path.length === 0
193
203
  ? root
194
204
  : (nodes.get(slots[i].path.join('.')) || root),
195
205
  slot = slots[i];
@@ -202,11 +212,9 @@ function generateTemplateCode(
202
212
 
203
213
  if (name === COMPILER_TYPES.Attributes) {
204
214
  let bindings = generateSpreadBindings(
205
- exprNodes[index],
206
215
  exprTexts[index] || 'undefined',
207
- elementVar,
208
- ctx.checker,
209
- COMPILER_NAMESPACE
216
+ element,
217
+ n => addImport(ctx, n)
210
218
  );
211
219
 
212
220
  for (let k = 0, o = bindings.length; k < o; k++) {
@@ -218,11 +226,11 @@ function generateTemplateCode(
218
226
  else {
219
227
  code.push(
220
228
  generateAttributeBinding(
221
- elementVar,
229
+ element,
222
230
  name,
223
231
  exprTexts[index++] || 'undefined',
224
232
  slot.attributes.statics[name] || '',
225
- COMPILER_NAMESPACE
233
+ n => addImport(ctx, n)
226
234
  )
227
235
  );
228
236
  }
@@ -230,7 +238,7 @@ function generateTemplateCode(
230
238
  }
231
239
  else {
232
240
  code.push(
233
- generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index])
241
+ generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index])
234
242
  );
235
243
  index++;
236
244
  }
@@ -243,41 +251,16 @@ function generateTemplateCode(
243
251
  }
244
252
 
245
253
  function getOrCreateTemplateId(ctx: CodegenContext, html: string): string {
246
- let id = ctx.htmlToTemplateId.get(html);
254
+ let id = ctx.templates.get(html);
247
255
 
248
256
  if (!id) {
249
- id = uid('tmpl');
250
- ctx.hoistedFactories.set(id, html);
251
- ctx.htmlToTemplateId.set(html, id);
257
+ id = uid('template');
258
+ ctx.templates.set(html, id);
252
259
  }
253
260
 
254
261
  return id;
255
262
  }
256
263
 
257
- function hasArraySlotImport(sourceFile: ts.SourceFile): boolean {
258
- for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
259
- let stmt = sourceFile.statements[i];
260
-
261
- if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
262
- continue;
263
- }
264
-
265
- let bindings = stmt.importClause.namedBindings;
266
-
267
- if (!ts.isNamedImports(bindings)) {
268
- continue;
269
- }
270
-
271
- for (let j = 0, m = bindings.elements.length; j < m; j++) {
272
- if (bindings.elements[j].name.text === 'ArraySlot') {
273
- return true;
274
- }
275
- }
276
- }
277
-
278
- return false;
279
- }
280
-
281
264
  function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
282
265
  return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
283
266
  }
@@ -299,15 +282,7 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
299
282
  }
300
283
 
301
284
 
302
- const addImport = (code: string): string => {
303
- return `import * as ${COMPILER_NAMESPACE} from '${PACKAGE_COMPILER}';\n` + code;
304
- };
305
-
306
- const removePackageImport = (code: string): string => {
307
- return code.replace(REGEX_PACKAGE_IMPORT, '');
308
- };
309
-
310
- 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 => {
311
286
  if (templates.length === 0) {
312
287
  return { changed: false, code: originalCode };
313
288
  }
@@ -331,12 +306,13 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
331
306
 
332
307
  let ctx: CodegenContext = {
333
308
  checker,
334
- hoistedFactories: new Map(),
335
- htmlToTemplateId: new Map(),
309
+ templates: new Map(),
310
+ imports: existingAliases ?? new Map(),
336
311
  printer,
337
312
  sourceFile
338
313
  },
339
- replacements: Replacement[] = [];
314
+ replacements: Replacement[] = [],
315
+ templateAlias = addImport(ctx, 'template');
340
316
 
341
317
  for (let i = 0, n = rootTemplates.length; i < n; i++) {
342
318
  let exprTexts: string[] = [],
@@ -380,20 +356,30 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
380
356
  let changed = replacements.length > 0,
381
357
  code = c.replaceReverse(originalCode, replacements);
382
358
 
383
- if (changed && ctx.hoistedFactories.size > 0) {
384
- 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
+ }
385
366
 
386
- for (let [id, html] of ctx.hoistedFactories) {
387
- factories.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
367
+ for (let [html, id] of ctx.templates) {
368
+ factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
388
369
  }
389
370
 
390
- code = addImport(factories.join('\n') + removePackageImport(code));
371
+ // Remove html entrypoint and add aliased imports
372
+ code = imports.modify(code, sourceFile, PACKAGE, {
373
+ add: new Set(aliasedImports),
374
+ remove: [COMPILER_ENTRYPOINT]
375
+ });
376
+ code = factories.join('\n') + '\n\n' + code;
391
377
  }
392
378
 
393
379
  return { changed, code };
394
380
  };
395
381
 
396
- const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile): string => {
382
+ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile, arraySlotAlias: string): string => {
397
383
  if (calls.length === 0) {
398
384
  return code;
399
385
  }
@@ -405,7 +391,7 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
405
391
 
406
392
  replacements.push({
407
393
  end: call.end,
408
- newText: `new ${COMPILER_NAMESPACE}.ArraySlot(
394
+ newText: `new ${arraySlotAlias}(
409
395
  ${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
410
396
  ${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
411
397
  )`,
@@ -416,14 +402,5 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
416
402
  return c.replaceReverse(code, replacements);
417
403
  };
418
404
 
419
- const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
420
- return ast.hasMatch(sourceFile, n =>
421
- ts.isNewExpression(n) &&
422
- ts.isPropertyAccessExpression(n.expression) &&
423
- n.expression.name.text === 'ArraySlot'
424
- ) && !hasArraySlotImport(sourceFile);
425
- };
426
-
427
-
428
- export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
405
+ export { generateCode, generateReactiveInlining };
429
406
  export type { CodegenResult };
@@ -1,8 +1,8 @@
1
- import { code as c } from '@esportsplus/typescript/compiler';
2
- import { addImport, generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen';
3
- import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants';
4
- import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
5
1
  import { ts } from '@esportsplus/typescript';
2
+ import { code as c, imports, uid } from '@esportsplus/typescript/compiler';
3
+ import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
4
+ import { generateCode, generateReactiveInlining } from './codegen';
5
+ import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
6
6
 
7
7
 
8
8
  type TransformResult = {
@@ -39,22 +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,
43
- reactiveCalls = findReactiveCalls(sourceFile),
42
+ existingAliases = new Map<string, string>(),
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
- checker = undefined;
49
- result = generateReactiveInlining(reactiveCalls, result, sourceFile);
50
+ existingAliases.set('ArraySlot', arraySlotAlias);
51
+ result = generateReactiveInlining(reactiveCalls, result, sourceFile, arraySlotAlias);
50
52
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
51
- needsImport = needsArraySlotImport(sourceFile);
52
53
  }
53
54
 
54
- let templates = findHtmlTemplates(sourceFile);
55
+ let templates = findHtmlTemplates(sourceFile, checker);
55
56
 
56
57
  if (templates.length > 0) {
57
- let codegenResult = generateCode(templates, result, sourceFile, checker);
58
+ let codegenResult = generateCode(templates, result, sourceFile, checker, existingAliases);
58
59
 
59
60
  if (codegenResult.changed) {
60
61
  changed = true;
@@ -63,8 +64,15 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
63
64
  }
64
65
  }
65
66
 
66
- if (needsImport && !codegenChanged) {
67
- result = addImport(result);
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 });
68
76
  }
69
77
 
70
78
  if (changed) {
@@ -76,9 +84,3 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
76
84
 
77
85
 
78
86
  export { transform };
79
- export * from '~/attributes';
80
- export * from '~/event';
81
- export { ArraySlot } from '~/slot/array';
82
- export { EffectSlot } from '~/slot/effect';
83
- export type * from '~/types';
84
- export * from '~/utilities';
@@ -1,4 +1,4 @@
1
- import { COMPILER_TYPES, PACKAGE, SLOT_HTML } from '../constants';
1
+ import { COMPILER_TYPES, PACKAGE, SLOT_HTML } from '~/constants';
2
2
 
3
3
 
4
4
  type NodePath = ('firstChild' | 'firstElementChild' | 'nextElementSibling' | 'nextSibling')[];
@@ -1,5 +1,5 @@
1
- import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants';
2
1
  import { ts } from '@esportsplus/typescript';
2
+ import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
3
3
 
4
4
 
5
5
  type ReactiveCallInfo = {
@@ -20,14 +20,55 @@ type TemplateInfo = {
20
20
  };
21
21
 
22
22
 
23
- function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[]): void {
23
+ function isHtmlFromPackage(node: ts.Identifier, checker: ts.TypeChecker | undefined): boolean {
24
+ // Fast path: check identifier name first
25
+ if (node.text !== COMPILER_ENTRYPOINT) {
26
+ return false;
27
+ }
28
+
29
+ // Without checker, fall back to string-based check
30
+ if (!checker) {
31
+ return true;
32
+ }
33
+
34
+ let symbol = checker.getSymbolAtLocation(node);
35
+
36
+ if (!symbol) {
37
+ return true;
38
+ }
39
+
40
+ // Follow aliases (re-exports) to find original symbol
41
+ if (symbol.flags & ts.SymbolFlags.Alias) {
42
+ symbol = checker.getAliasedSymbol(symbol);
43
+ }
44
+
45
+ let declarations = symbol.getDeclarations();
46
+
47
+ if (!declarations || declarations.length === 0) {
48
+ return true;
49
+ }
50
+
51
+ // Check if any declaration is from our package
52
+ for (let i = 0, n = declarations.length; i < n; i++) {
53
+ let fileName = declarations[i].getSourceFile().fileName;
54
+
55
+ // Check for package in node_modules path or direct package reference
56
+ if (fileName.includes(PACKAGE) || fileName.includes('@esportsplus/template')) {
57
+ return true;
58
+ }
59
+ }
60
+
61
+ return false;
62
+ }
63
+
64
+ function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[], checker: ts.TypeChecker | undefined): void {
24
65
  if (
25
66
  ts.isCallExpression(node) &&
26
67
  ts.isPropertyAccessExpression(node.expression) &&
27
68
  ts.isIdentifier(node.expression.expression) &&
28
- node.expression.expression.text === COMPILER_ENTRYPOINT &&
29
69
  node.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY &&
30
- node.arguments.length === 2
70
+ node.arguments.length === 2 &&
71
+ isHtmlFromPackage(node.expression.expression, checker)
31
72
  ) {
32
73
  calls.push({
33
74
  arrayArg: node.arguments[0],
@@ -38,15 +79,15 @@ function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[]): void {
38
79
  });
39
80
  }
40
81
 
41
- ts.forEachChild(node, child => visitReactiveCalls(child, calls));
82
+ ts.forEachChild(node, child => visitReactiveCalls(child, calls, checker));
42
83
  }
43
84
 
44
- function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[]): void {
85
+ function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[], checker: ts.TypeChecker | undefined): void {
45
86
  let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
46
87
  ? depth + 1
47
88
  : depth;
48
89
 
49
- if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag.text === COMPILER_ENTRYPOINT) {
90
+ if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isHtmlFromPackage(node.tag, checker)) {
50
91
  let expressions: ts.Expression[] = [],
51
92
  literals: string[] = [],
52
93
  template = node.template;
@@ -75,14 +116,14 @@ function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[])
75
116
  });
76
117
  }
77
118
 
78
- ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates));
119
+ ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates, checker));
79
120
  }
80
121
 
81
122
 
82
- const findHtmlTemplates = (sourceFile: ts.SourceFile): TemplateInfo[] => {
123
+ const findHtmlTemplates = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): TemplateInfo[] => {
83
124
  let templates: TemplateInfo[] = [];
84
125
 
85
- visitTemplates(sourceFile, 0, templates);
126
+ visitTemplates(sourceFile, 0, templates, checker);
86
127
 
87
128
  // Sort by depth descending (deepest first), then by position for stable ordering
88
129
  templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
@@ -90,10 +131,10 @@ const findHtmlTemplates = (sourceFile: ts.SourceFile): TemplateInfo[] => {
90
131
  return templates;
91
132
  };
92
133
 
93
- const findReactiveCalls = (sourceFile: ts.SourceFile): ReactiveCallInfo[] => {
134
+ const findReactiveCalls = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): ReactiveCallInfo[] => {
94
135
  let calls: ReactiveCallInfo[] = [];
95
136
 
96
- visitReactiveCalls(sourceFile, calls);
137
+ visitReactiveCalls(sourceFile, calls, checker);
97
138
 
98
139
  return calls;
99
140
  };