@esportsplus/template 0.31.6 → 0.32.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/transformer/codegen.d.ts +1 -2
- package/build/transformer/codegen.js +58 -21
- package/build/transformer/index.js +7 -9
- package/build/transformer/plugins/vite.js +4 -7
- package/package.json +3 -3
- package/src/transformer/codegen.ts +76 -25
- package/src/transformer/index.ts +7 -18
- package/src/transformer/plugins/vite.ts +6 -15
|
@@ -12,8 +12,7 @@ declare const getNames: () => {
|
|
|
12
12
|
event: string;
|
|
13
13
|
slot: string;
|
|
14
14
|
};
|
|
15
|
-
declare const initNamespace: () => void;
|
|
16
15
|
declare const needsArraySlotImport: (sourceFile: ts.SourceFile) => boolean;
|
|
17
16
|
declare const setTypeChecker: (checker: ts.TypeChecker | undefined) => void;
|
|
18
|
-
export { addArraySlotImport, generateCode, generateReactiveInlining, getNames,
|
|
17
|
+
export { addArraySlotImport, generateCode, generateReactiveInlining, getNames, needsArraySlotImport, setTypeChecker };
|
|
19
18
|
export type { CodegenResult };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { applyReplacementsReverse, uid } from '@esportsplus/typescript/transformer';
|
|
1
|
+
import { addImport, applyReplacementsReverse, uid } from '@esportsplus/typescript/transformer';
|
|
2
2
|
import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer.js';
|
|
3
3
|
import { ts } from '@esportsplus/typescript';
|
|
4
4
|
import parser from './parser.js';
|
|
5
5
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
6
|
-
|
|
7
|
-
let currentChecker, hoistedFactories = new Map(), htmlToTemplateId = new Map(), ns = '';
|
|
6
|
+
let currentChecker, hoistedFactories = new Map(), htmlToTemplateId = new Map(), nameArraySlot = '', nameAttr = '', nameEffectSlot = '', nameEvent = '', nameSlot = '', nameTemplate = '', needsArraySlot = false, needsAttr = false, needsEffectSlot = false, needsEvent = false, needsSlot = false;
|
|
8
7
|
function collectNestedTemplateReplacements(node, exprStart, sourceFile, replacements) {
|
|
9
8
|
if (isNestedHtmlTemplate(node)) {
|
|
10
9
|
replacements.push({
|
|
@@ -18,7 +17,24 @@ function collectNestedTemplateReplacements(node, exprStart, sourceFile, replacem
|
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
function generateImports() {
|
|
21
|
-
|
|
20
|
+
let specifiers = [];
|
|
21
|
+
if (needsArraySlot) {
|
|
22
|
+
specifiers.push(`ArraySlot as ${nameArraySlot}`);
|
|
23
|
+
}
|
|
24
|
+
if (needsEffectSlot) {
|
|
25
|
+
specifiers.push(`EffectSlot as ${nameEffectSlot}`);
|
|
26
|
+
}
|
|
27
|
+
if (needsAttr) {
|
|
28
|
+
specifiers.push(`attributes as ${nameAttr}`);
|
|
29
|
+
}
|
|
30
|
+
if (needsEvent) {
|
|
31
|
+
specifiers.push(`event as ${nameEvent}`);
|
|
32
|
+
}
|
|
33
|
+
if (needsSlot) {
|
|
34
|
+
specifiers.push(`slot as ${nameSlot}`);
|
|
35
|
+
}
|
|
36
|
+
specifiers.push(`template as ${nameTemplate}`);
|
|
37
|
+
return `import { ${specifiers.join(', ')} } from '@esportsplus/template';`;
|
|
22
38
|
}
|
|
23
39
|
function generateNestedTemplateCode(node, sourceFile) {
|
|
24
40
|
let expressions = [], exprTexts = [], literals = [], template = node.template;
|
|
@@ -38,7 +54,8 @@ function generateNestedTemplateCode(node, sourceFile) {
|
|
|
38
54
|
}
|
|
39
55
|
function generateNodeBinding(anchor, exprText, exprNode, sourceFile) {
|
|
40
56
|
if (!exprNode) {
|
|
41
|
-
|
|
57
|
+
needsSlot = true;
|
|
58
|
+
return `${nameSlot}(${anchor}, ${exprText});`;
|
|
42
59
|
}
|
|
43
60
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
44
61
|
return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(exprNode, sourceFile)}, ${anchor});`;
|
|
@@ -46,15 +63,18 @@ function generateNodeBinding(anchor, exprText, exprNode, sourceFile) {
|
|
|
46
63
|
let slotType = analyzeExpression(exprNode, currentChecker);
|
|
47
64
|
switch (slotType) {
|
|
48
65
|
case 'effect':
|
|
49
|
-
|
|
66
|
+
needsEffectSlot = true;
|
|
67
|
+
return `new ${nameEffectSlot}(${anchor}, ${exprText});`;
|
|
50
68
|
case 'array-slot':
|
|
51
|
-
|
|
69
|
+
needsArraySlot = true;
|
|
70
|
+
return `new ${nameArraySlot}(${anchor}, ${exprText});`;
|
|
52
71
|
case 'static':
|
|
53
72
|
return `${anchor}.textContent = ${exprText};`;
|
|
54
73
|
case 'document-fragment':
|
|
55
74
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
56
75
|
default:
|
|
57
|
-
|
|
76
|
+
needsSlot = true;
|
|
77
|
+
return `${nameSlot}(${anchor}, ${exprText});`;
|
|
58
78
|
}
|
|
59
79
|
}
|
|
60
80
|
function generateTemplateCode({ html, slots }, exprTexts, exprNodes, sourceFile, isArrowBody) {
|
|
@@ -97,12 +117,14 @@ function generateTemplateCode({ html, slots }, exprTexts, exprNodes, sourceFile,
|
|
|
97
117
|
if (name === 'spread') {
|
|
98
118
|
let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, sourceFile, currentChecker);
|
|
99
119
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
120
|
+
trackBindingUsage(bindings[k]);
|
|
100
121
|
code.push(bindings[k]);
|
|
101
122
|
}
|
|
102
123
|
index++;
|
|
103
124
|
}
|
|
104
125
|
else {
|
|
105
126
|
let binding = generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '');
|
|
127
|
+
trackBindingUsage(binding);
|
|
106
128
|
code.push(binding);
|
|
107
129
|
}
|
|
108
130
|
}
|
|
@@ -198,13 +220,34 @@ function rewriteExpression(expr, sourceFile) {
|
|
|
198
220
|
collectNestedTemplateReplacements(expr, exprStart, sourceFile, replacements);
|
|
199
221
|
return applyReplacementsReverse(expr.getText(sourceFile), replacements);
|
|
200
222
|
}
|
|
223
|
+
function trackBindingUsage(binding) {
|
|
224
|
+
if (binding.startsWith(nameEvent + '.')) {
|
|
225
|
+
needsEvent = true;
|
|
226
|
+
}
|
|
227
|
+
else if (binding.startsWith(nameAttr + '.')) {
|
|
228
|
+
needsAttr = true;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
201
231
|
const addArraySlotImport = (code) => {
|
|
202
|
-
return
|
|
232
|
+
return addImport(code, '@esportsplus/template', ['ArraySlot']);
|
|
203
233
|
};
|
|
204
234
|
const generateCode = (templates, originalCode, sourceFile) => {
|
|
205
235
|
if (templates.length === 0) {
|
|
206
236
|
return { changed: false, code: originalCode };
|
|
207
237
|
}
|
|
238
|
+
hoistedFactories.clear();
|
|
239
|
+
htmlToTemplateId.clear();
|
|
240
|
+
nameArraySlot = uid('ArraySlot');
|
|
241
|
+
nameAttr = uid('attr');
|
|
242
|
+
nameEffectSlot = uid('EffectSlot');
|
|
243
|
+
nameEvent = uid('event');
|
|
244
|
+
nameSlot = uid('slot');
|
|
245
|
+
nameTemplate = uid('template');
|
|
246
|
+
needsArraySlot = false;
|
|
247
|
+
needsAttr = false;
|
|
248
|
+
needsEffectSlot = false;
|
|
249
|
+
needsEvent = false;
|
|
250
|
+
needsSlot = false;
|
|
208
251
|
let rootTemplates = templates.filter(t => !isNestedTemplate(t, templates));
|
|
209
252
|
if (rootTemplates.length === 0) {
|
|
210
253
|
return { changed: false, code: originalCode };
|
|
@@ -237,9 +280,8 @@ const generateCode = (templates, originalCode, sourceFile) => {
|
|
|
237
280
|
if (changed && hoistedFactories.size > 0) {
|
|
238
281
|
let factories = [];
|
|
239
282
|
for (let [id, html] of hoistedFactories) {
|
|
240
|
-
factories.push(`const ${id} = ${
|
|
283
|
+
factories.push(`const ${id} = ${nameTemplate}(\`${html}\`);`);
|
|
241
284
|
}
|
|
242
|
-
code = code.replace(TEMPLATE_IMPORT, '');
|
|
243
285
|
code = generateImports() + '\n\n' + factories.join('\n') + '\n\n' + code;
|
|
244
286
|
}
|
|
245
287
|
return { changed, code };
|
|
@@ -252,7 +294,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
252
294
|
for (let i = calls.length - 1; i >= 0; i--) {
|
|
253
295
|
let call = calls[i];
|
|
254
296
|
result = result.slice(0, call.start);
|
|
255
|
-
result += `new ${
|
|
297
|
+
result += `new ${nameArraySlot}(
|
|
256
298
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
257
299
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
258
300
|
)`;
|
|
@@ -261,19 +303,14 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
261
303
|
return result;
|
|
262
304
|
};
|
|
263
305
|
const getNames = () => ({
|
|
264
|
-
attr:
|
|
265
|
-
event:
|
|
266
|
-
slot:
|
|
306
|
+
attr: nameAttr,
|
|
307
|
+
event: nameEvent,
|
|
308
|
+
slot: nameSlot
|
|
267
309
|
});
|
|
268
|
-
const initNamespace = () => {
|
|
269
|
-
hoistedFactories.clear();
|
|
270
|
-
htmlToTemplateId.clear();
|
|
271
|
-
ns = uid('t');
|
|
272
|
-
};
|
|
273
310
|
const needsArraySlotImport = (sourceFile) => {
|
|
274
311
|
return hasArraySlotUsage(sourceFile) && !hasArraySlotImport(sourceFile);
|
|
275
312
|
};
|
|
276
313
|
const setTypeChecker = (checker) => {
|
|
277
314
|
currentChecker = checker;
|
|
278
315
|
};
|
|
279
|
-
export { addArraySlotImport, generateCode, generateReactiveInlining, getNames,
|
|
316
|
+
export { addArraySlotImport, generateCode, generateReactiveInlining, getNames, needsArraySlotImport, setTypeChecker };
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { mightNeedTransform } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import { addArraySlotImport, generateCode, generateReactiveInlining,
|
|
2
|
+
import { addArraySlotImport, generateCode, generateReactiveInlining, needsArraySlotImport, setTypeChecker } from './codegen.js';
|
|
3
3
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
|
|
4
4
|
import { ts } from '@esportsplus/typescript';
|
|
5
5
|
const PATTERNS = ['html`', 'html.reactive'];
|
|
6
6
|
function createTransformer(program) {
|
|
7
|
-
let
|
|
7
|
+
let typeChecker = program.getTypeChecker();
|
|
8
8
|
return (_context) => {
|
|
9
9
|
return (sourceFile) => {
|
|
10
|
-
let code =
|
|
10
|
+
let code = sourceFile.getFullText();
|
|
11
11
|
if (!mightNeedTransform(code, { patterns: PATTERNS })) {
|
|
12
12
|
return sourceFile;
|
|
13
13
|
}
|
|
14
|
-
let reparsed = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
15
14
|
setTypeChecker(typeChecker);
|
|
16
|
-
let result = transformCode(code,
|
|
15
|
+
let result = transformCode(code, sourceFile);
|
|
17
16
|
if (!result.changed) {
|
|
18
17
|
return sourceFile;
|
|
19
18
|
}
|
|
@@ -23,7 +22,6 @@ function createTransformer(program) {
|
|
|
23
22
|
}
|
|
24
23
|
function transformCode(code, sourceFile) {
|
|
25
24
|
let changed = false, result = code;
|
|
26
|
-
initNamespace();
|
|
27
25
|
let reactiveCalls = findReactiveCalls(sourceFile);
|
|
28
26
|
if (reactiveCalls.length > 0) {
|
|
29
27
|
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
@@ -47,18 +45,18 @@ function transformCode(code, sourceFile) {
|
|
|
47
45
|
return { changed, code: result, sourceFile };
|
|
48
46
|
}
|
|
49
47
|
const transform = (sourceFile, program) => {
|
|
50
|
-
let
|
|
48
|
+
let code = sourceFile.getFullText();
|
|
51
49
|
if (!mightNeedTransform(code, { patterns: PATTERNS })) {
|
|
52
50
|
return { changed: false, code, sourceFile };
|
|
53
51
|
}
|
|
54
|
-
let reparsed = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
55
52
|
let programSourceFile = program.getSourceFile(sourceFile.fileName);
|
|
56
53
|
if (programSourceFile) {
|
|
57
54
|
setTypeChecker(program.getTypeChecker());
|
|
55
|
+
sourceFile = programSourceFile;
|
|
58
56
|
}
|
|
59
57
|
else {
|
|
60
58
|
setTypeChecker(undefined);
|
|
61
59
|
}
|
|
62
|
-
return transformCode(code,
|
|
60
|
+
return transformCode(code, sourceFile);
|
|
63
61
|
};
|
|
64
62
|
export { createTransformer, mightNeedTransform, PATTERNS, transform };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mightNeedTransform, PATTERNS, transform } from '../index.js';
|
|
2
2
|
import { program, TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
|
|
3
3
|
import { ts } from '@esportsplus/typescript';
|
|
4
4
|
export default (options) => {
|
|
@@ -17,14 +17,11 @@ export default (options) => {
|
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
|
-
let
|
|
21
|
-
if (
|
|
22
|
-
result.dispose();
|
|
20
|
+
let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile, program.get(root));
|
|
21
|
+
if (!result.changed) {
|
|
23
22
|
return null;
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
result.dispose();
|
|
27
|
-
return { code: output, map: null };
|
|
24
|
+
return { code: result.code, map: null };
|
|
28
25
|
}
|
|
29
26
|
catch (error) {
|
|
30
27
|
console.error(`@esportsplus/template: Error transforming ${id}:`, error);
|
package/package.json
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@esportsplus/queue": "^0.2.0",
|
|
5
|
-
"@esportsplus/reactivity": "^0.
|
|
5
|
+
"@esportsplus/reactivity": "^0.25.2",
|
|
6
6
|
"@esportsplus/utilities": "^0.27.2",
|
|
7
7
|
"serve": "^14.2.5"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@esportsplus/typescript": "^0.17.
|
|
10
|
+
"@esportsplus/typescript": "^0.17.3",
|
|
11
11
|
"@types/node": "^25.0.3",
|
|
12
12
|
"vite": "^7.3.0",
|
|
13
13
|
"vite-tsconfig-paths": "^6.0.3"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"type": "module",
|
|
42
42
|
"types": "./build/index.d.ts",
|
|
43
|
-
"version": "0.
|
|
43
|
+
"version": "0.32.0",
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsc",
|
|
46
46
|
"build:test": "vite build --config test/vite.config.ts",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyReplacementsReverse, uid } from '@esportsplus/typescript/transformer';
|
|
1
|
+
import { addImport, applyReplacementsReverse, 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';
|
|
@@ -33,13 +33,21 @@ type ParseResult = {
|
|
|
33
33
|
|
|
34
34
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
35
35
|
|
|
36
|
-
const TEMPLATE_IMPORT = /import\s+\{[^}]*\}\s+from\s+['"]@esportsplus\/template['"];?\s*\n?/g;
|
|
37
|
-
|
|
38
36
|
|
|
39
37
|
let currentChecker: ts.TypeChecker | undefined,
|
|
40
38
|
hoistedFactories = new Map<string, string>(),
|
|
41
39
|
htmlToTemplateId = new Map<string, string>(),
|
|
42
|
-
|
|
40
|
+
nameArraySlot = '',
|
|
41
|
+
nameAttr = '',
|
|
42
|
+
nameEffectSlot = '',
|
|
43
|
+
nameEvent = '',
|
|
44
|
+
nameSlot = '',
|
|
45
|
+
nameTemplate = '',
|
|
46
|
+
needsArraySlot = false,
|
|
47
|
+
needsAttr = false,
|
|
48
|
+
needsEffectSlot = false,
|
|
49
|
+
needsEvent = false,
|
|
50
|
+
needsSlot = false;
|
|
43
51
|
|
|
44
52
|
|
|
45
53
|
function collectNestedTemplateReplacements(
|
|
@@ -61,7 +69,31 @@ function collectNestedTemplateReplacements(
|
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
function generateImports(): string {
|
|
64
|
-
|
|
72
|
+
let specifiers: string[] = [];
|
|
73
|
+
|
|
74
|
+
if (needsArraySlot) {
|
|
75
|
+
specifiers.push(`ArraySlot as ${nameArraySlot}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (needsEffectSlot) {
|
|
79
|
+
specifiers.push(`EffectSlot as ${nameEffectSlot}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (needsAttr) {
|
|
83
|
+
specifiers.push(`attributes as ${nameAttr}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (needsEvent) {
|
|
87
|
+
specifiers.push(`event as ${nameEvent}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (needsSlot) {
|
|
91
|
+
specifiers.push(`slot as ${nameSlot}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
specifiers.push(`template as ${nameTemplate}`);
|
|
95
|
+
|
|
96
|
+
return `import { ${specifiers.join(', ')} } from '@esportsplus/template';`;
|
|
65
97
|
}
|
|
66
98
|
|
|
67
99
|
function generateNestedTemplateCode(node: ts.TaggedTemplateExpression, sourceFile: ts.SourceFile): string {
|
|
@@ -96,7 +128,8 @@ function generateNestedTemplateCode(node: ts.TaggedTemplateExpression, sourceFil
|
|
|
96
128
|
|
|
97
129
|
function generateNodeBinding(anchor: string, exprText: string, exprNode: ts.Expression | undefined, sourceFile: ts.SourceFile): string {
|
|
98
130
|
if (!exprNode) {
|
|
99
|
-
|
|
131
|
+
needsSlot = true;
|
|
132
|
+
return `${nameSlot}(${anchor}, ${exprText});`;
|
|
100
133
|
}
|
|
101
134
|
|
|
102
135
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
@@ -107,10 +140,12 @@ function generateNodeBinding(anchor: string, exprText: string, exprNode: ts.Expr
|
|
|
107
140
|
|
|
108
141
|
switch (slotType) {
|
|
109
142
|
case 'effect':
|
|
110
|
-
|
|
143
|
+
needsEffectSlot = true;
|
|
144
|
+
return `new ${nameEffectSlot}(${anchor}, ${exprText});`;
|
|
111
145
|
|
|
112
146
|
case 'array-slot':
|
|
113
|
-
|
|
147
|
+
needsArraySlot = true;
|
|
148
|
+
return `new ${nameArraySlot}(${anchor}, ${exprText});`;
|
|
114
149
|
|
|
115
150
|
case 'static':
|
|
116
151
|
return `${anchor}.textContent = ${exprText};`;
|
|
@@ -119,7 +154,8 @@ function generateNodeBinding(anchor: string, exprText: string, exprNode: ts.Expr
|
|
|
119
154
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
120
155
|
|
|
121
156
|
default:
|
|
122
|
-
|
|
157
|
+
needsSlot = true;
|
|
158
|
+
return `${nameSlot}(${anchor}, ${exprText});`;
|
|
123
159
|
}
|
|
124
160
|
}
|
|
125
161
|
|
|
@@ -201,6 +237,7 @@ function generateTemplateCode(
|
|
|
201
237
|
);
|
|
202
238
|
|
|
203
239
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
240
|
+
trackBindingUsage(bindings[k]);
|
|
204
241
|
code.push(bindings[k]);
|
|
205
242
|
}
|
|
206
243
|
|
|
@@ -214,6 +251,7 @@ function generateTemplateCode(
|
|
|
214
251
|
slot.attributes.statics[name] || ''
|
|
215
252
|
);
|
|
216
253
|
|
|
254
|
+
trackBindingUsage(binding);
|
|
217
255
|
code.push(binding);
|
|
218
256
|
}
|
|
219
257
|
}
|
|
@@ -343,11 +381,18 @@ function rewriteExpression(expr: ts.Expression, sourceFile: ts.SourceFile): stri
|
|
|
343
381
|
return applyReplacementsReverse(expr.getText(sourceFile), replacements);
|
|
344
382
|
}
|
|
345
383
|
|
|
384
|
+
function trackBindingUsage(binding: string): void {
|
|
385
|
+
if (binding.startsWith(nameEvent + '.')) {
|
|
386
|
+
needsEvent = true;
|
|
387
|
+
}
|
|
388
|
+
else if (binding.startsWith(nameAttr + '.')) {
|
|
389
|
+
needsAttr = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
346
392
|
|
|
347
393
|
|
|
348
394
|
const addArraySlotImport = (code: string): string => {
|
|
349
|
-
|
|
350
|
-
return `import * as ${ns} from '@esportsplus/template';\n\n` + code.replace(TEMPLATE_IMPORT, '');
|
|
395
|
+
return addImport(code, '@esportsplus/template', ['ArraySlot']);
|
|
351
396
|
};
|
|
352
397
|
|
|
353
398
|
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile): CodegenResult => {
|
|
@@ -355,6 +400,20 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
355
400
|
return { changed: false, code: originalCode };
|
|
356
401
|
}
|
|
357
402
|
|
|
403
|
+
hoistedFactories.clear();
|
|
404
|
+
htmlToTemplateId.clear();
|
|
405
|
+
nameArraySlot = uid('ArraySlot');
|
|
406
|
+
nameAttr = uid('attr');
|
|
407
|
+
nameEffectSlot = uid('EffectSlot');
|
|
408
|
+
nameEvent = uid('event');
|
|
409
|
+
nameSlot = uid('slot');
|
|
410
|
+
nameTemplate = uid('template');
|
|
411
|
+
needsArraySlot = false;
|
|
412
|
+
needsAttr = false;
|
|
413
|
+
needsEffectSlot = false;
|
|
414
|
+
needsEvent = false;
|
|
415
|
+
needsSlot = false;
|
|
416
|
+
|
|
358
417
|
let rootTemplates = templates.filter(t => !isNestedTemplate(t, templates));
|
|
359
418
|
|
|
360
419
|
if (rootTemplates.length === 0) {
|
|
@@ -409,11 +468,9 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
409
468
|
let factories: string[] = [];
|
|
410
469
|
|
|
411
470
|
for (let [id, html] of hoistedFactories) {
|
|
412
|
-
factories.push(`const ${id} = ${
|
|
471
|
+
factories.push(`const ${id} = ${nameTemplate}(\`${html}\`);`);
|
|
413
472
|
}
|
|
414
473
|
|
|
415
|
-
// Strip original @esportsplus/template imports (they lose symbol bindings in TS 5.9.3)
|
|
416
|
-
code = code.replace(TEMPLATE_IMPORT, '');
|
|
417
474
|
code = generateImports() + '\n\n' + factories.join('\n') + '\n\n' + code;
|
|
418
475
|
}
|
|
419
476
|
|
|
@@ -432,7 +489,7 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
432
489
|
let call = calls[i];
|
|
433
490
|
|
|
434
491
|
result = result.slice(0, call.start);
|
|
435
|
-
result += `new ${
|
|
492
|
+
result += `new ${nameArraySlot}(
|
|
436
493
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
437
494
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
438
495
|
)`;
|
|
@@ -443,17 +500,11 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
443
500
|
};
|
|
444
501
|
|
|
445
502
|
const getNames = () => ({
|
|
446
|
-
attr:
|
|
447
|
-
event:
|
|
448
|
-
slot:
|
|
503
|
+
attr: nameAttr,
|
|
504
|
+
event: nameEvent,
|
|
505
|
+
slot: nameSlot
|
|
449
506
|
});
|
|
450
507
|
|
|
451
|
-
const initNamespace = (): void => {
|
|
452
|
-
hoistedFactories.clear();
|
|
453
|
-
htmlToTemplateId.clear();
|
|
454
|
-
ns = uid('t');
|
|
455
|
-
};
|
|
456
|
-
|
|
457
508
|
const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
|
|
458
509
|
return hasArraySlotUsage(sourceFile) && !hasArraySlotImport(sourceFile);
|
|
459
510
|
};
|
|
@@ -463,5 +514,5 @@ const setTypeChecker = (checker: ts.TypeChecker | undefined): void => {
|
|
|
463
514
|
};
|
|
464
515
|
|
|
465
516
|
|
|
466
|
-
export { addArraySlotImport, generateCode, generateReactiveInlining, getNames,
|
|
517
|
+
export { addArraySlotImport, generateCode, generateReactiveInlining, getNames, needsArraySlotImport, setTypeChecker };
|
|
467
518
|
export type { CodegenResult };
|
package/src/transformer/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mightNeedTransform } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import { addArraySlotImport, generateCode, generateReactiveInlining,
|
|
2
|
+
import { addArraySlotImport, generateCode, generateReactiveInlining, needsArraySlotImport, setTypeChecker } from './codegen';
|
|
3
3
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
4
4
|
import { ts } from '@esportsplus/typescript';
|
|
5
5
|
|
|
@@ -15,24 +15,19 @@ const PATTERNS = ['html`', 'html.reactive'];
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
function createTransformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
|
|
18
|
-
let
|
|
19
|
-
typeChecker = program.getTypeChecker();
|
|
18
|
+
let typeChecker = program.getTypeChecker();
|
|
20
19
|
|
|
21
20
|
return (_context: ts.TransformationContext) => {
|
|
22
21
|
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
23
|
-
|
|
24
|
-
let code = printer.printFile(sourceFile);
|
|
22
|
+
let code = sourceFile.getFullText();
|
|
25
23
|
|
|
26
24
|
if (!mightNeedTransform(code, { patterns: PATTERNS })) {
|
|
27
25
|
return sourceFile;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
// Re-parse the printed code to get accurate positions
|
|
31
|
-
let reparsed = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
32
|
-
|
|
33
28
|
setTypeChecker(typeChecker);
|
|
34
29
|
|
|
35
|
-
let result = transformCode(code,
|
|
30
|
+
let result = transformCode(code, sourceFile);
|
|
36
31
|
|
|
37
32
|
if (!result.changed) {
|
|
38
33
|
return sourceFile;
|
|
@@ -47,8 +42,6 @@ function transformCode(code: string, sourceFile: ts.SourceFile): TransformResult
|
|
|
47
42
|
let changed = false,
|
|
48
43
|
result = code;
|
|
49
44
|
|
|
50
|
-
initNamespace();
|
|
51
|
-
|
|
52
45
|
let reactiveCalls = findReactiveCalls(sourceFile);
|
|
53
46
|
|
|
54
47
|
if (reactiveCalls.length > 0) {
|
|
@@ -81,27 +74,23 @@ function transformCode(code: string, sourceFile: ts.SourceFile): TransformResult
|
|
|
81
74
|
|
|
82
75
|
|
|
83
76
|
const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformResult => {
|
|
84
|
-
|
|
85
|
-
let printer = ts.createPrinter(),
|
|
86
|
-
code = printer.printFile(sourceFile);
|
|
77
|
+
let code = sourceFile.getFullText();
|
|
87
78
|
|
|
88
79
|
if (!mightNeedTransform(code, { patterns: PATTERNS })) {
|
|
89
80
|
return { changed: false, code, sourceFile };
|
|
90
81
|
}
|
|
91
82
|
|
|
92
|
-
// Re-parse the printed code to get accurate positions
|
|
93
|
-
let reparsed = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
94
|
-
|
|
95
83
|
let programSourceFile = program.getSourceFile(sourceFile.fileName);
|
|
96
84
|
|
|
97
85
|
if (programSourceFile) {
|
|
98
86
|
setTypeChecker(program.getTypeChecker());
|
|
87
|
+
sourceFile = programSourceFile;
|
|
99
88
|
}
|
|
100
89
|
else {
|
|
101
90
|
setTypeChecker(undefined);
|
|
102
91
|
}
|
|
103
92
|
|
|
104
|
-
return transformCode(code,
|
|
93
|
+
return transformCode(code, sourceFile);
|
|
105
94
|
};
|
|
106
95
|
|
|
107
96
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mightNeedTransform, PATTERNS, transform } from '../index';
|
|
2
2
|
import { program, TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
|
|
3
3
|
import type { Plugin, ResolvedConfig } from 'vite';
|
|
4
4
|
import { ts } from '@esportsplus/typescript';
|
|
@@ -25,23 +25,14 @@ export default (options?: { root?: string }): Plugin => {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
result = ts.transform(sourceFile, [transformer]),
|
|
33
|
-
transformed = result.transformed[0];
|
|
34
|
-
|
|
35
|
-
if (transformed === sourceFile) {
|
|
36
|
-
result.dispose();
|
|
28
|
+
let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true),
|
|
29
|
+
result = transform(sourceFile, program.get(root));
|
|
30
|
+
|
|
31
|
+
if (!result.changed) {
|
|
37
32
|
return null;
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
result.dispose();
|
|
43
|
-
|
|
44
|
-
return { code: output, map: null };
|
|
35
|
+
return { code: result.code, map: null };
|
|
45
36
|
}
|
|
46
37
|
catch (error) {
|
|
47
38
|
console.error(`@esportsplus/template: Error transforming ${id}:`, error);
|