@esportsplus/template 0.34.0 → 0.35.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.
@@ -4,9 +4,8 @@ type CodegenResult = {
4
4
  changed: boolean;
5
5
  code: string;
6
6
  };
7
- declare const addImport: (code: string) => string;
8
7
  declare const generateCode: (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => CodegenResult;
9
8
  declare const generateReactiveInlining: (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile) => string;
10
9
  declare const needsArraySlotImport: (sourceFile: ts.SourceFile) => boolean;
11
- export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
10
+ export { generateCode, generateReactiveInlining, needsArraySlotImport };
12
11
  export type { CodegenResult };
@@ -1,10 +1,9 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
- import { ast, code as c, uid } from '@esportsplus/typescript/compiler';
2
+ import { ast, code as c, imports, uid } from '@esportsplus/typescript/compiler';
3
+ import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '../constants.js';
3
4
  import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer.js';
4
- import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES, PACKAGE, PACKAGE_COMPILER } from '../constants.js';
5
5
  import parser from './parser.js';
6
6
  const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
7
- const REGEX_PACKAGE_IMPORT = new RegExp(`import\\s*\\{[^}]*\\}\\s*from\\s*['"]${PACKAGE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"];?\\n?`, 'g');
8
7
  let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
9
8
  function collectNestedTemplateReplacements(ctx, node, exprStart, replacements) {
10
9
  if (isNestedHtmlTemplate(node)) {
@@ -36,7 +35,8 @@ function generateNestedTemplateCode(ctx, node) {
36
35
  }
37
36
  function generateNodeBinding(ctx, anchor, exprText, exprNode) {
38
37
  if (!exprNode) {
39
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
38
+ ctx.neededImports.add('slot');
39
+ return `slot(${anchor}, ${exprText});`;
40
40
  }
41
41
  if (isNestedHtmlTemplate(exprNode)) {
42
42
  return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
@@ -44,15 +44,18 @@ function generateNodeBinding(ctx, anchor, exprText, exprNode) {
44
44
  let slotType = analyzeExpression(exprNode, ctx.checker);
45
45
  switch (slotType) {
46
46
  case COMPILER_TYPES.Effect:
47
- return `new ${COMPILER_NAMESPACE}.EffectSlot(${anchor}, ${exprText});`;
47
+ ctx.neededImports.add('EffectSlot');
48
+ return `new EffectSlot(${anchor}, ${exprText});`;
48
49
  case COMPILER_TYPES.ArraySlot:
49
- return `new ${COMPILER_NAMESPACE}.ArraySlot(${anchor}, ${exprText});`;
50
+ ctx.neededImports.add('ArraySlot');
51
+ return `new ArraySlot(${anchor}, ${exprText});`;
50
52
  case COMPILER_TYPES.Static:
51
53
  return `${anchor}.textContent = ${exprText};`;
52
54
  case COMPILER_TYPES.DocumentFragment:
53
55
  return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
54
56
  default:
55
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
57
+ ctx.neededImports.add('slot');
58
+ return `slot(${anchor}, ${exprText});`;
56
59
  }
57
60
  }
58
61
  function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArrowBody) {
@@ -81,7 +84,8 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
81
84
  }
82
85
  }
83
86
  let name = uid('element'), suffix = path.slice(startIdx).join('.');
84
- declarations.push(`${name} = ${ancestorVar}.${suffix} as ${COMPILER_NAMESPACE}.Element`);
87
+ ctx.neededImports.add('Element');
88
+ declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
85
89
  nodes.set(key, name);
86
90
  }
87
91
  code.push(isArrowBody ? '{' : `(() => {`, `let ${declarations.join(',\n')};`);
@@ -94,14 +98,14 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
94
98
  for (let j = 0, m = names.length; j < m; j++) {
95
99
  let name = names[j];
96
100
  if (name === COMPILER_TYPES.Attributes) {
97
- let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker, COMPILER_NAMESPACE);
101
+ let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker, ctx.neededImports);
98
102
  for (let k = 0, o = bindings.length; k < o; k++) {
99
103
  code.push(bindings[k]);
100
104
  }
101
105
  index++;
102
106
  }
103
107
  else {
104
- code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', COMPILER_NAMESPACE));
108
+ code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', ctx.neededImports));
105
109
  }
106
110
  }
107
111
  }
@@ -155,12 +159,6 @@ function rewriteExpression(ctx, expr) {
155
159
  collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
156
160
  return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
157
161
  }
158
- const addImport = (code) => {
159
- return `import * as ${COMPILER_NAMESPACE} from '${PACKAGE_COMPILER}';\n` + code;
160
- };
161
- const removePackageImport = (code) => {
162
- return code.replace(REGEX_PACKAGE_IMPORT, '');
163
- };
164
162
  const generateCode = (templates, originalCode, sourceFile, checker) => {
165
163
  if (templates.length === 0) {
166
164
  return { changed: false, code: originalCode };
@@ -180,6 +178,7 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
180
178
  checker,
181
179
  hoistedFactories: new Map(),
182
180
  htmlToTemplateId: new Map(),
181
+ neededImports: new Set(['template']),
183
182
  printer,
184
183
  sourceFile
185
184
  }, replacements = [];
@@ -210,9 +209,12 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
210
209
  if (changed && ctx.hoistedFactories.size > 0) {
211
210
  let factories = [];
212
211
  for (let [id, html] of ctx.hoistedFactories) {
213
- factories.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
212
+ factories.push(`const ${id} = template(\`${html}\`);`);
214
213
  }
215
- code = addImport(factories.join('\n') + removePackageImport(code));
214
+ code = imports.modify(factories.join('\n') + code, sourceFile, PACKAGE, {
215
+ add: ctx.neededImports,
216
+ remove: [COMPILER_ENTRYPOINT]
217
+ });
216
218
  }
217
219
  return { changed, code };
218
220
  };
@@ -225,7 +227,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
225
227
  let call = calls[i];
226
228
  replacements.push({
227
229
  end: call.end,
228
- newText: `new ${COMPILER_NAMESPACE}.ArraySlot(
230
+ newText: `new ArraySlot(
229
231
  ${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
230
232
  ${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
231
233
  )`,
@@ -236,7 +238,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
236
238
  };
237
239
  const needsArraySlotImport = (sourceFile) => {
238
240
  return ast.hasMatch(sourceFile, n => ts.isNewExpression(n) &&
239
- ts.isPropertyAccessExpression(n.expression) &&
240
- n.expression.name.text === 'ArraySlot') && !hasArraySlotImport(sourceFile);
241
+ ts.isIdentifier(n.expression) &&
242
+ n.expression.text === 'ArraySlot') && !hasArraySlotImport(sourceFile);
241
243
  };
242
- export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
244
+ export { generateCode, generateReactiveInlining, needsArraySlotImport };
@@ -6,9 +6,3 @@ type TransformResult = {
6
6
  };
7
7
  declare const transform: (sourceFile: ts.SourceFile, program: ts.Program) => TransformResult;
8
8
  export { transform };
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 type * from '../types.js';
14
- export * from '../utilities.js';
@@ -1,8 +1,8 @@
1
- import { code as c } from '@esportsplus/typescript/compiler';
2
- import { addImport, generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen.js';
3
- import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants.js';
4
- import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
5
1
  import { ts } from '@esportsplus/typescript';
2
+ import { code as c, imports } from '@esportsplus/typescript/compiler';
3
+ import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '../constants.js';
4
+ import { generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen.js';
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;
8
8
  const REGEX_FORWARD_SLASH = /\//g;
@@ -18,15 +18,14 @@ 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), result = code;
21
+ let changed = false, codegenChanged = false, needsImport = false, reactiveCalls = findReactiveCalls(sourceFile, checker), result = code;
22
22
  if (reactiveCalls.length > 0) {
23
23
  changed = true;
24
- checker = undefined;
25
24
  result = generateReactiveInlining(reactiveCalls, result, sourceFile);
26
25
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
27
26
  needsImport = needsArraySlotImport(sourceFile);
28
27
  }
29
- let templates = findHtmlTemplates(sourceFile);
28
+ let templates = findHtmlTemplates(sourceFile, checker);
30
29
  if (templates.length > 0) {
31
30
  let codegenResult = generateCode(templates, result, sourceFile, checker);
32
31
  if (codegenResult.changed) {
@@ -36,7 +35,7 @@ const transform = (sourceFile, program) => {
36
35
  }
37
36
  }
38
37
  if (needsImport && !codegenChanged) {
39
- result = addImport(result);
38
+ result = imports.modify(result, sourceFile, PACKAGE, { add: ['ArraySlot'] });
40
39
  }
41
40
  if (changed) {
42
41
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
@@ -44,8 +43,3 @@ const transform = (sourceFile, program) => {
44
43
  return { changed, code: result, sourceFile };
45
44
  };
46
45
  export { transform };
47
- export * from '../attributes.js';
48
- export * from '../event/index.js';
49
- export { ArraySlot } from '../slot/array.js';
50
- export { EffectSlot } from '../slot/effect.js';
51
- export * from '../utilities.js';
@@ -14,7 +14,7 @@ type TemplateInfo = {
14
14
  node: ts.TaggedTemplateExpression;
15
15
  start: number;
16
16
  };
17
- declare const findHtmlTemplates: (sourceFile: ts.SourceFile) => TemplateInfo[];
18
- declare const findReactiveCalls: (sourceFile: ts.SourceFile) => ReactiveCallInfo[];
17
+ declare const findHtmlTemplates: (sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => TemplateInfo[];
18
+ declare const findReactiveCalls: (sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => ReactiveCallInfo[];
19
19
  export { findHtmlTemplates, findReactiveCalls };
20
20
  export type { ReactiveCallInfo, TemplateInfo };
@@ -1,12 +1,38 @@
1
- import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants.js';
2
1
  import { ts } from '@esportsplus/typescript';
3
- function visitReactiveCalls(node, calls) {
2
+ import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '../constants.js';
3
+ function isHtmlFromPackage(node, checker) {
4
+ if (node.text !== COMPILER_ENTRYPOINT) {
5
+ return false;
6
+ }
7
+ if (!checker) {
8
+ return true;
9
+ }
10
+ let symbol = checker.getSymbolAtLocation(node);
11
+ if (!symbol) {
12
+ return true;
13
+ }
14
+ if (symbol.flags & ts.SymbolFlags.Alias) {
15
+ symbol = checker.getAliasedSymbol(symbol);
16
+ }
17
+ let declarations = symbol.getDeclarations();
18
+ if (!declarations || declarations.length === 0) {
19
+ return true;
20
+ }
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')) {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
29
+ function visitReactiveCalls(node, calls, checker) {
4
30
  if (ts.isCallExpression(node) &&
5
31
  ts.isPropertyAccessExpression(node.expression) &&
6
32
  ts.isIdentifier(node.expression.expression) &&
7
- node.expression.expression.text === COMPILER_ENTRYPOINT &&
8
33
  node.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY &&
9
- node.arguments.length === 2) {
34
+ node.arguments.length === 2 &&
35
+ isHtmlFromPackage(node.expression.expression, checker)) {
10
36
  calls.push({
11
37
  arrayArg: node.arguments[0],
12
38
  callbackArg: node.arguments[1],
@@ -15,13 +41,13 @@ function visitReactiveCalls(node, calls) {
15
41
  start: node.getStart()
16
42
  });
17
43
  }
18
- ts.forEachChild(node, child => visitReactiveCalls(child, calls));
44
+ ts.forEachChild(node, child => visitReactiveCalls(child, calls, checker));
19
45
  }
20
- function visitTemplates(node, depth, templates) {
46
+ function visitTemplates(node, depth, templates, checker) {
21
47
  let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
22
48
  ? depth + 1
23
49
  : depth;
24
- if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag.text === COMPILER_ENTRYPOINT) {
50
+ if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isHtmlFromPackage(node.tag, checker)) {
25
51
  let expressions = [], literals = [], template = node.template;
26
52
  if (ts.isNoSubstitutionTemplateLiteral(template)) {
27
53
  literals.push(template.text);
@@ -43,17 +69,17 @@ function visitTemplates(node, depth, templates) {
43
69
  start: node.getStart()
44
70
  });
45
71
  }
46
- ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates));
72
+ ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates, checker));
47
73
  }
48
- const findHtmlTemplates = (sourceFile) => {
74
+ const findHtmlTemplates = (sourceFile, checker) => {
49
75
  let templates = [];
50
- visitTemplates(sourceFile, 0, templates);
76
+ visitTemplates(sourceFile, 0, templates, checker);
51
77
  templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
52
78
  return templates;
53
79
  };
54
- const findReactiveCalls = (sourceFile) => {
80
+ const findReactiveCalls = (sourceFile, checker) => {
55
81
  let calls = [];
56
- visitReactiveCalls(sourceFile, calls);
82
+ visitReactiveCalls(sourceFile, calls, checker);
57
83
  return calls;
58
84
  };
59
85
  export { findHtmlTemplates, findReactiveCalls };
@@ -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, ns: string) => string;
5
- declare const generateSpreadBindings: (expr: ts.Expression, exprCode: string, elementVar: string, checker: ts.TypeChecker | undefined, ns: string) => string[];
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[];
6
6
  export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
@@ -119,35 +119,43 @@ function isTypeFunction(type, checker) {
119
119
  const analyzeExpression = (expr, checker) => {
120
120
  return inferCOMPILER_TYPES(expr, checker);
121
121
  };
122
- const generateAttributeBinding = (elementVar, name, expr, staticValue, ns) => {
122
+ const generateAttributeBinding = (elementVar, name, expr, staticValue, neededImports) => {
123
123
  if (name.startsWith('on') && name.length > 2) {
124
124
  let event = name.slice(2).toLowerCase(), key = name.toLowerCase();
125
125
  if (LIFECYCLE_EVENTS.has(key)) {
126
- return `${ns}.${key}(${elementVar}, ${expr});`;
126
+ neededImports.add(key);
127
+ return `${key}(${elementVar}, ${expr});`;
127
128
  }
128
129
  if (DIRECT_ATTACH_EVENTS.has(key)) {
129
- return `${ns}.on(${elementVar}, '${event}', ${expr});`;
130
+ neededImports.add('on');
131
+ return `on(${elementVar}, '${event}', ${expr});`;
130
132
  }
131
- return `${ns}.delegate(${elementVar}, '${event}', ${expr});`;
133
+ neededImports.add('delegate');
134
+ return `delegate(${elementVar}, '${event}', ${expr});`;
132
135
  }
133
136
  if (name === 'class') {
134
- return `${ns}.setClass(${elementVar}, '${staticValue}', ${expr});`;
137
+ neededImports.add('setClass');
138
+ return `setClass(${elementVar}, '${staticValue}', ${expr});`;
135
139
  }
136
140
  if (name === COMPILER_TYPES.Attributes) {
137
- return `${ns}.setProperties(${elementVar}, ${expr});`;
141
+ neededImports.add('setProperties');
142
+ return `setProperties(${elementVar}, ${expr});`;
138
143
  }
139
144
  if (name === 'style') {
140
- return `${ns}.setStyle(${elementVar}, '${staticValue}', ${expr});`;
145
+ neededImports.add('setStyle');
146
+ return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
141
147
  }
142
- return `${ns}.setProperty(${elementVar}, '${name}', ${expr});`;
148
+ neededImports.add('setProperty');
149
+ return `setProperty(${elementVar}, '${name}', ${expr});`;
143
150
  };
144
- const generateSpreadBindings = (expr, exprCode, elementVar, checker, ns) => {
151
+ const generateSpreadBindings = (expr, exprCode, elementVar, checker, neededImports) => {
145
152
  while (ts.isParenthesizedExpression(expr)) {
146
153
  expr = expr.expression;
147
154
  }
148
155
  let analysis = analyzeSpread(expr, checker);
149
156
  if (!analysis.canUnpack) {
150
- return [`${ns}.setProperties(${elementVar}, ${exprCode});`];
157
+ neededImports.add('setProperties');
158
+ return [`setProperties(${elementVar}, ${exprCode});`];
151
159
  }
152
160
  let lines = [];
153
161
  if (ts.isObjectLiteralExpression(expr)) {
@@ -170,14 +178,14 @@ const generateSpreadBindings = (expr, exprCode, elementVar, checker, ns) => {
170
178
  }
171
179
  }
172
180
  if (value !== null) {
173
- lines.push(generateAttributeBinding(elementVar, key, value, '', ns));
181
+ lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
174
182
  }
175
183
  }
176
184
  }
177
185
  else {
178
186
  for (let i = 0, n = analysis.keys.length; i < n; i++) {
179
187
  let key = analysis.keys[i];
180
- lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', ns));
188
+ lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
181
189
  }
182
190
  }
183
191
  return lines;
@@ -2,7 +2,6 @@ declare const ARRAY_SLOT: unique symbol;
2
2
  declare const CLEANUP: unique symbol;
3
3
  declare const COMPILER_ENTRYPOINT = "html";
4
4
  declare const COMPILER_ENTRYPOINT_REACTIVITY = "reactive";
5
- declare const COMPILER_NAMESPACE: string;
6
5
  declare const enum COMPILER_TYPES {
7
6
  ArraySlot = "array-slot",
8
7
  Attributes = "attributes",
@@ -17,10 +16,9 @@ declare const enum COMPILER_TYPES {
17
16
  declare const DIRECT_ATTACH_EVENTS: Set<string>;
18
17
  declare const LIFECYCLE_EVENTS: Set<string>;
19
18
  declare const PACKAGE = "@esportsplus/template";
20
- declare const PACKAGE_COMPILER = "@esportsplus/template/compiler";
21
19
  declare const SLOT_HTML = "<!--$-->";
22
20
  declare const STATE_HYDRATING = 0;
23
21
  declare const STATE_NONE = 1;
24
22
  declare const STATE_WAITING = 2;
25
23
  declare const STORE: unique symbol;
26
- export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, PACKAGE_COMPILER, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
24
+ export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
@@ -1,9 +1,7 @@
1
- import { uid } from '@esportsplus/typescript/compiler';
2
1
  const ARRAY_SLOT = Symbol('template.array.slot');
3
2
  const CLEANUP = Symbol('template.cleanup');
4
3
  const COMPILER_ENTRYPOINT = 'html';
5
4
  const COMPILER_ENTRYPOINT_REACTIVITY = 'reactive';
6
- const COMPILER_NAMESPACE = uid('template');
7
5
  var COMPILER_TYPES;
8
6
  (function (COMPILER_TYPES) {
9
7
  COMPILER_TYPES["ArraySlot"] = "array-slot";
@@ -30,10 +28,9 @@ const LIFECYCLE_EVENTS = new Set([
30
28
  'onconnect', 'ondisconnect', 'onrender', 'onresize', 'ontick'
31
29
  ]);
32
30
  const PACKAGE = '@esportsplus/template';
33
- const PACKAGE_COMPILER = '@esportsplus/template/compiler';
34
31
  const SLOT_HTML = '<!--$-->';
35
32
  const STATE_HYDRATING = 0;
36
33
  const STATE_NONE = 1;
37
34
  const STATE_WAITING = 2;
38
35
  const STORE = Symbol('template.store');
39
- export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, PACKAGE_COMPILER, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
36
+ export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
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,10 +1,10 @@
1
1
  import { SLOT_HTML } from './constants.js';
2
- let clonableTemplate = document.createElement('template'), clonableText = 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);
6
6
  const fragment = (html) => {
7
- let element = clonableTemplate.cloneNode();
7
+ let element = tmpl.cloneNode();
8
8
  element.innerHTML = html;
9
9
  return element.content;
10
10
  };
@@ -14,7 +14,7 @@ const template = (html) => {
14
14
  let cached;
15
15
  return () => {
16
16
  if (!cached) {
17
- let element = clonableTemplate.cloneNode();
17
+ let element = tmpl.cloneNode();
18
18
  element.innerHTML = html;
19
19
  cached = element.content;
20
20
  }
@@ -22,7 +22,7 @@ const template = (html) => {
22
22
  };
23
23
  };
24
24
  const text = (value) => {
25
- let element = clonableText.cloneNode();
25
+ let element = txt.cloneNode();
26
26
  if (value !== '') {
27
27
  element.nodeValue = value;
28
28
  }
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.0",
38
+ "version": "0.35.0",
45
39
  "scripts": {
46
40
  "build": "tsc",
47
41
  "build:test": "vite build --config test/vite.config.ts",
@@ -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
 
@@ -22,6 +19,7 @@ type CodegenContext = {
22
19
  checker?: ts.TypeChecker;
23
20
  hoistedFactories: Map<string, string>;
24
21
  htmlToTemplateId: Map<string, string>;
22
+ neededImports: Set<string>;
25
23
  printer: ts.Printer;
26
24
  sourceFile: ts.SourceFile;
27
25
  };
@@ -44,11 +42,6 @@ type ParseResult = {
44
42
 
45
43
  const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
46
44
 
47
- const REGEX_PACKAGE_IMPORT = new RegExp(
48
- `import\\s*\\{[^}]*\\}\\s*from\\s*['"]${PACKAGE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"];?\\n?`,
49
- 'g'
50
- );
51
-
52
45
 
53
46
  let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
54
47
 
@@ -103,7 +96,8 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
103
96
 
104
97
  function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
105
98
  if (!exprNode) {
106
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
99
+ ctx.neededImports.add('slot');
100
+ return `slot(${anchor}, ${exprText});`;
107
101
  }
108
102
 
109
103
  if (isNestedHtmlTemplate(exprNode)) {
@@ -114,10 +108,12 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
114
108
 
115
109
  switch (slotType) {
116
110
  case COMPILER_TYPES.Effect:
117
- return `new ${COMPILER_NAMESPACE}.EffectSlot(${anchor}, ${exprText});`;
111
+ ctx.neededImports.add('EffectSlot');
112
+ return `new EffectSlot(${anchor}, ${exprText});`;
118
113
 
119
114
  case COMPILER_TYPES.ArraySlot:
120
- return `new ${COMPILER_NAMESPACE}.ArraySlot(${anchor}, ${exprText});`;
115
+ ctx.neededImports.add('ArraySlot');
116
+ return `new ArraySlot(${anchor}, ${exprText});`;
121
117
 
122
118
  case COMPILER_TYPES.Static:
123
119
  return `${anchor}.textContent = ${exprText};`;
@@ -126,7 +122,8 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
126
122
  return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
127
123
 
128
124
  default:
129
- return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
125
+ ctx.neededImports.add('slot');
126
+ return `slot(${anchor}, ${exprText});`;
130
127
  }
131
128
  }
132
129
 
@@ -179,7 +176,8 @@ function generateTemplateCode(
179
176
  let name = uid('element'),
180
177
  suffix = path.slice(startIdx).join('.');
181
178
 
182
- declarations.push(`${name} = ${ancestorVar}.${suffix} as ${COMPILER_NAMESPACE}.Element`);
179
+ ctx.neededImports.add('Element');
180
+ declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
183
181
  nodes.set(key, name);
184
182
  }
185
183
 
@@ -206,7 +204,7 @@ function generateTemplateCode(
206
204
  exprTexts[index] || 'undefined',
207
205
  elementVar,
208
206
  ctx.checker,
209
- COMPILER_NAMESPACE
207
+ ctx.neededImports
210
208
  );
211
209
 
212
210
  for (let k = 0, o = bindings.length; k < o; k++) {
@@ -222,7 +220,7 @@ function generateTemplateCode(
222
220
  name,
223
221
  exprTexts[index++] || 'undefined',
224
222
  slot.attributes.statics[name] || '',
225
- COMPILER_NAMESPACE
223
+ ctx.neededImports
226
224
  )
227
225
  );
228
226
  }
@@ -299,14 +297,6 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
299
297
  }
300
298
 
301
299
 
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
300
  const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
311
301
  if (templates.length === 0) {
312
302
  return { changed: false, code: originalCode };
@@ -333,6 +323,7 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
333
323
  checker,
334
324
  hoistedFactories: new Map(),
335
325
  htmlToTemplateId: new Map(),
326
+ neededImports: new Set(['template']),
336
327
  printer,
337
328
  sourceFile
338
329
  },
@@ -384,10 +375,13 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
384
375
  let factories: string[] = [];
385
376
 
386
377
  for (let [id, html] of ctx.hoistedFactories) {
387
- factories.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
378
+ factories.push(`const ${id} = template(\`${html}\`);`);
388
379
  }
389
380
 
390
- code = addImport(factories.join('\n') + removePackageImport(code));
381
+ code = imports.modify(factories.join('\n') + code, sourceFile, PACKAGE, {
382
+ add: ctx.neededImports,
383
+ remove: [COMPILER_ENTRYPOINT]
384
+ });
391
385
  }
392
386
 
393
387
  return { changed, code };
@@ -405,7 +399,7 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
405
399
 
406
400
  replacements.push({
407
401
  end: call.end,
408
- newText: `new ${COMPILER_NAMESPACE}.ArraySlot(
402
+ newText: `new ArraySlot(
409
403
  ${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
410
404
  ${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
411
405
  )`,
@@ -419,11 +413,11 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
419
413
  const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
420
414
  return ast.hasMatch(sourceFile, n =>
421
415
  ts.isNewExpression(n) &&
422
- ts.isPropertyAccessExpression(n.expression) &&
423
- n.expression.name.text === 'ArraySlot'
416
+ ts.isIdentifier(n.expression) &&
417
+ n.expression.text === 'ArraySlot'
424
418
  ) && !hasArraySlotImport(sourceFile);
425
419
  };
426
420
 
427
421
 
428
- export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
422
+ export { generateCode, generateReactiveInlining, needsArraySlotImport };
429
423
  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 } from '@esportsplus/typescript/compiler';
3
+ import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
4
+ import { generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen';
5
+ import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
6
6
 
7
7
 
8
8
  type TransformResult = {
@@ -40,18 +40,17 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
40
40
  let changed = false,
41
41
  codegenChanged = false,
42
42
  needsImport = false,
43
- reactiveCalls = findReactiveCalls(sourceFile),
43
+ reactiveCalls = findReactiveCalls(sourceFile, checker),
44
44
  result = code;
45
45
 
46
46
  if (reactiveCalls.length > 0) {
47
47
  changed = true;
48
- checker = undefined;
49
48
  result = generateReactiveInlining(reactiveCalls, result, sourceFile);
50
49
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
51
50
  needsImport = needsArraySlotImport(sourceFile);
52
51
  }
53
52
 
54
- let templates = findHtmlTemplates(sourceFile);
53
+ let templates = findHtmlTemplates(sourceFile, checker);
55
54
 
56
55
  if (templates.length > 0) {
57
56
  let codegenResult = generateCode(templates, result, sourceFile, checker);
@@ -64,7 +63,7 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
64
63
  }
65
64
 
66
65
  if (needsImport && !codegenChanged) {
67
- result = addImport(result);
66
+ result = imports.modify(result, sourceFile, PACKAGE, { add: ['ArraySlot'] });
68
67
  }
69
68
 
70
69
  if (changed) {
@@ -76,9 +75,3 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
76
75
 
77
76
 
78
77
  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
  };
@@ -4,7 +4,7 @@ import {
4
4
  COMPILER_TYPES,
5
5
  DIRECT_ATTACH_EVENTS,
6
6
  LIFECYCLE_EVENTS
7
- } from '../constants';
7
+ } from '~/constants';
8
8
  import { ts } from '@esportsplus/typescript';
9
9
 
10
10
 
@@ -172,35 +172,42 @@ const analyzeExpression = (expr: ts.Expression, checker?: ts.TypeChecker): COMPI
172
172
  return inferCOMPILER_TYPES(expr, checker);
173
173
  };
174
174
 
175
- const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, ns: string): string => {
175
+ const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, neededImports: Set<string>): string => {
176
176
  if (name.startsWith('on') && name.length > 2) {
177
177
  let event = name.slice(2).toLowerCase(),
178
178
  key = name.toLowerCase();
179
179
 
180
180
  if (LIFECYCLE_EVENTS.has(key)) {
181
- return `${ns}.${key}(${elementVar}, ${expr});`;
181
+ neededImports.add(key);
182
+ return `${key}(${elementVar}, ${expr});`;
182
183
  }
183
184
 
184
185
  if (DIRECT_ATTACH_EVENTS.has(key)) {
185
- return `${ns}.on(${elementVar}, '${event}', ${expr});`;
186
+ neededImports.add('on');
187
+ return `on(${elementVar}, '${event}', ${expr});`;
186
188
  }
187
189
 
188
- return `${ns}.delegate(${elementVar}, '${event}', ${expr});`;
190
+ neededImports.add('delegate');
191
+ return `delegate(${elementVar}, '${event}', ${expr});`;
189
192
  }
190
193
 
191
194
  if (name === 'class') {
192
- return `${ns}.setClass(${elementVar}, '${staticValue}', ${expr});`;
195
+ neededImports.add('setClass');
196
+ return `setClass(${elementVar}, '${staticValue}', ${expr});`;
193
197
  }
194
198
 
195
199
  if (name === COMPILER_TYPES.Attributes) {
196
- return `${ns}.setProperties(${elementVar}, ${expr});`;
200
+ neededImports.add('setProperties');
201
+ return `setProperties(${elementVar}, ${expr});`;
197
202
  }
198
203
 
199
204
  if (name === 'style') {
200
- return `${ns}.setStyle(${elementVar}, '${staticValue}', ${expr});`;
205
+ neededImports.add('setStyle');
206
+ return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
201
207
  }
202
208
 
203
- return `${ns}.setProperty(${elementVar}, '${name}', ${expr});`;
209
+ neededImports.add('setProperty');
210
+ return `setProperty(${elementVar}, '${name}', ${expr});`;
204
211
  };
205
212
 
206
213
  const generateSpreadBindings = (
@@ -208,7 +215,7 @@ const generateSpreadBindings = (
208
215
  exprCode: string,
209
216
  elementVar: string,
210
217
  checker: ts.TypeChecker | undefined,
211
- ns: string
218
+ neededImports: Set<string>
212
219
  ): string[] => {
213
220
  while (ts.isParenthesizedExpression(expr)) {
214
221
  expr = expr.expression;
@@ -217,7 +224,8 @@ const generateSpreadBindings = (
217
224
  let analysis = analyzeSpread(expr, checker);
218
225
 
219
226
  if (!analysis.canUnpack) {
220
- return [`${ns}.setProperties(${elementVar}, ${exprCode});`];
227
+ neededImports.add('setProperties');
228
+ return [`setProperties(${elementVar}, ${exprCode});`];
221
229
  }
222
230
 
223
231
  let lines: string[] = [];
@@ -247,7 +255,7 @@ const generateSpreadBindings = (
247
255
  }
248
256
 
249
257
  if (value !== null) {
250
- lines.push(generateAttributeBinding(elementVar, key, value, '', ns));
258
+ lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
251
259
  }
252
260
  }
253
261
  }
@@ -255,7 +263,7 @@ const generateSpreadBindings = (
255
263
  for (let i = 0, n = analysis.keys.length; i < n; i++) {
256
264
  let key = analysis.keys[i];
257
265
 
258
- lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', ns));
266
+ lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
259
267
  }
260
268
  }
261
269
 
package/src/constants.ts CHANGED
@@ -1,17 +1,11 @@
1
- import { uid } from '@esportsplus/typescript/compiler';
2
-
3
-
4
1
  const ARRAY_SLOT = Symbol('template.array.slot');
5
2
 
6
3
  const CLEANUP = Symbol('template.cleanup');
7
4
 
8
-
9
5
  const COMPILER_ENTRYPOINT = 'html';
10
6
 
11
7
  const COMPILER_ENTRYPOINT_REACTIVITY = 'reactive';
12
8
 
13
- const COMPILER_NAMESPACE = uid('template');
14
-
15
9
  const enum COMPILER_TYPES {
16
10
  ArraySlot = 'array-slot',
17
11
  Attributes = 'attributes',
@@ -40,9 +34,6 @@ const LIFECYCLE_EVENTS = new Set<string>([
40
34
 
41
35
  const PACKAGE = '@esportsplus/template';
42
36
 
43
- const PACKAGE_COMPILER = '@esportsplus/template/compiler';
44
-
45
-
46
37
  const SLOT_HTML = '<!--$-->';
47
38
 
48
39
  const STATE_HYDRATING = 0;
@@ -57,9 +48,9 @@ const STORE = Symbol('template.store');
57
48
  export {
58
49
  ARRAY_SLOT,
59
50
  CLEANUP,
60
- COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES,
51
+ COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES,
61
52
  DIRECT_ATTACH_EVENTS,
62
53
  LIFECYCLE_EVENTS,
63
- PACKAGE, PACKAGE_COMPILER,
54
+ PACKAGE,
64
55
  SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE,
65
- };
56
+ };
package/src/index.ts CHANGED
@@ -1,5 +1,20 @@
1
- import './runtime';
1
+ import { CLEANUP, STORE } from './constants';
2
+
3
+
4
+ // Pre-allocate on Node prototype to optimize property access
5
+ if (typeof Node !== 'undefined') {
6
+ (Node.prototype as any)[CLEANUP] = null;
7
+ (Node.prototype as any)[STORE] = null;
8
+ }
9
+
10
+
2
11
  export { default as html } from './html';
3
12
  export { default as render } from './render';
4
13
  export { default as svg } from './svg';
14
+ export * from './attributes';
15
+ export * from './event';
16
+ export { ArraySlot } from './slot/array';
17
+ export { EffectSlot } from './slot/effect';
18
+ export { default as slot } from './slot';
5
19
  export type { Attributes, Element, Renderable } from './types';
20
+ export * from './utilities';
package/src/utilities.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { SLOT_HTML } from './constants';
2
2
 
3
3
 
4
- let clonableTemplate = document.createElement('template'),
5
- clonableText = document.createTextNode('');
4
+ let tmpl = document.createElement('template'),
5
+ txt = document.createTextNode('');
6
6
 
7
7
 
8
8
  // Firefox's importNode outperforms cloneNode in certain scenarios
@@ -12,7 +12,7 @@ const clone = typeof navigator !== 'undefined' && navigator.userAgent.includes('
12
12
 
13
13
  // Create a fragment from HTML string
14
14
  const fragment = (html: string): DocumentFragment => {
15
- let element = clonableTemplate.cloneNode() as HTMLTemplateElement;
15
+ let element = tmpl.cloneNode() as HTMLTemplateElement;
16
16
 
17
17
  element.innerHTML = html;
18
18
 
@@ -29,7 +29,8 @@ const template = (html: string) => {
29
29
 
30
30
  return () => {
31
31
  if (!cached) {
32
- let element = clonableTemplate.cloneNode() as HTMLTemplateElement;
32
+ let element = tmpl.cloneNode() as HTMLTemplateElement;
33
+
33
34
  element.innerHTML = html;
34
35
  cached = element.content;
35
36
  }
@@ -39,7 +40,7 @@ const template = (html: string) => {
39
40
  };
40
41
 
41
42
  const text = (value: string) => {
42
- let element = clonableText.cloneNode();
43
+ let element = txt.cloneNode();
43
44
 
44
45
  if (value !== '') {
45
46
  element.nodeValue = value;
@@ -1,7 +1,7 @@
1
1
  import { defineConfig } from 'vite';
2
2
  import { resolve } from 'path';
3
3
  import tsconfigPaths from 'vite-tsconfig-paths';
4
- import templatePlugin from '../src/compiler/vite';
4
+ import templatePlugin from '../src/compiler/plugins/vite';
5
5
 
6
6
 
7
7
  export default defineConfig({
@@ -1 +0,0 @@
1
- export {};
package/build/runtime.js DELETED
@@ -1,5 +0,0 @@
1
- import { CLEANUP, STORE } from './constants.js';
2
- if (typeof Node !== 'undefined') {
3
- Node.prototype[CLEANUP] = null;
4
- Node.prototype[STORE] = null;
5
- }
package/src/runtime.ts DELETED
@@ -1,8 +0,0 @@
1
- import { CLEANUP, STORE } from './constants';
2
-
3
-
4
- // Pre-allocate on Node prototype to optimize property access
5
- if (typeof Node !== 'undefined') {
6
- (Node.prototype as any)[CLEANUP] = null;
7
- (Node.prototype as any)[STORE] = null;
8
- }