@esportsplus/template 0.32.1 → 0.32.2

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,10 +1,10 @@
1
1
  import './runtime.js';
2
- export { default as attributes } from './attributes.js';
3
- export { default as event } from './event/index.js';
4
2
  export { default as html } from './html.js';
5
3
  export { default as render } from './render.js';
6
- export { default as slot } from './slot/index.js';
7
4
  export { default as svg } from './svg.js';
5
+ export { default as attributes } from './attributes.js';
6
+ export { default as event } from './event/index.js';
7
+ export { default as slot } from './slot/index.js';
8
8
  export { ArraySlot } from './slot/array.js';
9
9
  export { EffectSlot } from './slot/effect.js';
10
10
  export type { Attributes, Element, Renderable } from './types.js';
package/build/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import './runtime.js';
2
- export { default as attributes } from './attributes.js';
3
- export { default as event } from './event/index.js';
4
2
  export { default as html } from './html.js';
5
3
  export { default as render } from './render.js';
6
- export { default as slot } from './slot/index.js';
7
4
  export { default as svg } from './svg.js';
5
+ export { default as attributes } from './attributes.js';
6
+ export { default as event } from './event/index.js';
7
+ export { default as slot } from './slot/index.js';
8
8
  export { ArraySlot } from './slot/array.js';
9
9
  export { EffectSlot } from './slot/effect.js';
10
10
  export * from './utilities.js';
@@ -1,5 +1,5 @@
1
- import type { ReactiveCallInfo, TemplateInfo } from './ts-parser.js';
2
1
  import { ts } from '@esportsplus/typescript';
2
+ import type { ReactiveCallInfo, TemplateInfo } from './ts-parser.js';
3
3
  type CodegenResult = {
4
4
  changed: boolean;
5
5
  code: string;
@@ -1,6 +1,6 @@
1
+ import { ts } from '@esportsplus/typescript';
1
2
  import { code as c, uid } from '@esportsplus/typescript/transformer';
2
3
  import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer.js';
3
- import { ts } from '@esportsplus/typescript';
4
4
  import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES, PACKAGE } from '../constants.js';
5
5
  import parser from './parser.js';
6
6
  const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
@@ -89,18 +89,18 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
89
89
  ? root
90
90
  : (nodes.get(slots[i].path.join('.')) || root), slot = slots[i];
91
91
  if (slot.type === COMPILER_TYPES.AttributeSlot) {
92
- for (let j = 0, m = slot.attributes.names.length; j < m; j++) {
93
- let name = slot.attributes.names[j];
92
+ let names = slot.attributes.names;
93
+ for (let j = 0, m = names.length; j < m; j++) {
94
+ let name = names[j];
94
95
  if (name === 'spread') {
95
- let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.sourceFile, ctx.checker, COMPILER_NAMESPACE);
96
+ let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker, COMPILER_NAMESPACE);
96
97
  for (let k = 0, o = bindings.length; k < o; k++) {
97
98
  code.push(bindings[k]);
98
99
  }
99
100
  index++;
100
101
  }
101
102
  else {
102
- let binding = generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', COMPILER_NAMESPACE);
103
- code.push(binding);
103
+ code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', COMPILER_NAMESPACE));
104
104
  }
105
105
  }
106
106
  }
@@ -152,14 +152,6 @@ function hasMatch(node, predicate) {
152
152
  });
153
153
  return found;
154
154
  }
155
- function hasArraySlotUsage(node) {
156
- return hasMatch(node, n => ts.isNewExpression(n) &&
157
- ts.isPropertyAccessExpression(n.expression) &&
158
- n.expression.name.text === 'ArraySlot');
159
- }
160
- function hasNestedTemplates(node) {
161
- return hasMatch(node, n => isNestedHtmlTemplate(n));
162
- }
163
155
  function isNestedHtmlTemplate(expr) {
164
156
  return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
165
157
  }
@@ -176,11 +168,11 @@ function rewriteExpression(ctx, expr) {
176
168
  if (isNestedHtmlTemplate(expr)) {
177
169
  return generateNestedTemplateCode(ctx, expr);
178
170
  }
179
- if (!hasNestedTemplates(expr)) {
171
+ if (!hasMatch(expr, n => isNestedHtmlTemplate(n))) {
180
172
  return ctx.printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
181
173
  }
182
- let exprStart = expr.getStart(), replacements = [];
183
- collectNestedTemplateReplacements(ctx, expr, exprStart, replacements);
174
+ let replacements = [];
175
+ collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
184
176
  return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
185
177
  }
186
178
  const addArraySlotImport = (code) => {
@@ -190,14 +182,14 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
190
182
  if (templates.length === 0) {
191
183
  return { changed: false, code: originalCode };
192
184
  }
193
- let exprRanges = [];
185
+ let ranges = [];
194
186
  for (let i = 0, n = templates.length; i < n; i++) {
195
187
  let exprs = templates[i].expressions;
196
188
  for (let j = 0, m = exprs.length; j < m; j++) {
197
- exprRanges.push({ end: exprs[j].end, start: exprs[j].getStart() });
189
+ ranges.push({ end: exprs[j].end, start: exprs[j].getStart() });
198
190
  }
199
191
  }
200
- let rootTemplates = templates.filter(t => !isNestedTemplate(t, exprRanges));
192
+ let rootTemplates = templates.filter(t => !isNestedTemplate(t, ranges));
201
193
  if (rootTemplates.length === 0) {
202
194
  return { changed: false, code: originalCode };
203
195
  }
@@ -260,6 +252,8 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
260
252
  return c.replaceReverse(code, replacements);
261
253
  };
262
254
  const needsArraySlotImport = (sourceFile) => {
263
- return hasArraySlotUsage(sourceFile) && !hasArraySlotImport(sourceFile);
255
+ return hasMatch(sourceFile, n => ts.isNewExpression(n) &&
256
+ ts.isPropertyAccessExpression(n.expression) &&
257
+ n.expression.name.text === 'ArraySlot') && !hasArraySlotImport(sourceFile);
264
258
  };
265
259
  export { addArraySlotImport, generateCode, generateReactiveInlining, needsArraySlotImport };
@@ -4,8 +4,5 @@ type TransformResult = {
4
4
  code: string;
5
5
  sourceFile: ts.SourceFile;
6
6
  };
7
- declare const PATTERNS: string[];
8
- declare function createTransformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile>;
9
7
  declare const transform: (sourceFile: ts.SourceFile, program: ts.Program) => TransformResult;
10
- declare const transformCode: (code: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => TransformResult;
11
- export { createTransformer, transform, transformCode, PATTERNS };
8
+ export { transform };
@@ -4,44 +4,27 @@ import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constant
4
4
  import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
5
5
  import { ts } from '@esportsplus/typescript';
6
6
  const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
7
- function createTransformer(program) {
8
- let typeChecker = program.getTypeChecker();
9
- return (_context) => {
10
- return (sourceFile) => {
11
- let code = sourceFile.getFullText();
12
- if (!c.contains(code, { patterns: PATTERNS })) {
13
- return sourceFile;
14
- }
15
- let result = transformCode(code, sourceFile, typeChecker);
16
- if (!result.changed) {
17
- return sourceFile;
18
- }
19
- return result.sourceFile;
20
- };
21
- };
22
- }
7
+ const REGEX_BACKSLASH = /\\/g;
8
+ const REGEX_FORWARD_SLASH = /\//g;
23
9
  const transform = (sourceFile, program) => {
24
10
  let code = sourceFile.getFullText();
25
11
  if (!c.contains(code, { patterns: PATTERNS })) {
26
12
  return { changed: false, code, sourceFile };
27
13
  }
28
14
  let checker, fileName = sourceFile.fileName, programSourceFile = program.getSourceFile(fileName)
29
- || program.getSourceFile(fileName.replace(/\\/g, '/'))
30
- || program.getSourceFile(fileName.replace(/\//g, '\\'));
15
+ || program.getSourceFile(fileName.replace(REGEX_BACKSLASH, '/'))
16
+ || program.getSourceFile(fileName.replace(REGEX_FORWARD_SLASH, '\\'));
31
17
  if (programSourceFile) {
32
18
  checker = program.getTypeChecker();
33
19
  sourceFile = programSourceFile;
34
20
  }
35
- return transformCode(code, sourceFile, checker);
36
- };
37
- const transformCode = (code, sourceFile, checker) => {
38
- let changed = false, codegenChanged = false, needsImport = false, result = code, reactiveCalls = findReactiveCalls(sourceFile);
21
+ let changed = false, codegenChanged = false, needsImport = false, reactiveCalls = findReactiveCalls(sourceFile), result = code;
39
22
  if (reactiveCalls.length > 0) {
40
- result = generateReactiveInlining(reactiveCalls, result, sourceFile);
41
23
  changed = true;
24
+ checker = undefined;
25
+ result = generateReactiveInlining(reactiveCalls, result, sourceFile);
42
26
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
43
27
  needsImport = needsArraySlotImport(sourceFile);
44
- checker = undefined;
45
28
  }
46
29
  let templates = findHtmlTemplates(sourceFile);
47
30
  if (templates.length > 0) {
@@ -60,4 +43,4 @@ const transformCode = (code, sourceFile, checker) => {
60
43
  }
61
44
  return { changed, code: result, sourceFile };
62
45
  };
63
- export { createTransformer, transform, transformCode, PATTERNS };
46
+ export { transform };
@@ -1,3 +1,3 @@
1
- import { ts } from '@esportsplus/typescript';
2
- declare const _default: (program: ts.Program) => ts.TransformerFactory<ts.SourceFile>;
1
+ import { plugin } from '@esportsplus/typescript/transformer';
2
+ declare const _default: ReturnType<typeof plugin.tsc>;
3
3
  export default _default;
@@ -1,10 +1,3 @@
1
- import { transformCode } from '../../transformer/index.js';
2
- export default (program) => {
3
- let typeChecker = program.getTypeChecker();
4
- return () => {
5
- return (sourceFile) => {
6
- let result = transformCode(sourceFile.getFullText(), sourceFile, typeChecker);
7
- return result.changed ? result.sourceFile : sourceFile;
8
- };
9
- };
10
- };
1
+ import { plugin } from '@esportsplus/typescript/transformer';
2
+ import { transform } from '../index.js';
3
+ export default plugin.tsc(transform);
@@ -1,5 +1,13 @@
1
- import type { Plugin } from 'vite';
2
- declare const _default: (options?: {
1
+ declare const _default: ({ root }?: {
3
2
  root?: string;
4
- }) => Plugin;
3
+ }) => {
4
+ configResolved(config: import("vite").ResolvedConfig): void;
5
+ enforce: string;
6
+ name: string;
7
+ transform(code: string, id: string): {
8
+ code: string;
9
+ map: null;
10
+ } | null;
11
+ watchChange(id: string): void;
12
+ };
5
13
  export default _default;
@@ -1,35 +1,7 @@
1
- import { ts } from '@esportsplus/typescript';
2
- import { program, TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
1
+ import { plugin } from '@esportsplus/typescript/transformer';
3
2
  import { PACKAGE } from '../../constants.js';
4
- import { transform } from '../../transformer/index.js';
5
- export default (options) => {
6
- let root;
7
- return {
8
- configResolved(config) {
9
- root = options?.root ?? config.root;
10
- },
11
- enforce: 'pre',
12
- name: `${PACKAGE}/plugin-vite`,
13
- transform(code, id) {
14
- if (!TRANSFORM_PATTERN.test(id) || id.includes('node_modules')) {
15
- return null;
16
- }
17
- try {
18
- let result = transform(ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), program.get(root));
19
- if (!result.changed) {
20
- return null;
21
- }
22
- return { code: result.code, map: null };
23
- }
24
- catch (error) {
25
- console.error(`${PACKAGE}: Error transforming ${id}:`, error);
26
- return null;
27
- }
28
- },
29
- watchChange(id) {
30
- if (TRANSFORM_PATTERN.test(id)) {
31
- program.delete(root);
32
- }
33
- }
34
- };
35
- };
3
+ import { transform } from '../index.js';
4
+ export default plugin.vite({
5
+ name: PACKAGE,
6
+ transform
7
+ });
@@ -1,11 +1,5 @@
1
1
  import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
- function isFunctionNode(node) {
4
- return (ts.isArrowFunction(node) ||
5
- ts.isFunctionDeclaration(node) ||
6
- ts.isFunctionExpression(node) ||
7
- ts.isMethodDeclaration(node));
8
- }
9
3
  function visitReactiveCalls(node, calls) {
10
4
  if (ts.isCallExpression(node) &&
11
5
  ts.isPropertyAccessExpression(node.expression) &&
@@ -24,7 +18,9 @@ function visitReactiveCalls(node, calls) {
24
18
  ts.forEachChild(node, child => visitReactiveCalls(child, calls));
25
19
  }
26
20
  function visitTemplates(node, depth, templates) {
27
- let nextDepth = isFunctionNode(node) ? depth + 1 : depth;
21
+ let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
22
+ ? depth + 1
23
+ : depth;
28
24
  if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag.text === COMPILER_ENTRYPOINT) {
29
25
  let expressions = [], literals = [], template = node.template;
30
26
  if (ts.isNoSubstitutionTemplateLiteral(template)) {
@@ -2,5 +2,5 @@ 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
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, sourceFile: ts.SourceFile, checker: ts.TypeChecker | undefined, ns: string) => string[];
5
+ declare const generateSpreadBindings: (expr: ts.Expression, exprCode: string, elementVar: string, checker: ts.TypeChecker | undefined, ns: string) => string[];
6
6
  export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
@@ -24,28 +24,23 @@ function analyzeSpread(expr, checker) {
24
24
  }
25
25
  if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
26
26
  try {
27
- let keys = extractTypePropertyKeys(checker.getTypeAtLocation(expr));
27
+ let keys = [], props = checker.getTypeAtLocation(expr).getProperties();
28
+ for (let i = 0, n = props.length; i < n; i++) {
29
+ let name = props[i].getName();
30
+ if (name.startsWith('__') || name.startsWith('[')) {
31
+ continue;
32
+ }
33
+ keys.push(name);
34
+ }
28
35
  if (keys.length > 0) {
29
36
  return { canUnpack: true, keys };
30
37
  }
31
38
  }
32
- catch {
33
- }
39
+ catch { }
34
40
  }
35
41
  return { canUnpack: false, keys: [] };
36
42
  }
37
- function extractTypePropertyKeys(type) {
38
- let keys = [], props = type.getProperties();
39
- for (let i = 0, n = props.length; i < n; i++) {
40
- let name = props[i].getName();
41
- if (name.startsWith('__') || name.startsWith('[')) {
42
- continue;
43
- }
44
- keys.push(name);
45
- }
46
- return keys;
47
- }
48
- function inferCOMPILER_TYPES(expr, ctx) {
43
+ function inferCOMPILER_TYPES(expr, checker) {
49
44
  while (ts.isParenthesizedExpression(expr)) {
50
45
  expr = expr.expression;
51
46
  }
@@ -78,7 +73,7 @@ function inferCOMPILER_TYPES(expr, ctx) {
78
73
  return COMPILER_TYPES.Primitive;
79
74
  }
80
75
  if (ts.isConditionalExpression(expr)) {
81
- let whenFalse = inferCOMPILER_TYPES(expr.whenFalse, ctx), whenTrue = inferCOMPILER_TYPES(expr.whenTrue, ctx);
76
+ let whenFalse = inferCOMPILER_TYPES(expr.whenFalse, checker), whenTrue = inferCOMPILER_TYPES(expr.whenTrue, checker);
82
77
  if (whenTrue === whenFalse) {
83
78
  return whenTrue;
84
79
  }
@@ -87,46 +82,17 @@ function inferCOMPILER_TYPES(expr, ctx) {
87
82
  }
88
83
  return COMPILER_TYPES.Unknown;
89
84
  }
90
- if (ctx?.checker) {
91
- let checker = ctx.checker;
92
- if (ts.isIdentifier(expr)) {
93
- try {
94
- let type = checker.getTypeAtLocation(expr);
95
- if (isTypeFunction(type, checker)) {
96
- return COMPILER_TYPES.Effect;
97
- }
98
- if (isTypeArray(type, checker)) {
99
- return COMPILER_TYPES.ArraySlot;
100
- }
101
- }
102
- catch {
103
- }
104
- }
105
- if (ts.isPropertyAccessExpression(expr)) {
106
- try {
107
- let type = checker.getTypeAtLocation(expr);
108
- if (isTypeFunction(type, checker)) {
109
- return COMPILER_TYPES.Effect;
110
- }
111
- if (isTypeArray(type, checker)) {
112
- return COMPILER_TYPES.ArraySlot;
113
- }
85
+ if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
86
+ try {
87
+ let type = checker.getTypeAtLocation(expr);
88
+ if (isTypeFunction(type, checker)) {
89
+ return COMPILER_TYPES.Effect;
114
90
  }
115
- catch {
91
+ if (isTypeArray(type, checker)) {
92
+ return COMPILER_TYPES.ArraySlot;
116
93
  }
117
94
  }
118
- if (ts.isCallExpression(expr)) {
119
- try {
120
- let type = checker.getTypeAtLocation(expr);
121
- if (isTypeFunction(type, checker)) {
122
- return COMPILER_TYPES.Effect;
123
- }
124
- if (isTypeArray(type, checker)) {
125
- return COMPILER_TYPES.ArraySlot;
126
- }
127
- }
128
- catch {
129
- }
95
+ catch {
130
96
  }
131
97
  }
132
98
  return COMPILER_TYPES.Unknown;
@@ -135,8 +101,7 @@ function isTypeArray(type, checker) {
135
101
  if (checker.isArrayType(type)) {
136
102
  return true;
137
103
  }
138
- let symbol = type.getSymbol();
139
- return symbol?.getName() === 'ReactiveArray';
104
+ return type.getSymbol()?.getName() === 'ReactiveArray';
140
105
  }
141
106
  function isTypeFunction(type, checker) {
142
107
  if (type.getCallSignatures().length > 0) {
@@ -152,7 +117,7 @@ function isTypeFunction(type, checker) {
152
117
  return false;
153
118
  }
154
119
  const analyzeExpression = (expr, checker) => {
155
- return inferCOMPILER_TYPES(expr, checker ? { checker } : undefined);
120
+ return inferCOMPILER_TYPES(expr, checker);
156
121
  };
157
122
  const generateAttributeBinding = (elementVar, name, expr, staticValue, ns) => {
158
123
  if (name.startsWith('on') && name.length > 2) {
@@ -176,7 +141,7 @@ const generateAttributeBinding = (elementVar, name, expr, staticValue, ns) => {
176
141
  }
177
142
  return `${ns}.attributes.setProperty(${elementVar}, '${name}', ${expr});`;
178
143
  };
179
- const generateSpreadBindings = (expr, exprCode, elementVar, sourceFile, checker, ns) => {
144
+ const generateSpreadBindings = (expr, exprCode, elementVar, checker, ns) => {
180
145
  while (ts.isParenthesizedExpression(expr)) {
181
146
  expr = expr.expression;
182
147
  }
@@ -191,11 +156,11 @@ const generateSpreadBindings = (expr, exprCode, elementVar, sourceFile, checker,
191
156
  for (let j = 0, m = expr.properties.length; j < m; j++) {
192
157
  let prop = expr.properties[j];
193
158
  if (ts.isPropertyAssignment(prop)) {
194
- let name = ts.isIdentifier(prop.name)
159
+ let text = ts.isIdentifier(prop.name)
195
160
  ? prop.name.text
196
161
  : ts.isStringLiteral(prop.name) ? prop.name.text : null;
197
- if (name === key) {
198
- value = prop.initializer.getText(sourceFile);
162
+ if (text === key) {
163
+ value = prop.initializer.getText();
199
164
  break;
200
165
  }
201
166
  }
package/package.json CHANGED
@@ -2,12 +2,12 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/queue": "^0.2.0",
5
- "@esportsplus/reactivity": "^0.25.7",
5
+ "@esportsplus/reactivity": "^0.25.12",
6
6
  "@esportsplus/utilities": "^0.27.2",
7
7
  "serve": "^14.2.5"
8
8
  },
9
9
  "devDependencies": {
10
- "@esportsplus/typescript": "^0.17.5",
10
+ "@esportsplus/typescript": "^0.18.0",
11
11
  "@types/node": "^25.0.3",
12
12
  "vite": "^7.3.0",
13
13
  "vite-tsconfig-paths": "^6.0.3"
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "type": "module",
42
42
  "types": "./build/index.d.ts",
43
- "version": "0.32.1",
43
+ "version": "0.32.2",
44
44
  "scripts": {
45
45
  "build": "tsc",
46
46
  "build:test": "vite build --config test/vite.config.ts",
package/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import './runtime';
2
- export { default as attributes } from './attributes';
3
- export { default as event } from './event';
4
2
  export { default as html } from './html';
5
3
  export { default as render } from './render';
6
- export { default as slot } from './slot';
7
4
  export { default as svg } from './svg';
5
+
6
+ // Must be exported for compilation even if not used directly
7
+ export { default as attributes } from './attributes';
8
+ export { default as event } from './event';
9
+ export { default as slot } from './slot';
8
10
  export { ArraySlot } from './slot/array';
9
11
  export { EffectSlot } from './slot/effect';
10
12
  export type { Attributes, Element, Renderable } from './types';
@@ -1,14 +1,11 @@
1
- import { code as c, uid } from '@esportsplus/typescript/transformer';
2
- import type { Replacement } from '@esportsplus/typescript/transformer';
1
+ import { ts } from '@esportsplus/typescript';
2
+ import { code as c, uid, type Replacement } from '@esportsplus/typescript/transformer';
3
3
  import type { ReactiveCallInfo, TemplateInfo } from './ts-parser';
4
4
  import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer';
5
- import { ts } from '@esportsplus/typescript';
6
5
  import {
7
- COMPILER_ENTRYPOINT,
8
- COMPILER_NAMESPACE,
9
- COMPILER_TYPES,
6
+ COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES,
10
7
  PACKAGE
11
- } from '../constants';
8
+ } from '~/constants';
12
9
  import parser from './parser';
13
10
 
14
11
 
@@ -47,6 +44,7 @@ type ParseResult = {
47
44
 
48
45
  const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
49
46
 
47
+
50
48
  let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
51
49
 
52
50
 
@@ -192,15 +190,16 @@ function generateTemplateCode(
192
190
  slot = slots[i];
193
191
 
194
192
  if (slot.type === COMPILER_TYPES.AttributeSlot) {
195
- for (let j = 0, m = slot.attributes.names.length; j < m; j++) {
196
- let name = slot.attributes.names[j];
193
+ let names = slot.attributes.names;
194
+
195
+ for (let j = 0, m = names.length; j < m; j++) {
196
+ let name = names[j];
197
197
 
198
198
  if (name === 'spread') {
199
199
  let bindings = generateSpreadBindings(
200
200
  exprNodes[index],
201
201
  exprTexts[index] || 'undefined',
202
202
  elementVar,
203
- ctx.sourceFile,
204
203
  ctx.checker,
205
204
  COMPILER_NAMESPACE
206
205
  );
@@ -212,20 +211,22 @@ function generateTemplateCode(
212
211
  index++;
213
212
  }
214
213
  else {
215
- let binding = generateAttributeBinding(
214
+ code.push(
215
+ generateAttributeBinding(
216
216
  elementVar,
217
217
  name,
218
218
  exprTexts[index++] || 'undefined',
219
219
  slot.attributes.statics[name] || '',
220
220
  COMPILER_NAMESPACE
221
- );
222
-
223
- code.push(binding);
221
+ )
222
+ );
224
223
  }
225
224
  }
226
225
  }
227
226
  else {
228
- code.push(generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index]));
227
+ code.push(
228
+ generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index])
229
+ );
229
230
  index++;
230
231
  }
231
232
  }
@@ -288,18 +289,6 @@ function hasMatch(node: ts.Node, predicate: (n: ts.Node) => boolean): boolean {
288
289
  return found;
289
290
  }
290
291
 
291
- function hasArraySlotUsage(node: ts.Node): boolean {
292
- return hasMatch(node, n =>
293
- ts.isNewExpression(n) &&
294
- ts.isPropertyAccessExpression(n.expression) &&
295
- n.expression.name.text === 'ArraySlot'
296
- );
297
- }
298
-
299
- function hasNestedTemplates(node: ts.Node): boolean {
300
- return hasMatch(node, n => isNestedHtmlTemplate(n as ts.Expression));
301
- }
302
-
303
292
  function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
304
293
  return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
305
294
  }
@@ -321,14 +310,13 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
321
310
  return generateNestedTemplateCode(ctx, expr);
322
311
  }
323
312
 
324
- if (!hasNestedTemplates(expr)) {
313
+ if (!hasMatch(expr, n => isNestedHtmlTemplate(n as ts.Expression))) {
325
314
  return ctx.printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
326
315
  }
327
316
 
328
- let exprStart = expr.getStart(),
329
- replacements: Replacement[] = [];
317
+ let replacements: Replacement[] = [];
330
318
 
331
- collectNestedTemplateReplacements(ctx, expr, exprStart, replacements);
319
+ collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
332
320
 
333
321
  return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
334
322
  }
@@ -344,17 +332,17 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
344
332
  }
345
333
 
346
334
  // Precompute expression ranges for nested template detection
347
- let exprRanges: { end: number; start: number }[] = [];
335
+ let ranges: { end: number; start: number }[] = [];
348
336
 
349
337
  for (let i = 0, n = templates.length; i < n; i++) {
350
338
  let exprs = templates[i].expressions;
351
339
 
352
340
  for (let j = 0, m = exprs.length; j < m; j++) {
353
- exprRanges.push({ end: exprs[j].end, start: exprs[j].getStart() });
341
+ ranges.push({ end: exprs[j].end, start: exprs[j].getStart() });
354
342
  }
355
343
  }
356
344
 
357
- let rootTemplates = templates.filter(t => !isNestedTemplate(t, exprRanges));
345
+ let rootTemplates = templates.filter(t => !isNestedTemplate(t, ranges));
358
346
 
359
347
  if (rootTemplates.length === 0) {
360
348
  return { changed: false, code: originalCode };
@@ -448,7 +436,11 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
448
436
  };
449
437
 
450
438
  const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
451
- return hasArraySlotUsage(sourceFile) && !hasArraySlotImport(sourceFile);
439
+ return hasMatch(sourceFile, n =>
440
+ ts.isNewExpression(n) &&
441
+ ts.isPropertyAccessExpression(n.expression) &&
442
+ n.expression.name.text === 'ArraySlot'
443
+ ) && !hasArraySlotImport(sourceFile);
452
444
  };
453
445
 
454
446
 
@@ -14,28 +14,9 @@ type TransformResult = {
14
14
 
15
15
  const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
16
16
 
17
+ const REGEX_BACKSLASH = /\\/g;
17
18
 
18
- function createTransformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
19
- let typeChecker = program.getTypeChecker();
20
-
21
- return (_context: ts.TransformationContext) => {
22
- return (sourceFile: ts.SourceFile): ts.SourceFile => {
23
- let code = sourceFile.getFullText();
24
-
25
- if (!c.contains(code, { patterns: PATTERNS })) {
26
- return sourceFile;
27
- }
28
-
29
- let result = transformCode(code, sourceFile, typeChecker);
30
-
31
- if (!result.changed) {
32
- return sourceFile;
33
- }
34
-
35
- return result.sourceFile;
36
- };
37
- };
38
- }
19
+ const REGEX_FORWARD_SLASH = /\//g;
39
20
 
40
21
 
41
22
  const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformResult => {
@@ -48,30 +29,26 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
48
29
  let checker: ts.TypeChecker | undefined,
49
30
  fileName = sourceFile.fileName,
50
31
  programSourceFile = program.getSourceFile(fileName)
51
- || program.getSourceFile(fileName.replace(/\\/g, '/'))
52
- || program.getSourceFile(fileName.replace(/\//g, '\\'));
32
+ || program.getSourceFile(fileName.replace(REGEX_BACKSLASH, '/'))
33
+ || program.getSourceFile(fileName.replace(REGEX_FORWARD_SLASH, '\\'));
53
34
 
54
35
  if (programSourceFile) {
55
36
  checker = program.getTypeChecker();
56
37
  sourceFile = programSourceFile;
57
38
  }
58
39
 
59
- return transformCode(code, sourceFile, checker);
60
- };
61
-
62
- const transformCode = (code: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): TransformResult => {
63
40
  let changed = false,
64
41
  codegenChanged = false,
65
42
  needsImport = false,
66
- result = code,
67
- reactiveCalls = findReactiveCalls(sourceFile);
43
+ reactiveCalls = findReactiveCalls(sourceFile),
44
+ result = code;
68
45
 
69
46
  if (reactiveCalls.length > 0) {
70
- result = generateReactiveInlining(reactiveCalls, result, sourceFile);
71
47
  changed = true;
48
+ checker = undefined;
49
+ result = generateReactiveInlining(reactiveCalls, result, sourceFile);
72
50
  sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
73
51
  needsImport = needsArraySlotImport(sourceFile);
74
- checker = undefined;
75
52
  }
76
53
 
77
54
  let templates = findHtmlTemplates(sourceFile);
@@ -98,4 +75,4 @@ const transformCode = (code: string, sourceFile: ts.SourceFile, checker?: ts.Typ
98
75
  };
99
76
 
100
77
 
101
- export { createTransformer, transform, transformCode, PATTERNS };
78
+ export { transform };
@@ -1,15 +1,5 @@
1
- import { ts } from '@esportsplus/typescript';
2
- import { transformCode } from '~/transformer';
1
+ import { plugin } from '@esportsplus/typescript/transformer';
2
+ import { transform } from '..';
3
3
 
4
4
 
5
- export default (program: ts.Program): ts.TransformerFactory<ts.SourceFile> => {
6
- let typeChecker = program.getTypeChecker();
7
-
8
- return () => {
9
- return (sourceFile: ts.SourceFile): ts.SourceFile => {
10
- let result = transformCode(sourceFile.getFullText(), sourceFile, typeChecker);
11
-
12
- return result.changed ? result.sourceFile : sourceFile;
13
- };
14
- };
15
- };
5
+ export default plugin.tsc(transform) as ReturnType<typeof plugin.tsc>;
@@ -1,45 +1,9 @@
1
- import type { Plugin, ResolvedConfig } from 'vite';
2
- import { ts } from '@esportsplus/typescript';
3
- import { program, TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
4
- import { PACKAGE } from '~/constants';
5
- import { transform } from '~/transformer';
1
+ import { plugin } from '@esportsplus/typescript/transformer';
2
+ import { PACKAGE } from '../../constants';
3
+ import { transform } from '..';
6
4
 
7
5
 
8
- export default (options?: { root?: string }): Plugin => {
9
- let root: string;
10
-
11
- return {
12
- configResolved(config: ResolvedConfig) {
13
- root = options?.root ?? config.root;
14
- },
15
- enforce: 'pre',
16
- name: `${PACKAGE}/plugin-vite`,
17
- transform(code: string, id: string) {
18
- if (!TRANSFORM_PATTERN.test(id) || id.includes('node_modules')) {
19
- return null;
20
- }
21
-
22
- try {
23
- let result = transform(
24
- ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true),
25
- program.get(root)
26
- );
27
-
28
- if (!result.changed) {
29
- return null;
30
- }
31
-
32
- return { code: result.code, map: null };
33
- }
34
- catch (error) {
35
- console.error(`${PACKAGE}: Error transforming ${id}:`, error);
36
- return null;
37
- }
38
- },
39
- watchChange(id: string) {
40
- if (TRANSFORM_PATTERN.test(id)) {
41
- program.delete(root);
42
- }
43
- }
44
- };
45
- };
6
+ export default plugin.vite({
7
+ name: PACKAGE,
8
+ transform
9
+ });
@@ -20,15 +20,6 @@ type TemplateInfo = {
20
20
  };
21
21
 
22
22
 
23
- function isFunctionNode(node: ts.Node): boolean {
24
- return (
25
- ts.isArrowFunction(node) ||
26
- ts.isFunctionDeclaration(node) ||
27
- ts.isFunctionExpression(node) ||
28
- ts.isMethodDeclaration(node)
29
- );
30
- }
31
-
32
23
  function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[]): void {
33
24
  if (
34
25
  ts.isCallExpression(node) &&
@@ -51,7 +42,9 @@ function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[]): void {
51
42
  }
52
43
 
53
44
  function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[]): void {
54
- let nextDepth = isFunctionNode(node) ? depth + 1 : depth;
45
+ let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
46
+ ? depth + 1
47
+ : depth;
55
48
 
56
49
  if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag.text === COMPILER_ENTRYPOINT) {
57
50
  let expressions: ts.Expression[] = [],
@@ -8,10 +8,6 @@ import {
8
8
  import { ts } from '@esportsplus/typescript';
9
9
 
10
10
 
11
- type AnalyzerContext = {
12
- checker?: ts.TypeChecker;
13
- };
14
-
15
11
  type SpreadAnalysis = {
16
12
  canUnpack: boolean;
17
13
  keys: string[];
@@ -47,37 +43,30 @@ function analyzeSpread(expr: ts.Expression, checker?: ts.TypeChecker): SpreadAna
47
43
 
48
44
  if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
49
45
  try {
50
- let keys = extractTypePropertyKeys(checker.getTypeAtLocation(expr));
46
+ let keys: string[] = [],
47
+ props = checker.getTypeAtLocation(expr).getProperties();
48
+
49
+ for (let i = 0, n = props.length; i < n; i++) {
50
+ let name = props[i].getName();
51
+
52
+ if (name.startsWith('__') || name.startsWith('[')) {
53
+ continue;
54
+ }
55
+
56
+ keys.push(name);
57
+ }
51
58
 
52
59
  if (keys.length > 0) {
53
60
  return { canUnpack: true, keys };
54
61
  }
55
62
  }
56
- catch {
57
- }
63
+ catch { }
58
64
  }
59
65
 
60
66
  return { canUnpack: false, keys: [] };
61
67
  }
62
68
 
63
- function extractTypePropertyKeys(type: ts.Type): string[] {
64
- let keys: string[] = [],
65
- props = type.getProperties();
66
-
67
- for (let i = 0, n = props.length; i < n; i++) {
68
- let name = props[i].getName();
69
-
70
- if (name.startsWith('__') || name.startsWith('[')) {
71
- continue;
72
- }
73
-
74
- keys.push(name);
75
- }
76
-
77
- return keys;
78
- }
79
-
80
- function inferCOMPILER_TYPES(expr: ts.Expression, ctx?: AnalyzerContext): COMPILER_TYPES {
69
+ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES {
81
70
  while (ts.isParenthesizedExpression(expr)) {
82
71
  expr = expr.expression;
83
72
  }
@@ -121,8 +110,8 @@ function inferCOMPILER_TYPES(expr: ts.Expression, ctx?: AnalyzerContext): COMPIL
121
110
  }
122
111
 
123
112
  if (ts.isConditionalExpression(expr)) {
124
- let whenFalse = inferCOMPILER_TYPES(expr.whenFalse, ctx),
125
- whenTrue = inferCOMPILER_TYPES(expr.whenTrue, ctx);
113
+ let whenFalse = inferCOMPILER_TYPES(expr.whenFalse, checker),
114
+ whenTrue = inferCOMPILER_TYPES(expr.whenTrue, checker);
126
115
 
127
116
  if (whenTrue === whenFalse) {
128
117
  return whenTrue;
@@ -135,55 +124,19 @@ function inferCOMPILER_TYPES(expr: ts.Expression, ctx?: AnalyzerContext): COMPIL
135
124
  return COMPILER_TYPES.Unknown;
136
125
  }
137
126
 
138
- if (ctx?.checker) {
139
- let checker = ctx.checker;
140
-
141
- if (ts.isIdentifier(expr)) {
142
- try {
143
- let type = checker.getTypeAtLocation(expr);
144
-
145
- if (isTypeFunction(type, checker)) {
146
- return COMPILER_TYPES.Effect;
147
- }
127
+ if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
128
+ try {
129
+ let type = checker.getTypeAtLocation(expr);
148
130
 
149
- if (isTypeArray(type, checker)) {
150
- return COMPILER_TYPES.ArraySlot;
151
- }
131
+ if (isTypeFunction(type, checker)) {
132
+ return COMPILER_TYPES.Effect;
152
133
  }
153
- catch {
154
- }
155
- }
156
134
 
157
- if (ts.isPropertyAccessExpression(expr)) {
158
- try {
159
- let type = checker.getTypeAtLocation(expr);
160
-
161
- if (isTypeFunction(type, checker)) {
162
- return COMPILER_TYPES.Effect;
163
- }
164
-
165
- if (isTypeArray(type, checker)) {
166
- return COMPILER_TYPES.ArraySlot;
167
- }
168
- }
169
- catch {
135
+ if (isTypeArray(type, checker)) {
136
+ return COMPILER_TYPES.ArraySlot;
170
137
  }
171
138
  }
172
-
173
- if (ts.isCallExpression(expr)) {
174
- try {
175
- let type = checker.getTypeAtLocation(expr);
176
-
177
- if (isTypeFunction(type, checker)) {
178
- return COMPILER_TYPES.Effect;
179
- }
180
-
181
- if (isTypeArray(type, checker)) {
182
- return COMPILER_TYPES.ArraySlot;
183
- }
184
- }
185
- catch {
186
- }
139
+ catch {
187
140
  }
188
141
  }
189
142
 
@@ -195,9 +148,7 @@ function isTypeArray(type: ts.Type, checker: ts.TypeChecker): boolean {
195
148
  return true;
196
149
  }
197
150
 
198
- let symbol = type.getSymbol();
199
-
200
- return symbol?.getName() === 'ReactiveArray';
151
+ return type.getSymbol()?.getName() === 'ReactiveArray';
201
152
  }
202
153
 
203
154
  function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
@@ -218,7 +169,7 @@ function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
218
169
 
219
170
 
220
171
  const analyzeExpression = (expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES => {
221
- return inferCOMPILER_TYPES(expr, checker ? { checker } : undefined);
172
+ return inferCOMPILER_TYPES(expr, checker);
222
173
  };
223
174
 
224
175
  const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, ns: string): string => {
@@ -256,7 +207,6 @@ const generateSpreadBindings = (
256
207
  expr: ts.Expression,
257
208
  exprCode: string,
258
209
  elementVar: string,
259
- sourceFile: ts.SourceFile,
260
210
  checker: ts.TypeChecker | undefined,
261
211
  ns: string
262
212
  ): string[] => {
@@ -281,12 +231,12 @@ const generateSpreadBindings = (
281
231
  let prop = expr.properties[j];
282
232
 
283
233
  if (ts.isPropertyAssignment(prop)) {
284
- let name = ts.isIdentifier(prop.name)
285
- ? prop.name.text
286
- : ts.isStringLiteral(prop.name) ? prop.name.text : null;
234
+ let text = ts.isIdentifier(prop.name)
235
+ ? prop.name.text
236
+ : ts.isStringLiteral(prop.name) ? prop.name.text : null;
287
237
 
288
- if (name === key) {
289
- value = prop.initializer.getText(sourceFile);
238
+ if (text === key) {
239
+ value = prop.initializer.getText();
290
240
  break;
291
241
  }
292
242
  }