@esportsplus/template 0.34.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/compiler/codegen.d.ts +1 -2
- package/build/compiler/codegen.js +24 -22
- package/build/compiler/index.d.ts +0 -6
- package/build/compiler/index.js +7 -13
- package/build/compiler/ts-parser.d.ts +2 -2
- package/build/compiler/ts-parser.js +38 -12
- package/build/compiler/type-analyzer.d.ts +2 -2
- package/build/compiler/type-analyzer.js +20 -12
- package/build/constants.d.ts +1 -3
- package/build/constants.js +1 -4
- package/build/index.d.ts +6 -1
- package/build/index.js +11 -1
- package/build/utilities.js +1 -1
- package/package.json +7 -13
- package/src/compiler/codegen.ts +25 -31
- package/src/compiler/index.ts +7 -14
- package/src/compiler/parser.ts +1 -1
- package/src/compiler/ts-parser.ts +53 -12
- package/src/compiler/type-analyzer.ts +21 -13
- package/src/constants.ts +3 -12
- package/src/index.ts +16 -1
- package/src/utilities.ts +2 -2
- package/test/vite.config.ts +1 -1
- package/build/runtime.d.ts +0 -1
- package/build/runtime.js +0 -5
- package/src/runtime.ts +0 -8
|
@@ -4,9 +4,8 @@ type CodegenResult = {
|
|
|
4
4
|
changed: boolean;
|
|
5
5
|
code: string;
|
|
6
6
|
};
|
|
7
|
-
declare const addImport: (code: string) => string;
|
|
8
7
|
declare const generateCode: (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => CodegenResult;
|
|
9
8
|
declare const generateReactiveInlining: (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile) => string;
|
|
10
9
|
declare const needsArraySlotImport: (sourceFile: ts.SourceFile) => boolean;
|
|
11
|
-
export {
|
|
10
|
+
export { generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
12
11
|
export type { CodegenResult };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { ast, code as c, uid } from '@esportsplus/typescript/compiler';
|
|
2
|
+
import { ast, code as c, imports, uid } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '../constants.js';
|
|
3
4
|
import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer.js';
|
|
4
|
-
import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES, PACKAGE, PACKAGE_COMPILER } from '../constants.js';
|
|
5
5
|
import parser from './parser.js';
|
|
6
6
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
7
|
-
const REGEX_PACKAGE_IMPORT = new RegExp(`import\\s*\\{[^}]*\\}\\s*from\\s*['"]${PACKAGE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"];?\\n?`, 'g');
|
|
8
7
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
9
8
|
function collectNestedTemplateReplacements(ctx, node, exprStart, replacements) {
|
|
10
9
|
if (isNestedHtmlTemplate(node)) {
|
|
@@ -36,7 +35,8 @@ function generateNestedTemplateCode(ctx, node) {
|
|
|
36
35
|
}
|
|
37
36
|
function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
38
37
|
if (!exprNode) {
|
|
39
|
-
|
|
38
|
+
ctx.neededImports.add('slot');
|
|
39
|
+
return `slot(${anchor}, ${exprText});`;
|
|
40
40
|
}
|
|
41
41
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
42
42
|
return `${anchor}.parentNode.insertBefore(${generateNestedTemplateCode(ctx, exprNode)}, ${anchor});`;
|
|
@@ -44,15 +44,18 @@ function generateNodeBinding(ctx, anchor, exprText, exprNode) {
|
|
|
44
44
|
let slotType = analyzeExpression(exprNode, ctx.checker);
|
|
45
45
|
switch (slotType) {
|
|
46
46
|
case COMPILER_TYPES.Effect:
|
|
47
|
-
|
|
47
|
+
ctx.neededImports.add('EffectSlot');
|
|
48
|
+
return `new EffectSlot(${anchor}, ${exprText});`;
|
|
48
49
|
case COMPILER_TYPES.ArraySlot:
|
|
49
|
-
|
|
50
|
+
ctx.neededImports.add('ArraySlot');
|
|
51
|
+
return `new ArraySlot(${anchor}, ${exprText});`;
|
|
50
52
|
case COMPILER_TYPES.Static:
|
|
51
53
|
return `${anchor}.textContent = ${exprText};`;
|
|
52
54
|
case COMPILER_TYPES.DocumentFragment:
|
|
53
55
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
54
56
|
default:
|
|
55
|
-
|
|
57
|
+
ctx.neededImports.add('slot');
|
|
58
|
+
return `slot(${anchor}, ${exprText});`;
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArrowBody) {
|
|
@@ -81,7 +84,8 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
let name = uid('element'), suffix = path.slice(startIdx).join('.');
|
|
84
|
-
|
|
87
|
+
ctx.neededImports.add('Element');
|
|
88
|
+
declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
|
|
85
89
|
nodes.set(key, name);
|
|
86
90
|
}
|
|
87
91
|
code.push(isArrowBody ? '{' : `(() => {`, `let ${declarations.join(',\n')};`);
|
|
@@ -94,14 +98,14 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
94
98
|
for (let j = 0, m = names.length; j < m; j++) {
|
|
95
99
|
let name = names[j];
|
|
96
100
|
if (name === COMPILER_TYPES.Attributes) {
|
|
97
|
-
let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker,
|
|
101
|
+
let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker, ctx.neededImports);
|
|
98
102
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
99
103
|
code.push(bindings[k]);
|
|
100
104
|
}
|
|
101
105
|
index++;
|
|
102
106
|
}
|
|
103
107
|
else {
|
|
104
|
-
code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '',
|
|
108
|
+
code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', ctx.neededImports));
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
}
|
|
@@ -155,12 +159,6 @@ function rewriteExpression(ctx, expr) {
|
|
|
155
159
|
collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
|
|
156
160
|
return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
|
|
157
161
|
}
|
|
158
|
-
const addImport = (code) => {
|
|
159
|
-
return `import * as ${COMPILER_NAMESPACE} from '${PACKAGE_COMPILER}';\n` + code;
|
|
160
|
-
};
|
|
161
|
-
const removePackageImport = (code) => {
|
|
162
|
-
return code.replace(REGEX_PACKAGE_IMPORT, '');
|
|
163
|
-
};
|
|
164
162
|
const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
165
163
|
if (templates.length === 0) {
|
|
166
164
|
return { changed: false, code: originalCode };
|
|
@@ -180,6 +178,7 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
|
180
178
|
checker,
|
|
181
179
|
hoistedFactories: new Map(),
|
|
182
180
|
htmlToTemplateId: new Map(),
|
|
181
|
+
neededImports: new Set(['template']),
|
|
183
182
|
printer,
|
|
184
183
|
sourceFile
|
|
185
184
|
}, replacements = [];
|
|
@@ -210,9 +209,12 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
|
210
209
|
if (changed && ctx.hoistedFactories.size > 0) {
|
|
211
210
|
let factories = [];
|
|
212
211
|
for (let [id, html] of ctx.hoistedFactories) {
|
|
213
|
-
factories.push(`const ${id} =
|
|
212
|
+
factories.push(`const ${id} = template(\`${html}\`);`);
|
|
214
213
|
}
|
|
215
|
-
code =
|
|
214
|
+
code = imports.modify(factories.join('\n') + code, sourceFile, PACKAGE, {
|
|
215
|
+
add: ctx.neededImports,
|
|
216
|
+
remove: [COMPILER_ENTRYPOINT]
|
|
217
|
+
});
|
|
216
218
|
}
|
|
217
219
|
return { changed, code };
|
|
218
220
|
};
|
|
@@ -225,7 +227,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
225
227
|
let call = calls[i];
|
|
226
228
|
replacements.push({
|
|
227
229
|
end: call.end,
|
|
228
|
-
newText: `new
|
|
230
|
+
newText: `new ArraySlot(
|
|
229
231
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
230
232
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
231
233
|
)`,
|
|
@@ -236,7 +238,7 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
236
238
|
};
|
|
237
239
|
const needsArraySlotImport = (sourceFile) => {
|
|
238
240
|
return ast.hasMatch(sourceFile, n => ts.isNewExpression(n) &&
|
|
239
|
-
ts.
|
|
240
|
-
n.expression.
|
|
241
|
+
ts.isIdentifier(n.expression) &&
|
|
242
|
+
n.expression.text === 'ArraySlot') && !hasArraySlotImport(sourceFile);
|
|
241
243
|
};
|
|
242
|
-
export {
|
|
244
|
+
export { generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
@@ -6,9 +6,3 @@ type TransformResult = {
|
|
|
6
6
|
};
|
|
7
7
|
declare const transform: (sourceFile: ts.SourceFile, program: ts.Program) => TransformResult;
|
|
8
8
|
export { transform };
|
|
9
|
-
export * from '../attributes.js';
|
|
10
|
-
export * from '../event/index.js';
|
|
11
|
-
export { ArraySlot } from '../slot/array.js';
|
|
12
|
-
export { EffectSlot } from '../slot/effect.js';
|
|
13
|
-
export type * from '../types.js';
|
|
14
|
-
export * from '../utilities.js';
|
package/build/compiler/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { code as c } from '@esportsplus/typescript/compiler';
|
|
2
|
-
import { addImport, generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen.js';
|
|
3
|
-
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants.js';
|
|
4
|
-
import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
|
|
5
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { code as c, imports } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '../constants.js';
|
|
4
|
+
import { generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen.js';
|
|
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;
|
|
8
8
|
const REGEX_FORWARD_SLASH = /\//g;
|
|
@@ -18,15 +18,14 @@ const transform = (sourceFile, program) => {
|
|
|
18
18
|
checker = program.getTypeChecker();
|
|
19
19
|
sourceFile = programSourceFile;
|
|
20
20
|
}
|
|
21
|
-
let changed = false, codegenChanged = false, needsImport = false, reactiveCalls = findReactiveCalls(sourceFile), result = code;
|
|
21
|
+
let changed = false, codegenChanged = false, needsImport = false, reactiveCalls = findReactiveCalls(sourceFile, checker), result = code;
|
|
22
22
|
if (reactiveCalls.length > 0) {
|
|
23
23
|
changed = true;
|
|
24
|
-
checker = undefined;
|
|
25
24
|
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
26
25
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
27
26
|
needsImport = needsArraySlotImport(sourceFile);
|
|
28
27
|
}
|
|
29
|
-
let templates = findHtmlTemplates(sourceFile);
|
|
28
|
+
let templates = findHtmlTemplates(sourceFile, checker);
|
|
30
29
|
if (templates.length > 0) {
|
|
31
30
|
let codegenResult = generateCode(templates, result, sourceFile, checker);
|
|
32
31
|
if (codegenResult.changed) {
|
|
@@ -36,7 +35,7 @@ const transform = (sourceFile, program) => {
|
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
if (needsImport && !codegenChanged) {
|
|
39
|
-
result =
|
|
38
|
+
result = imports.modify(result, sourceFile, PACKAGE, { add: ['ArraySlot'] });
|
|
40
39
|
}
|
|
41
40
|
if (changed) {
|
|
42
41
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
@@ -44,8 +43,3 @@ const transform = (sourceFile, program) => {
|
|
|
44
43
|
return { changed, code: result, sourceFile };
|
|
45
44
|
};
|
|
46
45
|
export { transform };
|
|
47
|
-
export * from '../attributes.js';
|
|
48
|
-
export * from '../event/index.js';
|
|
49
|
-
export { ArraySlot } from '../slot/array.js';
|
|
50
|
-
export { EffectSlot } from '../slot/effect.js';
|
|
51
|
-
export * from '../utilities.js';
|
|
@@ -14,7 +14,7 @@ type TemplateInfo = {
|
|
|
14
14
|
node: ts.TaggedTemplateExpression;
|
|
15
15
|
start: number;
|
|
16
16
|
};
|
|
17
|
-
declare const findHtmlTemplates: (sourceFile: ts.SourceFile) => TemplateInfo[];
|
|
18
|
-
declare const findReactiveCalls: (sourceFile: ts.SourceFile) => ReactiveCallInfo[];
|
|
17
|
+
declare const findHtmlTemplates: (sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => TemplateInfo[];
|
|
18
|
+
declare const findReactiveCalls: (sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => ReactiveCallInfo[];
|
|
19
19
|
export { findHtmlTemplates, findReactiveCalls };
|
|
20
20
|
export type { ReactiveCallInfo, TemplateInfo };
|
|
@@ -1,12 +1,38 @@
|
|
|
1
|
-
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants.js';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
|
|
2
|
+
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '../constants.js';
|
|
3
|
+
function isHtmlFromPackage(node, checker) {
|
|
4
|
+
if (node.text !== COMPILER_ENTRYPOINT) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (!checker) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
let symbol = checker.getSymbolAtLocation(node);
|
|
11
|
+
if (!symbol) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
15
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
16
|
+
}
|
|
17
|
+
let declarations = symbol.getDeclarations();
|
|
18
|
+
if (!declarations || declarations.length === 0) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
for (let i = 0, n = declarations.length; i < n; i++) {
|
|
22
|
+
let fileName = declarations[i].getSourceFile().fileName;
|
|
23
|
+
if (fileName.includes(PACKAGE) || fileName.includes('@esportsplus/template')) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function visitReactiveCalls(node, calls, checker) {
|
|
4
30
|
if (ts.isCallExpression(node) &&
|
|
5
31
|
ts.isPropertyAccessExpression(node.expression) &&
|
|
6
32
|
ts.isIdentifier(node.expression.expression) &&
|
|
7
|
-
node.expression.expression.text === COMPILER_ENTRYPOINT &&
|
|
8
33
|
node.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY &&
|
|
9
|
-
node.arguments.length === 2
|
|
34
|
+
node.arguments.length === 2 &&
|
|
35
|
+
isHtmlFromPackage(node.expression.expression, checker)) {
|
|
10
36
|
calls.push({
|
|
11
37
|
arrayArg: node.arguments[0],
|
|
12
38
|
callbackArg: node.arguments[1],
|
|
@@ -15,13 +41,13 @@ function visitReactiveCalls(node, calls) {
|
|
|
15
41
|
start: node.getStart()
|
|
16
42
|
});
|
|
17
43
|
}
|
|
18
|
-
ts.forEachChild(node, child => visitReactiveCalls(child, calls));
|
|
44
|
+
ts.forEachChild(node, child => visitReactiveCalls(child, calls, checker));
|
|
19
45
|
}
|
|
20
|
-
function visitTemplates(node, depth, templates) {
|
|
46
|
+
function visitTemplates(node, depth, templates, checker) {
|
|
21
47
|
let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
|
|
22
48
|
? depth + 1
|
|
23
49
|
: depth;
|
|
24
|
-
if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag
|
|
50
|
+
if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isHtmlFromPackage(node.tag, checker)) {
|
|
25
51
|
let expressions = [], literals = [], template = node.template;
|
|
26
52
|
if (ts.isNoSubstitutionTemplateLiteral(template)) {
|
|
27
53
|
literals.push(template.text);
|
|
@@ -43,17 +69,17 @@ function visitTemplates(node, depth, templates) {
|
|
|
43
69
|
start: node.getStart()
|
|
44
70
|
});
|
|
45
71
|
}
|
|
46
|
-
ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates));
|
|
72
|
+
ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates, checker));
|
|
47
73
|
}
|
|
48
|
-
const findHtmlTemplates = (sourceFile) => {
|
|
74
|
+
const findHtmlTemplates = (sourceFile, checker) => {
|
|
49
75
|
let templates = [];
|
|
50
|
-
visitTemplates(sourceFile, 0, templates);
|
|
76
|
+
visitTemplates(sourceFile, 0, templates, checker);
|
|
51
77
|
templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
52
78
|
return templates;
|
|
53
79
|
};
|
|
54
|
-
const findReactiveCalls = (sourceFile) => {
|
|
80
|
+
const findReactiveCalls = (sourceFile, checker) => {
|
|
55
81
|
let calls = [];
|
|
56
|
-
visitReactiveCalls(sourceFile, calls);
|
|
82
|
+
visitReactiveCalls(sourceFile, calls, checker);
|
|
57
83
|
return calls;
|
|
58
84
|
};
|
|
59
85
|
export { findHtmlTemplates, findReactiveCalls };
|
|
@@ -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: (expr: ts.Expression, exprCode: string, elementVar: string, checker: ts.TypeChecker | undefined,
|
|
4
|
+
declare const generateAttributeBinding: (elementVar: string, name: string, expr: string, staticValue: string, neededImports: Set<string>) => string;
|
|
5
|
+
declare const generateSpreadBindings: (expr: ts.Expression, exprCode: string, elementVar: string, checker: ts.TypeChecker | undefined, neededImports: Set<string>) => string[];
|
|
6
6
|
export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
|
|
@@ -119,35 +119,43 @@ function isTypeFunction(type, checker) {
|
|
|
119
119
|
const analyzeExpression = (expr, checker) => {
|
|
120
120
|
return inferCOMPILER_TYPES(expr, checker);
|
|
121
121
|
};
|
|
122
|
-
const generateAttributeBinding = (elementVar, name, expr, staticValue,
|
|
122
|
+
const generateAttributeBinding = (elementVar, name, expr, staticValue, neededImports) => {
|
|
123
123
|
if (name.startsWith('on') && name.length > 2) {
|
|
124
124
|
let event = name.slice(2).toLowerCase(), key = name.toLowerCase();
|
|
125
125
|
if (LIFECYCLE_EVENTS.has(key)) {
|
|
126
|
-
|
|
126
|
+
neededImports.add(key);
|
|
127
|
+
return `${key}(${elementVar}, ${expr});`;
|
|
127
128
|
}
|
|
128
129
|
if (DIRECT_ATTACH_EVENTS.has(key)) {
|
|
129
|
-
|
|
130
|
+
neededImports.add('on');
|
|
131
|
+
return `on(${elementVar}, '${event}', ${expr});`;
|
|
130
132
|
}
|
|
131
|
-
|
|
133
|
+
neededImports.add('delegate');
|
|
134
|
+
return `delegate(${elementVar}, '${event}', ${expr});`;
|
|
132
135
|
}
|
|
133
136
|
if (name === 'class') {
|
|
134
|
-
|
|
137
|
+
neededImports.add('setClass');
|
|
138
|
+
return `setClass(${elementVar}, '${staticValue}', ${expr});`;
|
|
135
139
|
}
|
|
136
140
|
if (name === COMPILER_TYPES.Attributes) {
|
|
137
|
-
|
|
141
|
+
neededImports.add('setProperties');
|
|
142
|
+
return `setProperties(${elementVar}, ${expr});`;
|
|
138
143
|
}
|
|
139
144
|
if (name === 'style') {
|
|
140
|
-
|
|
145
|
+
neededImports.add('setStyle');
|
|
146
|
+
return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
|
|
141
147
|
}
|
|
142
|
-
|
|
148
|
+
neededImports.add('setProperty');
|
|
149
|
+
return `setProperty(${elementVar}, '${name}', ${expr});`;
|
|
143
150
|
};
|
|
144
|
-
const generateSpreadBindings = (expr, exprCode, elementVar, checker,
|
|
151
|
+
const generateSpreadBindings = (expr, exprCode, elementVar, checker, neededImports) => {
|
|
145
152
|
while (ts.isParenthesizedExpression(expr)) {
|
|
146
153
|
expr = expr.expression;
|
|
147
154
|
}
|
|
148
155
|
let analysis = analyzeSpread(expr, checker);
|
|
149
156
|
if (!analysis.canUnpack) {
|
|
150
|
-
|
|
157
|
+
neededImports.add('setProperties');
|
|
158
|
+
return [`setProperties(${elementVar}, ${exprCode});`];
|
|
151
159
|
}
|
|
152
160
|
let lines = [];
|
|
153
161
|
if (ts.isObjectLiteralExpression(expr)) {
|
|
@@ -170,14 +178,14 @@ const generateSpreadBindings = (expr, exprCode, elementVar, checker, ns) => {
|
|
|
170
178
|
}
|
|
171
179
|
}
|
|
172
180
|
if (value !== null) {
|
|
173
|
-
lines.push(generateAttributeBinding(elementVar, key, value, '',
|
|
181
|
+
lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
|
|
174
182
|
}
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
185
|
else {
|
|
178
186
|
for (let i = 0, n = analysis.keys.length; i < n; i++) {
|
|
179
187
|
let key = analysis.keys[i];
|
|
180
|
-
lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '',
|
|
188
|
+
lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
|
|
181
189
|
}
|
|
182
190
|
}
|
|
183
191
|
return lines;
|
package/build/constants.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ declare const ARRAY_SLOT: unique symbol;
|
|
|
2
2
|
declare const CLEANUP: unique symbol;
|
|
3
3
|
declare const COMPILER_ENTRYPOINT = "html";
|
|
4
4
|
declare const COMPILER_ENTRYPOINT_REACTIVITY = "reactive";
|
|
5
|
-
declare const COMPILER_NAMESPACE: string;
|
|
6
5
|
declare const enum COMPILER_TYPES {
|
|
7
6
|
ArraySlot = "array-slot",
|
|
8
7
|
Attributes = "attributes",
|
|
@@ -17,10 +16,9 @@ declare const enum COMPILER_TYPES {
|
|
|
17
16
|
declare const DIRECT_ATTACH_EVENTS: Set<string>;
|
|
18
17
|
declare const LIFECYCLE_EVENTS: Set<string>;
|
|
19
18
|
declare const PACKAGE = "@esportsplus/template";
|
|
20
|
-
declare const PACKAGE_COMPILER = "@esportsplus/template/compiler";
|
|
21
19
|
declare const SLOT_HTML = "<!--$-->";
|
|
22
20
|
declare const STATE_HYDRATING = 0;
|
|
23
21
|
declare const STATE_NONE = 1;
|
|
24
22
|
declare const STATE_WAITING = 2;
|
|
25
23
|
declare const STORE: unique symbol;
|
|
26
|
-
export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY,
|
|
24
|
+
export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
|
package/build/constants.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { uid } from '@esportsplus/typescript/compiler';
|
|
2
1
|
const ARRAY_SLOT = Symbol('template.array.slot');
|
|
3
2
|
const CLEANUP = Symbol('template.cleanup');
|
|
4
3
|
const COMPILER_ENTRYPOINT = 'html';
|
|
5
4
|
const COMPILER_ENTRYPOINT_REACTIVITY = 'reactive';
|
|
6
|
-
const COMPILER_NAMESPACE = uid('template');
|
|
7
5
|
var COMPILER_TYPES;
|
|
8
6
|
(function (COMPILER_TYPES) {
|
|
9
7
|
COMPILER_TYPES["ArraySlot"] = "array-slot";
|
|
@@ -30,10 +28,9 @@ const LIFECYCLE_EVENTS = new Set([
|
|
|
30
28
|
'onconnect', 'ondisconnect', 'onrender', 'onresize', 'ontick'
|
|
31
29
|
]);
|
|
32
30
|
const PACKAGE = '@esportsplus/template';
|
|
33
|
-
const PACKAGE_COMPILER = '@esportsplus/template/compiler';
|
|
34
31
|
const SLOT_HTML = '<!--$-->';
|
|
35
32
|
const STATE_HYDRATING = 0;
|
|
36
33
|
const STATE_NONE = 1;
|
|
37
34
|
const STATE_WAITING = 2;
|
|
38
35
|
const STORE = Symbol('template.store');
|
|
39
|
-
export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY,
|
|
36
|
+
export { ARRAY_SLOT, CLEANUP, COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES, DIRECT_ATTACH_EVENTS, LIFECYCLE_EVENTS, PACKAGE, SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE, };
|
package/build/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import './runtime.js';
|
|
2
1
|
export { default as html } from './html.js';
|
|
3
2
|
export { default as render } from './render.js';
|
|
4
3
|
export { default as svg } from './svg.js';
|
|
4
|
+
export * from './attributes.js';
|
|
5
|
+
export * from './event/index.js';
|
|
6
|
+
export { ArraySlot } from './slot/array.js';
|
|
7
|
+
export { EffectSlot } from './slot/effect.js';
|
|
8
|
+
export { default as slot } from './slot/index.js';
|
|
5
9
|
export type { Attributes, Element, Renderable } from './types.js';
|
|
10
|
+
export * from './utilities.js';
|
package/build/index.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import { CLEANUP, STORE } from './constants.js';
|
|
2
|
+
if (typeof Node !== 'undefined') {
|
|
3
|
+
Node.prototype[CLEANUP] = null;
|
|
4
|
+
Node.prototype[STORE] = null;
|
|
5
|
+
}
|
|
2
6
|
export { default as html } from './html.js';
|
|
3
7
|
export { default as render } from './render.js';
|
|
4
8
|
export { default as svg } from './svg.js';
|
|
9
|
+
export * from './attributes.js';
|
|
10
|
+
export * from './event/index.js';
|
|
11
|
+
export { ArraySlot } from './slot/array.js';
|
|
12
|
+
export { EffectSlot } from './slot/effect.js';
|
|
13
|
+
export { default as slot } from './slot/index.js';
|
|
14
|
+
export * from './utilities.js';
|
package/build/utilities.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SLOT_HTML } from './constants.js';
|
|
2
|
-
let tmpl = document
|
|
2
|
+
let tmpl = document.createElement('template'), txt = document.createTextNode('');
|
|
3
3
|
const clone = typeof navigator !== 'undefined' && navigator.userAgent.includes('Firefox')
|
|
4
4
|
? document.importNode.bind(document)
|
|
5
5
|
: (node, deep = true) => node.cloneNode(deep);
|
package/package.json
CHANGED
|
@@ -2,34 +2,28 @@
|
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@esportsplus/queue": "^0.2.0",
|
|
5
|
-
"@esportsplus/reactivity": "^0.
|
|
5
|
+
"@esportsplus/reactivity": "^0.27.0",
|
|
6
6
|
"@esportsplus/utilities": "^0.27.2",
|
|
7
7
|
"serve": "^14.2.5"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@esportsplus/typescript": "^0.
|
|
10
|
+
"@esportsplus/typescript": "^0.22.0",
|
|
11
11
|
"@types/node": "^25.0.3",
|
|
12
12
|
"vite": "^7.3.0",
|
|
13
13
|
"vite-tsconfig-paths": "^6.0.3"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
},
|
|
20
|
-
"./compiler": {
|
|
21
|
-
"import": "./build/compiler/index.js",
|
|
22
|
-
"types": "./build/compiler/index.d.ts"
|
|
17
|
+
"types": "./build/index.d.ts",
|
|
18
|
+
"default": "./build/index.js"
|
|
23
19
|
},
|
|
24
20
|
"./compiler/tsc": {
|
|
25
21
|
"types": "./build/compiler/plugins/tsc.d.ts",
|
|
26
|
-
"
|
|
27
|
-
"require": "./build/compiler/plugins/tsc.js"
|
|
22
|
+
"default": "./build/compiler/plugins/tsc.js"
|
|
28
23
|
},
|
|
29
24
|
"./compiler/vite": {
|
|
30
25
|
"types": "./build/compiler/plugins/vite.d.ts",
|
|
31
|
-
"
|
|
32
|
-
"require": "./build/compiler/plugins/vite.js"
|
|
26
|
+
"default": "./build/compiler/plugins/vite.js"
|
|
33
27
|
}
|
|
34
28
|
},
|
|
35
29
|
"main": "./build/index.js",
|
|
@@ -41,7 +35,7 @@
|
|
|
41
35
|
},
|
|
42
36
|
"type": "module",
|
|
43
37
|
"types": "./build/index.d.ts",
|
|
44
|
-
"version": "0.
|
|
38
|
+
"version": "0.35.0",
|
|
45
39
|
"scripts": {
|
|
46
40
|
"build": "tsc",
|
|
47
41
|
"build:test": "vite build --config test/vite.config.ts",
|
package/src/compiler/codegen.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { ast, code as c, uid, type Replacement } from '@esportsplus/typescript/compiler';
|
|
2
|
+
import { ast, code as c, imports, uid, type Replacement } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '~/constants';
|
|
3
4
|
import type { ReactiveCallInfo, TemplateInfo } from './ts-parser';
|
|
4
5
|
import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer';
|
|
5
|
-
import {
|
|
6
|
-
COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES,
|
|
7
|
-
PACKAGE, PACKAGE_COMPILER
|
|
8
|
-
} from '~/constants';
|
|
9
6
|
import parser from './parser';
|
|
10
7
|
|
|
11
8
|
|
|
@@ -22,6 +19,7 @@ type CodegenContext = {
|
|
|
22
19
|
checker?: ts.TypeChecker;
|
|
23
20
|
hoistedFactories: Map<string, string>;
|
|
24
21
|
htmlToTemplateId: Map<string, string>;
|
|
22
|
+
neededImports: Set<string>;
|
|
25
23
|
printer: ts.Printer;
|
|
26
24
|
sourceFile: ts.SourceFile;
|
|
27
25
|
};
|
|
@@ -44,11 +42,6 @@ type ParseResult = {
|
|
|
44
42
|
|
|
45
43
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
46
44
|
|
|
47
|
-
const REGEX_PACKAGE_IMPORT = new RegExp(
|
|
48
|
-
`import\\s*\\{[^}]*\\}\\s*from\\s*['"]${PACKAGE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"];?\\n?`,
|
|
49
|
-
'g'
|
|
50
|
-
);
|
|
51
|
-
|
|
52
45
|
|
|
53
46
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
54
47
|
|
|
@@ -103,7 +96,8 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
|
|
|
103
96
|
|
|
104
97
|
function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
|
|
105
98
|
if (!exprNode) {
|
|
106
|
-
|
|
99
|
+
ctx.neededImports.add('slot');
|
|
100
|
+
return `slot(${anchor}, ${exprText});`;
|
|
107
101
|
}
|
|
108
102
|
|
|
109
103
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
@@ -114,10 +108,12 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
114
108
|
|
|
115
109
|
switch (slotType) {
|
|
116
110
|
case COMPILER_TYPES.Effect:
|
|
117
|
-
|
|
111
|
+
ctx.neededImports.add('EffectSlot');
|
|
112
|
+
return `new EffectSlot(${anchor}, ${exprText});`;
|
|
118
113
|
|
|
119
114
|
case COMPILER_TYPES.ArraySlot:
|
|
120
|
-
|
|
115
|
+
ctx.neededImports.add('ArraySlot');
|
|
116
|
+
return `new ArraySlot(${anchor}, ${exprText});`;
|
|
121
117
|
|
|
122
118
|
case COMPILER_TYPES.Static:
|
|
123
119
|
return `${anchor}.textContent = ${exprText};`;
|
|
@@ -126,7 +122,8 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
126
122
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
127
123
|
|
|
128
124
|
default:
|
|
129
|
-
|
|
125
|
+
ctx.neededImports.add('slot');
|
|
126
|
+
return `slot(${anchor}, ${exprText});`;
|
|
130
127
|
}
|
|
131
128
|
}
|
|
132
129
|
|
|
@@ -179,7 +176,8 @@ function generateTemplateCode(
|
|
|
179
176
|
let name = uid('element'),
|
|
180
177
|
suffix = path.slice(startIdx).join('.');
|
|
181
178
|
|
|
182
|
-
|
|
179
|
+
ctx.neededImports.add('Element');
|
|
180
|
+
declarations.push(`${name} = ${ancestorVar}.${suffix} as Element`);
|
|
183
181
|
nodes.set(key, name);
|
|
184
182
|
}
|
|
185
183
|
|
|
@@ -206,7 +204,7 @@ function generateTemplateCode(
|
|
|
206
204
|
exprTexts[index] || 'undefined',
|
|
207
205
|
elementVar,
|
|
208
206
|
ctx.checker,
|
|
209
|
-
|
|
207
|
+
ctx.neededImports
|
|
210
208
|
);
|
|
211
209
|
|
|
212
210
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
@@ -222,7 +220,7 @@ function generateTemplateCode(
|
|
|
222
220
|
name,
|
|
223
221
|
exprTexts[index++] || 'undefined',
|
|
224
222
|
slot.attributes.statics[name] || '',
|
|
225
|
-
|
|
223
|
+
ctx.neededImports
|
|
226
224
|
)
|
|
227
225
|
);
|
|
228
226
|
}
|
|
@@ -299,14 +297,6 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
|
|
|
299
297
|
}
|
|
300
298
|
|
|
301
299
|
|
|
302
|
-
const addImport = (code: string): string => {
|
|
303
|
-
return `import * as ${COMPILER_NAMESPACE} from '${PACKAGE_COMPILER}';\n` + code;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const removePackageImport = (code: string): string => {
|
|
307
|
-
return code.replace(REGEX_PACKAGE_IMPORT, '');
|
|
308
|
-
};
|
|
309
|
-
|
|
310
300
|
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
|
|
311
301
|
if (templates.length === 0) {
|
|
312
302
|
return { changed: false, code: originalCode };
|
|
@@ -333,6 +323,7 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
333
323
|
checker,
|
|
334
324
|
hoistedFactories: new Map(),
|
|
335
325
|
htmlToTemplateId: new Map(),
|
|
326
|
+
neededImports: new Set(['template']),
|
|
336
327
|
printer,
|
|
337
328
|
sourceFile
|
|
338
329
|
},
|
|
@@ -384,10 +375,13 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
384
375
|
let factories: string[] = [];
|
|
385
376
|
|
|
386
377
|
for (let [id, html] of ctx.hoistedFactories) {
|
|
387
|
-
factories.push(`const ${id} =
|
|
378
|
+
factories.push(`const ${id} = template(\`${html}\`);`);
|
|
388
379
|
}
|
|
389
380
|
|
|
390
|
-
code =
|
|
381
|
+
code = imports.modify(factories.join('\n') + code, sourceFile, PACKAGE, {
|
|
382
|
+
add: ctx.neededImports,
|
|
383
|
+
remove: [COMPILER_ENTRYPOINT]
|
|
384
|
+
});
|
|
391
385
|
}
|
|
392
386
|
|
|
393
387
|
return { changed, code };
|
|
@@ -405,7 +399,7 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
405
399
|
|
|
406
400
|
replacements.push({
|
|
407
401
|
end: call.end,
|
|
408
|
-
newText: `new
|
|
402
|
+
newText: `new ArraySlot(
|
|
409
403
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
410
404
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
411
405
|
)`,
|
|
@@ -419,11 +413,11 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
419
413
|
const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
|
|
420
414
|
return ast.hasMatch(sourceFile, n =>
|
|
421
415
|
ts.isNewExpression(n) &&
|
|
422
|
-
ts.
|
|
423
|
-
n.expression.
|
|
416
|
+
ts.isIdentifier(n.expression) &&
|
|
417
|
+
n.expression.text === 'ArraySlot'
|
|
424
418
|
) && !hasArraySlotImport(sourceFile);
|
|
425
419
|
};
|
|
426
420
|
|
|
427
421
|
|
|
428
|
-
export {
|
|
422
|
+
export { generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
429
423
|
export type { CodegenResult };
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { code as c } from '@esportsplus/typescript/compiler';
|
|
2
|
-
import { addImport, generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen';
|
|
3
|
-
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants';
|
|
4
|
-
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
5
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { code as c, imports } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
|
|
4
|
+
import { generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen';
|
|
5
|
+
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
type TransformResult = {
|
|
@@ -40,18 +40,17 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
40
40
|
let changed = false,
|
|
41
41
|
codegenChanged = false,
|
|
42
42
|
needsImport = false,
|
|
43
|
-
reactiveCalls = findReactiveCalls(sourceFile),
|
|
43
|
+
reactiveCalls = findReactiveCalls(sourceFile, checker),
|
|
44
44
|
result = code;
|
|
45
45
|
|
|
46
46
|
if (reactiveCalls.length > 0) {
|
|
47
47
|
changed = true;
|
|
48
|
-
checker = undefined;
|
|
49
48
|
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
50
49
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
51
50
|
needsImport = needsArraySlotImport(sourceFile);
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
let templates = findHtmlTemplates(sourceFile);
|
|
53
|
+
let templates = findHtmlTemplates(sourceFile, checker);
|
|
55
54
|
|
|
56
55
|
if (templates.length > 0) {
|
|
57
56
|
let codegenResult = generateCode(templates, result, sourceFile, checker);
|
|
@@ -64,7 +63,7 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
if (needsImport && !codegenChanged) {
|
|
67
|
-
result =
|
|
66
|
+
result = imports.modify(result, sourceFile, PACKAGE, { add: ['ArraySlot'] });
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
if (changed) {
|
|
@@ -76,9 +75,3 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
76
75
|
|
|
77
76
|
|
|
78
77
|
export { transform };
|
|
79
|
-
export * from '~/attributes';
|
|
80
|
-
export * from '~/event';
|
|
81
|
-
export { ArraySlot } from '~/slot/array';
|
|
82
|
-
export { EffectSlot } from '~/slot/effect';
|
|
83
|
-
export type * from '~/types';
|
|
84
|
-
export * from '~/utilities';
|
package/src/compiler/parser.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
type ReactiveCallInfo = {
|
|
@@ -20,14 +20,55 @@ type TemplateInfo = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
function
|
|
23
|
+
function isHtmlFromPackage(node: ts.Identifier, checker: ts.TypeChecker | undefined): boolean {
|
|
24
|
+
// Fast path: check identifier name first
|
|
25
|
+
if (node.text !== COMPILER_ENTRYPOINT) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Without checker, fall back to string-based check
|
|
30
|
+
if (!checker) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let symbol = checker.getSymbolAtLocation(node);
|
|
35
|
+
|
|
36
|
+
if (!symbol) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Follow aliases (re-exports) to find original symbol
|
|
41
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
42
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let declarations = symbol.getDeclarations();
|
|
46
|
+
|
|
47
|
+
if (!declarations || declarations.length === 0) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if any declaration is from our package
|
|
52
|
+
for (let i = 0, n = declarations.length; i < n; i++) {
|
|
53
|
+
let fileName = declarations[i].getSourceFile().fileName;
|
|
54
|
+
|
|
55
|
+
// Check for package in node_modules path or direct package reference
|
|
56
|
+
if (fileName.includes(PACKAGE) || fileName.includes('@esportsplus/template')) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[], checker: ts.TypeChecker | undefined): void {
|
|
24
65
|
if (
|
|
25
66
|
ts.isCallExpression(node) &&
|
|
26
67
|
ts.isPropertyAccessExpression(node.expression) &&
|
|
27
68
|
ts.isIdentifier(node.expression.expression) &&
|
|
28
|
-
node.expression.expression.text === COMPILER_ENTRYPOINT &&
|
|
29
69
|
node.expression.name.text === COMPILER_ENTRYPOINT_REACTIVITY &&
|
|
30
|
-
node.arguments.length === 2
|
|
70
|
+
node.arguments.length === 2 &&
|
|
71
|
+
isHtmlFromPackage(node.expression.expression, checker)
|
|
31
72
|
) {
|
|
32
73
|
calls.push({
|
|
33
74
|
arrayArg: node.arguments[0],
|
|
@@ -38,15 +79,15 @@ function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[]): void {
|
|
|
38
79
|
});
|
|
39
80
|
}
|
|
40
81
|
|
|
41
|
-
ts.forEachChild(node, child => visitReactiveCalls(child, calls));
|
|
82
|
+
ts.forEachChild(node, child => visitReactiveCalls(child, calls, checker));
|
|
42
83
|
}
|
|
43
84
|
|
|
44
|
-
function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[]): void {
|
|
85
|
+
function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[], checker: ts.TypeChecker | undefined): void {
|
|
45
86
|
let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
|
|
46
87
|
? depth + 1
|
|
47
88
|
: depth;
|
|
48
89
|
|
|
49
|
-
if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag
|
|
90
|
+
if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && isHtmlFromPackage(node.tag, checker)) {
|
|
50
91
|
let expressions: ts.Expression[] = [],
|
|
51
92
|
literals: string[] = [],
|
|
52
93
|
template = node.template;
|
|
@@ -75,14 +116,14 @@ function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[])
|
|
|
75
116
|
});
|
|
76
117
|
}
|
|
77
118
|
|
|
78
|
-
ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates));
|
|
119
|
+
ts.forEachChild(node, child => visitTemplates(child, nextDepth, templates, checker));
|
|
79
120
|
}
|
|
80
121
|
|
|
81
122
|
|
|
82
|
-
const findHtmlTemplates = (sourceFile: ts.SourceFile): TemplateInfo[] => {
|
|
123
|
+
const findHtmlTemplates = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): TemplateInfo[] => {
|
|
83
124
|
let templates: TemplateInfo[] = [];
|
|
84
125
|
|
|
85
|
-
visitTemplates(sourceFile, 0, templates);
|
|
126
|
+
visitTemplates(sourceFile, 0, templates, checker);
|
|
86
127
|
|
|
87
128
|
// Sort by depth descending (deepest first), then by position for stable ordering
|
|
88
129
|
templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
@@ -90,10 +131,10 @@ const findHtmlTemplates = (sourceFile: ts.SourceFile): TemplateInfo[] => {
|
|
|
90
131
|
return templates;
|
|
91
132
|
};
|
|
92
133
|
|
|
93
|
-
const findReactiveCalls = (sourceFile: ts.SourceFile): ReactiveCallInfo[] => {
|
|
134
|
+
const findReactiveCalls = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): ReactiveCallInfo[] => {
|
|
94
135
|
let calls: ReactiveCallInfo[] = [];
|
|
95
136
|
|
|
96
|
-
visitReactiveCalls(sourceFile, calls);
|
|
137
|
+
visitReactiveCalls(sourceFile, calls, checker);
|
|
97
138
|
|
|
98
139
|
return calls;
|
|
99
140
|
};
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
COMPILER_TYPES,
|
|
5
5
|
DIRECT_ATTACH_EVENTS,
|
|
6
6
|
LIFECYCLE_EVENTS
|
|
7
|
-
} from '
|
|
7
|
+
} from '~/constants';
|
|
8
8
|
import { ts } from '@esportsplus/typescript';
|
|
9
9
|
|
|
10
10
|
|
|
@@ -172,35 +172,42 @@ const analyzeExpression = (expr: ts.Expression, checker?: ts.TypeChecker): COMPI
|
|
|
172
172
|
return inferCOMPILER_TYPES(expr, checker);
|
|
173
173
|
};
|
|
174
174
|
|
|
175
|
-
const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string,
|
|
175
|
+
const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, neededImports: Set<string>): string => {
|
|
176
176
|
if (name.startsWith('on') && name.length > 2) {
|
|
177
177
|
let event = name.slice(2).toLowerCase(),
|
|
178
178
|
key = name.toLowerCase();
|
|
179
179
|
|
|
180
180
|
if (LIFECYCLE_EVENTS.has(key)) {
|
|
181
|
-
|
|
181
|
+
neededImports.add(key);
|
|
182
|
+
return `${key}(${elementVar}, ${expr});`;
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
if (DIRECT_ATTACH_EVENTS.has(key)) {
|
|
185
|
-
|
|
186
|
+
neededImports.add('on');
|
|
187
|
+
return `on(${elementVar}, '${event}', ${expr});`;
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
|
|
190
|
+
neededImports.add('delegate');
|
|
191
|
+
return `delegate(${elementVar}, '${event}', ${expr});`;
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
if (name === 'class') {
|
|
192
|
-
|
|
195
|
+
neededImports.add('setClass');
|
|
196
|
+
return `setClass(${elementVar}, '${staticValue}', ${expr});`;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
if (name === COMPILER_TYPES.Attributes) {
|
|
196
|
-
|
|
200
|
+
neededImports.add('setProperties');
|
|
201
|
+
return `setProperties(${elementVar}, ${expr});`;
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
if (name === 'style') {
|
|
200
|
-
|
|
205
|
+
neededImports.add('setStyle');
|
|
206
|
+
return `setStyle(${elementVar}, '${staticValue}', ${expr});`;
|
|
201
207
|
}
|
|
202
208
|
|
|
203
|
-
|
|
209
|
+
neededImports.add('setProperty');
|
|
210
|
+
return `setProperty(${elementVar}, '${name}', ${expr});`;
|
|
204
211
|
};
|
|
205
212
|
|
|
206
213
|
const generateSpreadBindings = (
|
|
@@ -208,7 +215,7 @@ const generateSpreadBindings = (
|
|
|
208
215
|
exprCode: string,
|
|
209
216
|
elementVar: string,
|
|
210
217
|
checker: ts.TypeChecker | undefined,
|
|
211
|
-
|
|
218
|
+
neededImports: Set<string>
|
|
212
219
|
): string[] => {
|
|
213
220
|
while (ts.isParenthesizedExpression(expr)) {
|
|
214
221
|
expr = expr.expression;
|
|
@@ -217,7 +224,8 @@ const generateSpreadBindings = (
|
|
|
217
224
|
let analysis = analyzeSpread(expr, checker);
|
|
218
225
|
|
|
219
226
|
if (!analysis.canUnpack) {
|
|
220
|
-
|
|
227
|
+
neededImports.add('setProperties');
|
|
228
|
+
return [`setProperties(${elementVar}, ${exprCode});`];
|
|
221
229
|
}
|
|
222
230
|
|
|
223
231
|
let lines: string[] = [];
|
|
@@ -247,7 +255,7 @@ const generateSpreadBindings = (
|
|
|
247
255
|
}
|
|
248
256
|
|
|
249
257
|
if (value !== null) {
|
|
250
|
-
lines.push(generateAttributeBinding(elementVar, key, value, '',
|
|
258
|
+
lines.push(generateAttributeBinding(elementVar, key, value, '', neededImports));
|
|
251
259
|
}
|
|
252
260
|
}
|
|
253
261
|
}
|
|
@@ -255,7 +263,7 @@ const generateSpreadBindings = (
|
|
|
255
263
|
for (let i = 0, n = analysis.keys.length; i < n; i++) {
|
|
256
264
|
let key = analysis.keys[i];
|
|
257
265
|
|
|
258
|
-
lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '',
|
|
266
|
+
lines.push(generateAttributeBinding(elementVar, key, `${exprCode}.${key}`, '', neededImports));
|
|
259
267
|
}
|
|
260
268
|
}
|
|
261
269
|
|
package/src/constants.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { uid } from '@esportsplus/typescript/compiler';
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
const ARRAY_SLOT = Symbol('template.array.slot');
|
|
5
2
|
|
|
6
3
|
const CLEANUP = Symbol('template.cleanup');
|
|
7
4
|
|
|
8
|
-
|
|
9
5
|
const COMPILER_ENTRYPOINT = 'html';
|
|
10
6
|
|
|
11
7
|
const COMPILER_ENTRYPOINT_REACTIVITY = 'reactive';
|
|
12
8
|
|
|
13
|
-
const COMPILER_NAMESPACE = uid('template');
|
|
14
|
-
|
|
15
9
|
const enum COMPILER_TYPES {
|
|
16
10
|
ArraySlot = 'array-slot',
|
|
17
11
|
Attributes = 'attributes',
|
|
@@ -40,9 +34,6 @@ const LIFECYCLE_EVENTS = new Set<string>([
|
|
|
40
34
|
|
|
41
35
|
const PACKAGE = '@esportsplus/template';
|
|
42
36
|
|
|
43
|
-
const PACKAGE_COMPILER = '@esportsplus/template/compiler';
|
|
44
|
-
|
|
45
|
-
|
|
46
37
|
const SLOT_HTML = '<!--$-->';
|
|
47
38
|
|
|
48
39
|
const STATE_HYDRATING = 0;
|
|
@@ -57,9 +48,9 @@ const STORE = Symbol('template.store');
|
|
|
57
48
|
export {
|
|
58
49
|
ARRAY_SLOT,
|
|
59
50
|
CLEANUP,
|
|
60
|
-
COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY,
|
|
51
|
+
COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES,
|
|
61
52
|
DIRECT_ATTACH_EVENTS,
|
|
62
53
|
LIFECYCLE_EVENTS,
|
|
63
|
-
PACKAGE,
|
|
54
|
+
PACKAGE,
|
|
64
55
|
SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE,
|
|
65
|
-
};
|
|
56
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import { CLEANUP, STORE } from './constants';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// Pre-allocate on Node prototype to optimize property access
|
|
5
|
+
if (typeof Node !== 'undefined') {
|
|
6
|
+
(Node.prototype as any)[CLEANUP] = null;
|
|
7
|
+
(Node.prototype as any)[STORE] = null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
2
11
|
export { default as html } from './html';
|
|
3
12
|
export { default as render } from './render';
|
|
4
13
|
export { default as svg } from './svg';
|
|
14
|
+
export * from './attributes';
|
|
15
|
+
export * from './event';
|
|
16
|
+
export { ArraySlot } from './slot/array';
|
|
17
|
+
export { EffectSlot } from './slot/effect';
|
|
18
|
+
export { default as slot } from './slot';
|
|
5
19
|
export type { Attributes, Element, Renderable } from './types';
|
|
20
|
+
export * from './utilities';
|
package/src/utilities.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SLOT_HTML } from './constants';
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
let tmpl = document
|
|
5
|
-
txt = document
|
|
4
|
+
let tmpl = document.createElement('template'),
|
|
5
|
+
txt = document.createTextNode('');
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
// Firefox's importNode outperforms cloneNode in certain scenarios
|
package/test/vite.config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from 'vite';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
4
|
-
import templatePlugin from '../src/compiler/vite';
|
|
4
|
+
import templatePlugin from '../src/compiler/plugins/vite';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export default defineConfig({
|
package/build/runtime.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/build/runtime.js
DELETED
package/src/runtime.ts
DELETED