@esportsplus/template 0.38.2 → 0.39.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.
- package/build/compiler/codegen.d.ts +2 -1
- package/build/compiler/codegen.js +99 -98
- package/build/compiler/index.js +9 -17
- package/build/compiler/{analyzer.js → ts-analyzer.js} +1 -2
- package/build/compiler/ts-parser.d.ts +5 -1
- package/build/compiler/ts-parser.js +25 -43
- package/package.json +3 -3
- package/src/compiler/codegen.ts +134 -150
- package/src/compiler/index.ts +14 -28
- package/src/compiler/{analyzer.ts → ts-analyzer.ts} +1 -2
- package/src/compiler/ts-parser.ts +33 -64
- /package/build/compiler/{analyzer.d.ts → ts-analyzer.d.ts} +0 -0
|
@@ -6,6 +6,7 @@ type CodegenResult = {
|
|
|
6
6
|
replacements: ReplacementIntent[];
|
|
7
7
|
templates: Map<string, string>;
|
|
8
8
|
};
|
|
9
|
+
declare let printer: ts.Printer;
|
|
9
10
|
declare const generateCode: (templates: TemplateInfo[], sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => CodegenResult;
|
|
10
|
-
export { generateCode };
|
|
11
|
+
export { generateCode, printer };
|
|
11
12
|
export type { CodegenResult };
|
|
@@ -1,29 +1,10 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import { ast, uid } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS } from '../constants.js';
|
|
4
|
-
import {
|
|
4
|
+
import { extractTemplateParts } from './ts-parser.js';
|
|
5
|
+
import { analyze } from './ts-analyzer.js';
|
|
5
6
|
import parser from './parser.js';
|
|
6
|
-
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
7
7
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
8
|
-
function collectNestedReplacements(ctx, node, exprStart, replacements) {
|
|
9
|
-
if (isNestedHtmlTemplate(node)) {
|
|
10
|
-
replacements.push({
|
|
11
|
-
end: node.end - exprStart,
|
|
12
|
-
newText: generateNestedTemplateCode(ctx, node),
|
|
13
|
-
start: node.getStart(ctx.sourceFile) - exprStart
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
else if (isReactiveCall(node)) {
|
|
17
|
-
replacements.push({
|
|
18
|
-
end: node.end - exprStart,
|
|
19
|
-
newText: rewriteNestedReactiveCall(ctx, node),
|
|
20
|
-
start: node.getStart(ctx.sourceFile) - exprStart
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
ts.forEachChild(node, child => collectNestedReplacements(ctx, child, exprStart, replacements));
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
8
|
function generateAttributeBinding(element, name, expr, staticValue) {
|
|
28
9
|
if (name.startsWith('on') && name.length > 2) {
|
|
29
10
|
let event = name.slice(2).toLowerCase(), key = name.toLowerCase();
|
|
@@ -44,20 +25,11 @@ function generateAttributeBinding(element, name, expr, staticValue) {
|
|
|
44
25
|
return `${COMPILER_NAMESPACE}.setProperty(${element}, '${name}', ${expr});`;
|
|
45
26
|
}
|
|
46
27
|
function generateNestedTemplateCode(ctx, node) {
|
|
47
|
-
let expressions
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
else if (ts.isTemplateExpression(template)) {
|
|
52
|
-
literals.push(template.head.text);
|
|
53
|
-
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
54
|
-
let expr = template.templateSpans[i].expression;
|
|
55
|
-
expressions.push(expr);
|
|
56
|
-
literals.push(template.templateSpans[i].literal.text);
|
|
57
|
-
exprTexts.push(rewriteExpression(ctx, expr));
|
|
58
|
-
}
|
|
28
|
+
let { expressions, literals } = extractTemplateParts(node.template), exprTexts = [];
|
|
29
|
+
for (let i = 0, n = expressions.length; i < n; i++) {
|
|
30
|
+
exprTexts.push(rewriteExpression(ctx, expressions[i]));
|
|
59
31
|
}
|
|
60
|
-
return generateTemplateCode(ctx, parser.parse(literals), exprTexts, expressions,
|
|
32
|
+
return generateTemplateCode(ctx, parser.parse(literals), exprTexts, expressions, node);
|
|
61
33
|
}
|
|
62
34
|
function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
63
35
|
if (!exprNode) {
|
|
@@ -66,8 +38,7 @@ function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
|
66
38
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
67
39
|
return `${anchor}.parentNode!.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
|
|
68
40
|
}
|
|
69
|
-
|
|
70
|
-
switch (slotType) {
|
|
41
|
+
switch (analyze(exprNode, ctx.checker)) {
|
|
71
42
|
case COMPILER_TYPES.ArraySlot:
|
|
72
43
|
return `${anchor}.parentNode!.insertBefore(new ${COMPILER_NAMESPACE}.ArraySlot(${exprText}).fragment, ${anchor});`;
|
|
73
44
|
case COMPILER_TYPES.DocumentFragment:
|
|
@@ -80,11 +51,11 @@ function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
|
80
51
|
return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
|
|
81
52
|
}
|
|
82
53
|
}
|
|
83
|
-
function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes,
|
|
54
|
+
function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, templateNode) {
|
|
84
55
|
if (!slots || slots.length === 0) {
|
|
85
56
|
return `${getOrCreateTemplateId(ctx, html)}()`;
|
|
86
57
|
}
|
|
87
|
-
let code = [], declarations = [], index = 0, nodes = new Map(), root = uid('root');
|
|
58
|
+
let code = [], declarations = [], index = 0, isArrowBody = isArrowExpressionBody(templateNode), nodes = new Map(), root = uid('root');
|
|
88
59
|
declarations.push(`${root} = ${getOrCreateTemplateId(ctx, html)}()`);
|
|
89
60
|
nodes.set('', root);
|
|
90
61
|
for (let i = 0, n = slots.length; i < n; i++) {
|
|
@@ -150,6 +121,9 @@ function getOrCreateTemplateId(ctx, html) {
|
|
|
150
121
|
function isNestedHtmlTemplate(expr) {
|
|
151
122
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
152
123
|
}
|
|
124
|
+
function isArrowExpressionBody(node) {
|
|
125
|
+
return ts.isArrowFunction(node.parent) && node.parent.body === node;
|
|
126
|
+
}
|
|
153
127
|
function isReactiveCall(expr) {
|
|
154
128
|
return (ts.isCallExpression(expr) &&
|
|
155
129
|
ts.isPropertyAccessExpression(expr.expression) &&
|
|
@@ -157,73 +131,104 @@ function isReactiveCall(expr) {
|
|
|
157
131
|
expr.expression.expression.text === COMPILER_ENTRYPOINT &&
|
|
158
132
|
expr.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY);
|
|
159
133
|
}
|
|
160
|
-
function replaceReverse(text, replacements) {
|
|
161
|
-
let sorted = replacements.slice().sort((a, b) => b.start - a.start);
|
|
162
|
-
for (let i = 0, n = sorted.length; i < n; i++) {
|
|
163
|
-
let r = sorted[i];
|
|
164
|
-
text = text.slice(0, r.start) + r.newText + text.slice(r.end);
|
|
165
|
-
}
|
|
166
|
-
return text;
|
|
167
|
-
}
|
|
168
134
|
function rewriteExpression(ctx, expr) {
|
|
169
135
|
if (isNestedHtmlTemplate(expr)) {
|
|
170
136
|
return generateNestedTemplateCode(ctx, expr);
|
|
171
137
|
}
|
|
172
138
|
if (isReactiveCall(expr)) {
|
|
173
|
-
return
|
|
139
|
+
return `
|
|
140
|
+
${printer.printNode(ts.EmitHint.Expression, expr.arguments[0], ctx.sourceFile)},
|
|
141
|
+
${rewriteExpression(ctx, expr.arguments[1])}
|
|
142
|
+
`;
|
|
174
143
|
}
|
|
175
144
|
if (!ast.hasMatch(expr, n => isNestedHtmlTemplate(n) || isReactiveCall(n))) {
|
|
176
|
-
return
|
|
145
|
+
return printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
|
|
177
146
|
}
|
|
178
|
-
|
|
179
|
-
collectNestedReplacements(ctx, expr, expr.getStart(ctx.sourceFile), replacements);
|
|
180
|
-
return replaceReverse(expr.getText(ctx.sourceFile), replacements);
|
|
181
|
-
}
|
|
182
|
-
function rewriteReactiveCall(ctx, node) {
|
|
183
|
-
let arrayArg = node.arguments[0], arrayText = ctx.printer.printNode(ts.EmitHint.Expression, arrayArg, ctx.sourceFile), callbackArg = node.arguments[1], callbackText = rewriteExpression(ctx, callbackArg);
|
|
184
|
-
return `${arrayText}, ${callbackText}`;
|
|
147
|
+
return walkExpression(ctx, expr);
|
|
185
148
|
}
|
|
186
|
-
function
|
|
187
|
-
let arrayArg = node.arguments[0], arrayText = ctx.printer.printNode(ts.EmitHint.Expression, arrayArg, ctx.sourceFile), callbackArg = node.arguments[1], callbackText = rewriteExpression(ctx, callbackArg);
|
|
188
|
-
return `new ${COMPILER_NAMESPACE}.ArraySlot(${arrayText}, ${callbackText})`;
|
|
189
|
-
}
|
|
190
|
-
function discoverTemplatesInExpression(ctx, node) {
|
|
149
|
+
function walkExpression(ctx, node) {
|
|
191
150
|
if (isNestedHtmlTemplate(node)) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
151
|
+
return generateNestedTemplateCode(ctx, node);
|
|
152
|
+
}
|
|
153
|
+
if (isReactiveCall(node)) {
|
|
154
|
+
let call = node;
|
|
155
|
+
return `new ${COMPILER_NAMESPACE}.ArraySlot(${printer.printNode(ts.EmitHint.Expression, call.arguments[0], ctx.sourceFile)}, ${rewriteExpression(ctx, call.arguments[1])})`;
|
|
156
|
+
}
|
|
157
|
+
if (!ast.hasMatch(node, n => isNestedHtmlTemplate(n) || isReactiveCall(n))) {
|
|
158
|
+
return printer.printNode(ts.EmitHint.Expression, node, ctx.sourceFile);
|
|
159
|
+
}
|
|
160
|
+
if (ts.isConditionalExpression(node)) {
|
|
161
|
+
return `${walkExpression(ctx, node.condition)} ? ${walkExpression(ctx, node.whenTrue)} : ${walkExpression(ctx, node.whenFalse)}`;
|
|
162
|
+
}
|
|
163
|
+
if (ts.isBinaryExpression(node)) {
|
|
164
|
+
return `${walkExpression(ctx, node.left)} ${node.operatorToken.getText(ctx.sourceFile)} ${walkExpression(ctx, node.right)}`;
|
|
165
|
+
}
|
|
166
|
+
if (ts.isCallExpression(node)) {
|
|
167
|
+
let args = [];
|
|
168
|
+
for (let i = 0, n = node.arguments.length; i < n; i++) {
|
|
169
|
+
args.push(walkExpression(ctx, node.arguments[i]));
|
|
202
170
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
171
|
+
return `${walkExpression(ctx, node.expression)}(${args.join(', ')})`;
|
|
172
|
+
}
|
|
173
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
174
|
+
return `${walkExpression(ctx, node.expression)}.${node.name.text}`;
|
|
175
|
+
}
|
|
176
|
+
if (ts.isElementAccessExpression(node)) {
|
|
177
|
+
return `${walkExpression(ctx, node.expression)}[${walkExpression(ctx, node.argumentExpression)}]`;
|
|
178
|
+
}
|
|
179
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
180
|
+
return `(${walkExpression(ctx, node.expression)})`;
|
|
181
|
+
}
|
|
182
|
+
if (ts.isArrowFunction(node)) {
|
|
183
|
+
return `(${node.parameters.map(p => p.getText(ctx.sourceFile)).join(', ')}) => ${ts.isBlock(node.body) ? node.body.getText(ctx.sourceFile) : walkExpression(ctx, node.body)}`;
|
|
184
|
+
}
|
|
185
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
186
|
+
let elements = [];
|
|
187
|
+
for (let i = 0, n = node.elements.length; i < n; i++) {
|
|
188
|
+
elements.push(walkExpression(ctx, node.elements[i]));
|
|
207
189
|
}
|
|
190
|
+
return `[${elements.join(', ')}]`;
|
|
208
191
|
}
|
|
209
|
-
|
|
210
|
-
let
|
|
211
|
-
|
|
212
|
-
|
|
192
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
193
|
+
let properties = [];
|
|
194
|
+
for (let i = 0, n = node.properties.length; i < n; i++) {
|
|
195
|
+
let prop = node.properties[i];
|
|
196
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
197
|
+
properties.push(`${prop.name.getText(ctx.sourceFile)}: ${walkExpression(ctx, prop.initializer)}`);
|
|
198
|
+
}
|
|
199
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
200
|
+
properties.push(prop.name.text);
|
|
201
|
+
}
|
|
202
|
+
else if (ts.isSpreadAssignment(prop)) {
|
|
203
|
+
properties.push(`...${walkExpression(ctx, prop.expression)}`);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
properties.push(prop.getText(ctx.sourceFile));
|
|
207
|
+
}
|
|
213
208
|
}
|
|
209
|
+
return `{ ${properties.join(', ')} }`;
|
|
214
210
|
}
|
|
215
|
-
|
|
216
|
-
ts.
|
|
211
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
212
|
+
return `${ts.tokenToString(node.operator)}${walkExpression(ctx, node.operand)}`;
|
|
217
213
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
for (let
|
|
224
|
-
|
|
214
|
+
if (ts.isPostfixUnaryExpression(node)) {
|
|
215
|
+
return `${walkExpression(ctx, node.operand)}${ts.tokenToString(node.operator)}`;
|
|
216
|
+
}
|
|
217
|
+
if (ts.isTemplateExpression(node)) {
|
|
218
|
+
let parts = [node.head.text];
|
|
219
|
+
for (let i = 0, n = node.templateSpans.length; i < n; i++) {
|
|
220
|
+
let span = node.templateSpans[i];
|
|
221
|
+
parts.push('${' + walkExpression(ctx, span.expression) + '}' + span.literal.text);
|
|
225
222
|
}
|
|
223
|
+
return '`' + parts.join('') + '`';
|
|
224
|
+
}
|
|
225
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
226
|
+
return walkExpression(ctx, node.expression);
|
|
227
|
+
}
|
|
228
|
+
if (ts.isNonNullExpression(node)) {
|
|
229
|
+
return `${walkExpression(ctx, node.expression)}!`;
|
|
226
230
|
}
|
|
231
|
+
return printer.printNode(ts.EmitHint.Expression, node, ctx.sourceFile);
|
|
227
232
|
}
|
|
228
233
|
const generateCode = (templates, sourceFile, checker) => {
|
|
229
234
|
let result = {
|
|
@@ -247,7 +252,6 @@ const generateCode = (templates, sourceFile, checker) => {
|
|
|
247
252
|
}
|
|
248
253
|
let ctx = {
|
|
249
254
|
checker,
|
|
250
|
-
printer,
|
|
251
255
|
sourceFile,
|
|
252
256
|
templates: result.templates
|
|
253
257
|
};
|
|
@@ -255,30 +259,27 @@ const generateCode = (templates, sourceFile, checker) => {
|
|
|
255
259
|
let template = rootTemplates[i];
|
|
256
260
|
result.replacements.push({
|
|
257
261
|
generate: (sf) => {
|
|
258
|
-
let
|
|
262
|
+
let exprTexts = [], localCtx = {
|
|
259
263
|
checker,
|
|
260
|
-
printer,
|
|
261
264
|
sourceFile: sf,
|
|
262
265
|
templates: ctx.templates
|
|
263
266
|
}, parsed = parser.parse(template.literals);
|
|
264
267
|
for (let j = 0, m = template.expressions.length; j < m; j++) {
|
|
265
268
|
exprTexts.push(rewriteExpression(localCtx, template.expressions[j]));
|
|
266
269
|
}
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
270
|
+
if (isArrowExpressionBody(template.node) &&
|
|
271
|
+
template.node.parent.parameters.length === 0 &&
|
|
272
|
+
(!parsed.slots || parsed.slots.length === 0)) {
|
|
273
|
+
return getOrCreateTemplateId(localCtx, parsed.html);
|
|
272
274
|
}
|
|
273
|
-
return generateTemplateCode(localCtx, parsed, exprTexts, template.expressions,
|
|
275
|
+
return generateTemplateCode(localCtx, parsed, exprTexts, template.expressions, template.node);
|
|
274
276
|
},
|
|
275
277
|
node: template.node
|
|
276
278
|
});
|
|
277
279
|
}
|
|
278
|
-
discoverAllTemplates(ctx, templates);
|
|
279
280
|
for (let [html, id] of ctx.templates) {
|
|
280
281
|
result.prepend.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
|
|
281
282
|
}
|
|
282
283
|
return result;
|
|
283
284
|
};
|
|
284
|
-
export { generateCode };
|
|
285
|
+
export { generateCode, printer };
|
package/build/compiler/index.js
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { ast } from '@esportsplus/typescript/compiler';
|
|
2
3
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, PACKAGE } from '../constants.js';
|
|
3
|
-
import { generateCode } from './codegen.js';
|
|
4
|
+
import { generateCode, printer } from './codegen.js';
|
|
4
5
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
|
|
5
|
-
function isInRange(ranges, start, end) {
|
|
6
|
-
for (let i = 0, n = ranges.length; i < n; i++) {
|
|
7
|
-
let range = ranges[i];
|
|
8
|
-
if (start >= range.start && end <= range.end) {
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
15
6
|
const plugin = {
|
|
16
7
|
patterns: [
|
|
17
8
|
`${COMPILER_ENTRYPOINT}\``,
|
|
18
9
|
`${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`
|
|
19
10
|
],
|
|
20
11
|
transform: (ctx) => {
|
|
21
|
-
let importsIntent = [], prepend = [], replacements = [], removeImports = [];
|
|
22
|
-
let templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
23
|
-
let templateRanges = [];
|
|
12
|
+
let importsIntent = [], prepend = [], ranges = [], replacements = [], removeImports = [], templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
24
13
|
for (let i = 0, n = templates.length; i < n; i++) {
|
|
25
|
-
|
|
14
|
+
ranges.push({
|
|
26
15
|
end: templates[i].end,
|
|
27
16
|
start: templates[i].start
|
|
28
17
|
});
|
|
@@ -30,11 +19,14 @@ const plugin = {
|
|
|
30
19
|
let reactiveCalls = findReactiveCalls(ctx.sourceFile, ctx.checker);
|
|
31
20
|
for (let i = 0, n = reactiveCalls.length; i < n; i++) {
|
|
32
21
|
let call = reactiveCalls[i];
|
|
33
|
-
if (
|
|
22
|
+
if (ast.inRange(ranges, call.start, call.end)) {
|
|
34
23
|
continue;
|
|
35
24
|
}
|
|
36
25
|
replacements.push({
|
|
37
|
-
generate: (sourceFile) => `new ${COMPILER_NAMESPACE}.ArraySlot(
|
|
26
|
+
generate: (sourceFile) => `new ${COMPILER_NAMESPACE}.ArraySlot(
|
|
27
|
+
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
28
|
+
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
29
|
+
)`,
|
|
38
30
|
node: call.node
|
|
39
31
|
});
|
|
40
32
|
}
|
|
@@ -14,7 +14,11 @@ type TemplateInfo = {
|
|
|
14
14
|
node: ts.TaggedTemplateExpression;
|
|
15
15
|
start: number;
|
|
16
16
|
};
|
|
17
|
+
declare const extractTemplateParts: (template: ts.TemplateLiteral) => {
|
|
18
|
+
expressions: ts.Expression[];
|
|
19
|
+
literals: string[];
|
|
20
|
+
};
|
|
17
21
|
declare const findHtmlTemplates: (sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => TemplateInfo[];
|
|
18
22
|
declare const findReactiveCalls: (sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => ReactiveCallInfo[];
|
|
19
|
-
export { findHtmlTemplates, findReactiveCalls };
|
|
23
|
+
export { extractTemplateParts, findHtmlTemplates, findReactiveCalls };
|
|
20
24
|
export type { ReactiveCallInfo, TemplateInfo };
|
|
@@ -1,38 +1,14 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { imports } from '@esportsplus/typescript/compiler';
|
|
2
3
|
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
4
|
function visitReactiveCalls(node, calls, checker) {
|
|
30
5
|
if (ts.isCallExpression(node) &&
|
|
31
6
|
ts.isPropertyAccessExpression(node.expression) &&
|
|
32
7
|
ts.isIdentifier(node.expression.expression) &&
|
|
33
8
|
node.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY &&
|
|
34
9
|
node.arguments.length === 2 &&
|
|
35
|
-
|
|
10
|
+
node.expression.expression.text === COMPILER_ENTRYPOINT &&
|
|
11
|
+
(!checker || imports.inPackage(checker, node.expression.expression, PACKAGE, COMPILER_ENTRYPOINT))) {
|
|
36
12
|
calls.push({
|
|
37
13
|
arrayArg: node.arguments[0],
|
|
38
14
|
callbackArg: node.arguments[1],
|
|
@@ -47,19 +23,11 @@ function visitTemplates(node, depth, templates, checker) {
|
|
|
47
23
|
let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
|
|
48
24
|
? depth + 1
|
|
49
25
|
: depth;
|
|
50
|
-
if (ts.isTaggedTemplateExpression(node) &&
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
else if (ts.isTemplateExpression(template)) {
|
|
56
|
-
literals.push(template.head.text);
|
|
57
|
-
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
58
|
-
let span = template.templateSpans[i];
|
|
59
|
-
expressions.push(span.expression);
|
|
60
|
-
literals.push(span.literal.text);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
26
|
+
if (ts.isTaggedTemplateExpression(node) &&
|
|
27
|
+
ts.isIdentifier(node.tag) &&
|
|
28
|
+
node.tag.text === COMPILER_ENTRYPOINT &&
|
|
29
|
+
(!checker || imports.inPackage(checker, node.tag, PACKAGE, COMPILER_ENTRYPOINT))) {
|
|
30
|
+
let { expressions, literals } = extractTemplateParts(node.template);
|
|
63
31
|
templates.push({
|
|
64
32
|
depth,
|
|
65
33
|
end: node.end,
|
|
@@ -71,15 +39,29 @@ function visitTemplates(node, depth, templates, checker) {
|
|
|
71
39
|
}
|
|
72
40
|
ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates, checker));
|
|
73
41
|
}
|
|
42
|
+
const extractTemplateParts = (template) => {
|
|
43
|
+
let expressions = [], literals = [];
|
|
44
|
+
if (ts.isNoSubstitutionTemplateLiteral(template)) {
|
|
45
|
+
literals.push(template.text);
|
|
46
|
+
}
|
|
47
|
+
else if (ts.isTemplateExpression(template)) {
|
|
48
|
+
literals.push(template.head.text);
|
|
49
|
+
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
50
|
+
let span = template.templateSpans[i];
|
|
51
|
+
expressions.push(span.expression);
|
|
52
|
+
literals.push(span.literal.text);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { expressions, literals };
|
|
56
|
+
};
|
|
74
57
|
const findHtmlTemplates = (sourceFile, checker) => {
|
|
75
58
|
let templates = [];
|
|
76
59
|
visitTemplates(sourceFile, 0, templates, checker);
|
|
77
|
-
templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
78
|
-
return templates;
|
|
60
|
+
return templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
79
61
|
};
|
|
80
62
|
const findReactiveCalls = (sourceFile, checker) => {
|
|
81
63
|
let calls = [];
|
|
82
64
|
visitReactiveCalls(sourceFile, calls, checker);
|
|
83
65
|
return calls;
|
|
84
66
|
};
|
|
85
|
-
export { findHtmlTemplates, findReactiveCalls };
|
|
67
|
+
export { extractTemplateParts, findHtmlTemplates, findReactiveCalls };
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@esportsplus/queue": "^0.2.0",
|
|
5
|
-
"@esportsplus/reactivity": "^0.
|
|
6
|
-
"@esportsplus/typescript": "^0.
|
|
5
|
+
"@esportsplus/reactivity": "^0.29.1",
|
|
6
|
+
"@esportsplus/typescript": "^0.26.0",
|
|
7
7
|
"@esportsplus/utilities": "^0.27.2",
|
|
8
8
|
"serve": "^14.2.5"
|
|
9
9
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"type": "module",
|
|
37
37
|
"types": "./build/index.d.ts",
|
|
38
|
-
"version": "0.
|
|
38
|
+
"version": "0.39.0",
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc",
|
|
41
41
|
"build:test": "vite build --config test/vite.config.ts",
|
package/src/compiler/codegen.ts
CHANGED
|
@@ -2,8 +2,9 @@ import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
3
|
import { ast, uid } from '@esportsplus/typescript/compiler';
|
|
4
4
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS } from '~/constants';
|
|
5
|
+
import { extractTemplateParts } from './ts-parser';
|
|
5
6
|
import type { TemplateInfo } from './ts-parser';
|
|
6
|
-
import { analyze } from './analyzer';
|
|
7
|
+
import { analyze } from './ts-analyzer';
|
|
7
8
|
import parser from './parser';
|
|
8
9
|
|
|
9
10
|
|
|
@@ -18,7 +19,6 @@ type Attribute = {
|
|
|
18
19
|
|
|
19
20
|
type CodegenContext = {
|
|
20
21
|
checker?: ts.TypeChecker;
|
|
21
|
-
printer: ts.Printer;
|
|
22
22
|
sourceFile: ts.SourceFile;
|
|
23
23
|
templates: Map<string, string>;
|
|
24
24
|
};
|
|
@@ -39,45 +39,10 @@ type ParseResult = {
|
|
|
39
39
|
slots: (Attribute | Node)[] | null;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
type Replacement = {
|
|
43
|
-
end: number;
|
|
44
|
-
newText: string;
|
|
45
|
-
start: number;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
50
|
-
|
|
51
42
|
|
|
52
43
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
53
44
|
|
|
54
45
|
|
|
55
|
-
function collectNestedReplacements(
|
|
56
|
-
ctx: CodegenContext,
|
|
57
|
-
node: ts.Node,
|
|
58
|
-
exprStart: number,
|
|
59
|
-
replacements: Replacement[]
|
|
60
|
-
): void {
|
|
61
|
-
if (isNestedHtmlTemplate(node as ts.Expression)) {
|
|
62
|
-
replacements.push({
|
|
63
|
-
end: node.end - exprStart,
|
|
64
|
-
newText: generateNestedTemplateCode(ctx, node as ts.TaggedTemplateExpression),
|
|
65
|
-
start: node.getStart(ctx.sourceFile) - exprStart
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
else if (isReactiveCall(node as ts.Expression)) {
|
|
69
|
-
// Nested reactive calls need full ArraySlot construction (not just args)
|
|
70
|
-
replacements.push({
|
|
71
|
-
end: node.end - exprStart,
|
|
72
|
-
newText: rewriteNestedReactiveCall(ctx, node as ts.CallExpression),
|
|
73
|
-
start: node.getStart(ctx.sourceFile) - exprStart
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
ts.forEachChild(node, child => collectNestedReplacements(ctx, child, exprStart, replacements));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
46
|
function generateAttributeBinding(element: string, name: string, expr: string, staticValue: string): string {
|
|
82
47
|
if (name.startsWith('on') && name.length > 2) {
|
|
83
48
|
let event = name.slice(2).toLowerCase(),
|
|
@@ -106,24 +71,11 @@ function generateAttributeBinding(element: string, name: string, expr: string, s
|
|
|
106
71
|
}
|
|
107
72
|
|
|
108
73
|
function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplateExpression): string {
|
|
109
|
-
let expressions
|
|
110
|
-
exprTexts: string[] = []
|
|
111
|
-
literals: string[] = [],
|
|
112
|
-
template = node.template;
|
|
74
|
+
let { expressions, literals } = extractTemplateParts(node.template),
|
|
75
|
+
exprTexts: string[] = [];
|
|
113
76
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
else if (ts.isTemplateExpression(template)) {
|
|
118
|
-
literals.push(template.head.text);
|
|
119
|
-
|
|
120
|
-
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
121
|
-
let expr = template.templateSpans[i].expression;
|
|
122
|
-
|
|
123
|
-
expressions.push(expr);
|
|
124
|
-
literals.push(template.templateSpans[i].literal.text);
|
|
125
|
-
exprTexts.push(rewriteExpression(ctx, expr));
|
|
126
|
-
}
|
|
77
|
+
for (let i = 0, n = expressions.length; i < n; i++) {
|
|
78
|
+
exprTexts.push(rewriteExpression(ctx, expressions[i]));
|
|
127
79
|
}
|
|
128
80
|
|
|
129
81
|
return generateTemplateCode(
|
|
@@ -131,7 +83,7 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
|
|
|
131
83
|
parser.parse(literals) as ParseResult,
|
|
132
84
|
exprTexts,
|
|
133
85
|
expressions,
|
|
134
|
-
|
|
86
|
+
node
|
|
135
87
|
);
|
|
136
88
|
}
|
|
137
89
|
|
|
@@ -144,9 +96,7 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
144
96
|
return `${anchor}.parentNode!.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
|
|
145
97
|
}
|
|
146
98
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
switch (slotType) {
|
|
99
|
+
switch (analyze(exprNode, ctx.checker)) {
|
|
150
100
|
case COMPILER_TYPES.ArraySlot:
|
|
151
101
|
return `${anchor}.parentNode!.insertBefore(new ${COMPILER_NAMESPACE}.ArraySlot(${exprText}).fragment, ${anchor});`;
|
|
152
102
|
|
|
@@ -169,7 +119,7 @@ function generateTemplateCode(
|
|
|
169
119
|
{ html, slots }: ParseResult,
|
|
170
120
|
exprTexts: string[],
|
|
171
121
|
exprNodes: ts.Expression[],
|
|
172
|
-
|
|
122
|
+
templateNode: ts.Node
|
|
173
123
|
): string {
|
|
174
124
|
if (!slots || slots.length === 0) {
|
|
175
125
|
return `${getOrCreateTemplateId(ctx, html)}()`;
|
|
@@ -178,6 +128,7 @@ function generateTemplateCode(
|
|
|
178
128
|
let code: string[] = [],
|
|
179
129
|
declarations: string[] = [],
|
|
180
130
|
index = 0,
|
|
131
|
+
isArrowBody = isArrowExpressionBody(templateNode),
|
|
181
132
|
nodes = new Map<string, string>(),
|
|
182
133
|
root = uid('root');
|
|
183
134
|
|
|
@@ -286,6 +237,10 @@ function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExp
|
|
|
286
237
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
287
238
|
}
|
|
288
239
|
|
|
240
|
+
function isArrowExpressionBody(node: ts.Node): boolean {
|
|
241
|
+
return ts.isArrowFunction(node.parent) && (node.parent as ts.ArrowFunction).body === node;
|
|
242
|
+
}
|
|
243
|
+
|
|
289
244
|
function isReactiveCall(expr: ts.Expression): expr is ts.CallExpression {
|
|
290
245
|
return (
|
|
291
246
|
ts.isCallExpression(expr) &&
|
|
@@ -296,117 +251,152 @@ function isReactiveCall(expr: ts.Expression): expr is ts.CallExpression {
|
|
|
296
251
|
);
|
|
297
252
|
}
|
|
298
253
|
|
|
299
|
-
function replaceReverse(text: string, replacements: Replacement[]): string {
|
|
300
|
-
let sorted = replacements.slice().sort((a, b) => b.start - a.start);
|
|
301
|
-
|
|
302
|
-
for (let i = 0, n = sorted.length; i < n; i++) {
|
|
303
|
-
let r = sorted[i];
|
|
304
|
-
|
|
305
|
-
text = text.slice(0, r.start) + r.newText + text.slice(r.end);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return text;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
254
|
function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
|
|
312
255
|
if (isNestedHtmlTemplate(expr)) {
|
|
313
256
|
return generateNestedTemplateCode(ctx, expr);
|
|
314
257
|
}
|
|
315
258
|
|
|
259
|
+
// Returns just args "array, callback" - for direct slot usage where generateNodeBinding wraps it
|
|
316
260
|
if (isReactiveCall(expr)) {
|
|
317
|
-
return
|
|
261
|
+
return `
|
|
262
|
+
${printer.printNode(ts.EmitHint.Expression, expr.arguments[0], ctx.sourceFile)},
|
|
263
|
+
${rewriteExpression(ctx, expr.arguments[1] as ts.Expression)}
|
|
264
|
+
`;
|
|
318
265
|
}
|
|
319
266
|
|
|
320
267
|
if (!ast.hasMatch(expr, n => isNestedHtmlTemplate(n as ts.Expression) || isReactiveCall(n as ts.Expression))) {
|
|
321
|
-
return
|
|
268
|
+
return printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
|
|
322
269
|
}
|
|
323
270
|
|
|
324
|
-
|
|
271
|
+
return walkExpression(ctx, expr);
|
|
272
|
+
}
|
|
325
273
|
|
|
326
|
-
|
|
274
|
+
function walkExpression(ctx: CodegenContext, node: ts.Node): string {
|
|
275
|
+
// Intercept special nodes
|
|
276
|
+
if (isNestedHtmlTemplate(node as ts.Expression)) {
|
|
277
|
+
return generateNestedTemplateCode(ctx, node as ts.TaggedTemplateExpression);
|
|
278
|
+
}
|
|
327
279
|
|
|
328
|
-
|
|
329
|
-
|
|
280
|
+
if (isReactiveCall(node as ts.Expression)) {
|
|
281
|
+
let call = node as ts.CallExpression;
|
|
330
282
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
let arrayArg = node.arguments[0],
|
|
334
|
-
arrayText = ctx.printer.printNode(ts.EmitHint.Expression, arrayArg, ctx.sourceFile),
|
|
335
|
-
callbackArg = node.arguments[1],
|
|
336
|
-
callbackText = rewriteExpression(ctx, callbackArg as ts.Expression);
|
|
283
|
+
return `new ${COMPILER_NAMESPACE}.ArraySlot(${printer.printNode(ts.EmitHint.Expression, call.arguments[0], ctx.sourceFile)}, ${rewriteExpression(ctx, call.arguments[1] as ts.Expression)})`;
|
|
284
|
+
}
|
|
337
285
|
|
|
338
|
-
|
|
339
|
-
|
|
286
|
+
// No nested constructs - use printer
|
|
287
|
+
if (!ast.hasMatch(node, n => isNestedHtmlTemplate(n as ts.Expression) || isReactiveCall(n as ts.Expression))) {
|
|
288
|
+
return printer.printNode(ts.EmitHint.Expression, node as ts.Expression, ctx.sourceFile);
|
|
289
|
+
}
|
|
340
290
|
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
callbackArg = node.arguments[1],
|
|
346
|
-
callbackText = rewriteExpression(ctx, callbackArg as ts.Expression);
|
|
291
|
+
// Recursive handling by node type
|
|
292
|
+
if (ts.isConditionalExpression(node)) {
|
|
293
|
+
return `${walkExpression(ctx, node.condition)} ? ${walkExpression(ctx, node.whenTrue)} : ${walkExpression(ctx, node.whenFalse)}`;
|
|
294
|
+
}
|
|
347
295
|
|
|
348
|
-
|
|
349
|
-
}
|
|
296
|
+
if (ts.isBinaryExpression(node)) {
|
|
297
|
+
return `${walkExpression(ctx, node.left)} ${node.operatorToken.getText(ctx.sourceFile)} ${walkExpression(ctx, node.right)}`;
|
|
298
|
+
}
|
|
350
299
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (isNestedHtmlTemplate(node as ts.Expression)) {
|
|
354
|
-
let template = node as ts.TaggedTemplateExpression,
|
|
355
|
-
expressions: ts.Expression[] = [],
|
|
356
|
-
literals: string[] = [],
|
|
357
|
-
tpl = template.template;
|
|
300
|
+
if (ts.isCallExpression(node)) {
|
|
301
|
+
let args: string[] = [];
|
|
358
302
|
|
|
359
|
-
|
|
360
|
-
|
|
303
|
+
for (let i = 0, n = node.arguments.length; i < n; i++) {
|
|
304
|
+
args.push(walkExpression(ctx, node.arguments[i]));
|
|
361
305
|
}
|
|
362
|
-
else if (ts.isTemplateExpression(tpl)) {
|
|
363
|
-
literals.push(tpl.head.text);
|
|
364
306
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
307
|
+
return `${walkExpression(ctx, node.expression)}(${args.join(', ')})`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
311
|
+
return `${walkExpression(ctx, node.expression)}.${node.name.text}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (ts.isElementAccessExpression(node)) {
|
|
315
|
+
return `${walkExpression(ctx, node.expression)}[${walkExpression(ctx, node.argumentExpression)}]`;
|
|
316
|
+
}
|
|
370
317
|
|
|
371
|
-
|
|
318
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
319
|
+
return `(${walkExpression(ctx, node.expression)})`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (ts.isArrowFunction(node)) {
|
|
323
|
+
return `(${node.parameters.map(p => p.getText(ctx.sourceFile)).join(', ')}) => ${
|
|
324
|
+
ts.isBlock(node.body) ? node.body.getText(ctx.sourceFile) : walkExpression(ctx, node.body)
|
|
325
|
+
}`;
|
|
326
|
+
}
|
|
372
327
|
|
|
373
|
-
|
|
328
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
329
|
+
let elements: string[] = [];
|
|
374
330
|
|
|
375
|
-
for (let i = 0, n =
|
|
376
|
-
|
|
331
|
+
for (let i = 0, n = node.elements.length; i < n; i++) {
|
|
332
|
+
elements.push( walkExpression(ctx, node.elements[i]) );
|
|
377
333
|
}
|
|
334
|
+
|
|
335
|
+
return `[${elements.join(', ')}]`;
|
|
378
336
|
}
|
|
379
|
-
else if (isReactiveCall(node as ts.Expression)) {
|
|
380
|
-
let call = node as ts.CallExpression;
|
|
381
337
|
|
|
382
|
-
|
|
383
|
-
|
|
338
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
339
|
+
let properties: string[] = [];
|
|
340
|
+
|
|
341
|
+
for (let i = 0, n = node.properties.length; i < n; i++) {
|
|
342
|
+
let prop = node.properties[i];
|
|
343
|
+
|
|
344
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
345
|
+
properties.push(`${prop.name.getText(ctx.sourceFile)}: ${walkExpression(ctx, prop.initializer)}`);
|
|
346
|
+
}
|
|
347
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
348
|
+
properties.push(prop.name.text);
|
|
349
|
+
}
|
|
350
|
+
else if (ts.isSpreadAssignment(prop)) {
|
|
351
|
+
properties.push(`...${walkExpression(ctx, prop.expression)}`);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
properties.push(prop.getText(ctx.sourceFile));
|
|
355
|
+
}
|
|
384
356
|
}
|
|
357
|
+
|
|
358
|
+
return `{ ${properties.join(', ')} }`;
|
|
385
359
|
}
|
|
386
|
-
|
|
387
|
-
|
|
360
|
+
|
|
361
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
362
|
+
return `${ts.tokenToString(node.operator)}${walkExpression(ctx, node.operand)}`;
|
|
388
363
|
}
|
|
389
|
-
}
|
|
390
364
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
365
|
+
if (ts.isPostfixUnaryExpression(node)) {
|
|
366
|
+
return `${walkExpression(ctx, node.operand)}${ts.tokenToString(node.operator)}`;
|
|
367
|
+
}
|
|
394
368
|
|
|
395
|
-
|
|
369
|
+
if (ts.isTemplateExpression(node)) {
|
|
370
|
+
let parts = [node.head.text];
|
|
396
371
|
|
|
397
|
-
for (let
|
|
398
|
-
|
|
372
|
+
for (let i = 0, n = node.templateSpans.length; i < n; i++) {
|
|
373
|
+
let span = node.templateSpans[i];
|
|
374
|
+
|
|
375
|
+
parts.push('${' + walkExpression(ctx, span.expression) + '}' + span.literal.text);
|
|
399
376
|
}
|
|
377
|
+
|
|
378
|
+
return '`' + parts.join('') + '`';
|
|
400
379
|
}
|
|
380
|
+
|
|
381
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
382
|
+
return walkExpression(ctx, node.expression);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (ts.isNonNullExpression(node)) {
|
|
386
|
+
return `${walkExpression(ctx, node.expression)}!`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Fallback - use printer
|
|
390
|
+
return printer.printNode(ts.EmitHint.Expression, node as ts.Expression, ctx.sourceFile);
|
|
401
391
|
}
|
|
402
392
|
|
|
403
393
|
|
|
404
394
|
const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
|
|
405
395
|
let result: CodegenResult = {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
396
|
+
prepend: [],
|
|
397
|
+
replacements: [],
|
|
398
|
+
templates: new Map()
|
|
399
|
+
};
|
|
410
400
|
|
|
411
401
|
if (templates.length === 0) {
|
|
412
402
|
return result;
|
|
@@ -429,23 +419,19 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
429
419
|
}
|
|
430
420
|
|
|
431
421
|
let ctx: CodegenContext = {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
};
|
|
422
|
+
checker,
|
|
423
|
+
sourceFile,
|
|
424
|
+
templates: result.templates
|
|
425
|
+
};
|
|
437
426
|
|
|
438
427
|
for (let i = 0, n = rootTemplates.length; i < n; i++) {
|
|
439
428
|
let template = rootTemplates[i];
|
|
440
429
|
|
|
441
430
|
result.replacements.push({
|
|
442
431
|
generate: (sf) => {
|
|
443
|
-
let
|
|
444
|
-
exprTexts: string[] = [],
|
|
445
|
-
isArrowBody = codeBefore.trimEnd().endsWith('=>'),
|
|
432
|
+
let exprTexts: string[] = [],
|
|
446
433
|
localCtx: CodegenContext = {
|
|
447
434
|
checker,
|
|
448
|
-
printer,
|
|
449
435
|
sourceFile: sf,
|
|
450
436
|
templates: ctx.templates
|
|
451
437
|
},
|
|
@@ -455,12 +441,13 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
455
441
|
exprTexts.push(rewriteExpression(localCtx, template.expressions[j]));
|
|
456
442
|
}
|
|
457
443
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
444
|
+
// Zero-param arrow with no slots → just return template ID
|
|
445
|
+
if (
|
|
446
|
+
isArrowExpressionBody(template.node) &&
|
|
447
|
+
(template.node.parent as ts.ArrowFunction).parameters.length === 0 &&
|
|
448
|
+
(!parsed.slots || parsed.slots.length === 0)
|
|
449
|
+
) {
|
|
450
|
+
return getOrCreateTemplateId(localCtx, parsed.html);
|
|
464
451
|
}
|
|
465
452
|
|
|
466
453
|
return generateTemplateCode(
|
|
@@ -468,16 +455,13 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
468
455
|
parsed,
|
|
469
456
|
exprTexts,
|
|
470
457
|
template.expressions,
|
|
471
|
-
|
|
458
|
+
template.node
|
|
472
459
|
);
|
|
473
460
|
},
|
|
474
461
|
node: template.node
|
|
475
462
|
});
|
|
476
463
|
}
|
|
477
464
|
|
|
478
|
-
// Eager discovery: find all templates before prepend generation
|
|
479
|
-
discoverAllTemplates(ctx, templates);
|
|
480
|
-
|
|
481
465
|
for (let [html, id] of ctx.templates) {
|
|
482
466
|
result.prepend.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
|
|
483
467
|
}
|
|
@@ -486,5 +470,5 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
486
470
|
};
|
|
487
471
|
|
|
488
472
|
|
|
489
|
-
export { generateCode };
|
|
473
|
+
export { generateCode, printer };
|
|
490
474
|
export type { CodegenResult };
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,26 +1,11 @@
|
|
|
1
|
-
import type { ImportIntent, Plugin, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { ast } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import type { ImportIntent, Plugin, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
|
|
3
4
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, PACKAGE } from '~/constants';
|
|
4
|
-
import { generateCode } from './codegen';
|
|
5
|
+
import { generateCode, printer } from './codegen';
|
|
5
6
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
function isInRange(ranges: { end: number; start: number }[], start: number, end: number): boolean {
|
|
9
|
-
for (let i = 0, n = ranges.length; i < n; i++) {
|
|
10
|
-
let range = ranges[i];
|
|
11
|
-
|
|
12
|
-
if (start >= range.start && end <= range.end) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
22
|
-
|
|
23
|
-
|
|
24
9
|
const plugin: Plugin = {
|
|
25
10
|
patterns: [
|
|
26
11
|
`${COMPILER_ENTRYPOINT}\``,
|
|
@@ -30,17 +15,15 @@ const plugin: Plugin = {
|
|
|
30
15
|
transform: (ctx: TransformContext) => {
|
|
31
16
|
let importsIntent: ImportIntent[] = [],
|
|
32
17
|
prepend: string[] = [],
|
|
18
|
+
// Build ranges for all template nodes - reactive calls inside these are handled by template codegen
|
|
19
|
+
ranges: { end: number; start: number }[] = [],
|
|
33
20
|
replacements: ReplacementIntent[] = [],
|
|
34
|
-
removeImports: string[] = []
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
38
|
-
|
|
39
|
-
// Build ranges for all template nodes - reactive calls inside these are handled by template codegen
|
|
40
|
-
let templateRanges: { end: number; start: number }[] = [];
|
|
21
|
+
removeImports: string[] = [],
|
|
22
|
+
// Find templates first to build exclusion ranges
|
|
23
|
+
templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
41
24
|
|
|
42
25
|
for (let i = 0, n = templates.length; i < n; i++) {
|
|
43
|
-
|
|
26
|
+
ranges.push({
|
|
44
27
|
end: templates[i].end,
|
|
45
28
|
start: templates[i].start
|
|
46
29
|
});
|
|
@@ -53,12 +36,15 @@ const plugin: Plugin = {
|
|
|
53
36
|
let call = reactiveCalls[i];
|
|
54
37
|
|
|
55
38
|
// Skip reactive calls that are inside template expressions - handled by template codegen
|
|
56
|
-
if (
|
|
39
|
+
if (ast.inRange(ranges, call.start, call.end)) {
|
|
57
40
|
continue;
|
|
58
41
|
}
|
|
59
42
|
|
|
60
43
|
replacements.push({
|
|
61
|
-
generate: (sourceFile) => `new ${COMPILER_NAMESPACE}.ArraySlot(
|
|
44
|
+
generate: (sourceFile) => `new ${COMPILER_NAMESPACE}.ArraySlot(
|
|
45
|
+
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
46
|
+
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
47
|
+
)`,
|
|
62
48
|
node: call.node
|
|
63
49
|
});
|
|
64
50
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { imports } from '@esportsplus/typescript/compiler';
|
|
2
3
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
|
|
3
4
|
|
|
4
5
|
|
|
@@ -20,47 +21,6 @@ type TemplateInfo = {
|
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
|
|
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
24
|
function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[], checker: ts.TypeChecker | undefined): void {
|
|
65
25
|
if (
|
|
66
26
|
ts.isCallExpression(node) &&
|
|
@@ -68,7 +28,8 @@ function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[], checker: t
|
|
|
68
28
|
ts.isIdentifier(node.expression.expression) &&
|
|
69
29
|
node.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY &&
|
|
70
30
|
node.arguments.length === 2 &&
|
|
71
|
-
|
|
31
|
+
node.expression.expression.text === COMPILER_ENTRYPOINT &&
|
|
32
|
+
(!checker || imports.inPackage(checker, node.expression.expression, PACKAGE, COMPILER_ENTRYPOINT))
|
|
72
33
|
) {
|
|
73
34
|
calls.push({
|
|
74
35
|
arrayArg: node.arguments[0],
|
|
@@ -87,24 +48,13 @@ function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[],
|
|
|
87
48
|
? depth + 1
|
|
88
49
|
: depth;
|
|
89
50
|
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
else if (ts.isTemplateExpression(template)) {
|
|
99
|
-
literals.push(template.head.text);
|
|
100
|
-
|
|
101
|
-
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
102
|
-
let span = template.templateSpans[i];
|
|
103
|
-
|
|
104
|
-
expressions.push(span.expression);
|
|
105
|
-
literals.push(span.literal.text);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
51
|
+
if (
|
|
52
|
+
ts.isTaggedTemplateExpression(node) &&
|
|
53
|
+
ts.isIdentifier(node.tag) &&
|
|
54
|
+
node.tag.text === COMPILER_ENTRYPOINT &&
|
|
55
|
+
(!checker || imports.inPackage(checker, node.tag, PACKAGE, COMPILER_ENTRYPOINT))
|
|
56
|
+
) {
|
|
57
|
+
let { expressions, literals } = extractTemplateParts(node.template);
|
|
108
58
|
|
|
109
59
|
templates.push({
|
|
110
60
|
depth,
|
|
@@ -120,15 +70,34 @@ function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[],
|
|
|
120
70
|
}
|
|
121
71
|
|
|
122
72
|
|
|
73
|
+
const extractTemplateParts = (template: ts.TemplateLiteral): { expressions: ts.Expression[]; literals: string[] } => {
|
|
74
|
+
let expressions: ts.Expression[] = [],
|
|
75
|
+
literals: string[] = [];
|
|
76
|
+
|
|
77
|
+
if (ts.isNoSubstitutionTemplateLiteral(template)) {
|
|
78
|
+
literals.push(template.text);
|
|
79
|
+
}
|
|
80
|
+
else if (ts.isTemplateExpression(template)) {
|
|
81
|
+
literals.push(template.head.text);
|
|
82
|
+
|
|
83
|
+
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
84
|
+
let span = template.templateSpans[i];
|
|
85
|
+
|
|
86
|
+
expressions.push(span.expression);
|
|
87
|
+
literals.push(span.literal.text);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { expressions, literals };
|
|
92
|
+
};
|
|
93
|
+
|
|
123
94
|
const findHtmlTemplates = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): TemplateInfo[] => {
|
|
124
95
|
let templates: TemplateInfo[] = [];
|
|
125
96
|
|
|
126
97
|
visitTemplates(sourceFile, 0, templates, checker);
|
|
127
98
|
|
|
128
99
|
// Sort by depth descending (deepest first), then by position for stable ordering
|
|
129
|
-
templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
130
|
-
|
|
131
|
-
return templates;
|
|
100
|
+
return templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
132
101
|
};
|
|
133
102
|
|
|
134
103
|
const findReactiveCalls = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): ReactiveCallInfo[] => {
|
|
@@ -140,5 +109,5 @@ const findReactiveCalls = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker):
|
|
|
140
109
|
};
|
|
141
110
|
|
|
142
111
|
|
|
143
|
-
export { findHtmlTemplates, findReactiveCalls };
|
|
112
|
+
export { extractTemplateParts, findHtmlTemplates, findReactiveCalls };
|
|
144
113
|
export type { ReactiveCallInfo, TemplateInfo };
|
|
File without changes
|