@esportsplus/template 0.35.0 → 0.35.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.d.ts +3 -3
- package/build/attributes.js +4 -1
- package/build/compiler/codegen.d.ts +3 -4
- package/build/compiler/codegen.js +44 -59
- package/build/compiler/index.js +13 -8
- package/build/compiler/type-analyzer.d.ts +2 -2
- package/build/compiler/type-analyzer.js +17 -113
- package/package.json +1 -1
- package/src/attributes.ts +9 -6
- package/src/compiler/codegen.ts +61 -78
- package/src/compiler/index.ts +17 -8
- package/src/compiler/type-analyzer.ts +23 -149
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 === '') {
|
|
@@ -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 };
|
|
@@ -5,6 +5,14 @@ import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } f
|
|
|
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
|
+
}
|
|
8
16
|
function collectNestedTemplateReplacements(ctx, node, exprStart, replacements) {
|
|
9
17
|
if (isNestedHtmlTemplate(node)) {
|
|
10
18
|
replacements.push({
|
|
@@ -35,8 +43,7 @@ function generateNestedTemplateCode(ctx, node) {
|
|
|
35
43
|
}
|
|
36
44
|
function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
37
45
|
if (!exprNode) {
|
|
38
|
-
ctx
|
|
39
|
-
return `slot(${anchor}, ${exprText});`;
|
|
46
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
40
47
|
}
|
|
41
48
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
42
49
|
return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
|
|
@@ -44,18 +51,15 @@ function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
|
44
51
|
let slotType = analyzeExpression(exprNode, ctx.checker);
|
|
45
52
|
switch (slotType) {
|
|
46
53
|
case COMPILER_TYPES.Effect:
|
|
47
|
-
ctx
|
|
48
|
-
return `new EffectSlot(${anchor}, ${exprText});`;
|
|
54
|
+
return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
|
|
49
55
|
case COMPILER_TYPES.ArraySlot:
|
|
50
|
-
ctx
|
|
51
|
-
return `new ArraySlot(${anchor}, ${exprText});`;
|
|
56
|
+
return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
|
|
52
57
|
case COMPILER_TYPES.Static:
|
|
53
58
|
return `${anchor}.textContent = ${exprText};`;
|
|
54
59
|
case COMPILER_TYPES.DocumentFragment:
|
|
55
60
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
56
61
|
default:
|
|
57
|
-
ctx
|
|
58
|
-
return `slot(${anchor}, ${exprText});`;
|
|
62
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArrowBody) {
|
|
@@ -74,23 +78,25 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
74
78
|
if (nodes.has(key)) {
|
|
75
79
|
continue;
|
|
76
80
|
}
|
|
77
|
-
let
|
|
81
|
+
let ancestor = root, start = 0;
|
|
78
82
|
for (let j = path.length - 1; j >= 0; j--) {
|
|
79
83
|
let prefix = path.slice(0, j).join('.');
|
|
80
84
|
if (nodes.has(prefix)) {
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
ancestor = nodes.get(prefix);
|
|
86
|
+
start = j;
|
|
83
87
|
break;
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
|
-
let name = uid('element'),
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
let alias = addImport(ctx, 'Element'), name = uid('element'), segments = path.slice(start), value = `${ancestor}.${segments.join('!.')}`;
|
|
91
|
+
if (ancestor === root && segments[0] === 'firstChild') {
|
|
92
|
+
value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${alias})`);
|
|
93
|
+
}
|
|
94
|
+
declarations.push(`${name} = ${value} as ${alias}`);
|
|
89
95
|
nodes.set(key, name);
|
|
90
96
|
}
|
|
91
97
|
code.push(isArrowBody ? '{' : `(() => {`, `let ${declarations.join(',\n')};`);
|
|
92
98
|
for (let i = 0, n = slots.length; i < n; i++) {
|
|
93
|
-
let
|
|
99
|
+
let element = slots[i].path.length === 0
|
|
94
100
|
? root
|
|
95
101
|
: (nodes.get(slots[i].path.join('.')) || root), slot = slots[i];
|
|
96
102
|
if (slot.type === COMPILER_TYPES.Attribute) {
|
|
@@ -98,19 +104,19 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
98
104
|
for (let j = 0, m = names.length; j < m; j++) {
|
|
99
105
|
let name = names[j];
|
|
100
106
|
if (name === COMPILER_TYPES.Attributes) {
|
|
101
|
-
let bindings = generateSpreadBindings(
|
|
107
|
+
let bindings = generateSpreadBindings(exprTexts[index] || 'undefined', element, n => addImport(ctx, n));
|
|
102
108
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
103
109
|
code.push(bindings[k]);
|
|
104
110
|
}
|
|
105
111
|
index++;
|
|
106
112
|
}
|
|
107
113
|
else {
|
|
108
|
-
code.push(generateAttributeBinding(
|
|
114
|
+
code.push(generateAttributeBinding(element, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', n => addImport(ctx, n)));
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
}
|
|
112
118
|
else {
|
|
113
|
-
code.push(generateNodeBinding(ctx,
|
|
119
|
+
code.push(generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index]));
|
|
114
120
|
index++;
|
|
115
121
|
}
|
|
116
122
|
}
|
|
@@ -119,32 +125,13 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
119
125
|
return code.join('\n');
|
|
120
126
|
}
|
|
121
127
|
function getOrCreateTemplateId(ctx, html) {
|
|
122
|
-
let id = ctx.
|
|
128
|
+
let id = ctx.templates.get(html);
|
|
123
129
|
if (!id) {
|
|
124
|
-
id = uid('
|
|
125
|
-
ctx.
|
|
126
|
-
ctx.htmlToTemplateId.set(html, id);
|
|
130
|
+
id = uid('template');
|
|
131
|
+
ctx.templates.set(html, id);
|
|
127
132
|
}
|
|
128
133
|
return id;
|
|
129
134
|
}
|
|
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
135
|
function isNestedHtmlTemplate(expr) {
|
|
149
136
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
150
137
|
}
|
|
@@ -159,7 +146,7 @@ function rewriteExpression(ctx, expr) {
|
|
|
159
146
|
collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
|
|
160
147
|
return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
|
|
161
148
|
}
|
|
162
|
-
const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
149
|
+
const generateCode = (templates, originalCode, sourceFile, checker, existingAliases) => {
|
|
163
150
|
if (templates.length === 0) {
|
|
164
151
|
return { changed: false, code: originalCode };
|
|
165
152
|
}
|
|
@@ -176,12 +163,11 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
|
176
163
|
}
|
|
177
164
|
let ctx = {
|
|
178
165
|
checker,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
neededImports: new Set(['template']),
|
|
166
|
+
templates: new Map(),
|
|
167
|
+
imports: existingAliases ?? new Map(),
|
|
182
168
|
printer,
|
|
183
169
|
sourceFile
|
|
184
|
-
}, replacements = [];
|
|
170
|
+
}, replacements = [], templateAlias = addImport(ctx, 'template');
|
|
185
171
|
for (let i = 0, n = rootTemplates.length; i < n; i++) {
|
|
186
172
|
let exprTexts = [], template = rootTemplates[i];
|
|
187
173
|
for (let j = 0, m = template.expressions.length; j < m; j++) {
|
|
@@ -206,19 +192,23 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
|
206
192
|
});
|
|
207
193
|
}
|
|
208
194
|
let changed = replacements.length > 0, code = c.replaceReverse(originalCode, replacements);
|
|
209
|
-
if (changed && ctx.
|
|
210
|
-
let factories = [];
|
|
211
|
-
for (let [
|
|
212
|
-
|
|
195
|
+
if (changed && ctx.templates.size > 0) {
|
|
196
|
+
let aliasedImports = [], factories = [];
|
|
197
|
+
for (let [name, alias] of ctx.imports) {
|
|
198
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
199
|
+
}
|
|
200
|
+
for (let [html, id] of ctx.templates) {
|
|
201
|
+
factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
|
|
213
202
|
}
|
|
214
|
-
code = imports.modify(
|
|
215
|
-
add:
|
|
203
|
+
code = imports.modify(code, sourceFile, PACKAGE, {
|
|
204
|
+
add: new Set(aliasedImports),
|
|
216
205
|
remove: [COMPILER_ENTRYPOINT]
|
|
217
206
|
});
|
|
207
|
+
code = factories.join('\n') + '\n\n' + code;
|
|
218
208
|
}
|
|
219
209
|
return { changed, code };
|
|
220
210
|
};
|
|
221
|
-
const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
211
|
+
const generateReactiveInlining = (calls, code, sourceFile, arraySlotAlias) => {
|
|
222
212
|
if (calls.length === 0) {
|
|
223
213
|
return code;
|
|
224
214
|
}
|
|
@@ -227,7 +217,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
227
217
|
let call = calls[i];
|
|
228
218
|
replacements.push({
|
|
229
219
|
end: call.end,
|
|
230
|
-
newText: `new
|
|
220
|
+
newText: `new ${arraySlotAlias}(
|
|
231
221
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
232
222
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
233
223
|
)`,
|
|
@@ -236,9 +226,4 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
236
226
|
}
|
|
237
227
|
return c.replaceReverse(code, replacements);
|
|
238
228
|
};
|
|
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 };
|
|
229
|
+
export { generateCode, generateReactiveInlining };
|
package/build/compiler/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
|
|
7
7
|
const REGEX_BACKSLASH = /\\/g;
|
|
@@ -18,24 +18,29 @@ const transform = (sourceFile, program) => {
|
|
|
18
18
|
checker = program.getTypeChecker();
|
|
19
19
|
sourceFile = programSourceFile;
|
|
20
20
|
}
|
|
21
|
-
let changed = false, codegenChanged = false,
|
|
21
|
+
let changed = false, codegenChanged = false, existingAliases = new Map(), reactiveCalls = findReactiveCalls(sourceFile, checker), result = code;
|
|
22
22
|
if (reactiveCalls.length > 0) {
|
|
23
|
+
let arraySlotAlias = uid('ArraySlot');
|
|
23
24
|
changed = true;
|
|
24
|
-
|
|
25
|
+
existingAliases.set('ArraySlot', arraySlotAlias);
|
|
26
|
+
result = generateReactiveInlining(reactiveCalls, result, sourceFile, arraySlotAlias);
|
|
25
27
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
26
|
-
needsImport = needsArraySlotImport(sourceFile);
|
|
27
28
|
}
|
|
28
29
|
let templates = findHtmlTemplates(sourceFile, checker);
|
|
29
30
|
if (templates.length > 0) {
|
|
30
|
-
let codegenResult = generateCode(templates, result, sourceFile, checker);
|
|
31
|
+
let codegenResult = generateCode(templates, result, sourceFile, checker, existingAliases);
|
|
31
32
|
if (codegenResult.changed) {
|
|
32
33
|
changed = true;
|
|
33
34
|
codegenChanged = true;
|
|
34
35
|
result = codegenResult.code;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
38
|
+
if (existingAliases.size > 0 && !codegenChanged) {
|
|
39
|
+
let aliasedImports = [];
|
|
40
|
+
for (let [name, alias] of existingAliases) {
|
|
41
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
42
|
+
}
|
|
43
|
+
result = imports.modify(result, sourceFile, PACKAGE, { add: aliasedImports });
|
|
39
44
|
}
|
|
40
45
|
if (changed) {
|
|
41
46
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { COMPILER_TYPES } from '../constants.js';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
3
|
declare const analyzeExpression: (expr: ts.Expression, checker?: ts.TypeChecker) => COMPILER_TYPES;
|
|
4
|
-
declare const generateAttributeBinding: (elementVar: string, name: string, expr: string, staticValue: string,
|
|
5
|
-
declare const generateSpreadBindings: (
|
|
4
|
+
declare const generateAttributeBinding: (elementVar: string, name: string, expr: string, staticValue: string, addImport: (name: string) => string) => string;
|
|
5
|
+
declare const generateSpreadBindings: (exprCode: string, elementVar: string, addImport: (name: string) => string) => string[];
|
|
6
6
|
export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
|
|
@@ -1,45 +1,5 @@
|
|
|
1
1
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS } from '../constants.js';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
function analyzeSpread(expr, checker) {
|
|
4
|
-
while (ts.isParenthesizedExpression(expr)) {
|
|
5
|
-
expr = expr.expression;
|
|
6
|
-
}
|
|
7
|
-
if (ts.isObjectLiteralExpression(expr)) {
|
|
8
|
-
let keys = [];
|
|
9
|
-
for (let i = 0, n = expr.properties.length; i < n; i++) {
|
|
10
|
-
let prop = expr.properties[i];
|
|
11
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
12
|
-
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
|
|
13
|
-
keys.push(prop.name.text);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
17
|
-
keys.push(prop.name.text);
|
|
18
|
-
}
|
|
19
|
-
else if (ts.isSpreadAssignment(prop)) {
|
|
20
|
-
return { canUnpack: false, keys: [] };
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return { canUnpack: true, keys };
|
|
24
|
-
}
|
|
25
|
-
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
|
|
26
|
-
try {
|
|
27
|
-
let keys = [], props = checker.getTypeAtLocation(expr).getProperties();
|
|
28
|
-
for (let i = 0, n = props.length; i < n; i++) {
|
|
29
|
-
let name = props[i].getName();
|
|
30
|
-
if (name.startsWith('__') || name.startsWith('[')) {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
keys.push(name);
|
|
34
|
-
}
|
|
35
|
-
if (keys.length > 0) {
|
|
36
|
-
return { canUnpack: true, keys };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
catch { }
|
|
40
|
-
}
|
|
41
|
-
return { canUnpack: false, keys: [] };
|
|
42
|
-
}
|
|
43
3
|
function inferCOMPILER_TYPES(expr, checker) {
|
|
44
4
|
while (ts.isParenthesizedExpression(expr)) {
|
|
45
5
|
expr = expr.expression;
|
|
@@ -57,9 +17,6 @@ function inferCOMPILER_TYPES(expr, checker) {
|
|
|
57
17
|
if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT) {
|
|
58
18
|
return COMPILER_TYPES.DocumentFragment;
|
|
59
19
|
}
|
|
60
|
-
if (ts.isArrayLiteralExpression(expr)) {
|
|
61
|
-
return COMPILER_TYPES.ArraySlot;
|
|
62
|
-
}
|
|
63
20
|
if (ts.isNumericLiteral(expr) ||
|
|
64
21
|
ts.isStringLiteral(expr) ||
|
|
65
22
|
ts.isNoSubstitutionTemplateLiteral(expr) ||
|
|
@@ -88,106 +45,53 @@ function inferCOMPILER_TYPES(expr, checker) {
|
|
|
88
45
|
if (isTypeFunction(type, checker)) {
|
|
89
46
|
return COMPILER_TYPES.Effect;
|
|
90
47
|
}
|
|
91
|
-
if (isTypeArray(type, checker)) {
|
|
92
|
-
return COMPILER_TYPES.ArraySlot;
|
|
93
|
-
}
|
|
94
48
|
}
|
|
95
49
|
catch {
|
|
96
50
|
}
|
|
97
51
|
}
|
|
98
52
|
return COMPILER_TYPES.Unknown;
|
|
99
53
|
}
|
|
100
|
-
function isTypeArray(type, checker) {
|
|
101
|
-
if (checker.isArrayType(type)) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
return type.getSymbol()?.getName() === 'ReactiveArray';
|
|
105
|
-
}
|
|
106
54
|
function isTypeFunction(type, checker) {
|
|
107
|
-
if (type.getCallSignatures().length > 0) {
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
55
|
if (type.isUnion()) {
|
|
56
|
+
let allFunctions = true, hasFunction = false;
|
|
111
57
|
for (let i = 0, n = type.types.length; i < n; i++) {
|
|
112
58
|
if (isTypeFunction(type.types[i], checker)) {
|
|
113
|
-
|
|
59
|
+
hasFunction = true;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
allFunctions = false;
|
|
114
63
|
}
|
|
115
64
|
}
|
|
65
|
+
return hasFunction && allFunctions;
|
|
116
66
|
}
|
|
117
|
-
return
|
|
67
|
+
return type.getCallSignatures().length > 0;
|
|
118
68
|
}
|
|
119
69
|
const analyzeExpression = (expr, checker) => {
|
|
120
70
|
return inferCOMPILER_TYPES(expr, checker);
|
|
121
71
|
};
|
|
122
|
-
const generateAttributeBinding = (elementVar, name, expr, staticValue,
|
|
72
|
+
const generateAttributeBinding = (elementVar, name, expr, staticValue, addImport) => {
|
|
123
73
|
if (name.startsWith('on') && name.length > 2) {
|
|
124
74
|
let event = name.slice(2).toLowerCase(), key = name.toLowerCase();
|
|
125
75
|
if (LIFECYCLE_EVENTS.has(key)) {
|
|
126
|
-
|
|
127
|
-
return `${key}(${elementVar}, ${expr});`;
|
|
76
|
+
return `${addImport(key)}(${elementVar}, ${expr});`;
|
|
128
77
|
}
|
|
129
78
|
if (DIRECT_ATTACH_EVENTS.has(key)) {
|
|
130
|
-
|
|
131
|
-
return `on(${elementVar}, '${event}', ${expr});`;
|
|
79
|
+
return `${addImport('on')}(${elementVar}, '${event}', ${expr});`;
|
|
132
80
|
}
|
|
133
|
-
|
|
134
|
-
return `delegate(${elementVar}, '${event}', ${expr});`;
|
|
81
|
+
return `${addImport('delegate')}(${elementVar}, '${event}', ${expr});`;
|
|
135
82
|
}
|
|
136
83
|
if (name === 'class') {
|
|
137
|
-
|
|
138
|
-
return `setClass(${elementVar}, '${staticValue}', ${expr});`;
|
|
84
|
+
return `${addImport('setClass')}(${elementVar}, '${staticValue}', ${expr});`;
|
|
139
85
|
}
|
|
140
86
|
if (name === COMPILER_TYPES.Attributes) {
|
|
141
|
-
|
|
142
|
-
return `setProperties(${elementVar}, ${expr});`;
|
|
87
|
+
return `${addImport('setProperties')}(${elementVar}, ${expr});`;
|
|
143
88
|
}
|
|
144
89
|
if (name === 'style') {
|
|
145
|
-
|
|
146
|
-
return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
|
|
90
|
+
return `${addImport('setStyle')}(${elementVar}, '${staticValue}', ${expr});`;
|
|
147
91
|
}
|
|
148
|
-
|
|
149
|
-
return `setProperty(${elementVar}, '${name}', ${expr});`;
|
|
92
|
+
return `${addImport('setProperty')}(${elementVar}, '${name}', ${expr});`;
|
|
150
93
|
};
|
|
151
|
-
const generateSpreadBindings = (
|
|
152
|
-
|
|
153
|
-
expr = expr.expression;
|
|
154
|
-
}
|
|
155
|
-
let analysis = analyzeSpread(expr, checker);
|
|
156
|
-
if (!analysis.canUnpack) {
|
|
157
|
-
neededImports.add('setProperties');
|
|
158
|
-
return [`setProperties(${elementVar}, ${exprCode});`];
|
|
159
|
-
}
|
|
160
|
-
let lines = [];
|
|
161
|
-
if (ts.isObjectLiteralExpression(expr)) {
|
|
162
|
-
for (let i = 0, n = analysis.keys.length; i < n; i++) {
|
|
163
|
-
let key = analysis.keys[i], value = null;
|
|
164
|
-
for (let j = 0, m = expr.properties.length; j < m; j++) {
|
|
165
|
-
let prop = expr.properties[j];
|
|
166
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
167
|
-
let text = ts.isIdentifier(prop.name)
|
|
168
|
-
? prop.name.text
|
|
169
|
-
: ts.isStringLiteral(prop.name) ? prop.name.text : null;
|
|
170
|
-
if (text === key) {
|
|
171
|
-
value = prop.initializer.getText();
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
else if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === key) {
|
|
176
|
-
value = prop.name.text;
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (value !== null) {
|
|
181
|
-
lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
for (let i = 0, n = analysis.keys.length; i < n; i++) {
|
|
187
|
-
let key = analysis.keys[i];
|
|
188
|
-
lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return lines;
|
|
94
|
+
const generateSpreadBindings = (exprCode, elementVar, addImport) => {
|
|
95
|
+
return [`${addImport('setProperties')}(${elementVar}, ${exprCode});`];
|
|
192
96
|
};
|
|
193
97
|
export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
|
package/package.json
CHANGED
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
|
|
package/src/compiler/codegen.ts
CHANGED
|
@@ -17,11 +17,10 @@ type Attribute = {
|
|
|
17
17
|
|
|
18
18
|
type CodegenContext = {
|
|
19
19
|
checker?: ts.TypeChecker;
|
|
20
|
-
|
|
21
|
-
htmlToTemplateId: Map<string, string>;
|
|
22
|
-
neededImports: Set<string>;
|
|
20
|
+
imports: Map<string, string>;
|
|
23
21
|
printer: ts.Printer;
|
|
24
22
|
sourceFile: ts.SourceFile;
|
|
23
|
+
templates: Map<string, string>;
|
|
25
24
|
};
|
|
26
25
|
|
|
27
26
|
type CodegenResult = {
|
|
@@ -46,6 +45,17 @@ const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
|
46
45
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
47
46
|
|
|
48
47
|
|
|
48
|
+
function addImport(ctx: CodegenContext, name: string): string {
|
|
49
|
+
let alias = ctx.imports.get(name);
|
|
50
|
+
|
|
51
|
+
if (!alias) {
|
|
52
|
+
alias = uid(name);
|
|
53
|
+
ctx.imports.set(name, alias);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return alias;
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
function collectNestedTemplateReplacements(
|
|
50
60
|
ctx: CodegenContext,
|
|
51
61
|
node: ts.Node,
|
|
@@ -96,8 +106,7 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
|
|
|
96
106
|
|
|
97
107
|
function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
|
|
98
108
|
if (!exprNode) {
|
|
99
|
-
ctx
|
|
100
|
-
return `slot(${anchor}, ${exprText});`;
|
|
109
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
@@ -108,12 +117,10 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
108
117
|
|
|
109
118
|
switch (slotType) {
|
|
110
119
|
case COMPILER_TYPES.Effect:
|
|
111
|
-
ctx
|
|
112
|
-
return `new EffectSlot(${anchor}, ${exprText});`;
|
|
120
|
+
return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
|
|
113
121
|
|
|
114
122
|
case COMPILER_TYPES.ArraySlot:
|
|
115
|
-
ctx
|
|
116
|
-
return `new ArraySlot(${anchor}, ${exprText});`;
|
|
123
|
+
return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
|
|
117
124
|
|
|
118
125
|
case COMPILER_TYPES.Static:
|
|
119
126
|
return `${anchor}.textContent = ${exprText};`;
|
|
@@ -122,8 +129,7 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
122
129
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
123
130
|
|
|
124
131
|
default:
|
|
125
|
-
ctx
|
|
126
|
-
return `slot(${anchor}, ${exprText});`;
|
|
132
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
|
|
@@ -160,24 +166,30 @@ function generateTemplateCode(
|
|
|
160
166
|
continue;
|
|
161
167
|
}
|
|
162
168
|
|
|
163
|
-
let
|
|
164
|
-
|
|
169
|
+
let ancestor = root,
|
|
170
|
+
start = 0;
|
|
165
171
|
|
|
166
172
|
for (let j = path.length - 1; j >= 0; j--) {
|
|
167
173
|
let prefix = path.slice(0, j).join('.');
|
|
168
174
|
|
|
169
175
|
if (nodes.has(prefix)) {
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
ancestor = nodes.get(prefix)!;
|
|
177
|
+
start = j;
|
|
172
178
|
break;
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
let
|
|
177
|
-
|
|
182
|
+
let alias = addImport(ctx, 'Element'),
|
|
183
|
+
name = uid('element'),
|
|
184
|
+
segments = path.slice(start),
|
|
185
|
+
value = `${ancestor}.${segments.join('!.')}`;
|
|
186
|
+
|
|
187
|
+
// Cast root.firstChild to Element since DocumentFragment.firstChild returns ChildNode
|
|
188
|
+
if (ancestor === root && segments[0] === 'firstChild') {
|
|
189
|
+
value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${alias})`);
|
|
190
|
+
}
|
|
178
191
|
|
|
179
|
-
|
|
180
|
-
declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
|
|
192
|
+
declarations.push(`${name} = ${value} as ${alias}`);
|
|
181
193
|
nodes.set(key, name);
|
|
182
194
|
}
|
|
183
195
|
|
|
@@ -187,7 +199,7 @@ function generateTemplateCode(
|
|
|
187
199
|
);
|
|
188
200
|
|
|
189
201
|
for (let i = 0, n = slots.length; i < n; i++) {
|
|
190
|
-
let
|
|
202
|
+
let element = slots[i].path.length === 0
|
|
191
203
|
? root
|
|
192
204
|
: (nodes.get(slots[i].path.join('.')) || root),
|
|
193
205
|
slot = slots[i];
|
|
@@ -200,11 +212,9 @@ function generateTemplateCode(
|
|
|
200
212
|
|
|
201
213
|
if (name === COMPILER_TYPES.Attributes) {
|
|
202
214
|
let bindings = generateSpreadBindings(
|
|
203
|
-
exprNodes[index],
|
|
204
215
|
exprTexts[index] || 'undefined',
|
|
205
|
-
|
|
206
|
-
ctx
|
|
207
|
-
ctx.neededImports
|
|
216
|
+
element,
|
|
217
|
+
n => addImport(ctx, n)
|
|
208
218
|
);
|
|
209
219
|
|
|
210
220
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
@@ -216,11 +226,11 @@ function generateTemplateCode(
|
|
|
216
226
|
else {
|
|
217
227
|
code.push(
|
|
218
228
|
generateAttributeBinding(
|
|
219
|
-
|
|
229
|
+
element,
|
|
220
230
|
name,
|
|
221
231
|
exprTexts[index++] || 'undefined',
|
|
222
232
|
slot.attributes.statics[name] || '',
|
|
223
|
-
ctx
|
|
233
|
+
n => addImport(ctx, n)
|
|
224
234
|
)
|
|
225
235
|
);
|
|
226
236
|
}
|
|
@@ -228,7 +238,7 @@ function generateTemplateCode(
|
|
|
228
238
|
}
|
|
229
239
|
else {
|
|
230
240
|
code.push(
|
|
231
|
-
generateNodeBinding(ctx,
|
|
241
|
+
generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index])
|
|
232
242
|
);
|
|
233
243
|
index++;
|
|
234
244
|
}
|
|
@@ -241,41 +251,16 @@ function generateTemplateCode(
|
|
|
241
251
|
}
|
|
242
252
|
|
|
243
253
|
function getOrCreateTemplateId(ctx: CodegenContext, html: string): string {
|
|
244
|
-
let id = ctx.
|
|
254
|
+
let id = ctx.templates.get(html);
|
|
245
255
|
|
|
246
256
|
if (!id) {
|
|
247
|
-
id = uid('
|
|
248
|
-
ctx.
|
|
249
|
-
ctx.htmlToTemplateId.set(html, id);
|
|
257
|
+
id = uid('template');
|
|
258
|
+
ctx.templates.set(html, id);
|
|
250
259
|
}
|
|
251
260
|
|
|
252
261
|
return id;
|
|
253
262
|
}
|
|
254
263
|
|
|
255
|
-
function hasArraySlotImport(sourceFile: ts.SourceFile): boolean {
|
|
256
|
-
for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
|
|
257
|
-
let stmt = sourceFile.statements[i];
|
|
258
|
-
|
|
259
|
-
if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
let bindings = stmt.importClause.namedBindings;
|
|
264
|
-
|
|
265
|
-
if (!ts.isNamedImports(bindings)) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
for (let j = 0, m = bindings.elements.length; j < m; j++) {
|
|
270
|
-
if (bindings.elements[j].name.text === 'ArraySlot') {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
264
|
function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
|
|
280
265
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
281
266
|
}
|
|
@@ -297,7 +282,7 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
|
|
|
297
282
|
}
|
|
298
283
|
|
|
299
284
|
|
|
300
|
-
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
|
|
285
|
+
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker, existingAliases?: Map<string, string>): CodegenResult => {
|
|
301
286
|
if (templates.length === 0) {
|
|
302
287
|
return { changed: false, code: originalCode };
|
|
303
288
|
}
|
|
@@ -321,13 +306,13 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
321
306
|
|
|
322
307
|
let ctx: CodegenContext = {
|
|
323
308
|
checker,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
neededImports: new Set(['template']),
|
|
309
|
+
templates: new Map(),
|
|
310
|
+
imports: existingAliases ?? new Map(),
|
|
327
311
|
printer,
|
|
328
312
|
sourceFile
|
|
329
313
|
},
|
|
330
|
-
replacements: Replacement[] = []
|
|
314
|
+
replacements: Replacement[] = [],
|
|
315
|
+
templateAlias = addImport(ctx, 'template');
|
|
331
316
|
|
|
332
317
|
for (let i = 0, n = rootTemplates.length; i < n; i++) {
|
|
333
318
|
let exprTexts: string[] = [],
|
|
@@ -371,23 +356,30 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
371
356
|
let changed = replacements.length > 0,
|
|
372
357
|
code = c.replaceReverse(originalCode, replacements);
|
|
373
358
|
|
|
374
|
-
if (changed && ctx.
|
|
375
|
-
let
|
|
359
|
+
if (changed && ctx.templates.size > 0) {
|
|
360
|
+
let aliasedImports: string[] = [],
|
|
361
|
+
factories: string[] = [];
|
|
362
|
+
|
|
363
|
+
for (let [name, alias] of ctx.imports) {
|
|
364
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
365
|
+
}
|
|
376
366
|
|
|
377
|
-
for (let [
|
|
378
|
-
factories.push(`const ${id} =
|
|
367
|
+
for (let [html, id] of ctx.templates) {
|
|
368
|
+
factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
|
|
379
369
|
}
|
|
380
370
|
|
|
381
|
-
|
|
382
|
-
|
|
371
|
+
// Remove html entrypoint and add aliased imports
|
|
372
|
+
code = imports.modify(code, sourceFile, PACKAGE, {
|
|
373
|
+
add: new Set(aliasedImports),
|
|
383
374
|
remove: [COMPILER_ENTRYPOINT]
|
|
384
375
|
});
|
|
376
|
+
code = factories.join('\n') + '\n\n' + code;
|
|
385
377
|
}
|
|
386
378
|
|
|
387
379
|
return { changed, code };
|
|
388
380
|
};
|
|
389
381
|
|
|
390
|
-
const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile): string => {
|
|
382
|
+
const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile, arraySlotAlias: string): string => {
|
|
391
383
|
if (calls.length === 0) {
|
|
392
384
|
return code;
|
|
393
385
|
}
|
|
@@ -399,7 +391,7 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
399
391
|
|
|
400
392
|
replacements.push({
|
|
401
393
|
end: call.end,
|
|
402
|
-
newText: `new
|
|
394
|
+
newText: `new ${arraySlotAlias}(
|
|
403
395
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
404
396
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
405
397
|
)`,
|
|
@@ -410,14 +402,5 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
410
402
|
return c.replaceReverse(code, replacements);
|
|
411
403
|
};
|
|
412
404
|
|
|
413
|
-
|
|
414
|
-
return ast.hasMatch(sourceFile, n =>
|
|
415
|
-
ts.isNewExpression(n) &&
|
|
416
|
-
ts.isIdentifier(n.expression) &&
|
|
417
|
-
n.expression.text === 'ArraySlot'
|
|
418
|
-
) && !hasArraySlotImport(sourceFile);
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
export { generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
405
|
+
export { generateCode, generateReactiveInlining };
|
|
423
406
|
export type { CodegenResult };
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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';
|
|
4
|
-
import { generateCode, generateReactiveInlining
|
|
4
|
+
import { generateCode, generateReactiveInlining } from './codegen';
|
|
5
5
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
6
6
|
|
|
7
7
|
|
|
@@ -39,21 +39,23 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
39
39
|
|
|
40
40
|
let changed = false,
|
|
41
41
|
codegenChanged = false,
|
|
42
|
-
|
|
42
|
+
existingAliases = new Map<string, string>(),
|
|
43
43
|
reactiveCalls = findReactiveCalls(sourceFile, checker),
|
|
44
44
|
result = code;
|
|
45
45
|
|
|
46
46
|
if (reactiveCalls.length > 0) {
|
|
47
|
+
let arraySlotAlias = uid('ArraySlot');
|
|
48
|
+
|
|
47
49
|
changed = true;
|
|
48
|
-
|
|
50
|
+
existingAliases.set('ArraySlot', arraySlotAlias);
|
|
51
|
+
result = generateReactiveInlining(reactiveCalls, result, sourceFile, arraySlotAlias);
|
|
49
52
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
50
|
-
needsImport = needsArraySlotImport(sourceFile);
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
let templates = findHtmlTemplates(sourceFile, checker);
|
|
54
56
|
|
|
55
57
|
if (templates.length > 0) {
|
|
56
|
-
let codegenResult = generateCode(templates, result, sourceFile, checker);
|
|
58
|
+
let codegenResult = generateCode(templates, result, sourceFile, checker, existingAliases);
|
|
57
59
|
|
|
58
60
|
if (codegenResult.changed) {
|
|
59
61
|
changed = true;
|
|
@@ -62,8 +64,15 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
+
// Add aliased ArraySlot import if reactive calls were processed but codegen didn't run
|
|
68
|
+
if (existingAliases.size > 0 && !codegenChanged) {
|
|
69
|
+
let aliasedImports: string[] = [];
|
|
70
|
+
|
|
71
|
+
for (let [name, alias] of existingAliases) {
|
|
72
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
result = imports.modify(result, sourceFile, PACKAGE, { add: aliasedImports });
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
if (changed) {
|
|
@@ -8,64 +8,6 @@ import {
|
|
|
8
8
|
import { ts } from '@esportsplus/typescript';
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
type SpreadAnalysis = {
|
|
12
|
-
canUnpack: boolean;
|
|
13
|
-
keys: string[];
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function analyzeSpread(expr: ts.Expression, checker?: ts.TypeChecker): SpreadAnalysis {
|
|
18
|
-
while (ts.isParenthesizedExpression(expr)) {
|
|
19
|
-
expr = expr.expression;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (ts.isObjectLiteralExpression(expr)) {
|
|
23
|
-
let keys: string[] = [];
|
|
24
|
-
|
|
25
|
-
for (let i = 0, n = expr.properties.length; i < n; i++) {
|
|
26
|
-
let prop = expr.properties[i];
|
|
27
|
-
|
|
28
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
29
|
-
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
|
|
30
|
-
keys.push(prop.name.text);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
34
|
-
keys.push(prop.name.text);
|
|
35
|
-
}
|
|
36
|
-
else if (ts.isSpreadAssignment(prop)) {
|
|
37
|
-
return { canUnpack: false, keys: [] };
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return { canUnpack: true, keys };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
|
|
45
|
-
try {
|
|
46
|
-
let keys: string[] = [],
|
|
47
|
-
props = checker.getTypeAtLocation(expr).getProperties();
|
|
48
|
-
|
|
49
|
-
for (let i = 0, n = props.length; i < n; i++) {
|
|
50
|
-
let name = props[i].getName();
|
|
51
|
-
|
|
52
|
-
if (name.startsWith('__') || name.startsWith('[')) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
keys.push(name);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (keys.length > 0) {
|
|
60
|
-
return { canUnpack: true, keys };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch { }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { canUnpack: false, keys: [] };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
11
|
function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES {
|
|
70
12
|
while (ts.isParenthesizedExpression(expr)) {
|
|
71
13
|
expr = expr.expression;
|
|
@@ -75,6 +17,7 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
|
|
|
75
17
|
return COMPILER_TYPES.Effect;
|
|
76
18
|
}
|
|
77
19
|
|
|
20
|
+
// Only html.reactive() calls become ArraySlot - handled by generateReactiveInlining
|
|
78
21
|
if (
|
|
79
22
|
ts.isCallExpression(expr) &&
|
|
80
23
|
ts.isPropertyAccessExpression(expr.expression) &&
|
|
@@ -89,10 +32,6 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
|
|
|
89
32
|
return COMPILER_TYPES.DocumentFragment;
|
|
90
33
|
}
|
|
91
34
|
|
|
92
|
-
if (ts.isArrayLiteralExpression(expr)) {
|
|
93
|
-
return COMPILER_TYPES.ArraySlot;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
35
|
if (
|
|
97
36
|
ts.isNumericLiteral(expr) ||
|
|
98
37
|
ts.isStringLiteral(expr) ||
|
|
@@ -131,10 +70,6 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
|
|
|
131
70
|
if (isTypeFunction(type, checker)) {
|
|
132
71
|
return COMPILER_TYPES.Effect;
|
|
133
72
|
}
|
|
134
|
-
|
|
135
|
-
if (isTypeArray(type, checker)) {
|
|
136
|
-
return COMPILER_TYPES.ArraySlot;
|
|
137
|
-
}
|
|
138
73
|
}
|
|
139
74
|
catch {
|
|
140
75
|
}
|
|
@@ -143,28 +78,26 @@ function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COM
|
|
|
143
78
|
return COMPILER_TYPES.Unknown;
|
|
144
79
|
}
|
|
145
80
|
|
|
146
|
-
function isTypeArray(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
147
|
-
if (checker.isArrayType(type)) {
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return type.getSymbol()?.getName() === 'ReactiveArray';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
81
|
function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
82
|
+
// Union types that mix functions with non-functions (e.g., Renderable)
|
|
83
|
+
// should fall through to runtime slot dispatch
|
|
159
84
|
if (type.isUnion()) {
|
|
85
|
+
let allFunctions = true,
|
|
86
|
+
hasFunction = false;
|
|
87
|
+
|
|
160
88
|
for (let i = 0, n = type.types.length; i < n; i++) {
|
|
161
89
|
if (isTypeFunction(type.types[i], checker)) {
|
|
162
|
-
|
|
90
|
+
hasFunction = true;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
allFunctions = false;
|
|
163
94
|
}
|
|
164
95
|
}
|
|
96
|
+
|
|
97
|
+
return hasFunction && allFunctions;
|
|
165
98
|
}
|
|
166
99
|
|
|
167
|
-
return
|
|
100
|
+
return type.getCallSignatures().length > 0;
|
|
168
101
|
}
|
|
169
102
|
|
|
170
103
|
|
|
@@ -172,102 +105,43 @@ const analyzeExpression = (expr: ts.Expression, checker?: ts.TypeChecker): COMPI
|
|
|
172
105
|
return inferCOMPILER_TYPES(expr, checker);
|
|
173
106
|
};
|
|
174
107
|
|
|
175
|
-
const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string,
|
|
108
|
+
const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, addImport: (name: string) => string): string => {
|
|
176
109
|
if (name.startsWith('on') && name.length > 2) {
|
|
177
110
|
let event = name.slice(2).toLowerCase(),
|
|
178
111
|
key = name.toLowerCase();
|
|
179
112
|
|
|
180
113
|
if (LIFECYCLE_EVENTS.has(key)) {
|
|
181
|
-
|
|
182
|
-
return `${key}(${elementVar}, ${expr});`;
|
|
114
|
+
return `${addImport(key)}(${elementVar}, ${expr});`;
|
|
183
115
|
}
|
|
184
116
|
|
|
185
117
|
if (DIRECT_ATTACH_EVENTS.has(key)) {
|
|
186
|
-
|
|
187
|
-
return `on(${elementVar}, '${event}', ${expr});`;
|
|
118
|
+
return `${addImport('on')}(${elementVar}, '${event}', ${expr});`;
|
|
188
119
|
}
|
|
189
120
|
|
|
190
|
-
|
|
191
|
-
return `delegate(${elementVar}, '${event}', ${expr});`;
|
|
121
|
+
return `${addImport('delegate')}(${elementVar}, '${event}', ${expr});`;
|
|
192
122
|
}
|
|
193
123
|
|
|
194
124
|
if (name === 'class') {
|
|
195
|
-
|
|
196
|
-
return `setClass(${elementVar}, '${staticValue}', ${expr});`;
|
|
125
|
+
return `${addImport('setClass')}(${elementVar}, '${staticValue}', ${expr});`;
|
|
197
126
|
}
|
|
198
127
|
|
|
199
128
|
if (name === COMPILER_TYPES.Attributes) {
|
|
200
|
-
|
|
201
|
-
return `setProperties(${elementVar}, ${expr});`;
|
|
129
|
+
return `${addImport('setProperties')}(${elementVar}, ${expr});`;
|
|
202
130
|
}
|
|
203
131
|
|
|
204
132
|
if (name === 'style') {
|
|
205
|
-
|
|
206
|
-
return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
|
|
133
|
+
return `${addImport('setStyle')}(${elementVar}, '${staticValue}', ${expr});`;
|
|
207
134
|
}
|
|
208
135
|
|
|
209
|
-
|
|
210
|
-
return `setProperty(${elementVar}, '${name}', ${expr});`;
|
|
136
|
+
return `${addImport('setProperty')}(${elementVar}, '${name}', ${expr});`;
|
|
211
137
|
};
|
|
212
138
|
|
|
213
139
|
const generateSpreadBindings = (
|
|
214
|
-
expr: ts.Expression,
|
|
215
140
|
exprCode: string,
|
|
216
141
|
elementVar: string,
|
|
217
|
-
|
|
218
|
-
neededImports: Set<string>
|
|
142
|
+
addImport: (name: string) => string
|
|
219
143
|
): string[] => {
|
|
220
|
-
|
|
221
|
-
expr = expr.expression;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
let analysis = analyzeSpread(expr, checker);
|
|
225
|
-
|
|
226
|
-
if (!analysis.canUnpack) {
|
|
227
|
-
neededImports.add('setProperties');
|
|
228
|
-
return [`setProperties(${elementVar}, ${exprCode});`];
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
let lines: string[] = [];
|
|
232
|
-
|
|
233
|
-
if (ts.isObjectLiteralExpression(expr)) {
|
|
234
|
-
for (let i = 0, n = analysis.keys.length; i < n; i++) {
|
|
235
|
-
let key = analysis.keys[i],
|
|
236
|
-
value: string | null = null;
|
|
237
|
-
|
|
238
|
-
for (let j = 0, m = expr.properties.length; j < m; j++) {
|
|
239
|
-
let prop = expr.properties[j];
|
|
240
|
-
|
|
241
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
242
|
-
let text = ts.isIdentifier(prop.name)
|
|
243
|
-
? prop.name.text
|
|
244
|
-
: ts.isStringLiteral(prop.name) ? prop.name.text : null;
|
|
245
|
-
|
|
246
|
-
if (text === key) {
|
|
247
|
-
value = prop.initializer.getText();
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
else if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === key) {
|
|
252
|
-
value = prop.name.text;
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (value !== null) {
|
|
258
|
-
lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
for (let i = 0, n = analysis.keys.length; i < n; i++) {
|
|
264
|
-
let key = analysis.keys[i];
|
|
265
|
-
|
|
266
|
-
lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return lines;
|
|
144
|
+
return [`${addImport('setProperties')}(${elementVar}, ${exprCode});`];
|
|
271
145
|
};
|
|
272
146
|
|
|
273
147
|
|