@esportsplus/template 0.32.1 → 0.32.3
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/index.d.ts +3 -3
- package/build/index.js +3 -3
- package/build/transformer/codegen.d.ts +3 -3
- package/build/transformer/codegen.js +19 -46
- package/build/transformer/index.d.ts +1 -4
- package/build/transformer/index.js +10 -27
- package/build/transformer/plugins/tsc.d.ts +2 -2
- package/build/transformer/plugins/tsc.js +3 -10
- package/build/transformer/plugins/vite.d.ts +11 -3
- package/build/transformer/plugins/vite.js +6 -34
- package/build/transformer/ts-parser.js +3 -7
- package/build/transformer/type-analyzer.d.ts +1 -1
- package/build/transformer/type-analyzer.js +25 -60
- package/package.json +3 -3
- package/src/index.ts +5 -3
- package/src/transformer/codegen.ts +30 -66
- package/src/transformer/index.ts +11 -34
- package/src/transformer/plugins/tsc.ts +3 -13
- package/src/transformer/plugins/vite.ts +7 -43
- package/src/transformer/ts-parser.ts +3 -10
- package/src/transformer/type-analyzer.ts +31 -81
package/build/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import './runtime.js';
|
|
2
|
-
export { default as attributes } from './attributes.js';
|
|
3
|
-
export { default as event } from './event/index.js';
|
|
4
2
|
export { default as html } from './html.js';
|
|
5
3
|
export { default as render } from './render.js';
|
|
6
|
-
export { default as slot } from './slot/index.js';
|
|
7
4
|
export { default as svg } from './svg.js';
|
|
5
|
+
export { default as attributes } from './attributes.js';
|
|
6
|
+
export { default as event } from './event/index.js';
|
|
7
|
+
export { default as slot } from './slot/index.js';
|
|
8
8
|
export { ArraySlot } from './slot/array.js';
|
|
9
9
|
export { EffectSlot } from './slot/effect.js';
|
|
10
10
|
export type { Attributes, Element, Renderable } from './types.js';
|
package/build/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import './runtime.js';
|
|
2
|
-
export { default as attributes } from './attributes.js';
|
|
3
|
-
export { default as event } from './event/index.js';
|
|
4
2
|
export { default as html } from './html.js';
|
|
5
3
|
export { default as render } from './render.js';
|
|
6
|
-
export { default as slot } from './slot/index.js';
|
|
7
4
|
export { default as svg } from './svg.js';
|
|
5
|
+
export { default as attributes } from './attributes.js';
|
|
6
|
+
export { default as event } from './event/index.js';
|
|
7
|
+
export { default as slot } from './slot/index.js';
|
|
8
8
|
export { ArraySlot } from './slot/array.js';
|
|
9
9
|
export { EffectSlot } from './slot/effect.js';
|
|
10
10
|
export * from './utilities.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { ReactiveCallInfo, TemplateInfo } from './ts-parser.js';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import type { ReactiveCallInfo, TemplateInfo } from './ts-parser.js';
|
|
3
3
|
type CodegenResult = {
|
|
4
4
|
changed: boolean;
|
|
5
5
|
code: string;
|
|
6
6
|
};
|
|
7
|
-
declare const
|
|
7
|
+
declare const addImport: (code: string) => string;
|
|
8
8
|
declare const generateCode: (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker) => CodegenResult;
|
|
9
9
|
declare const generateReactiveInlining: (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile) => string;
|
|
10
10
|
declare const needsArraySlotImport: (sourceFile: ts.SourceFile) => boolean;
|
|
11
|
-
export {
|
|
11
|
+
export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
12
12
|
export type { CodegenResult };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { code as c, uid } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer.js';
|
|
3
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { ast, code as c, uid } from '@esportsplus/typescript/transformer';
|
|
3
|
+
import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer.js';
|
|
4
4
|
import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES, PACKAGE } from '../constants.js';
|
|
5
5
|
import parser from './parser.js';
|
|
6
6
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
@@ -89,18 +89,18 @@ function generateTemplateCode(ctx, { html, slots }, exprTexts, exprNodes, isArro
|
|
|
89
89
|
? root
|
|
90
90
|
: (nodes.get(slots[i].path.join('.')) || root), slot = slots[i];
|
|
91
91
|
if (slot.type === COMPILER_TYPES.AttributeSlot) {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
let names = slot.attributes.names;
|
|
93
|
+
for (let j = 0, m = names.length; j < m; j++) {
|
|
94
|
+
let name = names[j];
|
|
94
95
|
if (name === 'spread') {
|
|
95
|
-
let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.
|
|
96
|
+
let bindings = generateSpreadBindings(exprNodes[index], exprTexts[index] || 'undefined', elementVar, ctx.checker, COMPILER_NAMESPACE);
|
|
96
97
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
97
98
|
code.push(bindings[k]);
|
|
98
99
|
}
|
|
99
100
|
index++;
|
|
100
101
|
}
|
|
101
102
|
else {
|
|
102
|
-
|
|
103
|
-
code.push(binding);
|
|
103
|
+
code.push(generateAttributeBinding(elementVar, name, exprTexts[index++] || 'undefined', slot.attributes.statics[name] || '', COMPILER_NAMESPACE));
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -140,64 +140,35 @@ function hasArraySlotImport(sourceFile) {
|
|
|
140
140
|
}
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
|
-
function hasMatch(node, predicate) {
|
|
144
|
-
if (predicate(node)) {
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
let found = false;
|
|
148
|
-
ts.forEachChild(node, child => {
|
|
149
|
-
if (!found) {
|
|
150
|
-
found = hasMatch(child, predicate);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
return found;
|
|
154
|
-
}
|
|
155
|
-
function hasArraySlotUsage(node) {
|
|
156
|
-
return hasMatch(node, n => ts.isNewExpression(n) &&
|
|
157
|
-
ts.isPropertyAccessExpression(n.expression) &&
|
|
158
|
-
n.expression.name.text === 'ArraySlot');
|
|
159
|
-
}
|
|
160
|
-
function hasNestedTemplates(node) {
|
|
161
|
-
return hasMatch(node, n => isNestedHtmlTemplate(n));
|
|
162
|
-
}
|
|
163
143
|
function isNestedHtmlTemplate(expr) {
|
|
164
144
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
165
145
|
}
|
|
166
|
-
function isNestedTemplate(template, exprRanges) {
|
|
167
|
-
for (let i = 0, n = exprRanges.length; i < n; i++) {
|
|
168
|
-
let range = exprRanges[i];
|
|
169
|
-
if (template.start >= range.start && template.end <= range.end) {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
146
|
function rewriteExpression(ctx, expr) {
|
|
176
147
|
if (isNestedHtmlTemplate(expr)) {
|
|
177
148
|
return generateNestedTemplateCode(ctx, expr);
|
|
178
149
|
}
|
|
179
|
-
if (!
|
|
150
|
+
if (!ast.hasMatch(expr, n => isNestedHtmlTemplate(n))) {
|
|
180
151
|
return ctx.printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
|
|
181
152
|
}
|
|
182
|
-
let
|
|
183
|
-
collectNestedTemplateReplacements(ctx, expr,
|
|
153
|
+
let replacements = [];
|
|
154
|
+
collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
|
|
184
155
|
return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
|
|
185
156
|
}
|
|
186
|
-
const
|
|
157
|
+
const addImport = (code) => {
|
|
187
158
|
return `import * as ${COMPILER_NAMESPACE} from '${PACKAGE}';\n` + code;
|
|
188
159
|
};
|
|
189
160
|
const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
190
161
|
if (templates.length === 0) {
|
|
191
162
|
return { changed: false, code: originalCode };
|
|
192
163
|
}
|
|
193
|
-
let
|
|
164
|
+
let ranges = [];
|
|
194
165
|
for (let i = 0, n = templates.length; i < n; i++) {
|
|
195
166
|
let exprs = templates[i].expressions;
|
|
196
167
|
for (let j = 0, m = exprs.length; j < m; j++) {
|
|
197
|
-
|
|
168
|
+
ranges.push({ end: exprs[j].end, start: exprs[j].getStart() });
|
|
198
169
|
}
|
|
199
170
|
}
|
|
200
|
-
let rootTemplates = templates.filter(t => !
|
|
171
|
+
let rootTemplates = templates.filter(t => !ast.inRange(ranges, t.start, t.end));
|
|
201
172
|
if (rootTemplates.length === 0) {
|
|
202
173
|
return { changed: false, code: originalCode };
|
|
203
174
|
}
|
|
@@ -237,7 +208,7 @@ const generateCode = (templates, originalCode, sourceFile, checker) => {
|
|
|
237
208
|
for (let [id, html] of ctx.hoistedFactories) {
|
|
238
209
|
factories.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
|
|
239
210
|
}
|
|
240
|
-
code =
|
|
211
|
+
code = addImport(factories.join('\n') + code);
|
|
241
212
|
}
|
|
242
213
|
return { changed, code };
|
|
243
214
|
};
|
|
@@ -260,6 +231,8 @@ const generateReactiveInlining = (calls, code, sourceFile) => {
|
|
|
260
231
|
return c.replaceReverse(code, replacements);
|
|
261
232
|
};
|
|
262
233
|
const needsArraySlotImport = (sourceFile) => {
|
|
263
|
-
return
|
|
234
|
+
return ast.hasMatch(sourceFile, n => ts.isNewExpression(n) &&
|
|
235
|
+
ts.isPropertyAccessExpression(n.expression) &&
|
|
236
|
+
n.expression.name.text === 'ArraySlot') && !hasArraySlotImport(sourceFile);
|
|
264
237
|
};
|
|
265
|
-
export {
|
|
238
|
+
export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
@@ -4,8 +4,5 @@ type TransformResult = {
|
|
|
4
4
|
code: string;
|
|
5
5
|
sourceFile: ts.SourceFile;
|
|
6
6
|
};
|
|
7
|
-
declare const PATTERNS: string[];
|
|
8
|
-
declare function createTransformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile>;
|
|
9
7
|
declare const transform: (sourceFile: ts.SourceFile, program: ts.Program) => TransformResult;
|
|
10
|
-
|
|
11
|
-
export { createTransformer, transform, transformCode, PATTERNS };
|
|
8
|
+
export { transform };
|
|
@@ -1,47 +1,30 @@
|
|
|
1
1
|
import { code as c } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import {
|
|
2
|
+
import { addImport, generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen.js';
|
|
3
3
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants.js';
|
|
4
4
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser.js';
|
|
5
5
|
import { ts } from '@esportsplus/typescript';
|
|
6
6
|
const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return (_context) => {
|
|
10
|
-
return (sourceFile) => {
|
|
11
|
-
let code = sourceFile.getFullText();
|
|
12
|
-
if (!c.contains(code, { patterns: PATTERNS })) {
|
|
13
|
-
return sourceFile;
|
|
14
|
-
}
|
|
15
|
-
let result = transformCode(code, sourceFile, typeChecker);
|
|
16
|
-
if (!result.changed) {
|
|
17
|
-
return sourceFile;
|
|
18
|
-
}
|
|
19
|
-
return result.sourceFile;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
}
|
|
7
|
+
const REGEX_BACKSLASH = /\\/g;
|
|
8
|
+
const REGEX_FORWARD_SLASH = /\//g;
|
|
23
9
|
const transform = (sourceFile, program) => {
|
|
24
10
|
let code = sourceFile.getFullText();
|
|
25
11
|
if (!c.contains(code, { patterns: PATTERNS })) {
|
|
26
12
|
return { changed: false, code, sourceFile };
|
|
27
13
|
}
|
|
28
14
|
let checker, fileName = sourceFile.fileName, programSourceFile = program.getSourceFile(fileName)
|
|
29
|
-
|| program.getSourceFile(fileName.replace(
|
|
30
|
-
|| program.getSourceFile(fileName.replace(
|
|
15
|
+
|| program.getSourceFile(fileName.replace(REGEX_BACKSLASH, '/'))
|
|
16
|
+
|| program.getSourceFile(fileName.replace(REGEX_FORWARD_SLASH, '\\'));
|
|
31
17
|
if (programSourceFile) {
|
|
32
18
|
checker = program.getTypeChecker();
|
|
33
19
|
sourceFile = programSourceFile;
|
|
34
20
|
}
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
const transformCode = (code, sourceFile, checker) => {
|
|
38
|
-
let changed = false, codegenChanged = false, needsImport = false, result = code, reactiveCalls = findReactiveCalls(sourceFile);
|
|
21
|
+
let changed = false, codegenChanged = false, needsImport = false, reactiveCalls = findReactiveCalls(sourceFile), result = code;
|
|
39
22
|
if (reactiveCalls.length > 0) {
|
|
40
|
-
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
41
23
|
changed = true;
|
|
24
|
+
checker = undefined;
|
|
25
|
+
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
42
26
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
43
27
|
needsImport = needsArraySlotImport(sourceFile);
|
|
44
|
-
checker = undefined;
|
|
45
28
|
}
|
|
46
29
|
let templates = findHtmlTemplates(sourceFile);
|
|
47
30
|
if (templates.length > 0) {
|
|
@@ -53,11 +36,11 @@ const transformCode = (code, sourceFile, checker) => {
|
|
|
53
36
|
}
|
|
54
37
|
}
|
|
55
38
|
if (needsImport && !codegenChanged) {
|
|
56
|
-
result =
|
|
39
|
+
result = addImport(result);
|
|
57
40
|
}
|
|
58
41
|
if (changed) {
|
|
59
42
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
60
43
|
}
|
|
61
44
|
return { changed, code: result, sourceFile };
|
|
62
45
|
};
|
|
63
|
-
export {
|
|
46
|
+
export { transform };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
declare const _default:
|
|
1
|
+
import { plugin } from '@esportsplus/typescript/transformer';
|
|
2
|
+
declare const _default: ReturnType<typeof plugin.tsc>;
|
|
3
3
|
export default _default;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return () => {
|
|
5
|
-
return (sourceFile) => {
|
|
6
|
-
let result = transformCode(sourceFile.getFullText(), sourceFile, typeChecker);
|
|
7
|
-
return result.changed ? result.sourceFile : sourceFile;
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
};
|
|
1
|
+
import { plugin } from '@esportsplus/typescript/transformer';
|
|
2
|
+
import { transform } from '../index.js';
|
|
3
|
+
export default plugin.tsc(transform);
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
declare const _default: (options?: {
|
|
1
|
+
declare const _default: ({ root }?: {
|
|
3
2
|
root?: string;
|
|
4
|
-
}) =>
|
|
3
|
+
}) => {
|
|
4
|
+
configResolved(config: import("vite").ResolvedConfig): void;
|
|
5
|
+
enforce: string;
|
|
6
|
+
name: string;
|
|
7
|
+
transform(code: string, id: string): {
|
|
8
|
+
code: string;
|
|
9
|
+
map: null;
|
|
10
|
+
} | null;
|
|
11
|
+
watchChange(id: string): void;
|
|
12
|
+
};
|
|
5
13
|
export default _default;
|
|
@@ -1,35 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { program, TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
|
|
1
|
+
import { plugin } from '@esportsplus/typescript/transformer';
|
|
3
2
|
import { PACKAGE } from '../../constants.js';
|
|
4
|
-
import { transform } from '
|
|
5
|
-
export default (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
root = options?.root ?? config.root;
|
|
10
|
-
},
|
|
11
|
-
enforce: 'pre',
|
|
12
|
-
name: `${PACKAGE}/plugin-vite`,
|
|
13
|
-
transform(code, id) {
|
|
14
|
-
if (!TRANSFORM_PATTERN.test(id) || id.includes('node_modules')) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
try {
|
|
18
|
-
let result = transform(ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), program.get(root));
|
|
19
|
-
if (!result.changed) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
return { code: result.code, map: null };
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
console.error(`${PACKAGE}: Error transforming ${id}:`, error);
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
watchChange(id) {
|
|
30
|
-
if (TRANSFORM_PATTERN.test(id)) {
|
|
31
|
-
program.delete(root);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
};
|
|
3
|
+
import { transform } from '../index.js';
|
|
4
|
+
export default plugin.vite({
|
|
5
|
+
name: PACKAGE,
|
|
6
|
+
transform
|
|
7
|
+
});
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants.js';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
function isFunctionNode(node) {
|
|
4
|
-
return (ts.isArrowFunction(node) ||
|
|
5
|
-
ts.isFunctionDeclaration(node) ||
|
|
6
|
-
ts.isFunctionExpression(node) ||
|
|
7
|
-
ts.isMethodDeclaration(node));
|
|
8
|
-
}
|
|
9
3
|
function visitReactiveCalls(node, calls) {
|
|
10
4
|
if (ts.isCallExpression(node) &&
|
|
11
5
|
ts.isPropertyAccessExpression(node.expression) &&
|
|
@@ -24,7 +18,9 @@ function visitReactiveCalls(node, calls) {
|
|
|
24
18
|
ts.forEachChild(node, child => visitReactiveCalls(child, calls));
|
|
25
19
|
}
|
|
26
20
|
function visitTemplates(node, depth, templates) {
|
|
27
|
-
let nextDepth =
|
|
21
|
+
let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
|
|
22
|
+
? depth + 1
|
|
23
|
+
: depth;
|
|
28
24
|
if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag.text === COMPILER_ENTRYPOINT) {
|
|
29
25
|
let expressions = [], literals = [], template = node.template;
|
|
30
26
|
if (ts.isNoSubstitutionTemplateLiteral(template)) {
|
|
@@ -2,5 +2,5 @@ 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
4
|
declare const generateAttributeBinding: (elementVar: string, name: string, expr: string, staticValue: string, ns: string) => string;
|
|
5
|
-
declare const generateSpreadBindings: (expr: ts.Expression, exprCode: string, elementVar: string,
|
|
5
|
+
declare const generateSpreadBindings: (expr: ts.Expression, exprCode: string, elementVar: string, checker: ts.TypeChecker | undefined, ns: string) => string[];
|
|
6
6
|
export { analyzeExpression, generateAttributeBinding, generateSpreadBindings };
|
|
@@ -24,28 +24,23 @@ function analyzeSpread(expr, checker) {
|
|
|
24
24
|
}
|
|
25
25
|
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
|
|
26
26
|
try {
|
|
27
|
-
let keys =
|
|
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
|
+
}
|
|
28
35
|
if (keys.length > 0) {
|
|
29
36
|
return { canUnpack: true, keys };
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
|
-
catch {
|
|
33
|
-
}
|
|
39
|
+
catch { }
|
|
34
40
|
}
|
|
35
41
|
return { canUnpack: false, keys: [] };
|
|
36
42
|
}
|
|
37
|
-
function
|
|
38
|
-
let keys = [], props = type.getProperties();
|
|
39
|
-
for (let i = 0, n = props.length; i < n; i++) {
|
|
40
|
-
let name = props[i].getName();
|
|
41
|
-
if (name.startsWith('__') || name.startsWith('[')) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
keys.push(name);
|
|
45
|
-
}
|
|
46
|
-
return keys;
|
|
47
|
-
}
|
|
48
|
-
function inferCOMPILER_TYPES(expr, ctx) {
|
|
43
|
+
function inferCOMPILER_TYPES(expr, checker) {
|
|
49
44
|
while (ts.isParenthesizedExpression(expr)) {
|
|
50
45
|
expr = expr.expression;
|
|
51
46
|
}
|
|
@@ -78,7 +73,7 @@ function inferCOMPILER_TYPES(expr, ctx) {
|
|
|
78
73
|
return COMPILER_TYPES.Primitive;
|
|
79
74
|
}
|
|
80
75
|
if (ts.isConditionalExpression(expr)) {
|
|
81
|
-
let whenFalse = inferCOMPILER_TYPES(expr.whenFalse,
|
|
76
|
+
let whenFalse = inferCOMPILER_TYPES(expr.whenFalse, checker), whenTrue = inferCOMPILER_TYPES(expr.whenTrue, checker);
|
|
82
77
|
if (whenTrue === whenFalse) {
|
|
83
78
|
return whenTrue;
|
|
84
79
|
}
|
|
@@ -87,46 +82,17 @@ function inferCOMPILER_TYPES(expr, ctx) {
|
|
|
87
82
|
}
|
|
88
83
|
return COMPILER_TYPES.Unknown;
|
|
89
84
|
}
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (isTypeFunction(type, checker)) {
|
|
96
|
-
return COMPILER_TYPES.Effect;
|
|
97
|
-
}
|
|
98
|
-
if (isTypeArray(type, checker)) {
|
|
99
|
-
return COMPILER_TYPES.ArraySlot;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
106
|
-
try {
|
|
107
|
-
let type = checker.getTypeAtLocation(expr);
|
|
108
|
-
if (isTypeFunction(type, checker)) {
|
|
109
|
-
return COMPILER_TYPES.Effect;
|
|
110
|
-
}
|
|
111
|
-
if (isTypeArray(type, checker)) {
|
|
112
|
-
return COMPILER_TYPES.ArraySlot;
|
|
113
|
-
}
|
|
85
|
+
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
|
|
86
|
+
try {
|
|
87
|
+
let type = checker.getTypeAtLocation(expr);
|
|
88
|
+
if (isTypeFunction(type, checker)) {
|
|
89
|
+
return COMPILER_TYPES.Effect;
|
|
114
90
|
}
|
|
115
|
-
|
|
91
|
+
if (isTypeArray(type, checker)) {
|
|
92
|
+
return COMPILER_TYPES.ArraySlot;
|
|
116
93
|
}
|
|
117
94
|
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
let type = checker.getTypeAtLocation(expr);
|
|
121
|
-
if (isTypeFunction(type, checker)) {
|
|
122
|
-
return COMPILER_TYPES.Effect;
|
|
123
|
-
}
|
|
124
|
-
if (isTypeArray(type, checker)) {
|
|
125
|
-
return COMPILER_TYPES.ArraySlot;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
}
|
|
95
|
+
catch {
|
|
130
96
|
}
|
|
131
97
|
}
|
|
132
98
|
return COMPILER_TYPES.Unknown;
|
|
@@ -135,8 +101,7 @@ function isTypeArray(type, checker) {
|
|
|
135
101
|
if (checker.isArrayType(type)) {
|
|
136
102
|
return true;
|
|
137
103
|
}
|
|
138
|
-
|
|
139
|
-
return symbol?.getName() === 'ReactiveArray';
|
|
104
|
+
return type.getSymbol()?.getName() === 'ReactiveArray';
|
|
140
105
|
}
|
|
141
106
|
function isTypeFunction(type, checker) {
|
|
142
107
|
if (type.getCallSignatures().length > 0) {
|
|
@@ -152,7 +117,7 @@ function isTypeFunction(type, checker) {
|
|
|
152
117
|
return false;
|
|
153
118
|
}
|
|
154
119
|
const analyzeExpression = (expr, checker) => {
|
|
155
|
-
return inferCOMPILER_TYPES(expr, checker
|
|
120
|
+
return inferCOMPILER_TYPES(expr, checker);
|
|
156
121
|
};
|
|
157
122
|
const generateAttributeBinding = (elementVar, name, expr, staticValue, ns) => {
|
|
158
123
|
if (name.startsWith('on') && name.length > 2) {
|
|
@@ -176,7 +141,7 @@ const generateAttributeBinding = (elementVar, name, expr, staticValue, ns) => {
|
|
|
176
141
|
}
|
|
177
142
|
return `${ns}.attributes.setProperty(${elementVar}, '${name}', ${expr});`;
|
|
178
143
|
};
|
|
179
|
-
const generateSpreadBindings = (expr, exprCode, elementVar,
|
|
144
|
+
const generateSpreadBindings = (expr, exprCode, elementVar, checker, ns) => {
|
|
180
145
|
while (ts.isParenthesizedExpression(expr)) {
|
|
181
146
|
expr = expr.expression;
|
|
182
147
|
}
|
|
@@ -191,11 +156,11 @@ const generateSpreadBindings = (expr, exprCode, elementVar, sourceFile, checker,
|
|
|
191
156
|
for (let j = 0, m = expr.properties.length; j < m; j++) {
|
|
192
157
|
let prop = expr.properties[j];
|
|
193
158
|
if (ts.isPropertyAssignment(prop)) {
|
|
194
|
-
let
|
|
159
|
+
let text = ts.isIdentifier(prop.name)
|
|
195
160
|
? prop.name.text
|
|
196
161
|
: ts.isStringLiteral(prop.name) ? prop.name.text : null;
|
|
197
|
-
if (
|
|
198
|
-
value = prop.initializer.getText(
|
|
162
|
+
if (text === key) {
|
|
163
|
+
value = prop.initializer.getText();
|
|
199
164
|
break;
|
|
200
165
|
}
|
|
201
166
|
}
|
package/package.json
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@esportsplus/queue": "^0.2.0",
|
|
5
|
-
"@esportsplus/reactivity": "^0.25.
|
|
5
|
+
"@esportsplus/reactivity": "^0.25.14",
|
|
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.20.0",
|
|
11
11
|
"@types/node": "^25.0.3",
|
|
12
12
|
"vite": "^7.3.0",
|
|
13
13
|
"vite-tsconfig-paths": "^6.0.3"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"type": "module",
|
|
42
42
|
"types": "./build/index.d.ts",
|
|
43
|
-
"version": "0.32.
|
|
43
|
+
"version": "0.32.3",
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsc",
|
|
46
46
|
"build:test": "vite build --config test/vite.config.ts",
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import './runtime';
|
|
2
|
-
export { default as attributes } from './attributes';
|
|
3
|
-
export { default as event } from './event';
|
|
4
2
|
export { default as html } from './html';
|
|
5
3
|
export { default as render } from './render';
|
|
6
|
-
export { default as slot } from './slot';
|
|
7
4
|
export { default as svg } from './svg';
|
|
5
|
+
|
|
6
|
+
// Must be exported for compilation even if not used directly
|
|
7
|
+
export { default as attributes } from './attributes';
|
|
8
|
+
export { default as event } from './event';
|
|
9
|
+
export { default as slot } from './slot';
|
|
8
10
|
export { ArraySlot } from './slot/array';
|
|
9
11
|
export { EffectSlot } from './slot/effect';
|
|
10
12
|
export type { Attributes, Element, Renderable } from './types';
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
1
|
+
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { ast, code as c, uid, type Replacement } from '@esportsplus/typescript/transformer';
|
|
3
3
|
import type { ReactiveCallInfo, TemplateInfo } from './ts-parser';
|
|
4
4
|
import { analyzeExpression, generateAttributeBinding, generateSpreadBindings } from './type-analyzer';
|
|
5
|
-
import { ts } from '@esportsplus/typescript';
|
|
6
5
|
import {
|
|
7
|
-
COMPILER_ENTRYPOINT,
|
|
8
|
-
COMPILER_NAMESPACE,
|
|
9
|
-
COMPILER_TYPES,
|
|
6
|
+
COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, COMPILER_TYPES,
|
|
10
7
|
PACKAGE
|
|
11
|
-
} from '
|
|
8
|
+
} from '~/constants';
|
|
12
9
|
import parser from './parser';
|
|
13
10
|
|
|
14
11
|
|
|
@@ -47,6 +44,7 @@ type ParseResult = {
|
|
|
47
44
|
|
|
48
45
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
49
46
|
|
|
47
|
+
|
|
50
48
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
51
49
|
|
|
52
50
|
|
|
@@ -192,15 +190,16 @@ function generateTemplateCode(
|
|
|
192
190
|
slot = slots[i];
|
|
193
191
|
|
|
194
192
|
if (slot.type === COMPILER_TYPES.AttributeSlot) {
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
let names = slot.attributes.names;
|
|
194
|
+
|
|
195
|
+
for (let j = 0, m = names.length; j < m; j++) {
|
|
196
|
+
let name = names[j];
|
|
197
197
|
|
|
198
198
|
if (name === 'spread') {
|
|
199
199
|
let bindings = generateSpreadBindings(
|
|
200
200
|
exprNodes[index],
|
|
201
201
|
exprTexts[index] || 'undefined',
|
|
202
202
|
elementVar,
|
|
203
|
-
ctx.sourceFile,
|
|
204
203
|
ctx.checker,
|
|
205
204
|
COMPILER_NAMESPACE
|
|
206
205
|
);
|
|
@@ -212,20 +211,22 @@ function generateTemplateCode(
|
|
|
212
211
|
index++;
|
|
213
212
|
}
|
|
214
213
|
else {
|
|
215
|
-
|
|
214
|
+
code.push(
|
|
215
|
+
generateAttributeBinding(
|
|
216
216
|
elementVar,
|
|
217
217
|
name,
|
|
218
218
|
exprTexts[index++] || 'undefined',
|
|
219
219
|
slot.attributes.statics[name] || '',
|
|
220
220
|
COMPILER_NAMESPACE
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
code.push(binding);
|
|
221
|
+
)
|
|
222
|
+
);
|
|
224
223
|
}
|
|
225
224
|
}
|
|
226
225
|
}
|
|
227
226
|
else {
|
|
228
|
-
code.push(
|
|
227
|
+
code.push(
|
|
228
|
+
generateNodeBinding(ctx, elementVar, exprTexts[index] || 'undefined', exprNodes[index])
|
|
229
|
+
);
|
|
229
230
|
index++;
|
|
230
231
|
}
|
|
231
232
|
}
|
|
@@ -272,69 +273,28 @@ function hasArraySlotImport(sourceFile: ts.SourceFile): boolean {
|
|
|
272
273
|
return false;
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
function hasMatch(node: ts.Node, predicate: (n: ts.Node) => boolean): boolean {
|
|
276
|
-
if (predicate(node)) {
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
let found = false;
|
|
281
|
-
|
|
282
|
-
ts.forEachChild(node, child => {
|
|
283
|
-
if (!found) {
|
|
284
|
-
found = hasMatch(child, predicate);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
return found;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function hasArraySlotUsage(node: ts.Node): boolean {
|
|
292
|
-
return hasMatch(node, n =>
|
|
293
|
-
ts.isNewExpression(n) &&
|
|
294
|
-
ts.isPropertyAccessExpression(n.expression) &&
|
|
295
|
-
n.expression.name.text === 'ArraySlot'
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function hasNestedTemplates(node: ts.Node): boolean {
|
|
300
|
-
return hasMatch(node, n => isNestedHtmlTemplate(n as ts.Expression));
|
|
301
|
-
}
|
|
302
|
-
|
|
303
276
|
function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
|
|
304
277
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
305
278
|
}
|
|
306
279
|
|
|
307
|
-
function isNestedTemplate(template: TemplateInfo, exprRanges: { end: number; start: number }[]): boolean {
|
|
308
|
-
for (let i = 0, n = exprRanges.length; i < n; i++) {
|
|
309
|
-
let range = exprRanges[i];
|
|
310
|
-
|
|
311
|
-
if (template.start >= range.start && template.end <= range.end) {
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
280
|
function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
|
|
320
281
|
if (isNestedHtmlTemplate(expr)) {
|
|
321
282
|
return generateNestedTemplateCode(ctx, expr);
|
|
322
283
|
}
|
|
323
284
|
|
|
324
|
-
if (!
|
|
285
|
+
if (!ast.hasMatch(expr, n => isNestedHtmlTemplate(n as ts.Expression))) {
|
|
325
286
|
return ctx.printer.printNode(ts.EmitHint.Expression, expr, ctx.sourceFile);
|
|
326
287
|
}
|
|
327
288
|
|
|
328
|
-
let
|
|
329
|
-
replacements: Replacement[] = [];
|
|
289
|
+
let replacements: Replacement[] = [];
|
|
330
290
|
|
|
331
|
-
collectNestedTemplateReplacements(ctx, expr,
|
|
291
|
+
collectNestedTemplateReplacements(ctx, expr, expr.getStart(), replacements);
|
|
332
292
|
|
|
333
293
|
return c.replaceReverse(expr.getText(ctx.sourceFile), replacements);
|
|
334
294
|
}
|
|
335
295
|
|
|
336
296
|
|
|
337
|
-
const
|
|
297
|
+
const addImport = (code: string): string => {
|
|
338
298
|
return `import * as ${COMPILER_NAMESPACE} from '${PACKAGE}';\n` + code;
|
|
339
299
|
};
|
|
340
300
|
|
|
@@ -344,17 +304,17 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
344
304
|
}
|
|
345
305
|
|
|
346
306
|
// Precompute expression ranges for nested template detection
|
|
347
|
-
let
|
|
307
|
+
let ranges: { end: number; start: number }[] = [];
|
|
348
308
|
|
|
349
309
|
for (let i = 0, n = templates.length; i < n; i++) {
|
|
350
310
|
let exprs = templates[i].expressions;
|
|
351
311
|
|
|
352
312
|
for (let j = 0, m = exprs.length; j < m; j++) {
|
|
353
|
-
|
|
313
|
+
ranges.push({ end: exprs[j].end, start: exprs[j].getStart() });
|
|
354
314
|
}
|
|
355
315
|
}
|
|
356
316
|
|
|
357
|
-
let rootTemplates = templates.filter(t => !
|
|
317
|
+
let rootTemplates = templates.filter(t => !ast.inRange(ranges, t.start, t.end));
|
|
358
318
|
|
|
359
319
|
if (rootTemplates.length === 0) {
|
|
360
320
|
return { changed: false, code: originalCode };
|
|
@@ -418,7 +378,7 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
418
378
|
factories.push(`const ${id} = ${COMPILER_NAMESPACE}.template(\`${html}\`);`);
|
|
419
379
|
}
|
|
420
380
|
|
|
421
|
-
code =
|
|
381
|
+
code = addImport(factories.join('\n') + code);
|
|
422
382
|
}
|
|
423
383
|
|
|
424
384
|
return { changed, code };
|
|
@@ -448,9 +408,13 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
448
408
|
};
|
|
449
409
|
|
|
450
410
|
const needsArraySlotImport = (sourceFile: ts.SourceFile): boolean => {
|
|
451
|
-
return
|
|
411
|
+
return ast.hasMatch(sourceFile, n =>
|
|
412
|
+
ts.isNewExpression(n) &&
|
|
413
|
+
ts.isPropertyAccessExpression(n.expression) &&
|
|
414
|
+
n.expression.name.text === 'ArraySlot'
|
|
415
|
+
) && !hasArraySlotImport(sourceFile);
|
|
452
416
|
};
|
|
453
417
|
|
|
454
418
|
|
|
455
|
-
export {
|
|
419
|
+
export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
456
420
|
export type { CodegenResult };
|
package/src/transformer/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { code as c } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import {
|
|
2
|
+
import { addImport, generateCode, generateReactiveInlining, needsArraySlotImport } from './codegen';
|
|
3
3
|
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY } from '../constants';
|
|
4
4
|
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
5
5
|
import { ts } from '@esportsplus/typescript';
|
|
@@ -14,28 +14,9 @@ type TransformResult = {
|
|
|
14
14
|
|
|
15
15
|
const PATTERNS = [`${COMPILER_ENTRYPOINT}\``, `${COMPILER_ENTRYPOINT}.${COMPILER_ENTRYPOINT_REACTIVITY}`];
|
|
16
16
|
|
|
17
|
+
const REGEX_BACKSLASH = /\\/g;
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
let typeChecker = program.getTypeChecker();
|
|
20
|
-
|
|
21
|
-
return (_context: ts.TransformationContext) => {
|
|
22
|
-
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
23
|
-
let code = sourceFile.getFullText();
|
|
24
|
-
|
|
25
|
-
if (!c.contains(code, { patterns: PATTERNS })) {
|
|
26
|
-
return sourceFile;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let result = transformCode(code, sourceFile, typeChecker);
|
|
30
|
-
|
|
31
|
-
if (!result.changed) {
|
|
32
|
-
return sourceFile;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return result.sourceFile;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
}
|
|
19
|
+
const REGEX_FORWARD_SLASH = /\//g;
|
|
39
20
|
|
|
40
21
|
|
|
41
22
|
const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformResult => {
|
|
@@ -48,30 +29,26 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
48
29
|
let checker: ts.TypeChecker | undefined,
|
|
49
30
|
fileName = sourceFile.fileName,
|
|
50
31
|
programSourceFile = program.getSourceFile(fileName)
|
|
51
|
-
|| program.getSourceFile(fileName.replace(
|
|
52
|
-
|| program.getSourceFile(fileName.replace(
|
|
32
|
+
|| program.getSourceFile(fileName.replace(REGEX_BACKSLASH, '/'))
|
|
33
|
+
|| program.getSourceFile(fileName.replace(REGEX_FORWARD_SLASH, '\\'));
|
|
53
34
|
|
|
54
35
|
if (programSourceFile) {
|
|
55
36
|
checker = program.getTypeChecker();
|
|
56
37
|
sourceFile = programSourceFile;
|
|
57
38
|
}
|
|
58
39
|
|
|
59
|
-
return transformCode(code, sourceFile, checker);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const transformCode = (code: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): TransformResult => {
|
|
63
40
|
let changed = false,
|
|
64
41
|
codegenChanged = false,
|
|
65
42
|
needsImport = false,
|
|
66
|
-
|
|
67
|
-
|
|
43
|
+
reactiveCalls = findReactiveCalls(sourceFile),
|
|
44
|
+
result = code;
|
|
68
45
|
|
|
69
46
|
if (reactiveCalls.length > 0) {
|
|
70
|
-
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
71
47
|
changed = true;
|
|
48
|
+
checker = undefined;
|
|
49
|
+
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
72
50
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
73
51
|
needsImport = needsArraySlotImport(sourceFile);
|
|
74
|
-
checker = undefined;
|
|
75
52
|
}
|
|
76
53
|
|
|
77
54
|
let templates = findHtmlTemplates(sourceFile);
|
|
@@ -87,7 +64,7 @@ const transformCode = (code: string, sourceFile: ts.SourceFile, checker?: ts.Typ
|
|
|
87
64
|
}
|
|
88
65
|
|
|
89
66
|
if (needsImport && !codegenChanged) {
|
|
90
|
-
result =
|
|
67
|
+
result = addImport(result);
|
|
91
68
|
}
|
|
92
69
|
|
|
93
70
|
if (changed) {
|
|
@@ -98,4 +75,4 @@ const transformCode = (code: string, sourceFile: ts.SourceFile, checker?: ts.Typ
|
|
|
98
75
|
};
|
|
99
76
|
|
|
100
77
|
|
|
101
|
-
export {
|
|
78
|
+
export { transform };
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { plugin } from '@esportsplus/typescript/transformer';
|
|
2
|
+
import { transform } from '..';
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
export default (
|
|
6
|
-
let typeChecker = program.getTypeChecker();
|
|
7
|
-
|
|
8
|
-
return () => {
|
|
9
|
-
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
10
|
-
let result = transformCode(sourceFile.getFullText(), sourceFile, typeChecker);
|
|
11
|
-
|
|
12
|
-
return result.changed ? result.sourceFile : sourceFile;
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
};
|
|
5
|
+
export default plugin.tsc(transform) as ReturnType<typeof plugin.tsc>;
|
|
@@ -1,45 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { PACKAGE } from '~/constants';
|
|
5
|
-
import { transform } from '~/transformer';
|
|
1
|
+
import { plugin } from '@esportsplus/typescript/transformer';
|
|
2
|
+
import { PACKAGE } from '../../constants';
|
|
3
|
+
import { transform } from '..';
|
|
6
4
|
|
|
7
5
|
|
|
8
|
-
export default (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
configResolved(config: ResolvedConfig) {
|
|
13
|
-
root = options?.root ?? config.root;
|
|
14
|
-
},
|
|
15
|
-
enforce: 'pre',
|
|
16
|
-
name: `${PACKAGE}/plugin-vite`,
|
|
17
|
-
transform(code: string, id: string) {
|
|
18
|
-
if (!TRANSFORM_PATTERN.test(id) || id.includes('node_modules')) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
let result = transform(
|
|
24
|
-
ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true),
|
|
25
|
-
program.get(root)
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
if (!result.changed) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return { code: result.code, map: null };
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
console.error(`${PACKAGE}: Error transforming ${id}:`, error);
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
watchChange(id: string) {
|
|
40
|
-
if (TRANSFORM_PATTERN.test(id)) {
|
|
41
|
-
program.delete(root);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
};
|
|
6
|
+
export default plugin.vite({
|
|
7
|
+
name: PACKAGE,
|
|
8
|
+
transform
|
|
9
|
+
});
|
|
@@ -20,15 +20,6 @@ type TemplateInfo = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
function isFunctionNode(node: ts.Node): boolean {
|
|
24
|
-
return (
|
|
25
|
-
ts.isArrowFunction(node) ||
|
|
26
|
-
ts.isFunctionDeclaration(node) ||
|
|
27
|
-
ts.isFunctionExpression(node) ||
|
|
28
|
-
ts.isMethodDeclaration(node)
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
23
|
function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[]): void {
|
|
33
24
|
if (
|
|
34
25
|
ts.isCallExpression(node) &&
|
|
@@ -51,7 +42,9 @@ function visitReactiveCalls(node: ts.Node, calls: ReactiveCallInfo[]): void {
|
|
|
51
42
|
}
|
|
52
43
|
|
|
53
44
|
function visitTemplates(node: ts.Node, depth: number, templates: TemplateInfo[]): void {
|
|
54
|
-
let nextDepth =
|
|
45
|
+
let nextDepth = (ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
|
|
46
|
+
? depth + 1
|
|
47
|
+
: depth;
|
|
55
48
|
|
|
56
49
|
if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && node.tag.text === COMPILER_ENTRYPOINT) {
|
|
57
50
|
let expressions: ts.Expression[] = [],
|
|
@@ -8,10 +8,6 @@ import {
|
|
|
8
8
|
import { ts } from '@esportsplus/typescript';
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
type AnalyzerContext = {
|
|
12
|
-
checker?: ts.TypeChecker;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
11
|
type SpreadAnalysis = {
|
|
16
12
|
canUnpack: boolean;
|
|
17
13
|
keys: string[];
|
|
@@ -47,37 +43,30 @@ function analyzeSpread(expr: ts.Expression, checker?: ts.TypeChecker): SpreadAna
|
|
|
47
43
|
|
|
48
44
|
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))) {
|
|
49
45
|
try {
|
|
50
|
-
let keys =
|
|
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
|
+
}
|
|
51
58
|
|
|
52
59
|
if (keys.length > 0) {
|
|
53
60
|
return { canUnpack: true, keys };
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
|
-
catch {
|
|
57
|
-
}
|
|
63
|
+
catch { }
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
return { canUnpack: false, keys: [] };
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
function
|
|
64
|
-
let keys: string[] = [],
|
|
65
|
-
props = type.getProperties();
|
|
66
|
-
|
|
67
|
-
for (let i = 0, n = props.length; i < n; i++) {
|
|
68
|
-
let name = props[i].getName();
|
|
69
|
-
|
|
70
|
-
if (name.startsWith('__') || name.startsWith('[')) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
keys.push(name);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return keys;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function inferCOMPILER_TYPES(expr: ts.Expression, ctx?: AnalyzerContext): COMPILER_TYPES {
|
|
69
|
+
function inferCOMPILER_TYPES(expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES {
|
|
81
70
|
while (ts.isParenthesizedExpression(expr)) {
|
|
82
71
|
expr = expr.expression;
|
|
83
72
|
}
|
|
@@ -121,8 +110,8 @@ function inferCOMPILER_TYPES(expr: ts.Expression, ctx?: AnalyzerContext): COMPIL
|
|
|
121
110
|
}
|
|
122
111
|
|
|
123
112
|
if (ts.isConditionalExpression(expr)) {
|
|
124
|
-
let whenFalse = inferCOMPILER_TYPES(expr.whenFalse,
|
|
125
|
-
whenTrue = inferCOMPILER_TYPES(expr.whenTrue,
|
|
113
|
+
let whenFalse = inferCOMPILER_TYPES(expr.whenFalse, checker),
|
|
114
|
+
whenTrue = inferCOMPILER_TYPES(expr.whenTrue, checker);
|
|
126
115
|
|
|
127
116
|
if (whenTrue === whenFalse) {
|
|
128
117
|
return whenTrue;
|
|
@@ -135,55 +124,19 @@ function inferCOMPILER_TYPES(expr: ts.Expression, ctx?: AnalyzerContext): COMPIL
|
|
|
135
124
|
return COMPILER_TYPES.Unknown;
|
|
136
125
|
}
|
|
137
126
|
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (ts.isIdentifier(expr)) {
|
|
142
|
-
try {
|
|
143
|
-
let type = checker.getTypeAtLocation(expr);
|
|
144
|
-
|
|
145
|
-
if (isTypeFunction(type, checker)) {
|
|
146
|
-
return COMPILER_TYPES.Effect;
|
|
147
|
-
}
|
|
127
|
+
if (checker && (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr))) {
|
|
128
|
+
try {
|
|
129
|
+
let type = checker.getTypeAtLocation(expr);
|
|
148
130
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
131
|
+
if (isTypeFunction(type, checker)) {
|
|
132
|
+
return COMPILER_TYPES.Effect;
|
|
152
133
|
}
|
|
153
|
-
catch {
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
134
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
let type = checker.getTypeAtLocation(expr);
|
|
160
|
-
|
|
161
|
-
if (isTypeFunction(type, checker)) {
|
|
162
|
-
return COMPILER_TYPES.Effect;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (isTypeArray(type, checker)) {
|
|
166
|
-
return COMPILER_TYPES.ArraySlot;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
135
|
+
if (isTypeArray(type, checker)) {
|
|
136
|
+
return COMPILER_TYPES.ArraySlot;
|
|
170
137
|
}
|
|
171
138
|
}
|
|
172
|
-
|
|
173
|
-
if (ts.isCallExpression(expr)) {
|
|
174
|
-
try {
|
|
175
|
-
let type = checker.getTypeAtLocation(expr);
|
|
176
|
-
|
|
177
|
-
if (isTypeFunction(type, checker)) {
|
|
178
|
-
return COMPILER_TYPES.Effect;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (isTypeArray(type, checker)) {
|
|
182
|
-
return COMPILER_TYPES.ArraySlot;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
}
|
|
139
|
+
catch {
|
|
187
140
|
}
|
|
188
141
|
}
|
|
189
142
|
|
|
@@ -195,9 +148,7 @@ function isTypeArray(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
|
195
148
|
return true;
|
|
196
149
|
}
|
|
197
150
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return symbol?.getName() === 'ReactiveArray';
|
|
151
|
+
return type.getSymbol()?.getName() === 'ReactiveArray';
|
|
201
152
|
}
|
|
202
153
|
|
|
203
154
|
function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
@@ -218,7 +169,7 @@ function isTypeFunction(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
|
218
169
|
|
|
219
170
|
|
|
220
171
|
const analyzeExpression = (expr: ts.Expression, checker?: ts.TypeChecker): COMPILER_TYPES => {
|
|
221
|
-
return inferCOMPILER_TYPES(expr, checker
|
|
172
|
+
return inferCOMPILER_TYPES(expr, checker);
|
|
222
173
|
};
|
|
223
174
|
|
|
224
175
|
const generateAttributeBinding = (elementVar: string, name: string, expr: string, staticValue: string, ns: string): string => {
|
|
@@ -256,7 +207,6 @@ const generateSpreadBindings = (
|
|
|
256
207
|
expr: ts.Expression,
|
|
257
208
|
exprCode: string,
|
|
258
209
|
elementVar: string,
|
|
259
|
-
sourceFile: ts.SourceFile,
|
|
260
210
|
checker: ts.TypeChecker | undefined,
|
|
261
211
|
ns: string
|
|
262
212
|
): string[] => {
|
|
@@ -281,12 +231,12 @@ const generateSpreadBindings = (
|
|
|
281
231
|
let prop = expr.properties[j];
|
|
282
232
|
|
|
283
233
|
if (ts.isPropertyAssignment(prop)) {
|
|
284
|
-
let
|
|
285
|
-
|
|
286
|
-
|
|
234
|
+
let text = ts.isIdentifier(prop.name)
|
|
235
|
+
? prop.name.text
|
|
236
|
+
: ts.isStringLiteral(prop.name) ? prop.name.text : null;
|
|
287
237
|
|
|
288
|
-
if (
|
|
289
|
-
value = prop.initializer.getText(
|
|
238
|
+
if (text === key) {
|
|
239
|
+
value = prop.initializer.getText();
|
|
290
240
|
break;
|
|
291
241
|
}
|
|
292
242
|
}
|