@esportsplus/template 0.31.7 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/attributes.js +1 -2
- package/build/constants.d.ts +18 -3
- package/build/constants.js +31 -4
- package/build/html.d.ts +3 -3
- package/build/slot/array.d.ts +2 -2
- package/build/slot/array.js +5 -4
- package/build/slot/render.js +3 -2
- package/build/transformer/codegen.d.ts +2 -9
- package/build/transformer/codegen.js +87 -101
- package/build/transformer/index.d.ts +2 -3
- package/build/transformer/index.js +34 -32
- package/build/transformer/parser.d.ts +3 -2
- package/build/transformer/parser.js +4 -4
- package/build/transformer/plugins/tsc.js +8 -2
- package/build/transformer/plugins/vite.js +9 -14
- package/build/transformer/ts-parser.d.ts +1 -2
- package/build/transformer/ts-parser.js +25 -34
- package/build/transformer/type-analyzer.d.ts +4 -5
- package/build/transformer/type-analyzer.js +61 -71
- package/build/types.d.ts +1 -1
- package/package.json +7 -7
- package/src/attributes.ts +1 -4
- package/src/constants.ts +42 -6
- package/src/html.ts +3 -3
- package/src/slot/array.ts +9 -6
- package/src/slot/render.ts +5 -2
- package/src/transformer/codegen.ts +113 -124
- package/src/transformer/index.ts +44 -49
- package/src/transformer/parser.ts +10 -7
- package/src/transformer/plugins/tsc.ts +10 -2
- package/src/transformer/plugins/vite.ts +12 -25
- package/src/transformer/ts-parser.ts +31 -44
- package/src/transformer/type-analyzer.ts +75 -93
- package/src/types.ts +1 -1
- package/test/vite.config.ts +1 -1
- package/build/event/constants.d.ts +0 -3
- package/build/event/constants.js +0 -13
- package/src/event/constants.ts +0 -16
- package/storage/rewrite-analysis-2026-01-04.md +0 -439
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { code as c, uid } from '@esportsplus/typescript/transformer';
|
|
2
2
|
import type { Replacement } from '@esportsplus/typescript/transformer';
|
|
3
3
|
import type { ReactiveCallInfo, TemplateInfo } from './ts-parser';
|
|
4
4
|
import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer';
|
|
5
5
|
import { ts } from '@esportsplus/typescript';
|
|
6
|
+
import {
|
|
7
|
+
COMPILER_ENTRYPOINT,
|
|
8
|
+
COMPILER_NAMESPACE,
|
|
9
|
+
COMPILER_TYPES,
|
|
10
|
+
PACKAGE
|
|
11
|
+
} from '../constants';
|
|
6
12
|
import parser from './parser';
|
|
7
13
|
|
|
8
14
|
|
|
@@ -12,7 +18,15 @@ type AttributeSlot = {
|
|
|
12
18
|
statics: Record<string, string>;
|
|
13
19
|
};
|
|
14
20
|
path: string[];
|
|
15
|
-
type:
|
|
21
|
+
type: COMPILER_TYPES.AttributeSlot;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type CodegenContext = {
|
|
25
|
+
checker?: ts.TypeChecker;
|
|
26
|
+
hoistedFactories: Map<string, string>;
|
|
27
|
+
htmlToTemplateId: Map<string, string>;
|
|
28
|
+
printer: ts.Printer;
|
|
29
|
+
sourceFile: ts.SourceFile;
|
|
16
30
|
};
|
|
17
31
|
|
|
18
32
|
type CodegenResult = {
|
|
@@ -22,7 +36,7 @@ type CodegenResult = {
|
|
|
22
36
|
|
|
23
37
|
type NodeSlot = {
|
|
24
38
|
path: string[];
|
|
25
|
-
type:
|
|
39
|
+
type: COMPILER_TYPES.NodeSlot;
|
|
26
40
|
};
|
|
27
41
|
|
|
28
42
|
type ParseResult = {
|
|
@@ -33,38 +47,28 @@ type ParseResult = {
|
|
|
33
47
|
|
|
34
48
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
35
49
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let currentChecker: ts.TypeChecker | undefined,
|
|
40
|
-
hoistedFactories = new Map<string, string>(),
|
|
41
|
-
htmlToTemplateId = new Map<string, string>(),
|
|
42
|
-
ns = '';
|
|
50
|
+
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
43
51
|
|
|
44
52
|
|
|
45
53
|
function collectNestedTemplateReplacements(
|
|
54
|
+
ctx: CodegenContext,
|
|
46
55
|
node: ts.Node,
|
|
47
56
|
exprStart: number,
|
|
48
|
-
sourceFile: ts.SourceFile,
|
|
49
57
|
replacements: Replacement[]
|
|
50
58
|
): void {
|
|
51
59
|
if (isNestedHtmlTemplate(node as ts.Expression)) {
|
|
52
60
|
replacements.push({
|
|
53
61
|
end: node.end - exprStart,
|
|
54
|
-
newText: generateNestedTemplateCode(node as ts.TaggedTemplateExpression
|
|
62
|
+
newText: generateNestedTemplateCode(ctx, node as ts.TaggedTemplateExpression),
|
|
55
63
|
start: node.getStart() - exprStart
|
|
56
64
|
});
|
|
57
65
|
}
|
|
58
66
|
else {
|
|
59
|
-
ts.forEachChild(node, child => collectNestedTemplateReplacements(child, exprStart,
|
|
67
|
+
ts.forEachChild(node, child => collectNestedTemplateReplacements(ctx, child, exprStart, replacements));
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
function
|
|
64
|
-
return `import * as ${ns} from '@esportsplus/template';`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function generateNestedTemplateCode(node: ts.TaggedTemplateExpression, sourceFile: ts.SourceFile): string {
|
|
71
|
+
function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplateExpression): string {
|
|
68
72
|
let expressions: ts.Expression[] = [],
|
|
69
73
|
exprTexts: string[] = [],
|
|
70
74
|
literals: string[] = [],
|
|
@@ -81,57 +85,57 @@ function generateNestedTemplateCode(node: ts.TaggedTemplateExpression, sourceFil
|
|
|
81
85
|
|
|
82
86
|
expressions.push(expr);
|
|
83
87
|
literals.push(template.templateSpans[i].literal.text);
|
|
84
|
-
exprTexts.push(rewriteExpression(
|
|
88
|
+
exprTexts.push(rewriteExpression(ctx, expr));
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
return generateTemplateCode(
|
|
93
|
+
ctx,
|
|
89
94
|
parser.parse(literals) as ParseResult,
|
|
90
95
|
exprTexts,
|
|
91
96
|
expressions,
|
|
92
|
-
sourceFile,
|
|
93
97
|
false
|
|
94
98
|
);
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
function generateNodeBinding(anchor: string, exprText: string, exprNode: ts.Expression | undefined
|
|
101
|
+
function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
|
|
98
102
|
if (!exprNode) {
|
|
99
|
-
return `${
|
|
103
|
+
return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
103
|
-
return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(
|
|
107
|
+
return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
let slotType = analyzeExpression(exprNode,
|
|
110
|
+
let slotType = analyzeExpression(exprNode, ctx.checker);
|
|
107
111
|
|
|
108
112
|
switch (slotType) {
|
|
109
|
-
case
|
|
110
|
-
return `new ${
|
|
113
|
+
case COMPILER_TYPES.Effect:
|
|
114
|
+
return `new ${COMPILER_NAMESPACE}.EffectSlot(${anchor}, ${exprText});`;
|
|
111
115
|
|
|
112
|
-
case
|
|
113
|
-
return `new ${
|
|
116
|
+
case COMPILER_TYPES.ArraySlot:
|
|
117
|
+
return `new ${COMPILER_NAMESPACE}.ArraySlot(${anchor}, ${exprText});`;
|
|
114
118
|
|
|
115
|
-
case
|
|
119
|
+
case COMPILER_TYPES.Static:
|
|
116
120
|
return `${anchor}.textContent = ${exprText};`;
|
|
117
121
|
|
|
118
|
-
case
|
|
122
|
+
case COMPILER_TYPES.DocumentFragment:
|
|
119
123
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
120
124
|
|
|
121
125
|
default:
|
|
122
|
-
return `${
|
|
126
|
+
return `${COMPILER_NAMESPACE}.slot(${anchor}, ${exprText});`;
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
function generateTemplateCode(
|
|
131
|
+
ctx: CodegenContext,
|
|
127
132
|
{ html, slots }: ParseResult,
|
|
128
133
|
exprTexts: string[],
|
|
129
134
|
exprNodes: ts.Expression[],
|
|
130
|
-
sourceFile: ts.SourceFile,
|
|
131
135
|
isArrowBody: boolean
|
|
132
136
|
): string {
|
|
133
137
|
if (!slots || slots.length === 0) {
|
|
134
|
-
return `${getOrCreateTemplateId(html)}()`;
|
|
138
|
+
return `${getOrCreateTemplateId(ctx, html)}()`;
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
let code: string[] = [],
|
|
@@ -140,7 +144,7 @@ function generateTemplateCode(
|
|
|
140
144
|
nodes = new Map<string, string>(),
|
|
141
145
|
root = uid('root');
|
|
142
146
|
|
|
143
|
-
declarations.push(`${root} = ${getOrCreateTemplateId(html)}()`);
|
|
147
|
+
declarations.push(`${root} = ${getOrCreateTemplateId(ctx, html)}()`);
|
|
144
148
|
nodes.set('', root);
|
|
145
149
|
|
|
146
150
|
for (let i = 0, n = slots.length; i < n; i++) {
|
|
@@ -187,7 +191,7 @@ function generateTemplateCode(
|
|
|
187
191
|
: (nodes.get(slots[i].path.join('.')) || root),
|
|
188
192
|
slot = slots[i];
|
|
189
193
|
|
|
190
|
-
if (slot.type ===
|
|
194
|
+
if (slot.type === COMPILER_TYPES.AttributeSlot) {
|
|
191
195
|
for (let j = 0, m = slot.attributes.names.length; j < m; j++) {
|
|
192
196
|
let name = slot.attributes.names[j];
|
|
193
197
|
|
|
@@ -196,8 +200,9 @@ function generateTemplateCode(
|
|
|
196
200
|
exprNodes[index],
|
|
197
201
|
exprTexts[index] || 'undefined',
|
|
198
202
|
elementVar,
|
|
199
|
-
sourceFile,
|
|
200
|
-
|
|
203
|
+
ctx.sourceFile,
|
|
204
|
+
ctx.checker,
|
|
205
|
+
COMPILER_NAMESPACE
|
|
201
206
|
);
|
|
202
207
|
|
|
203
208
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
@@ -211,7 +216,8 @@ function generateTemplateCode(
|
|
|
211
216
|
elementVar,
|
|
212
217
|
name,
|
|
213
218
|
exprTexts[index++] || 'undefined',
|
|
214
|
-
slot.attributes.statics[name] || ''
|
|
219
|
+
slot.attributes.statics[name] || '',
|
|
220
|
+
COMPILER_NAMESPACE
|
|
215
221
|
);
|
|
216
222
|
|
|
217
223
|
code.push(binding);
|
|
@@ -219,7 +225,7 @@ function generateTemplateCode(
|
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
227
|
else {
|
|
222
|
-
code.push(generateNodeBinding(elementVar, exprTexts[index] || 'undefined', exprNodes[index]
|
|
228
|
+
code.push(generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index]));
|
|
223
229
|
index++;
|
|
224
230
|
}
|
|
225
231
|
}
|
|
@@ -230,13 +236,13 @@ function generateTemplateCode(
|
|
|
230
236
|
return code.join('\n');
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
function getOrCreateTemplateId(html: string): string {
|
|
234
|
-
let id = htmlToTemplateId.get(html);
|
|
239
|
+
function getOrCreateTemplateId(ctx: CodegenContext, html: string): string {
|
|
240
|
+
let id = ctx.htmlToTemplateId.get(html);
|
|
235
241
|
|
|
236
242
|
if (!id) {
|
|
237
243
|
id = uid('tmpl');
|
|
238
|
-
hoistedFactories.set(id, html);
|
|
239
|
-
htmlToTemplateId.set(html, id);
|
|
244
|
+
ctx.hoistedFactories.set(id, html);
|
|
245
|
+
ctx.htmlToTemplateId.set(html, id);
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
return id;
|
|
@@ -266,109 +272,109 @@ function hasArraySlotImport(sourceFile: ts.SourceFile): boolean {
|
|
|
266
272
|
return false;
|
|
267
273
|
}
|
|
268
274
|
|
|
269
|
-
function
|
|
270
|
-
if (
|
|
271
|
-
ts.isNewExpression(node) &&
|
|
272
|
-
ts.isIdentifier(node.expression) &&
|
|
273
|
-
node.expression.text === 'ArraySlot'
|
|
274
|
-
) {
|
|
275
|
+
function hasMatch(node: ts.Node, predicate: (n: ts.Node) => boolean): boolean {
|
|
276
|
+
if (predicate(node)) {
|
|
275
277
|
return true;
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
let found = false;
|
|
279
281
|
|
|
280
282
|
ts.forEachChild(node, child => {
|
|
281
|
-
if (!found
|
|
282
|
-
found =
|
|
283
|
+
if (!found) {
|
|
284
|
+
found = hasMatch(child, predicate);
|
|
283
285
|
}
|
|
284
286
|
});
|
|
285
287
|
|
|
286
288
|
return found;
|
|
287
289
|
}
|
|
288
290
|
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
ts.forEachChild(node, child => {
|
|
297
|
-
if (!found && hasNestedTemplates(child)) {
|
|
298
|
-
found = true;
|
|
299
|
-
}
|
|
300
|
-
});
|
|
291
|
+
function hasArraySlotUsage(node: ts.Node): boolean {
|
|
292
|
+
return hasMatch(node, n =>
|
|
293
|
+
ts.isNewExpression(n) &&
|
|
294
|
+
ts.isPropertyAccessExpression(n.expression) &&
|
|
295
|
+
n.expression.name.text === 'ArraySlot'
|
|
296
|
+
);
|
|
297
|
+
}
|
|
301
298
|
|
|
302
|
-
|
|
299
|
+
function hasNestedTemplates(node: ts.Node): boolean {
|
|
300
|
+
return hasMatch(node, n => isNestedHtmlTemplate(n as ts.Expression));
|
|
303
301
|
}
|
|
304
302
|
|
|
305
303
|
function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
|
|
306
|
-
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text ===
|
|
304
|
+
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
307
305
|
}
|
|
308
306
|
|
|
309
|
-
function isNestedTemplate(template: TemplateInfo,
|
|
310
|
-
for (let i = 0, n =
|
|
311
|
-
let
|
|
307
|
+
function isNestedTemplate(template: TemplateInfo, exprRanges: { end: number; start: number }[]): boolean {
|
|
308
|
+
for (let i = 0, n = exprRanges.length; i < n; i++) {
|
|
309
|
+
let range = exprRanges[i];
|
|
312
310
|
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
for (let j = 0, m = other.expressions.length; j < m; j++) {
|
|
318
|
-
let expr = other.expressions[j];
|
|
319
|
-
|
|
320
|
-
if (template.start >= expr.getStart() && template.end <= expr.end) {
|
|
321
|
-
return true;
|
|
322
|
-
}
|
|
311
|
+
if (template.start >= range.start && template.end <= range.end) {
|
|
312
|
+
return true;
|
|
323
313
|
}
|
|
324
314
|
}
|
|
325
315
|
|
|
326
316
|
return false;
|
|
327
317
|
}
|
|
328
318
|
|
|
329
|
-
function rewriteExpression(
|
|
319
|
+
function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
|
|
330
320
|
if (isNestedHtmlTemplate(expr)) {
|
|
331
|
-
return generateNestedTemplateCode(
|
|
321
|
+
return generateNestedTemplateCode(ctx, expr);
|
|
332
322
|
}
|
|
333
323
|
|
|
334
324
|
if (!hasNestedTemplates(expr)) {
|
|
335
|
-
return
|
|
325
|
+
return ctx.printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
|
|
336
326
|
}
|
|
337
327
|
|
|
338
328
|
let exprStart = expr.getStart(),
|
|
339
329
|
replacements: Replacement[] = [];
|
|
340
330
|
|
|
341
|
-
collectNestedTemplateReplacements(expr, exprStart,
|
|
331
|
+
collectNestedTemplateReplacements(ctx, expr, exprStart, replacements);
|
|
342
332
|
|
|
343
|
-
return
|
|
333
|
+
return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
|
|
344
334
|
}
|
|
345
335
|
|
|
346
336
|
|
|
347
|
-
|
|
348
337
|
const addArraySlotImport = (code: string): string => {
|
|
349
|
-
|
|
350
|
-
return `import * as ${ns} from '@esportsplus/template';\n\n` + code.replace(TEMPLATE_IMPORT, '');
|
|
338
|
+
return `import * as ${COMPILER_NAMESPACE} from '${PACKAGE}';\n` + code;
|
|
351
339
|
};
|
|
352
340
|
|
|
353
|
-
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile): CodegenResult => {
|
|
341
|
+
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
|
|
354
342
|
if (templates.length === 0) {
|
|
355
343
|
return { changed: false, code: originalCode };
|
|
356
344
|
}
|
|
357
345
|
|
|
358
|
-
|
|
346
|
+
// Precompute expression ranges for nested template detection
|
|
347
|
+
let exprRanges: { end: number; start: number }[] = [];
|
|
348
|
+
|
|
349
|
+
for (let i = 0, n = templates.length; i < n; i++) {
|
|
350
|
+
let exprs = templates[i].expressions;
|
|
351
|
+
|
|
352
|
+
for (let j = 0, m = exprs.length; j < m; j++) {
|
|
353
|
+
exprRanges.push({ end: exprs[j].end, start: exprs[j].getStart() });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let rootTemplates = templates.filter(t => !isNestedTemplate(t, exprRanges));
|
|
359
358
|
|
|
360
359
|
if (rootTemplates.length === 0) {
|
|
361
360
|
return { changed: false, code: originalCode };
|
|
362
361
|
}
|
|
363
362
|
|
|
364
|
-
let
|
|
363
|
+
let ctx: CodegenContext = {
|
|
364
|
+
checker,
|
|
365
|
+
hoistedFactories: new Map(),
|
|
366
|
+
htmlToTemplateId: new Map(),
|
|
367
|
+
printer,
|
|
368
|
+
sourceFile
|
|
369
|
+
},
|
|
370
|
+
replacements: Replacement[] = [];
|
|
365
371
|
|
|
366
372
|
for (let i = 0, n = rootTemplates.length; i < n; i++) {
|
|
367
373
|
let exprTexts: string[] = [],
|
|
368
374
|
template = rootTemplates[i];
|
|
369
375
|
|
|
370
376
|
for (let j = 0, m = template.expressions.length; j < m; j++) {
|
|
371
|
-
exprTexts.push(rewriteExpression(template.expressions[j]
|
|
377
|
+
exprTexts.push(rewriteExpression(ctx, template.expressions[j]));
|
|
372
378
|
}
|
|
373
379
|
|
|
374
380
|
let codeBefore = originalCode.slice(0, template.start),
|
|
@@ -382,7 +388,7 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
382
388
|
if (arrowMatch) {
|
|
383
389
|
replacements.push({
|
|
384
390
|
end: template.end,
|
|
385
|
-
newText: getOrCreateTemplateId(parsed.html),
|
|
391
|
+
newText: getOrCreateTemplateId(ctx, parsed.html),
|
|
386
392
|
start: template.start - arrowMatch[0].length
|
|
387
393
|
});
|
|
388
394
|
continue;
|
|
@@ -392,10 +398,10 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
392
398
|
replacements.push({
|
|
393
399
|
end: template.end,
|
|
394
400
|
newText: generateTemplateCode(
|
|
401
|
+
ctx,
|
|
395
402
|
parsed,
|
|
396
403
|
exprTexts,
|
|
397
404
|
template.expressions,
|
|
398
|
-
sourceFile,
|
|
399
405
|
isArrowBody
|
|
400
406
|
),
|
|
401
407
|
start: template.start
|
|
@@ -403,18 +409,16 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
let changed = replacements.length > 0,
|
|
406
|
-
code =
|
|
412
|
+
code = c.replaceReverse(originalCode, replacements);
|
|
407
413
|
|
|
408
|
-
if (changed && hoistedFactories.size > 0) {
|
|
414
|
+
if (changed && ctx.hoistedFactories.size > 0) {
|
|
409
415
|
let factories: string[] = [];
|
|
410
416
|
|
|
411
|
-
for (let [id, html] of hoistedFactories) {
|
|
412
|
-
factories.push(`const ${id} = ${
|
|
417
|
+
for (let [id, html] of ctx.hoistedFactories) {
|
|
418
|
+
factories.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
|
|
413
419
|
}
|
|
414
420
|
|
|
415
|
-
|
|
416
|
-
code = code.replace(TEMPLATE_IMPORT, '');
|
|
417
|
-
code = generateImports() + '\n\n' + factories.join('\n') + '\n\n' + code;
|
|
421
|
+
code = `import * as ${COMPILER_NAMESPACE} from '${PACKAGE}';\n\n` + factories.join('\n') + '\n\n' + code;
|
|
418
422
|
}
|
|
419
423
|
|
|
420
424
|
return { changed, code };
|
|
@@ -425,43 +429,28 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
425
429
|
return code;
|
|
426
430
|
}
|
|
427
431
|
|
|
428
|
-
let
|
|
429
|
-
result = code;
|
|
432
|
+
let replacements: Replacement[] = [];
|
|
430
433
|
|
|
431
|
-
for (let i = calls.length
|
|
434
|
+
for (let i = 0, n = calls.length; i < n; i++) {
|
|
432
435
|
let call = calls[i];
|
|
433
436
|
|
|
434
|
-
|
|
435
|
-
|
|
437
|
+
replacements.push({
|
|
438
|
+
end: call.end,
|
|
439
|
+
newText: `new ${COMPILER_NAMESPACE}.ArraySlot(
|
|
436
440
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
437
441
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
438
|
-
)
|
|
439
|
-
|
|
442
|
+
)`,
|
|
443
|
+
start: call.start
|
|
444
|
+
});
|
|
440
445
|
}
|
|
441
446
|
|
|
442
|
-
return
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
const getNames = () => ({
|
|
446
|
-
attr: `${ns}.attributes`,
|
|
447
|
-
event: `${ns}.event`,
|
|
448
|
-
slot: `${ns}.slot`
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
const initNamespace = (): void => {
|
|
452
|
-
hoistedFactories.clear();
|
|
453
|
-
htmlToTemplateId.clear();
|
|
454
|
-
ns = uid('t');
|
|
447
|
+
return c.replaceReverse(code, replacements);
|
|
455
448
|
};
|
|
456
449
|
|
|
457
450
|
const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
|
|
458
451
|
return hasArraySlotUsage(sourceFile) && !hasArraySlotImport(sourceFile);
|
|
459
452
|
};
|
|
460
453
|
|
|
461
|
-
const setTypeChecker = (checker: ts.TypeChecker | undefined): void => {
|
|
462
|
-
currentChecker = checker;
|
|
463
|
-
};
|
|
464
|
-
|
|
465
454
|
|
|
466
|
-
export { addArraySlotImport, generateCode, generateReactiveInlining,
|
|
455
|
+
export { addArraySlotImport, generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
467
456
|
export type { CodegenResult };
|
package/src/transformer/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { addArraySlotImport, generateCode, generateReactiveInlining,
|
|
1
|
+
import { code as c } from '@esportsplus/typescript/transformer';
|
|
2
|
+
import { addArraySlotImport, generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen';
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants';
|
|
3
4
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
4
5
|
import { ts } from '@esportsplus/typescript';
|
|
5
6
|
|
|
@@ -11,28 +12,21 @@ type TransformResult = {
|
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
const PATTERNS = [
|
|
15
|
+
const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
function createTransformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
|
|
18
|
-
let
|
|
19
|
-
typeChecker = program.getTypeChecker();
|
|
19
|
+
let typeChecker = program.getTypeChecker();
|
|
20
20
|
|
|
21
21
|
return (_context: ts.TransformationContext) => {
|
|
22
22
|
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
23
|
-
|
|
24
|
-
let code = printer.printFile(sourceFile);
|
|
23
|
+
let code = sourceFile.getFullText();
|
|
25
24
|
|
|
26
|
-
if (!
|
|
25
|
+
if (!c.contains(code, { patterns: PATTERNS })) {
|
|
27
26
|
return sourceFile;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
let reparsed = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
32
|
-
|
|
33
|
-
setTypeChecker(typeChecker);
|
|
34
|
-
|
|
35
|
-
let result = transformCode(code, reparsed);
|
|
29
|
+
let result = transformCode(code, sourceFile, typeChecker);
|
|
36
30
|
|
|
37
31
|
if (!result.changed) {
|
|
38
32
|
return sourceFile;
|
|
@@ -43,64 +37,65 @@ function createTransformer(program: ts.Program): ts.TransformerFactory<ts.Source
|
|
|
43
37
|
};
|
|
44
38
|
}
|
|
45
39
|
|
|
46
|
-
function transformCode(code: string, sourceFile: ts.SourceFile): TransformResult {
|
|
47
|
-
let changed = false,
|
|
48
|
-
result = code;
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformResult => {
|
|
42
|
+
let code = sourceFile.getFullText();
|
|
43
|
+
|
|
44
|
+
if (!c.contains(code, { patterns: PATTERNS })) {
|
|
45
|
+
return { changed: false, code, sourceFile };
|
|
46
|
+
}
|
|
51
47
|
|
|
52
|
-
let
|
|
48
|
+
let checker: ts.TypeChecker | undefined,
|
|
49
|
+
fileName = sourceFile.fileName,
|
|
50
|
+
programSourceFile = program.getSourceFile(fileName)
|
|
51
|
+
|| program.getSourceFile(fileName.replace(/\\/g, '/'))
|
|
52
|
+
|| program.getSourceFile(fileName.replace(/\//g, '\\'));
|
|
53
|
+
|
|
54
|
+
if (programSourceFile) {
|
|
55
|
+
checker = program.getTypeChecker();
|
|
56
|
+
sourceFile = programSourceFile;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return transformCode(code, sourceFile, checker);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const transformCode = (code: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): TransformResult => {
|
|
63
|
+
let changed = false,
|
|
64
|
+
codegenChanged = false,
|
|
65
|
+
needsImport = false,
|
|
66
|
+
result = code,
|
|
67
|
+
reactiveCalls = findReactiveCalls(sourceFile);
|
|
53
68
|
|
|
54
69
|
if (reactiveCalls.length > 0) {
|
|
55
70
|
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
56
|
-
// Always add namespace import when reactive calls are inlined (strips original named imports)
|
|
57
|
-
result = addArraySlotImport(result);
|
|
58
71
|
changed = true;
|
|
59
72
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
needsImport = needsArraySlotImport(sourceFile);
|
|
74
|
+
checker = undefined;
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
let templates = findHtmlTemplates(sourceFile);
|
|
65
78
|
|
|
66
79
|
if (templates.length > 0) {
|
|
67
|
-
let codegenResult = generateCode(templates, result, sourceFile);
|
|
80
|
+
let codegenResult = generateCode(templates, result, sourceFile, checker);
|
|
68
81
|
|
|
69
82
|
if (codegenResult.changed) {
|
|
70
83
|
changed = true;
|
|
84
|
+
codegenChanged = true;
|
|
71
85
|
result = codegenResult.code;
|
|
72
|
-
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
73
86
|
}
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformResult => {
|
|
81
|
-
// Use printer to get current text representation (handles chained transformers)
|
|
82
|
-
let printer = ts.createPrinter(),
|
|
83
|
-
code = printer.printFile(sourceFile);
|
|
84
|
-
|
|
85
|
-
if (!mightNeedTransform(code, { patterns: PATTERNS })) {
|
|
86
|
-
return { changed: false, code, sourceFile };
|
|
89
|
+
if (needsImport && !codegenChanged) {
|
|
90
|
+
result = addArraySlotImport(result);
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let programSourceFile = program.getSourceFile(sourceFile.fileName);
|
|
93
|
-
|
|
94
|
-
if (programSourceFile) {
|
|
95
|
-
setTypeChecker(program.getTypeChecker());
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
setTypeChecker(undefined);
|
|
93
|
+
if (changed) {
|
|
94
|
+
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
return
|
|
97
|
+
return { changed, code: result, sourceFile };
|
|
102
98
|
};
|
|
103
99
|
|
|
104
100
|
|
|
105
|
-
export
|
|
106
|
-
export { createTransformer, mightNeedTransform, PATTERNS, transform };
|
|
101
|
+
export { createTransformer, transform, transformCode, PATTERNS };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
COMPILER_TYPES,
|
|
3
|
+
PACKAGE,
|
|
4
|
+
SLOT_HTML
|
|
5
|
+
} from '../constants';
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
type NodePath = ('firstChild' | 'firstElementChild' | 'nextElementSibling' | 'nextSibling')[];
|
|
@@ -92,8 +95,8 @@ const parse = (literals: string[]) => {
|
|
|
92
95
|
parsed = html.split(SLOT_MARKER),
|
|
93
96
|
slot = 0,
|
|
94
97
|
slots: (
|
|
95
|
-
{ path: NodePath; type:
|
|
96
|
-
{ attributes: typeof attributes[string]; path: NodePath; type:
|
|
98
|
+
{ path: NodePath; type: COMPILER_TYPES.NodeSlot } |
|
|
99
|
+
{ attributes: typeof attributes[string]; path: NodePath; type: COMPILER_TYPES.AttributeSlot }
|
|
97
100
|
)[] = [];
|
|
98
101
|
|
|
99
102
|
{
|
|
@@ -186,10 +189,10 @@ const parse = (literals: string[]) => {
|
|
|
186
189
|
let attrs = attributes[attr];
|
|
187
190
|
|
|
188
191
|
if (!attrs) {
|
|
189
|
-
throw new Error(
|
|
192
|
+
throw new Error(`${PACKAGE}: attribute metadata could not be found for '${attr}'`);
|
|
190
193
|
}
|
|
191
194
|
|
|
192
|
-
slots.push({ attributes: attrs, path, type:
|
|
195
|
+
slots.push({ attributes: attrs, path, type: COMPILER_TYPES.AttributeSlot });
|
|
193
196
|
|
|
194
197
|
for (let i = 0, n = attrs.names.length; i < n; i++) {
|
|
195
198
|
buffer += parsed[slot++];
|
|
@@ -204,7 +207,7 @@ const parse = (literals: string[]) => {
|
|
|
204
207
|
}
|
|
205
208
|
else if (type === NODE_SLOT) {
|
|
206
209
|
buffer += parsed[slot++] + SLOT_HTML;
|
|
207
|
-
slots.push({ path: methods(parent.children, parent.path, 'firstChild', 'nextSibling'), type:
|
|
210
|
+
slots.push({ path: methods(parent.children, parent.path, 'firstChild', 'nextSibling'), type: COMPILER_TYPES.NodeSlot });
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
if (n === slot) {
|