@esportsplus/template 0.38.1 → 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 -94
- package/build/compiler/index.js +13 -22
- 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 +136 -140
- package/src/compiler/index.ts +18 -35
- 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: rewriteReactiveCall(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,69 +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
|
|
149
|
+
function walkExpression(ctx, node) {
|
|
187
150
|
if (isNestedHtmlTemplate(node)) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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]));
|
|
198
170
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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]));
|
|
203
189
|
}
|
|
190
|
+
return `[${elements.join(', ')}]`;
|
|
204
191
|
}
|
|
205
|
-
|
|
206
|
-
let
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
}
|
|
209
208
|
}
|
|
209
|
+
return `{ ${properties.join(', ')} }`;
|
|
210
210
|
}
|
|
211
|
-
|
|
212
|
-
ts.
|
|
211
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
212
|
+
return `${ts.tokenToString(node.operator)}${walkExpression(ctx, node.operand)}`;
|
|
213
213
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
for (let
|
|
220
|
-
|
|
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);
|
|
221
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)}!`;
|
|
222
230
|
}
|
|
231
|
+
return printer.printNode(ts.EmitHint.Expression, node, ctx.sourceFile);
|
|
223
232
|
}
|
|
224
233
|
const generateCode = (templates, sourceFile, checker) => {
|
|
225
234
|
let result = {
|
|
@@ -243,7 +252,6 @@ const generateCode = (templates, sourceFile, checker) => {
|
|
|
243
252
|
}
|
|
244
253
|
let ctx = {
|
|
245
254
|
checker,
|
|
246
|
-
printer,
|
|
247
255
|
sourceFile,
|
|
248
256
|
templates: result.templates
|
|
249
257
|
};
|
|
@@ -251,30 +259,27 @@ const generateCode = (templates, sourceFile, checker) => {
|
|
|
251
259
|
let template = rootTemplates[i];
|
|
252
260
|
result.replacements.push({
|
|
253
261
|
generate: (sf) => {
|
|
254
|
-
let
|
|
262
|
+
let exprTexts = [], localCtx = {
|
|
255
263
|
checker,
|
|
256
|
-
printer,
|
|
257
264
|
sourceFile: sf,
|
|
258
265
|
templates: ctx.templates
|
|
259
266
|
}, parsed = parser.parse(template.literals);
|
|
260
267
|
for (let j = 0, m = template.expressions.length; j < m; j++) {
|
|
261
268
|
exprTexts.push(rewriteExpression(localCtx, template.expressions[j]));
|
|
262
269
|
}
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
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);
|
|
268
274
|
}
|
|
269
|
-
return generateTemplateCode(localCtx, parsed, exprTexts, template.expressions,
|
|
275
|
+
return generateTemplateCode(localCtx, parsed, exprTexts, template.expressions, template.node);
|
|
270
276
|
},
|
|
271
277
|
node: template.node
|
|
272
278
|
});
|
|
273
279
|
}
|
|
274
|
-
discoverAllTemplates(ctx, templates);
|
|
275
280
|
for (let [html, id] of ctx.templates) {
|
|
276
281
|
result.prepend.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
|
|
277
282
|
}
|
|
278
283
|
return result;
|
|
279
284
|
};
|
|
280
|
-
export { generateCode };
|
|
285
|
+
export { generateCode, printer };
|
package/build/compiler/index.js
CHANGED
|
@@ -1,29 +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
|
-
const PATTERNS = [
|
|
6
|
-
`${COMPILER_ENTRYPOINT}\``,
|
|
7
|
-
`${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`
|
|
8
|
-
];
|
|
9
|
-
function isInRange(ranges, start, end) {
|
|
10
|
-
for (let i = 0, n = ranges.length; i < n; i++) {
|
|
11
|
-
let range = ranges[i];
|
|
12
|
-
if (start >= range.start && end <= range.end) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
19
6
|
const plugin = {
|
|
20
|
-
patterns:
|
|
7
|
+
patterns: [
|
|
8
|
+
`${COMPILER_ENTRYPOINT}\``,
|
|
9
|
+
`${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`
|
|
10
|
+
],
|
|
21
11
|
transform: (ctx) => {
|
|
22
|
-
let importsIntent = [], prepend = [], replacements = [], removeImports = [];
|
|
23
|
-
let templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
24
|
-
let templateRanges = [];
|
|
12
|
+
let importsIntent = [], prepend = [], ranges = [], replacements = [], removeImports = [], templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
25
13
|
for (let i = 0, n = templates.length; i < n; i++) {
|
|
26
|
-
|
|
14
|
+
ranges.push({
|
|
27
15
|
end: templates[i].end,
|
|
28
16
|
start: templates[i].start
|
|
29
17
|
});
|
|
@@ -31,11 +19,14 @@ const plugin = {
|
|
|
31
19
|
let reactiveCalls = findReactiveCalls(ctx.sourceFile, ctx.checker);
|
|
32
20
|
for (let i = 0, n = reactiveCalls.length; i < n; i++) {
|
|
33
21
|
let call = reactiveCalls[i];
|
|
34
|
-
if (
|
|
22
|
+
if (ast.inRange(ranges, call.start, call.end)) {
|
|
35
23
|
continue;
|
|
36
24
|
}
|
|
37
25
|
replacements.push({
|
|
38
|
-
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
|
+
)`,
|
|
39
30
|
node: call.node
|
|
40
31
|
});
|
|
41
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,44 +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
|
-
replacements.push({
|
|
70
|
-
end: node.end - exprStart,
|
|
71
|
-
newText: rewriteReactiveCall(ctx, node as ts.CallExpression),
|
|
72
|
-
start: node.getStart(ctx.sourceFile) - exprStart
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
ts.forEachChild(node, child => collectNestedReplacements(ctx, child, exprStart, replacements));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
46
|
function generateAttributeBinding(element: string, name: string, expr: string, staticValue: string): string {
|
|
81
47
|
if (name.startsWith('on') && name.length > 2) {
|
|
82
48
|
let event = name.slice(2).toLowerCase(),
|
|
@@ -105,24 +71,11 @@ function generateAttributeBinding(element: string, name: string, expr: string, s
|
|
|
105
71
|
}
|
|
106
72
|
|
|
107
73
|
function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplateExpression): string {
|
|
108
|
-
let expressions
|
|
109
|
-
exprTexts: string[] = []
|
|
110
|
-
literals: string[] = [],
|
|
111
|
-
template = node.template;
|
|
74
|
+
let { expressions, literals } = extractTemplateParts(node.template),
|
|
75
|
+
exprTexts: string[] = [];
|
|
112
76
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
else if (ts.isTemplateExpression(template)) {
|
|
117
|
-
literals.push(template.head.text);
|
|
118
|
-
|
|
119
|
-
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
120
|
-
let expr = template.templateSpans[i].expression;
|
|
121
|
-
|
|
122
|
-
expressions.push(expr);
|
|
123
|
-
literals.push(template.templateSpans[i].literal.text);
|
|
124
|
-
exprTexts.push(rewriteExpression(ctx, expr));
|
|
125
|
-
}
|
|
77
|
+
for (let i = 0, n = expressions.length; i < n; i++) {
|
|
78
|
+
exprTexts.push(rewriteExpression(ctx, expressions[i]));
|
|
126
79
|
}
|
|
127
80
|
|
|
128
81
|
return generateTemplateCode(
|
|
@@ -130,7 +83,7 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
|
|
|
130
83
|
parser.parse(literals) as ParseResult,
|
|
131
84
|
exprTexts,
|
|
132
85
|
expressions,
|
|
133
|
-
|
|
86
|
+
node
|
|
134
87
|
);
|
|
135
88
|
}
|
|
136
89
|
|
|
@@ -143,9 +96,7 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
143
96
|
return `${anchor}.parentNode!.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
|
|
144
97
|
}
|
|
145
98
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
switch (slotType) {
|
|
99
|
+
switch (analyze(exprNode, ctx.checker)) {
|
|
149
100
|
case COMPILER_TYPES.ArraySlot:
|
|
150
101
|
return `${anchor}.parentNode!.insertBefore(new ${COMPILER_NAMESPACE}.ArraySlot(${exprText}).fragment, ${anchor});`;
|
|
151
102
|
|
|
@@ -168,7 +119,7 @@ function generateTemplateCode(
|
|
|
168
119
|
{ html, slots }: ParseResult,
|
|
169
120
|
exprTexts: string[],
|
|
170
121
|
exprNodes: ts.Expression[],
|
|
171
|
-
|
|
122
|
+
templateNode: ts.Node
|
|
172
123
|
): string {
|
|
173
124
|
if (!slots || slots.length === 0) {
|
|
174
125
|
return `${getOrCreateTemplateId(ctx, html)}()`;
|
|
@@ -177,6 +128,7 @@ function generateTemplateCode(
|
|
|
177
128
|
let code: string[] = [],
|
|
178
129
|
declarations: string[] = [],
|
|
179
130
|
index = 0,
|
|
131
|
+
isArrowBody = isArrowExpressionBody(templateNode),
|
|
180
132
|
nodes = new Map<string, string>(),
|
|
181
133
|
root = uid('root');
|
|
182
134
|
|
|
@@ -285,6 +237,10 @@ function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExp
|
|
|
285
237
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
286
238
|
}
|
|
287
239
|
|
|
240
|
+
function isArrowExpressionBody(node: ts.Node): boolean {
|
|
241
|
+
return ts.isArrowFunction(node.parent) && (node.parent as ts.ArrowFunction).body === node;
|
|
242
|
+
}
|
|
243
|
+
|
|
288
244
|
function isReactiveCall(expr: ts.Expression): expr is ts.CallExpression {
|
|
289
245
|
return (
|
|
290
246
|
ts.isCallExpression(expr) &&
|
|
@@ -295,106 +251,152 @@ function isReactiveCall(expr: ts.Expression): expr is ts.CallExpression {
|
|
|
295
251
|
);
|
|
296
252
|
}
|
|
297
253
|
|
|
298
|
-
function replaceReverse(text: string, replacements: Replacement[]): string {
|
|
299
|
-
let sorted = replacements.slice().sort((a, b) => b.start - a.start);
|
|
300
|
-
|
|
301
|
-
for (let i = 0, n = sorted.length; i < n; i++) {
|
|
302
|
-
let r = sorted[i];
|
|
303
|
-
|
|
304
|
-
text = text.slice(0, r.start) + r.newText + text.slice(r.end);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return text;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
254
|
function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
|
|
311
255
|
if (isNestedHtmlTemplate(expr)) {
|
|
312
256
|
return generateNestedTemplateCode(ctx, expr);
|
|
313
257
|
}
|
|
314
258
|
|
|
259
|
+
// Returns just args "array, callback" - for direct slot usage where generateNodeBinding wraps it
|
|
315
260
|
if (isReactiveCall(expr)) {
|
|
316
|
-
return
|
|
261
|
+
return `
|
|
262
|
+
${printer.printNode(ts.EmitHint.Expression, expr.arguments[0], ctx.sourceFile)},
|
|
263
|
+
${rewriteExpression(ctx, expr.arguments[1] as ts.Expression)}
|
|
264
|
+
`;
|
|
317
265
|
}
|
|
318
266
|
|
|
319
267
|
if (!ast.hasMatch(expr, n => isNestedHtmlTemplate(n as ts.Expression) || isReactiveCall(n as ts.Expression))) {
|
|
320
|
-
return
|
|
268
|
+
return printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
|
|
321
269
|
}
|
|
322
270
|
|
|
323
|
-
|
|
271
|
+
return walkExpression(ctx, expr);
|
|
272
|
+
}
|
|
324
273
|
|
|
325
|
-
|
|
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
|
+
}
|
|
326
279
|
|
|
327
|
-
|
|
328
|
-
|
|
280
|
+
if (isReactiveCall(node as ts.Expression)) {
|
|
281
|
+
let call = node as ts.CallExpression;
|
|
329
282
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
arrayText = ctx.printer.printNode(ts.EmitHint.Expression, arrayArg, ctx.sourceFile),
|
|
333
|
-
callbackArg = node.arguments[1],
|
|
334
|
-
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
|
+
}
|
|
335
285
|
|
|
336
|
-
|
|
337
|
-
|
|
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
|
+
}
|
|
338
290
|
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
expressions: ts.Expression[] = [],
|
|
344
|
-
literals: string[] = [],
|
|
345
|
-
tpl = template.template;
|
|
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
|
+
}
|
|
346
295
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
else if (ts.isTemplateExpression(tpl)) {
|
|
351
|
-
literals.push(tpl.head.text);
|
|
296
|
+
if (ts.isBinaryExpression(node)) {
|
|
297
|
+
return `${walkExpression(ctx, node.left)} ${node.operatorToken.getText(ctx.sourceFile)} ${walkExpression(ctx, node.right)}`;
|
|
298
|
+
}
|
|
352
299
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
300
|
+
if (ts.isCallExpression(node)) {
|
|
301
|
+
let args: string[] = [];
|
|
302
|
+
|
|
303
|
+
for (let i = 0, n = node.arguments.length; i < n; i++) {
|
|
304
|
+
args.push(walkExpression(ctx, node.arguments[i]));
|
|
357
305
|
}
|
|
358
306
|
|
|
359
|
-
|
|
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
|
+
}
|
|
317
|
+
|
|
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
|
+
}
|
|
360
327
|
|
|
361
|
-
|
|
328
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
329
|
+
let elements: string[] = [];
|
|
362
330
|
|
|
363
|
-
for (let i = 0, n =
|
|
364
|
-
|
|
331
|
+
for (let i = 0, n = node.elements.length; i < n; i++) {
|
|
332
|
+
elements.push( walkExpression(ctx, node.elements[i]) );
|
|
365
333
|
}
|
|
334
|
+
|
|
335
|
+
return `[${elements.join(', ')}]`;
|
|
366
336
|
}
|
|
367
|
-
else if (isReactiveCall(node as ts.Expression)) {
|
|
368
|
-
let call = node as ts.CallExpression;
|
|
369
337
|
|
|
370
|
-
|
|
371
|
-
|
|
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
|
+
}
|
|
372
356
|
}
|
|
357
|
+
|
|
358
|
+
return `{ ${properties.join(', ')} }`;
|
|
373
359
|
}
|
|
374
|
-
|
|
375
|
-
|
|
360
|
+
|
|
361
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
362
|
+
return `${ts.tokenToString(node.operator)}${walkExpression(ctx, node.operand)}`;
|
|
376
363
|
}
|
|
377
|
-
}
|
|
378
364
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
365
|
+
if (ts.isPostfixUnaryExpression(node)) {
|
|
366
|
+
return `${walkExpression(ctx, node.operand)}${ts.tokenToString(node.operator)}`;
|
|
367
|
+
}
|
|
382
368
|
|
|
383
|
-
|
|
369
|
+
if (ts.isTemplateExpression(node)) {
|
|
370
|
+
let parts = [node.head.text];
|
|
384
371
|
|
|
385
|
-
for (let
|
|
386
|
-
|
|
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);
|
|
387
376
|
}
|
|
377
|
+
|
|
378
|
+
return '`' + parts.join('') + '`';
|
|
388
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);
|
|
389
391
|
}
|
|
390
392
|
|
|
391
393
|
|
|
392
394
|
const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
|
|
393
395
|
let result: CodegenResult = {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
prepend: [],
|
|
397
|
+
replacements: [],
|
|
398
|
+
templates: new Map()
|
|
399
|
+
};
|
|
398
400
|
|
|
399
401
|
if (templates.length === 0) {
|
|
400
402
|
return result;
|
|
@@ -417,23 +419,19 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
417
419
|
}
|
|
418
420
|
|
|
419
421
|
let ctx: CodegenContext = {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
};
|
|
422
|
+
checker,
|
|
423
|
+
sourceFile,
|
|
424
|
+
templates: result.templates
|
|
425
|
+
};
|
|
425
426
|
|
|
426
427
|
for (let i = 0, n = rootTemplates.length; i < n; i++) {
|
|
427
428
|
let template = rootTemplates[i];
|
|
428
429
|
|
|
429
430
|
result.replacements.push({
|
|
430
431
|
generate: (sf) => {
|
|
431
|
-
let
|
|
432
|
-
exprTexts: string[] = [],
|
|
433
|
-
isArrowBody = codeBefore.trimEnd().endsWith('=>'),
|
|
432
|
+
let exprTexts: string[] = [],
|
|
434
433
|
localCtx: CodegenContext = {
|
|
435
434
|
checker,
|
|
436
|
-
printer,
|
|
437
435
|
sourceFile: sf,
|
|
438
436
|
templates: ctx.templates
|
|
439
437
|
},
|
|
@@ -443,12 +441,13 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
443
441
|
exprTexts.push(rewriteExpression(localCtx, template.expressions[j]));
|
|
444
442
|
}
|
|
445
443
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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);
|
|
452
451
|
}
|
|
453
452
|
|
|
454
453
|
return generateTemplateCode(
|
|
@@ -456,16 +455,13 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
456
455
|
parsed,
|
|
457
456
|
exprTexts,
|
|
458
457
|
template.expressions,
|
|
459
|
-
|
|
458
|
+
template.node
|
|
460
459
|
);
|
|
461
460
|
},
|
|
462
461
|
node: template.node
|
|
463
462
|
});
|
|
464
463
|
}
|
|
465
464
|
|
|
466
|
-
// Eager discovery: find all templates before prepend generation
|
|
467
|
-
discoverAllTemplates(ctx, templates);
|
|
468
|
-
|
|
469
465
|
for (let [html, id] of ctx.templates) {
|
|
470
466
|
result.prepend.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
|
|
471
467
|
}
|
|
@@ -474,5 +470,5 @@ const generateCode = (templates: TemplateInfo[], sourceFile: ts.SourceFile, chec
|
|
|
474
470
|
};
|
|
475
471
|
|
|
476
472
|
|
|
477
|
-
export { generateCode };
|
|
473
|
+
export { generateCode, printer };
|
|
478
474
|
export type { CodegenResult };
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,49 +1,29 @@
|
|
|
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
|
-
const PATTERNS = [
|
|
9
|
-
`${COMPILER_ENTRYPOINT}\``,
|
|
10
|
-
`${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function isInRange(ranges: { end: number; start: number }[], start: number, end: number): boolean {
|
|
15
|
-
for (let i = 0, n = ranges.length; i < n; i++) {
|
|
16
|
-
let range = ranges[i];
|
|
17
|
-
|
|
18
|
-
if (start >= range.start && end <= range.end) {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
28
|
-
|
|
29
|
-
|
|
30
9
|
const plugin: Plugin = {
|
|
31
|
-
patterns:
|
|
10
|
+
patterns: [
|
|
11
|
+
`${COMPILER_ENTRYPOINT}\``,
|
|
12
|
+
`${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`
|
|
13
|
+
],
|
|
32
14
|
|
|
33
15
|
transform: (ctx: TransformContext) => {
|
|
34
16
|
let importsIntent: ImportIntent[] = [],
|
|
35
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 }[] = [],
|
|
36
20
|
replacements: ReplacementIntent[] = [],
|
|
37
|
-
removeImports: string[] = []
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
let templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
41
|
-
|
|
42
|
-
// Build ranges for all template nodes - reactive calls inside these are handled by template codegen
|
|
43
|
-
let templateRanges: { end: number; start: number }[] = [];
|
|
21
|
+
removeImports: string[] = [],
|
|
22
|
+
// Find templates first to build exclusion ranges
|
|
23
|
+
templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
44
24
|
|
|
45
25
|
for (let i = 0, n = templates.length; i < n; i++) {
|
|
46
|
-
|
|
26
|
+
ranges.push({
|
|
47
27
|
end: templates[i].end,
|
|
48
28
|
start: templates[i].start
|
|
49
29
|
});
|
|
@@ -56,12 +36,15 @@ const plugin: Plugin = {
|
|
|
56
36
|
let call = reactiveCalls[i];
|
|
57
37
|
|
|
58
38
|
// Skip reactive calls that are inside template expressions - handled by template codegen
|
|
59
|
-
if (
|
|
39
|
+
if (ast.inRange(ranges, call.start, call.end)) {
|
|
60
40
|
continue;
|
|
61
41
|
}
|
|
62
42
|
|
|
63
43
|
replacements.push({
|
|
64
|
-
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
|
+
)`,
|
|
65
48
|
node: call.node
|
|
66
49
|
});
|
|
67
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
|