@esportsplus/template 0.35.0 → 0.37.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/attributes.d.ts +3 -3
- package/build/attributes.js +4 -1
- package/build/compiler/analyzer.d.ts +4 -0
- package/build/compiler/analyzer.js +65 -0
- package/build/compiler/codegen.d.ts +3 -4
- package/build/compiler/codegen.js +67 -66
- package/build/compiler/index.d.ts +4 -2
- package/build/compiler/index.js +55 -18
- package/build/compiler/plugins/tsc.js +2 -2
- package/build/compiler/plugins/vite.d.ts +4 -4
- package/build/compiler/plugins/vite.js +2 -1
- package/build/compiler/ts-parser.js +2 -2
- package/package.json +4 -4
- package/src/attributes.ts +9 -6
- package/src/compiler/analyzer.ts +92 -0
- package/src/compiler/codegen.ts +95 -91
- package/src/compiler/index.ts +85 -23
- package/src/compiler/plugins/tsc.ts +2 -2
- package/src/compiler/plugins/vite.ts +2 -1
- package/src/compiler/ts-parser.ts +2 -2
- package/test/vite.config.ts +1 -1
- package/build/compiler/type-analyzer.d.ts +0 -6
- package/build/compiler/type-analyzer.js +0 -193
- package/src/compiler/type-analyzer.ts +0 -274
package/build/attributes.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Attributes, Element } from './types.js';
|
|
2
|
-
declare const setClass: (element: Element, classlist: false | string
|
|
2
|
+
declare const setClass: (element: Element, classlist: false | string, value: unknown) => void;
|
|
3
3
|
declare const setProperty: (element: Element, name: string, value: unknown) => void;
|
|
4
|
-
declare const setProperties: (element: Element, value
|
|
5
|
-
declare const setStyle: (element: Element, styles: false | string
|
|
4
|
+
declare const setProperties: (element: Element, value?: Attributes | Attributes[] | false | null | undefined) => void;
|
|
5
|
+
declare const setStyle: (element: Element, styles: false | string, value: unknown) => void;
|
|
6
6
|
export { setClass, setProperty, setProperties, setStyle };
|
package/build/attributes.js
CHANGED
|
@@ -199,7 +199,10 @@ const setProperty = (element, name, value) => {
|
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
201
|
const setProperties = function (element, value) {
|
|
202
|
-
if (
|
|
202
|
+
if (!value) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
else if (isObject(value)) {
|
|
203
206
|
for (let name in value) {
|
|
204
207
|
let v = value[name];
|
|
205
208
|
if (v == null || v === false || v === '') {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES } from '../constants.js';
|
|
2
|
+
import { ts } from '@esportsplus/typescript';
|
|
3
|
+
function isTypeFunction(type, checker) {
|
|
4
|
+
if (type.isUnion()) {
|
|
5
|
+
for (let i = 0, n = type.types.length; i < n; i++) {
|
|
6
|
+
if (!isTypeFunction(type.types[i], checker)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return type.types.length > 0;
|
|
11
|
+
}
|
|
12
|
+
return type.getCallSignatures().length > 0;
|
|
13
|
+
}
|
|
14
|
+
const analyze = (expr, checker) => {
|
|
15
|
+
while (ts.isParenthesizedExpression(expr)) {
|
|
16
|
+
expr = expr.expression;
|
|
17
|
+
}
|
|
18
|
+
if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
|
|
19
|
+
return COMPILER_TYPES.Effect;
|
|
20
|
+
}
|
|
21
|
+
if (ts.isCallExpression(expr) &&
|
|
22
|
+
ts.isPropertyAccessExpression(expr.expression) &&
|
|
23
|
+
ts.isIdentifier(expr.expression.expression) &&
|
|
24
|
+
expr.expression.expression.text === COMPILER_ENTRYPOINT &&
|
|
25
|
+
expr.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY) {
|
|
26
|
+
return COMPILER_TYPES.ArraySlot;
|
|
27
|
+
}
|
|
28
|
+
if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT) {
|
|
29
|
+
return COMPILER_TYPES.DocumentFragment;
|
|
30
|
+
}
|
|
31
|
+
if (ts.isNumericLiteral(expr) ||
|
|
32
|
+
ts.isStringLiteral(expr) ||
|
|
33
|
+
ts.isNoSubstitutionTemplateLiteral(expr) ||
|
|
34
|
+
expr.kind === ts.SyntaxKind.TrueKeyword ||
|
|
35
|
+
expr.kind === ts.SyntaxKind.FalseKeyword ||
|
|
36
|
+
expr.kind === ts.SyntaxKind.NullKeyword ||
|
|
37
|
+
expr.kind === ts.SyntaxKind.UndefinedKeyword) {
|
|
38
|
+
return COMPILER_TYPES.Static;
|
|
39
|
+
}
|
|
40
|
+
if (ts.isTemplateExpression(expr)) {
|
|
41
|
+
return COMPILER_TYPES.Primitive;
|
|
42
|
+
}
|
|
43
|
+
if (ts.isConditionalExpression(expr)) {
|
|
44
|
+
let whenFalse = analyze(expr.whenFalse, checker), whenTrue = analyze(expr.whenTrue, checker);
|
|
45
|
+
if (whenTrue === whenFalse) {
|
|
46
|
+
return whenTrue;
|
|
47
|
+
}
|
|
48
|
+
if (whenTrue === COMPILER_TYPES.Effect || whenFalse === COMPILER_TYPES.Effect) {
|
|
49
|
+
return COMPILER_TYPES.Effect;
|
|
50
|
+
}
|
|
51
|
+
return COMPILER_TYPES.Unknown;
|
|
52
|
+
}
|
|
53
|
+
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
|
|
54
|
+
try {
|
|
55
|
+
let type = checker.getTypeAtLocation(expr);
|
|
56
|
+
if (isTypeFunction(type, checker)) {
|
|
57
|
+
return COMPILER_TYPES.Effect;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return COMPILER_TYPES.Unknown;
|
|
64
|
+
};
|
|
65
|
+
export { analyze };
|
|
@@ -4,8 +4,7 @@ type CodegenResult = {
|
|
|
4
4
|
changed: boolean;
|
|
5
5
|
code: string;
|
|
6
6
|
};
|
|
7
|
-
declare const generateCode: (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => CodegenResult;
|
|
8
|
-
declare const generateReactiveInlining: (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile) => string;
|
|
9
|
-
|
|
10
|
-
export { generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
7
|
+
declare const generateCode: (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker, existingAliases?: Map<string, string>) => CodegenResult;
|
|
8
|
+
declare const generateReactiveInlining: (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile, arraySlotAlias: string) => string;
|
|
9
|
+
export { generateCode, generateReactiveInlining };
|
|
11
10
|
export type { CodegenResult };
|
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import { ast, code as c, imports, uid } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '../constants.js';
|
|
4
|
-
import {
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE } from '../constants.js';
|
|
4
|
+
import { analyze } from './analyzer.js';
|
|
5
5
|
import parser from './parser.js';
|
|
6
6
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
7
7
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
8
|
+
function addImport(ctx, name) {
|
|
9
|
+
let alias = ctx.imports.get(name);
|
|
10
|
+
if (!alias) {
|
|
11
|
+
alias = uid(name);
|
|
12
|
+
ctx.imports.set(name, alias);
|
|
13
|
+
}
|
|
14
|
+
return alias;
|
|
15
|
+
}
|
|
16
|
+
function generateAttributeBinding(ctx, element, name, expr, staticValue) {
|
|
17
|
+
if (name.startsWith('on') && name.length > 2) {
|
|
18
|
+
let event = name.slice(2).toLowerCase(), key = name.toLowerCase();
|
|
19
|
+
if (LIFECYCLE_EVENTS.has(key)) {
|
|
20
|
+
return `${addImport(ctx, key)}(${element}, ${expr});`;
|
|
21
|
+
}
|
|
22
|
+
if (DIRECT_ATTACH_EVENTS.has(key)) {
|
|
23
|
+
return `${addImport(ctx, 'on')}(${element}, '${event}', ${expr});`;
|
|
24
|
+
}
|
|
25
|
+
return `${addImport(ctx, 'delegate')}(${element}, '${event}', ${expr});`;
|
|
26
|
+
}
|
|
27
|
+
if (name === 'class') {
|
|
28
|
+
return `${addImport(ctx, 'setClass')}(${element}, '${staticValue}', ${expr});`;
|
|
29
|
+
}
|
|
30
|
+
if (name === 'style') {
|
|
31
|
+
return `${addImport(ctx, 'setStyle')}(${element}, '${staticValue}', ${expr});`;
|
|
32
|
+
}
|
|
33
|
+
return `${addImport(ctx, 'setProperty')}(${element}, '${name}', ${expr});`;
|
|
34
|
+
}
|
|
8
35
|
function collectNestedTemplateReplacements(ctx, node, exprStart, replacements) {
|
|
9
36
|
if (isNestedHtmlTemplate(node)) {
|
|
10
37
|
replacements.push({
|
|
@@ -35,27 +62,23 @@ function generateNestedTemplateCode(ctx, node) {
|
|
|
35
62
|
}
|
|
36
63
|
function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
37
64
|
if (!exprNode) {
|
|
38
|
-
ctx
|
|
39
|
-
return `slot(${anchor}, ${exprText});`;
|
|
65
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
40
66
|
}
|
|
41
67
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
42
68
|
return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
|
|
43
69
|
}
|
|
44
|
-
let slotType =
|
|
70
|
+
let slotType = analyze(exprNode, ctx.checker);
|
|
45
71
|
switch (slotType) {
|
|
46
72
|
case COMPILER_TYPES.Effect:
|
|
47
|
-
ctx
|
|
48
|
-
return `new EffectSlot(${anchor}, ${exprText});`;
|
|
73
|
+
return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
|
|
49
74
|
case COMPILER_TYPES.ArraySlot:
|
|
50
|
-
ctx
|
|
51
|
-
return `new ArraySlot(${anchor}, ${exprText});`;
|
|
75
|
+
return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
|
|
52
76
|
case COMPILER_TYPES.Static:
|
|
53
77
|
return `${anchor}.textContent = ${exprText};`;
|
|
54
78
|
case COMPILER_TYPES.DocumentFragment:
|
|
55
79
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
56
80
|
default:
|
|
57
|
-
ctx
|
|
58
|
-
return `slot(${anchor}, ${exprText});`;
|
|
81
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
59
82
|
}
|
|
60
83
|
}
|
|
61
84
|
function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArrowBody) {
|
|
@@ -74,23 +97,25 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
74
97
|
if (nodes.has(key)) {
|
|
75
98
|
continue;
|
|
76
99
|
}
|
|
77
|
-
let
|
|
100
|
+
let ancestor = root, start = 0;
|
|
78
101
|
for (let j = path.length - 1; j >= 0; j--) {
|
|
79
102
|
let prefix = path.slice(0, j).join('.');
|
|
80
103
|
if (nodes.has(prefix)) {
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
ancestor = nodes.get(prefix);
|
|
105
|
+
start = j;
|
|
83
106
|
break;
|
|
84
107
|
}
|
|
85
108
|
}
|
|
86
|
-
let name = uid('element'),
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
let alias = addImport(ctx, 'Element'), name = uid('element'), segments = path.slice(start), value = `${ancestor}.${segments.join('!.')}`;
|
|
110
|
+
if (ancestor === root && segments[0] === 'firstChild') {
|
|
111
|
+
value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${alias})`);
|
|
112
|
+
}
|
|
113
|
+
declarations.push(`${name} = ${value} as ${alias}`);
|
|
89
114
|
nodes.set(key, name);
|
|
90
115
|
}
|
|
91
116
|
code.push(isArrowBody ? '{' : `(() => {`, `let ${declarations.join(',\n')};`);
|
|
92
117
|
for (let i = 0, n = slots.length; i < n; i++) {
|
|
93
|
-
let
|
|
118
|
+
let element = slots[i].path.length === 0
|
|
94
119
|
? root
|
|
95
120
|
: (nodes.get(slots[i].path.join('.')) || root), slot = slots[i];
|
|
96
121
|
if (slot.type === COMPILER_TYPES.Attribute) {
|
|
@@ -98,19 +123,16 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
98
123
|
for (let j = 0, m = names.length; j < m; j++) {
|
|
99
124
|
let name = names[j];
|
|
100
125
|
if (name === COMPILER_TYPES.Attributes) {
|
|
101
|
-
|
|
102
|
-
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
103
|
-
code.push(bindings[k]);
|
|
104
|
-
}
|
|
126
|
+
code.push(`${addImport(ctx, 'setProperties')}(${element}, ${exprTexts[index] || 'undefined'});`);
|
|
105
127
|
index++;
|
|
106
128
|
}
|
|
107
129
|
else {
|
|
108
|
-
code.push(generateAttributeBinding(
|
|
130
|
+
code.push(generateAttributeBinding(ctx, element, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || ''));
|
|
109
131
|
}
|
|
110
132
|
}
|
|
111
133
|
}
|
|
112
134
|
else {
|
|
113
|
-
code.push(generateNodeBinding(ctx,
|
|
135
|
+
code.push(generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index]));
|
|
114
136
|
index++;
|
|
115
137
|
}
|
|
116
138
|
}
|
|
@@ -119,32 +141,13 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
119
141
|
return code.join('\n');
|
|
120
142
|
}
|
|
121
143
|
function getOrCreateTemplateId(ctx, html) {
|
|
122
|
-
let id = ctx.
|
|
144
|
+
let id = ctx.templates.get(html);
|
|
123
145
|
if (!id) {
|
|
124
|
-
id = uid('
|
|
125
|
-
ctx.
|
|
126
|
-
ctx.htmlToTemplateId.set(html, id);
|
|
146
|
+
id = uid('template');
|
|
147
|
+
ctx.templates.set(html, id);
|
|
127
148
|
}
|
|
128
149
|
return id;
|
|
129
150
|
}
|
|
130
|
-
function hasArraySlotImport(sourceFile) {
|
|
131
|
-
for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
|
|
132
|
-
let stmt = sourceFile.statements[i];
|
|
133
|
-
if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
let bindings = stmt.importClause.namedBindings;
|
|
137
|
-
if (!ts.isNamedImports(bindings)) {
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
for (let j = 0, m = bindings.elements.length; j < m; j++) {
|
|
141
|
-
if (bindings.elements[j].name.text === 'ArraySlot') {
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
151
|
function isNestedHtmlTemplate(expr) {
|
|
149
152
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
150
153
|
}
|
|
@@ -159,7 +162,7 @@ function rewriteExpression(ctx, expr) {
|
|
|
159
162
|
collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
|
|
160
163
|
return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
|
|
161
164
|
}
|
|
162
|
-
const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
165
|
+
const generateCode = (templates, originalCode, sourceFile, checker, existingAliases) => {
|
|
163
166
|
if (templates.length === 0) {
|
|
164
167
|
return { changed: false, code: originalCode };
|
|
165
168
|
}
|
|
@@ -176,12 +179,11 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
|
176
179
|
}
|
|
177
180
|
let ctx = {
|
|
178
181
|
checker,
|
|
179
|
-
|
|
180
|
-
htmlToTemplateId: new Map(),
|
|
181
|
-
neededImports: new Set(['template']),
|
|
182
|
+
imports: existingAliases ?? new Map(),
|
|
182
183
|
printer,
|
|
183
|
-
sourceFile
|
|
184
|
-
|
|
184
|
+
sourceFile,
|
|
185
|
+
templates: new Map(),
|
|
186
|
+
}, replacements = [], templateAlias = addImport(ctx, 'template');
|
|
185
187
|
for (let i = 0, n = rootTemplates.length; i < n; i++) {
|
|
186
188
|
let exprTexts = [], template = rootTemplates[i];
|
|
187
189
|
for (let j = 0, m = template.expressions.length; j < m; j++) {
|
|
@@ -206,19 +208,23 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
|
206
208
|
});
|
|
207
209
|
}
|
|
208
210
|
let changed = replacements.length > 0, code = c.replaceReverse(originalCode, replacements);
|
|
209
|
-
if (changed && ctx.
|
|
210
|
-
let factories = [];
|
|
211
|
-
for (let [
|
|
212
|
-
|
|
211
|
+
if (changed && ctx.templates.size > 0) {
|
|
212
|
+
let aliasedImports = [], factories = [], updatedSourceFile = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
213
|
+
for (let [name, alias] of ctx.imports) {
|
|
214
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
213
215
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
for (let [html, id] of ctx.templates) {
|
|
217
|
+
factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
|
|
218
|
+
}
|
|
219
|
+
code = imports.modify(code, updatedSourceFile, PACKAGE, {
|
|
220
|
+
add: new Set(aliasedImports),
|
|
216
221
|
remove: [COMPILER_ENTRYPOINT]
|
|
217
222
|
});
|
|
223
|
+
code = factories.join('\n') + '\n\n' + code;
|
|
218
224
|
}
|
|
219
225
|
return { changed, code };
|
|
220
226
|
};
|
|
221
|
-
const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
227
|
+
const generateReactiveInlining = (calls, code, sourceFile, arraySlotAlias) => {
|
|
222
228
|
if (calls.length === 0) {
|
|
223
229
|
return code;
|
|
224
230
|
}
|
|
@@ -227,7 +233,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
227
233
|
let call = calls[i];
|
|
228
234
|
replacements.push({
|
|
229
235
|
end: call.end,
|
|
230
|
-
newText: `new
|
|
236
|
+
newText: `new ${arraySlotAlias}(
|
|
231
237
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
232
238
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
233
239
|
)`,
|
|
@@ -236,9 +242,4 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
236
242
|
}
|
|
237
243
|
return c.replaceReverse(code, replacements);
|
|
238
244
|
};
|
|
239
|
-
|
|
240
|
-
return ast.hasMatch(sourceFile, n => ts.isNewExpression(n) &&
|
|
241
|
-
ts.isIdentifier(n.expression) &&
|
|
242
|
-
n.expression.text === 'ArraySlot') && !hasArraySlotImport(sourceFile);
|
|
243
|
-
};
|
|
244
|
-
export { generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
245
|
+
export { generateCode, generateReactiveInlining };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { PluginContext } from '@esportsplus/typescript/compiler';
|
|
1
2
|
import { ts } from '@esportsplus/typescript';
|
|
2
3
|
type TransformResult = {
|
|
3
4
|
changed: boolean;
|
|
4
5
|
code: string;
|
|
5
6
|
sourceFile: ts.SourceFile;
|
|
6
7
|
};
|
|
7
|
-
declare const
|
|
8
|
-
|
|
8
|
+
declare const analyze: (sourceFile: ts.SourceFile, program: ts.Program, context: PluginContext) => void;
|
|
9
|
+
declare const transform: (sourceFile: ts.SourceFile, program: ts.Program, context?: PluginContext) => TransformResult;
|
|
10
|
+
export { analyze, transform };
|
package/build/compiler/index.js
CHANGED
|
@@ -1,45 +1,82 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { code as c, imports } from '@esportsplus/typescript/compiler';
|
|
2
|
+
import { code as c, imports, uid } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '../constants.js';
|
|
4
|
-
import { generateCode, generateReactiveInlining
|
|
4
|
+
import { generateCode, generateReactiveInlining } from './codegen.js';
|
|
5
5
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
|
|
6
|
+
const CONTEXT_KEY = 'template:analyzed';
|
|
6
7
|
const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
|
|
7
8
|
const REGEX_BACKSLASH = /\\/g;
|
|
8
9
|
const REGEX_FORWARD_SLASH = /\//g;
|
|
9
|
-
|
|
10
|
+
function getAnalyzedFile(context, filename) {
|
|
11
|
+
return context?.get(CONTEXT_KEY)?.get(filename);
|
|
12
|
+
}
|
|
13
|
+
const analyze = (sourceFile, program, context) => {
|
|
10
14
|
let code = sourceFile.getFullText();
|
|
11
15
|
if (!c.contains(code, { patterns: PATTERNS })) {
|
|
12
|
-
return
|
|
16
|
+
return;
|
|
13
17
|
}
|
|
14
|
-
let checker,
|
|
15
|
-
|| program.getSourceFile(
|
|
16
|
-
|| program.getSourceFile(
|
|
18
|
+
let checker = program.getTypeChecker(), filename = sourceFile.fileName, files = context.get(CONTEXT_KEY), programSourceFile = program.getSourceFile(filename)
|
|
19
|
+
|| program.getSourceFile(filename.replace(REGEX_BACKSLASH, '/'))
|
|
20
|
+
|| program.getSourceFile(filename.replace(REGEX_FORWARD_SLASH, '\\'));
|
|
17
21
|
if (programSourceFile) {
|
|
18
|
-
checker = program.getTypeChecker();
|
|
19
22
|
sourceFile = programSourceFile;
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
if (!files) {
|
|
25
|
+
files = new Map();
|
|
26
|
+
context.set(CONTEXT_KEY, files);
|
|
27
|
+
}
|
|
28
|
+
files.set(filename, {
|
|
29
|
+
reactiveCalls: findReactiveCalls(sourceFile, checker),
|
|
30
|
+
templates: findHtmlTemplates(sourceFile, checker)
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const transform = (sourceFile, program, context) => {
|
|
34
|
+
let code = sourceFile.getFullText(), filename = sourceFile.fileName;
|
|
35
|
+
let analyzed = getAnalyzedFile(context, filename);
|
|
36
|
+
if (!analyzed) {
|
|
37
|
+
if (!c.contains(code, { patterns: PATTERNS })) {
|
|
38
|
+
return { changed: false, code, sourceFile };
|
|
39
|
+
}
|
|
40
|
+
let checker = program.getTypeChecker(), programSourceFile = program.getSourceFile(filename)
|
|
41
|
+
|| program.getSourceFile(filename.replace(REGEX_BACKSLASH, '/'))
|
|
42
|
+
|| program.getSourceFile(filename.replace(REGEX_FORWARD_SLASH, '\\'));
|
|
43
|
+
if (programSourceFile) {
|
|
44
|
+
sourceFile = programSourceFile;
|
|
45
|
+
}
|
|
46
|
+
analyzed = {
|
|
47
|
+
reactiveCalls: findReactiveCalls(sourceFile, checker),
|
|
48
|
+
templates: findHtmlTemplates(sourceFile, checker)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
let changed = false, codegenChanged = false, existingAliases = new Map(), result = code;
|
|
52
|
+
if (analyzed.reactiveCalls.length > 0) {
|
|
23
53
|
changed = true;
|
|
24
|
-
|
|
54
|
+
existingAliases.set('ArraySlot', uid('ArraySlot'));
|
|
55
|
+
result = generateReactiveInlining(analyzed.reactiveCalls, result, sourceFile, existingAliases.get('ArraySlot'));
|
|
25
56
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
26
|
-
|
|
57
|
+
analyzed = {
|
|
58
|
+
reactiveCalls: [],
|
|
59
|
+
templates: findHtmlTemplates(sourceFile, program.getTypeChecker())
|
|
60
|
+
};
|
|
27
61
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let codegenResult = generateCode(templates, result, sourceFile, checker);
|
|
62
|
+
if (analyzed.templates.length > 0) {
|
|
63
|
+
let codegenResult = generateCode(analyzed.templates, result, sourceFile, program.getTypeChecker(), existingAliases);
|
|
31
64
|
if (codegenResult.changed) {
|
|
32
65
|
changed = true;
|
|
33
66
|
codegenChanged = true;
|
|
34
67
|
result = codegenResult.code;
|
|
35
68
|
}
|
|
36
69
|
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
70
|
+
if (existingAliases.size > 0 && !codegenChanged) {
|
|
71
|
+
let aliasedImports = [];
|
|
72
|
+
for (let [name, alias] of existingAliases) {
|
|
73
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
74
|
+
}
|
|
75
|
+
result = imports.modify(result, sourceFile, PACKAGE, { add: aliasedImports });
|
|
39
76
|
}
|
|
40
77
|
if (changed) {
|
|
41
78
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
42
79
|
}
|
|
43
80
|
return { changed, code: result, sourceFile };
|
|
44
81
|
};
|
|
45
|
-
export { transform };
|
|
82
|
+
export { analyze, transform };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
2
|
-
import { transform } from '../index.js';
|
|
3
|
-
export default plugin.tsc(transform);
|
|
2
|
+
import { analyze, transform } from '../index.js';
|
|
3
|
+
export default plugin.tsc({ analyze, transform });
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
declare const _default: ({ root }?: {
|
|
2
2
|
root?: string;
|
|
3
3
|
}) => {
|
|
4
|
-
configResolved(config:
|
|
5
|
-
enforce:
|
|
4
|
+
configResolved: (config: unknown) => void;
|
|
5
|
+
enforce: "pre";
|
|
6
6
|
name: string;
|
|
7
|
-
transform(code: string, id: string)
|
|
7
|
+
transform: (code: string, id: string) => {
|
|
8
8
|
code: string;
|
|
9
9
|
map: null;
|
|
10
10
|
} | null;
|
|
11
|
-
watchChange(id: string)
|
|
11
|
+
watchChange: (id: string) => void;
|
|
12
12
|
};
|
|
13
13
|
export default _default;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
2
2
|
import { PACKAGE } from '../../constants.js';
|
|
3
|
-
import { transform } from '../index.js';
|
|
3
|
+
import { analyze, transform } from '../index.js';
|
|
4
4
|
export default plugin.vite({
|
|
5
|
+
analyze,
|
|
5
6
|
name: PACKAGE,
|
|
6
7
|
transform
|
|
7
8
|
});
|
|
@@ -19,8 +19,8 @@ function isHtmlFromPackage(node, checker) {
|
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
21
|
for (let i = 0, n = declarations.length; i < n; i++) {
|
|
22
|
-
let
|
|
23
|
-
if (
|
|
22
|
+
let filename = declarations[i].getSourceFile().fileName;
|
|
23
|
+
if (filename.includes(PACKAGE) || filename.includes('@esportsplus/template')) {
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
26
|
}
|
package/package.json
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@esportsplus/queue": "^0.2.0",
|
|
5
|
-
"@esportsplus/reactivity": "^0.27.
|
|
5
|
+
"@esportsplus/reactivity": "^0.27.3",
|
|
6
|
+
"@esportsplus/typescript": "^0.24.2",
|
|
6
7
|
"@esportsplus/utilities": "^0.27.2",
|
|
7
8
|
"serve": "^14.2.5"
|
|
8
9
|
},
|
|
9
10
|
"devDependencies": {
|
|
10
|
-
"@esportsplus/typescript": "^0.22.0",
|
|
11
11
|
"@types/node": "^25.0.3",
|
|
12
|
-
"vite": "^7.3.
|
|
12
|
+
"vite": "^7.3.1",
|
|
13
13
|
"vite-tsconfig-paths": "^6.0.3"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"type": "module",
|
|
37
37
|
"types": "./build/index.d.ts",
|
|
38
|
-
"version": "0.
|
|
38
|
+
"version": "0.37.0",
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc",
|
|
41
41
|
"build:test": "vite build --config test/vite.config.ts",
|
package/src/attributes.ts
CHANGED
|
@@ -61,7 +61,7 @@ function list(
|
|
|
61
61
|
changed = false,
|
|
62
62
|
delimiter = delimiters[name],
|
|
63
63
|
store = (ctx ??= context(element)).store ??= {},
|
|
64
|
-
dynamic = store[name] as Set<string
|
|
64
|
+
dynamic = store[name] as Set<string>;
|
|
65
65
|
|
|
66
66
|
if (dynamic === undefined) {
|
|
67
67
|
let value = (element.getAttribute(name) || '').trim();
|
|
@@ -105,7 +105,7 @@ function list(
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
let cold = store[id] as Record<PropertyKey, true
|
|
108
|
+
let cold = store[id] as Record<PropertyKey, true>;
|
|
109
109
|
|
|
110
110
|
if (cold !== undefined) {
|
|
111
111
|
for (let part in cold) {
|
|
@@ -274,7 +274,7 @@ function task() {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
|
|
277
|
-
const setClass = (element: Element, classlist: false | string
|
|
277
|
+
const setClass = (element: Element, classlist: false | string, value: unknown) => {
|
|
278
278
|
let ctx = context(element),
|
|
279
279
|
store = ctx.store ??= {};
|
|
280
280
|
|
|
@@ -298,8 +298,11 @@ const setProperty = (element: Element, name: string, value: unknown) => {
|
|
|
298
298
|
}
|
|
299
299
|
};
|
|
300
300
|
|
|
301
|
-
const setProperties = function (element: Element, value
|
|
302
|
-
if (
|
|
301
|
+
const setProperties = function (element: Element, value?: Attributes | Attributes[] | false | null | undefined) {
|
|
302
|
+
if (!value) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
else if (isObject(value)) {
|
|
303
306
|
for (let name in value) {
|
|
304
307
|
let v = value[name];
|
|
305
308
|
|
|
@@ -317,7 +320,7 @@ const setProperties = function (element: Element, value: Attributes | Attributes
|
|
|
317
320
|
}
|
|
318
321
|
};
|
|
319
322
|
|
|
320
|
-
const setStyle = (element: Element, styles: false | string
|
|
323
|
+
const setStyle = (element: Element, styles: false | string, value: unknown) => {
|
|
321
324
|
let ctx = context(element),
|
|
322
325
|
store = ctx.store ??= {};
|
|
323
326
|
|