@esportsplus/template 0.38.2 → 0.40.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/README.md +5 -26
- package/build/compiler/codegen.d.ts +3 -2
- package/build/compiler/codegen.js +102 -142
- package/build/compiler/constants.d.ts +16 -0
- package/build/compiler/constants.js +19 -0
- package/build/compiler/index.d.ts +6 -3
- package/build/compiler/index.js +29 -38
- package/build/compiler/parser.d.ts +3 -3
- package/build/compiler/parser.js +5 -4
- package/build/compiler/plugins/tsc.d.ts +3 -2
- package/build/compiler/plugins/tsc.js +4 -2
- package/build/compiler/plugins/vite.js +4 -3
- package/build/compiler/{analyzer.d.ts → ts-analyzer.d.ts} +2 -2
- package/build/compiler/{analyzer.js → ts-analyzer.js} +16 -18
- package/build/compiler/ts-parser.d.ts +5 -1
- package/build/compiler/ts-parser.js +27 -45
- package/build/constants.d.ts +1 -16
- package/build/constants.js +1 -19
- package/package.json +7 -3
- package/src/compiler/codegen.ts +135 -217
- package/src/compiler/constants.ts +26 -0
- package/src/compiler/index.ts +33 -58
- package/src/compiler/parser.ts +7 -6
- package/src/compiler/plugins/tsc.ts +4 -2
- package/src/compiler/plugins/vite.ts +4 -3
- package/src/compiler/{analyzer.ts → ts-analyzer.ts} +17 -20
- package/src/compiler/ts-parser.ts +35 -67
- package/src/constants.ts +0 -25
- package/test/counter.ts +113 -0
- package/test/effects.ts +1 -1
- package/test/events.ts +1 -1
- package/test/imported-values.ts +1 -1
- package/test/integration/tsconfig.json +0 -1
- package/test/nested.ts +20 -1
- package/test/slots.ts +1 -1
- package/test/spread.ts +1 -1
- package/test/static.ts +1 -1
- package/test/templates.ts +1 -1
- package/test/vite.config.ts +2 -1
package/src/compiler/index.ts
CHANGED
|
@@ -1,94 +1,69 @@
|
|
|
1
|
-
import type { ImportIntent, Plugin, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { ast } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import type { ImportIntent, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
|
|
4
|
+
import { ENTRYPOINT, ENTRYPOINT_REACTIVITY, NAMESPACE, PACKAGE } from './constants';
|
|
5
|
+
import { generateCode, printer } from './codegen';
|
|
5
6
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
for (let i = 0, n = ranges.length; i < n; i++) {
|
|
10
|
-
let range = ranges[i];
|
|
11
|
-
|
|
12
|
-
if (start >= range.start && end <= range.end) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const plugin: Plugin = {
|
|
9
|
+
export default {
|
|
25
10
|
patterns: [
|
|
26
|
-
`${
|
|
27
|
-
`${
|
|
11
|
+
`${ENTRYPOINT}\``,
|
|
12
|
+
`${ENTRYPOINT}.${ENTRYPOINT_REACTIVITY}`
|
|
28
13
|
],
|
|
29
|
-
|
|
30
14
|
transform: (ctx: TransformContext) => {
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Build ranges for all template nodes - reactive calls inside these are handled by template codegen
|
|
40
|
-
let templateRanges: { end: number; start: number }[] = [];
|
|
15
|
+
let intents = {
|
|
16
|
+
imports: [] as ImportIntent[],
|
|
17
|
+
prepend: [] as string[],
|
|
18
|
+
replacements: [] as ReplacementIntent[]
|
|
19
|
+
},
|
|
20
|
+
ranges: { end: number; start: number }[] = [],
|
|
21
|
+
remove: string[] = [],
|
|
22
|
+
templates = findHtmlTemplates(ctx.sourceFile, ctx.checker);
|
|
41
23
|
|
|
42
24
|
for (let i = 0, n = templates.length; i < n; i++) {
|
|
43
|
-
|
|
25
|
+
ranges.push({
|
|
44
26
|
end: templates[i].end,
|
|
45
27
|
start: templates[i].start
|
|
46
28
|
});
|
|
47
29
|
}
|
|
48
30
|
|
|
49
|
-
|
|
50
|
-
let reactiveCalls = findReactiveCalls(ctx.sourceFile, ctx.checker);
|
|
31
|
+
let calls = findReactiveCalls(ctx.sourceFile, ctx.checker);
|
|
51
32
|
|
|
52
|
-
for (let i = 0, n =
|
|
53
|
-
let call =
|
|
33
|
+
for (let i = 0, n = calls.length; i < n; i++) {
|
|
34
|
+
let call = calls[i];
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
if (isInRange(templateRanges, call.start, call.end)) {
|
|
36
|
+
if (ast.inRange(ranges, call.start, call.end)) {
|
|
57
37
|
continue;
|
|
58
38
|
}
|
|
59
39
|
|
|
60
|
-
replacements.push({
|
|
61
|
-
generate: (sourceFile) => `new ${
|
|
40
|
+
intents.replacements.push({
|
|
41
|
+
generate: (sourceFile) => `new ${NAMESPACE}.ArraySlot(
|
|
42
|
+
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
43
|
+
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
44
|
+
)`,
|
|
62
45
|
node: call.node
|
|
63
46
|
});
|
|
64
47
|
}
|
|
65
48
|
|
|
66
|
-
// Transform html`` templates
|
|
67
49
|
if (templates.length > 0) {
|
|
68
50
|
let result = generateCode(templates, ctx.sourceFile, ctx.checker);
|
|
69
51
|
|
|
70
|
-
prepend.push(...result.prepend);
|
|
71
|
-
replacements.push(...result.replacements);
|
|
72
|
-
|
|
52
|
+
intents.prepend.push(...result.prepend);
|
|
53
|
+
intents.replacements.push(...result.replacements);
|
|
54
|
+
remove.push(ENTRYPOINT);
|
|
73
55
|
}
|
|
74
56
|
|
|
75
|
-
if (replacements.length === 0 && prepend.length === 0) {
|
|
57
|
+
if (intents.replacements.length === 0 && intents.prepend.length === 0) {
|
|
76
58
|
return {};
|
|
77
59
|
}
|
|
78
60
|
|
|
79
|
-
|
|
80
|
-
namespace:
|
|
61
|
+
intents.imports.push({
|
|
62
|
+
namespace: NAMESPACE,
|
|
81
63
|
package: PACKAGE,
|
|
82
|
-
remove:
|
|
64
|
+
remove: remove
|
|
83
65
|
});
|
|
84
66
|
|
|
85
|
-
return
|
|
86
|
-
imports: importsIntent,
|
|
87
|
-
prepend,
|
|
88
|
-
replacements
|
|
89
|
-
};
|
|
67
|
+
return intents;
|
|
90
68
|
}
|
|
91
69
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
export default plugin;
|
package/src/compiler/parser.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SLOT_HTML } from '../constants';
|
|
2
|
+
import { PACKAGE, TYPES } from './constants';
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
type NodePath = ('firstChild' | 'firstElementChild' | 'nextElementSibling' | 'nextSibling')[];
|
|
@@ -90,8 +91,8 @@ const parse = (literals: string[]) => {
|
|
|
90
91
|
parsed = html.split(SLOT_MARKER),
|
|
91
92
|
slot = 0,
|
|
92
93
|
slots: (
|
|
93
|
-
{ path: NodePath; type:
|
|
94
|
-
{ attributes: typeof attributes[string]; path: NodePath; type:
|
|
94
|
+
{ path: NodePath; type: TYPES.Node } |
|
|
95
|
+
{ attributes: typeof attributes[string]; path: NodePath; type: TYPES.Attribute }
|
|
95
96
|
)[] = [];
|
|
96
97
|
|
|
97
98
|
{
|
|
@@ -154,7 +155,7 @@ const parse = (literals: string[]) => {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
else {
|
|
157
|
-
names.push(
|
|
158
|
+
names.push(TYPES.Attributes);
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
}
|
|
@@ -184,7 +185,7 @@ const parse = (literals: string[]) => {
|
|
|
184
185
|
throw new Error(`${PACKAGE}: attribute metadata could not be found for '${attr}'`);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
slots.push({ attributes: attrs, path, type:
|
|
188
|
+
slots.push({ attributes: attrs, path, type: TYPES.Attribute });
|
|
188
189
|
|
|
189
190
|
for (let i = 0, n = attrs.names.length; i < n; i++) {
|
|
190
191
|
buffer += parsed[slot++];
|
|
@@ -201,7 +202,7 @@ const parse = (literals: string[]) => {
|
|
|
201
202
|
buffer += parsed[slot++] + SLOT_HTML;
|
|
202
203
|
slots.push({
|
|
203
204
|
path: methods(parent.children, parent.path, 'firstChild', 'nextSibling'),
|
|
204
|
-
type:
|
|
205
|
+
type: TYPES.Node
|
|
205
206
|
});
|
|
206
207
|
}
|
|
207
208
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import plugin from '
|
|
1
|
+
import { plugin } from '@esportsplus/typescript/compiler';
|
|
2
|
+
import reactivity from '@esportsplus/reactivity/compiler';
|
|
3
|
+
import template from '..';
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
export default plugin
|
|
6
|
+
export default plugin.tsc([reactivity, template]) as ReturnType<typeof plugin.tsc>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
2
|
-
import { PACKAGE } from '
|
|
3
|
-
import
|
|
2
|
+
import { PACKAGE } from '../constants';
|
|
3
|
+
import reactivity from '@esportsplus/reactivity/compiler';
|
|
4
|
+
import template from '..';
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
export default plugin.vite({
|
|
7
8
|
name: PACKAGE,
|
|
8
|
-
plugins: [
|
|
9
|
+
plugins: [reactivity, template]
|
|
9
10
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_TYPES } from '~/constants';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { ENTRYPOINT, ENTRYPOINT_REACTIVITY, TYPES } from './constants';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// Union types that mix functions with non-functions (e.g., Renderable)
|
|
@@ -19,13 +19,13 @@ function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
const analyze = (expr: ts.Expression, checker?: ts.TypeChecker):
|
|
22
|
+
const analyze = (expr: ts.Expression, checker?: ts.TypeChecker): TYPES => {
|
|
23
23
|
while (ts.isParenthesizedExpression(expr)) {
|
|
24
24
|
expr = expr.expression;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
|
|
28
|
-
return
|
|
28
|
+
return TYPES.Effect;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Only html.reactive() calls become ArraySlot - handled by generateReactiveInlining
|
|
@@ -33,14 +33,14 @@ const analyze = (expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES
|
|
|
33
33
|
ts.isCallExpression(expr) &&
|
|
34
34
|
ts.isPropertyAccessExpression(expr.expression) &&
|
|
35
35
|
ts.isIdentifier(expr.expression.expression) &&
|
|
36
|
-
expr.expression.expression.text ===
|
|
37
|
-
expr.expression.name.text ===
|
|
36
|
+
expr.expression.expression.text === ENTRYPOINT &&
|
|
37
|
+
expr.expression.name.text === ENTRYPOINT_REACTIVITY
|
|
38
38
|
) {
|
|
39
|
-
return
|
|
39
|
+
return TYPES.ArraySlot;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text ===
|
|
43
|
-
return
|
|
42
|
+
if (ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === ENTRYPOINT) {
|
|
43
|
+
return TYPES.DocumentFragment;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (
|
|
@@ -52,11 +52,11 @@ const analyze = (expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES
|
|
|
52
52
|
expr.kind === ts.SyntaxKind.NullKeyword ||
|
|
53
53
|
expr.kind === ts.SyntaxKind.UndefinedKeyword
|
|
54
54
|
) {
|
|
55
|
-
return
|
|
55
|
+
return TYPES.Static;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
if (ts.isTemplateExpression(expr)) {
|
|
59
|
-
return
|
|
59
|
+
return TYPES.Primitive;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (ts.isConditionalExpression(expr)) {
|
|
@@ -67,26 +67,23 @@ const analyze = (expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES
|
|
|
67
67
|
return whenTrue;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
if (whenTrue ===
|
|
71
|
-
return
|
|
70
|
+
if (whenTrue === TYPES.Effect || whenFalse === TYPES.Effect) {
|
|
71
|
+
return TYPES.Effect;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
return
|
|
74
|
+
return TYPES.Unknown;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
|
|
78
78
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (isTypeFunction(type, checker)) {
|
|
82
|
-
return COMPILER_TYPES.Effect;
|
|
79
|
+
if (isTypeFunction(checker.getTypeAtLocation(expr), checker)) {
|
|
80
|
+
return TYPES.Effect;
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
|
-
catch {
|
|
86
|
-
}
|
|
83
|
+
catch {}
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
return
|
|
86
|
+
return TYPES.Unknown;
|
|
90
87
|
};
|
|
91
88
|
|
|
92
89
|
export { analyze };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import {
|
|
2
|
+
import { imports } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { ENTRYPOINT, ENTRYPOINT_REACTIVITY, PACKAGE } from './constants';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
type ReactiveCallInfo = {
|
|
@@ -20,55 +21,15 @@ type TemplateInfo = {
|
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
|
|
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
24
|
function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[], checker: ts.TypeChecker | undefined): void {
|
|
65
25
|
if (
|
|
66
26
|
ts.isCallExpression(node) &&
|
|
67
27
|
ts.isPropertyAccessExpression(node.expression) &&
|
|
68
28
|
ts.isIdentifier(node.expression.expression) &&
|
|
69
|
-
node.expression.name.text ===
|
|
29
|
+
node.expression.name.text === ENTRYPOINT_REACTIVITY &&
|
|
70
30
|
node.arguments.length === 2 &&
|
|
71
|
-
|
|
31
|
+
node.expression.expression.text === ENTRYPOINT &&
|
|
32
|
+
(!checker || imports.includes(checker, node.expression.expression, PACKAGE, ENTRYPOINT))
|
|
72
33
|
) {
|
|
73
34
|
calls.push({
|
|
74
35
|
arrayArg: node.arguments[0],
|
|
@@ -87,24 +48,13 @@ function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[],
|
|
|
87
48
|
? depth + 1
|
|
88
49
|
: depth;
|
|
89
50
|
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
else if (ts.isTemplateExpression(template)) {
|
|
99
|
-
literals.push(template.head.text);
|
|
100
|
-
|
|
101
|
-
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
102
|
-
let span = template.templateSpans[i];
|
|
103
|
-
|
|
104
|
-
expressions.push(span.expression);
|
|
105
|
-
literals.push(span.literal.text);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
51
|
+
if (
|
|
52
|
+
ts.isTaggedTemplateExpression(node) &&
|
|
53
|
+
ts.isIdentifier(node.tag) &&
|
|
54
|
+
node.tag.text === ENTRYPOINT &&
|
|
55
|
+
(!checker || imports.includes(checker, node.tag, PACKAGE, ENTRYPOINT))
|
|
56
|
+
) {
|
|
57
|
+
let { expressions, literals } = extractTemplateParts(node.template);
|
|
108
58
|
|
|
109
59
|
templates.push({
|
|
110
60
|
depth,
|
|
@@ -120,15 +70,33 @@ function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[],
|
|
|
120
70
|
}
|
|
121
71
|
|
|
122
72
|
|
|
73
|
+
const extractTemplateParts = (template: ts.TemplateLiteral): { expressions: ts.Expression[]; literals: string[] } => {
|
|
74
|
+
let expressions: ts.Expression[] = [],
|
|
75
|
+
literals: string[] = [];
|
|
76
|
+
|
|
77
|
+
if (ts.isNoSubstitutionTemplateLiteral(template)) {
|
|
78
|
+
literals.push(template.text);
|
|
79
|
+
}
|
|
80
|
+
else if (ts.isTemplateExpression(template)) {
|
|
81
|
+
literals.push(template.head.text);
|
|
82
|
+
|
|
83
|
+
for (let i = 0, n = template.templateSpans.length; i < n; i++) {
|
|
84
|
+
let span = template.templateSpans[i];
|
|
85
|
+
|
|
86
|
+
expressions.push(span.expression);
|
|
87
|
+
literals.push(span.literal.text);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { expressions, literals };
|
|
92
|
+
};
|
|
93
|
+
|
|
123
94
|
const findHtmlTemplates = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): TemplateInfo[] => {
|
|
124
95
|
let templates: TemplateInfo[] = [];
|
|
125
96
|
|
|
126
97
|
visitTemplates(sourceFile, 0, templates, checker);
|
|
127
98
|
|
|
128
|
-
|
|
129
|
-
templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
130
|
-
|
|
131
|
-
return templates;
|
|
99
|
+
return templates.sort((a, b) => a.depth !== b.depth ? b.depth - a.depth : a.start - b.start);
|
|
132
100
|
};
|
|
133
101
|
|
|
134
102
|
const findReactiveCalls = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker): ReactiveCallInfo[] => {
|
|
@@ -140,5 +108,5 @@ const findReactiveCalls = (sourceFile: ts.SourceFile, checker?: ts.TypeChecker):
|
|
|
140
108
|
};
|
|
141
109
|
|
|
142
110
|
|
|
143
|
-
export { findHtmlTemplates, findReactiveCalls };
|
|
111
|
+
export { extractTemplateParts, findHtmlTemplates, findReactiveCalls };
|
|
144
112
|
export type { ReactiveCallInfo, TemplateInfo };
|
package/src/constants.ts
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
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
|
-
const COMPILER_ENTRYPOINT = 'html';
|
|
9
|
-
|
|
10
|
-
const COMPILER_ENTRYPOINT_REACTIVITY = 'reactive';
|
|
11
|
-
|
|
12
|
-
const COMPILER_NAMESPACE = uid('template');
|
|
13
|
-
|
|
14
|
-
const enum COMPILER_TYPES {
|
|
15
|
-
ArraySlot = 'array-slot',
|
|
16
|
-
Attributes = 'attributes',
|
|
17
|
-
Attribute = 'attribute',
|
|
18
|
-
DocumentFragment = 'document-fragment',
|
|
19
|
-
Effect = 'effect',
|
|
20
|
-
Node = 'node',
|
|
21
|
-
Primitive = 'primitive',
|
|
22
|
-
Static = 'static',
|
|
23
|
-
Unknown = 'unknown'
|
|
24
|
-
};
|
|
25
|
-
|
|
26
5
|
const DIRECT_ATTACH_EVENTS = new Set<string>([
|
|
27
6
|
'onblur',
|
|
28
7
|
'onerror',
|
|
@@ -37,8 +16,6 @@ const LIFECYCLE_EVENTS = new Set<string>([
|
|
|
37
16
|
'onconnect', 'ondisconnect', 'onrender', 'onresize', 'ontick'
|
|
38
17
|
]);
|
|
39
18
|
|
|
40
|
-
const PACKAGE = '@esportsplus/template';
|
|
41
|
-
|
|
42
19
|
const SLOT_HTML = '<!--$-->';
|
|
43
20
|
|
|
44
21
|
const STATE_HYDRATING = 0;
|
|
@@ -53,9 +30,7 @@ const STORE = Symbol('template.store');
|
|
|
53
30
|
export {
|
|
54
31
|
ARRAY_SLOT,
|
|
55
32
|
CLEANUP,
|
|
56
|
-
COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, COMPILER_NAMESPACE, COMPILER_TYPES,
|
|
57
33
|
DIRECT_ATTACH_EVENTS,
|
|
58
34
|
LIFECYCLE_EVENTS,
|
|
59
|
-
PACKAGE,
|
|
60
35
|
SLOT_HTML, STATE_HYDRATING, STATE_NONE, STATE_WAITING, STORE,
|
|
61
36
|
};
|
package/test/counter.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { effect, reactive } from '@esportsplus/reactivity'
|
|
2
|
+
import { html, type Attributes } from '@esportsplus/template';
|
|
3
|
+
import { omit } from '@esportsplus/utilities';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const OMIT = ['currency', 'decimals', 'delay', 'max', 'state', 'suffix', 'value'];
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
let formatters: Record<string, Intl.NumberFormat> = {};
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export default (attributes: Attributes & {
|
|
13
|
+
currency?: 'IGNORE' | 'EUR' | 'GBP' | 'USD';
|
|
14
|
+
decimals?: number;
|
|
15
|
+
delay?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
state?: { value: number },
|
|
18
|
+
suffix?: string;
|
|
19
|
+
value: number;
|
|
20
|
+
}) => {
|
|
21
|
+
let { currency, decimals, delay, max, suffix, value } = attributes,
|
|
22
|
+
api = attributes.state || reactive({ value: -1 }),
|
|
23
|
+
formatter = currency === 'IGNORE'
|
|
24
|
+
? undefined
|
|
25
|
+
: formatters[currency || 'USD'] ??= new Intl.NumberFormat('en-US', {
|
|
26
|
+
style: 'currency',
|
|
27
|
+
currency: currency || 'USD'
|
|
28
|
+
}),
|
|
29
|
+
rendering = true,
|
|
30
|
+
state = reactive({
|
|
31
|
+
length: 0,
|
|
32
|
+
test: () => 'sds',
|
|
33
|
+
render: [] as string[]
|
|
34
|
+
}),
|
|
35
|
+
render = reactive([] as string[]);
|
|
36
|
+
|
|
37
|
+
decimals ??= 2;
|
|
38
|
+
|
|
39
|
+
effect(() => {
|
|
40
|
+
if (api.value !== -1) {
|
|
41
|
+
value = api.value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let padding = (max || value).toFixed(decimals).length - value.toFixed(decimals).length,
|
|
45
|
+
values = value.toString().padStart( value.toString().length + padding, '1') as any;
|
|
46
|
+
|
|
47
|
+
if (formatter) {
|
|
48
|
+
values = formatter.format(values);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
values = Number(values).toLocaleString([], {
|
|
52
|
+
minimumFractionDigits: 0,
|
|
53
|
+
maximumFractionDigits: decimals
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
values = values.split('');
|
|
58
|
+
|
|
59
|
+
if (suffix) {
|
|
60
|
+
values.push(' ', ...suffix.split(''));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
state.length = values.length;
|
|
64
|
+
|
|
65
|
+
for (let i = 0, n = values.length; i < n; i++) {
|
|
66
|
+
let value = values[i];
|
|
67
|
+
|
|
68
|
+
if (!isNaN(parseInt(value, 10)) && (rendering === true || padding > 0)) {
|
|
69
|
+
padding--;
|
|
70
|
+
value = '0';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
render[i] = value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (rendering === true) {
|
|
77
|
+
rendering = false;
|
|
78
|
+
setTimeout(() => api.value = value, delay || 1000);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return html`
|
|
83
|
+
<div class='counter' ${omit(attributes, OMIT)}>
|
|
84
|
+
${() => {
|
|
85
|
+
let n = state.length;
|
|
86
|
+
|
|
87
|
+
if (n === 0) {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return html.reactive(render, function (value) {
|
|
92
|
+
if (isNaN(parseInt(value, 10))) {
|
|
93
|
+
return html`
|
|
94
|
+
<span class='counter-character counter-character--symbol'>
|
|
95
|
+
${value}
|
|
96
|
+
</span>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return html`
|
|
101
|
+
<div class=' counter-character'>
|
|
102
|
+
<div class='counter-character-track' style='${`--value: ${value}`}'>
|
|
103
|
+
<span>9</span>
|
|
104
|
+
${[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((value) => html`<span>${value}</span>`)}
|
|
105
|
+
<span>0</span>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
`;
|
|
109
|
+
});
|
|
110
|
+
}}
|
|
111
|
+
</div>
|
|
112
|
+
`;
|
|
113
|
+
};
|
package/test/effects.ts
CHANGED
package/test/events.ts
CHANGED
package/test/imported-values.ts
CHANGED
package/test/nested.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Tests nested html`` calls, array mapping, and conditional templates
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
import { html } from '
|
|
5
|
+
import { html } from '@esportsplus/template';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
// =============================================================================
|
|
@@ -273,6 +273,25 @@ export const stressList50 = (items: string[]) =>
|
|
|
273
273
|
export const stressList100 = (items: string[]) =>
|
|
274
274
|
html`<ul>${items.slice(0, 100).map(item => html`<li>${item}</li>`)}</ul>`;
|
|
275
275
|
|
|
276
|
+
// Block body arrow function with nested template
|
|
277
|
+
export const blockBodyNested = (items: { name: string; active: boolean }[]) =>
|
|
278
|
+
html`<ul>${items.map((item) => {
|
|
279
|
+
let cls = item.active ? 'active' : 'inactive';
|
|
280
|
+
return html`<li class="${cls}">${item.name}</li>`;
|
|
281
|
+
})}</ul>`;
|
|
282
|
+
|
|
283
|
+
// Block body with conditional
|
|
284
|
+
export const blockBodyConditional = (data: { show: boolean; items: string[] }) =>
|
|
285
|
+
html`<div>${() => {
|
|
286
|
+
if (!data.show) {
|
|
287
|
+
return '';
|
|
288
|
+
}
|
|
289
|
+
return html`<ul>${data.items.map((item) => {
|
|
290
|
+
let processed = item.toUpperCase();
|
|
291
|
+
return html`<li>${processed}</li>`;
|
|
292
|
+
})}</ul>`;
|
|
293
|
+
}}</div>`;
|
|
294
|
+
|
|
276
295
|
// Complex nested structure
|
|
277
296
|
export const stressComplex = (data: {
|
|
278
297
|
header: { title: string; subtitle: string };
|