@esportsplus/template 0.34.1 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/attributes.d.ts +3 -3
- package/build/attributes.js +4 -1
- package/build/compiler/codegen.d.ts +3 -5
- package/build/compiler/codegen.js +48 -61
- package/build/compiler/index.d.ts +0 -6
- package/build/compiler/index.js +16 -17
- 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 +17 -105
- 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/attributes.ts +9 -6
- package/src/compiler/codegen.ts +65 -88
- package/src/compiler/index.ts +21 -19
- package/src/compiler/parser.ts +1 -1
- package/src/compiler/ts-parser.ts +53 -12
- package/src/compiler/type-analyzer.ts +24 -142
- 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
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.1",
|
|
45
39
|
"scripts": {
|
|
46
40
|
"build": "tsc",
|
|
47
41
|
"build:test": "vite build --config test/vite.config.ts",
|
package/src/attributes.ts
CHANGED
|
@@ -61,7 +61,7 @@ function list(
|
|
|
61
61
|
changed = false,
|
|
62
62
|
delimiter = delimiters[name],
|
|
63
63
|
store = (ctx ??= context(element)).store ??= {},
|
|
64
|
-
dynamic = store[name] as Set<string
|
|
64
|
+
dynamic = store[name] as Set<string>;
|
|
65
65
|
|
|
66
66
|
if (dynamic === undefined) {
|
|
67
67
|
let value = (element.getAttribute(name) || '').trim();
|
|
@@ -105,7 +105,7 @@ function list(
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
let cold = store[id] as Record<PropertyKey, true
|
|
108
|
+
let cold = store[id] as Record<PropertyKey, true>;
|
|
109
109
|
|
|
110
110
|
if (cold !== undefined) {
|
|
111
111
|
for (let part in cold) {
|
|
@@ -274,7 +274,7 @@ function task() {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
|
|
277
|
-
const setClass = (element: Element, classlist: false | string
|
|
277
|
+
const setClass = (element: Element, classlist: false | string, value: unknown) => {
|
|
278
278
|
let ctx = context(element),
|
|
279
279
|
store = ctx.store ??= {};
|
|
280
280
|
|
|
@@ -298,8 +298,11 @@ const setProperty = (element: Element, name: string, value: unknown) => {
|
|
|
298
298
|
}
|
|
299
299
|
};
|
|
300
300
|
|
|
301
|
-
const setProperties = function (element: Element, value
|
|
302
|
-
if (
|
|
301
|
+
const setProperties = function (element: Element, value?: Attributes | Attributes[] | false | null | undefined) {
|
|
302
|
+
if (!value) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
else if (isObject(value)) {
|
|
303
306
|
for (let name in value) {
|
|
304
307
|
let v = value[name];
|
|
305
308
|
|
|
@@ -317,7 +320,7 @@ const setProperties = function (element: Element, value: Attributes | Attributes
|
|
|
317
320
|
}
|
|
318
321
|
};
|
|
319
322
|
|
|
320
|
-
const setStyle = (element: Element, styles: false | string
|
|
323
|
+
const setStyle = (element: Element, styles: false | string, value: unknown) => {
|
|
321
324
|
let ctx = context(element),
|
|
322
325
|
store = ctx.store ??= {};
|
|
323
326
|
|
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
|
|
|
@@ -20,10 +17,10 @@ type Attribute = {
|
|
|
20
17
|
|
|
21
18
|
type CodegenContext = {
|
|
22
19
|
checker?: ts.TypeChecker;
|
|
23
|
-
|
|
24
|
-
htmlToTemplateId: Map<string, string>;
|
|
20
|
+
imports: Map<string, string>;
|
|
25
21
|
printer: ts.Printer;
|
|
26
22
|
sourceFile: ts.SourceFile;
|
|
23
|
+
templates: Map<string, string>;
|
|
27
24
|
};
|
|
28
25
|
|
|
29
26
|
type CodegenResult = {
|
|
@@ -44,15 +41,21 @@ type ParseResult = {
|
|
|
44
41
|
|
|
45
42
|
const ARROW_EMPTY_PARAMS = /\(\s*\)\s*=>\s*$/;
|
|
46
43
|
|
|
47
|
-
const REGEX_PACKAGE_IMPORT = new RegExp(
|
|
48
|
-
`import\\s*\\{[^}]*\\}\\s*from\\s*['"]${PACKAGE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"];?\\n?`,
|
|
49
|
-
'g'
|
|
50
|
-
);
|
|
51
|
-
|
|
52
44
|
|
|
53
45
|
let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
54
46
|
|
|
55
47
|
|
|
48
|
+
function addImport(ctx: CodegenContext, name: string): string {
|
|
49
|
+
let alias = ctx.imports.get(name);
|
|
50
|
+
|
|
51
|
+
if (!alias) {
|
|
52
|
+
alias = uid(name);
|
|
53
|
+
ctx.imports.set(name, alias);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return alias;
|
|
57
|
+
}
|
|
58
|
+
|
|
56
59
|
function collectNestedTemplateReplacements(
|
|
57
60
|
ctx: CodegenContext,
|
|
58
61
|
node: ts.Node,
|
|
@@ -103,7 +106,7 @@ function generateNestedTemplateCode(ctx: CodegenContext, node: ts.TaggedTemplate
|
|
|
103
106
|
|
|
104
107
|
function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: string, exprNode: ts.Expression | undefined): string {
|
|
105
108
|
if (!exprNode) {
|
|
106
|
-
return `${
|
|
109
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
if (isNestedHtmlTemplate(exprNode)) {
|
|
@@ -114,10 +117,10 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
114
117
|
|
|
115
118
|
switch (slotType) {
|
|
116
119
|
case COMPILER_TYPES.Effect:
|
|
117
|
-
return `new ${
|
|
120
|
+
return `new ${addImport(ctx, 'EffectSlot')}(${anchor}, ${exprText});`;
|
|
118
121
|
|
|
119
122
|
case COMPILER_TYPES.ArraySlot:
|
|
120
|
-
return `new ${
|
|
123
|
+
return `new ${addImport(ctx, 'ArraySlot')}(${anchor}, ${exprText});`;
|
|
121
124
|
|
|
122
125
|
case COMPILER_TYPES.Static:
|
|
123
126
|
return `${anchor}.textContent = ${exprText};`;
|
|
@@ -126,7 +129,7 @@ function generateNodeBinding(ctx: CodegenContext, anchor: string, exprText: stri
|
|
|
126
129
|
return `${anchor}.parentNode.insertBefore(${exprText}, ${anchor});`;
|
|
127
130
|
|
|
128
131
|
default:
|
|
129
|
-
return `${
|
|
132
|
+
return `${addImport(ctx, 'slot')}(${anchor}, ${exprText});`;
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
|
|
@@ -163,23 +166,30 @@ function generateTemplateCode(
|
|
|
163
166
|
continue;
|
|
164
167
|
}
|
|
165
168
|
|
|
166
|
-
let
|
|
167
|
-
|
|
169
|
+
let ancestor = root,
|
|
170
|
+
start = 0;
|
|
168
171
|
|
|
169
172
|
for (let j = path.length - 1; j >= 0; j--) {
|
|
170
173
|
let prefix = path.slice(0, j).join('.');
|
|
171
174
|
|
|
172
175
|
if (nodes.has(prefix)) {
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
ancestor = nodes.get(prefix)!;
|
|
177
|
+
start = j;
|
|
175
178
|
break;
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
|
|
179
|
-
let
|
|
180
|
-
|
|
182
|
+
let alias = addImport(ctx, 'Element'),
|
|
183
|
+
name = uid('element'),
|
|
184
|
+
segments = path.slice(start),
|
|
185
|
+
value = `${ancestor}.${segments.join('!.')}`;
|
|
186
|
+
|
|
187
|
+
// Cast root.firstChild to Element since DocumentFragment.firstChild returns ChildNode
|
|
188
|
+
if (ancestor === root && segments[0] === 'firstChild') {
|
|
189
|
+
value = value.replace(`${ancestor}.firstChild!`, `(${ancestor}.firstChild! as ${alias})`);
|
|
190
|
+
}
|
|
181
191
|
|
|
182
|
-
declarations.push(`${name} = ${
|
|
192
|
+
declarations.push(`${name} = ${value} as ${alias}`);
|
|
183
193
|
nodes.set(key, name);
|
|
184
194
|
}
|
|
185
195
|
|
|
@@ -189,7 +199,7 @@ function generateTemplateCode(
|
|
|
189
199
|
);
|
|
190
200
|
|
|
191
201
|
for (let i = 0, n = slots.length; i < n; i++) {
|
|
192
|
-
let
|
|
202
|
+
let element = slots[i].path.length === 0
|
|
193
203
|
? root
|
|
194
204
|
: (nodes.get(slots[i].path.join('.')) || root),
|
|
195
205
|
slot = slots[i];
|
|
@@ -202,11 +212,9 @@ function generateTemplateCode(
|
|
|
202
212
|
|
|
203
213
|
if (name === COMPILER_TYPES.Attributes) {
|
|
204
214
|
let bindings = generateSpreadBindings(
|
|
205
|
-
exprNodes[index],
|
|
206
215
|
exprTexts[index] || 'undefined',
|
|
207
|
-
|
|
208
|
-
ctx
|
|
209
|
-
COMPILER_NAMESPACE
|
|
216
|
+
element,
|
|
217
|
+
n => addImport(ctx, n)
|
|
210
218
|
);
|
|
211
219
|
|
|
212
220
|
for (let k = 0, o = bindings.length; k < o; k++) {
|
|
@@ -218,11 +226,11 @@ function generateTemplateCode(
|
|
|
218
226
|
else {
|
|
219
227
|
code.push(
|
|
220
228
|
generateAttributeBinding(
|
|
221
|
-
|
|
229
|
+
element,
|
|
222
230
|
name,
|
|
223
231
|
exprTexts[index++] || 'undefined',
|
|
224
232
|
slot.attributes.statics[name] || '',
|
|
225
|
-
|
|
233
|
+
n => addImport(ctx, n)
|
|
226
234
|
)
|
|
227
235
|
);
|
|
228
236
|
}
|
|
@@ -230,7 +238,7 @@ function generateTemplateCode(
|
|
|
230
238
|
}
|
|
231
239
|
else {
|
|
232
240
|
code.push(
|
|
233
|
-
generateNodeBinding(ctx,
|
|
241
|
+
generateNodeBinding(ctx, element, exprTexts[index] || 'undefined', exprNodes[index])
|
|
234
242
|
);
|
|
235
243
|
index++;
|
|
236
244
|
}
|
|
@@ -243,41 +251,16 @@ function generateTemplateCode(
|
|
|
243
251
|
}
|
|
244
252
|
|
|
245
253
|
function getOrCreateTemplateId(ctx: CodegenContext, html: string): string {
|
|
246
|
-
let id = ctx.
|
|
254
|
+
let id = ctx.templates.get(html);
|
|
247
255
|
|
|
248
256
|
if (!id) {
|
|
249
|
-
id = uid('
|
|
250
|
-
ctx.
|
|
251
|
-
ctx.htmlToTemplateId.set(html, id);
|
|
257
|
+
id = uid('template');
|
|
258
|
+
ctx.templates.set(html, id);
|
|
252
259
|
}
|
|
253
260
|
|
|
254
261
|
return id;
|
|
255
262
|
}
|
|
256
263
|
|
|
257
|
-
function hasArraySlotImport(sourceFile: ts.SourceFile): boolean {
|
|
258
|
-
for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
|
|
259
|
-
let stmt = sourceFile.statements[i];
|
|
260
|
-
|
|
261
|
-
if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
let bindings = stmt.importClause.namedBindings;
|
|
266
|
-
|
|
267
|
-
if (!ts.isNamedImports(bindings)) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
for (let j = 0, m = bindings.elements.length; j < m; j++) {
|
|
272
|
-
if (bindings.elements[j].name.text === 'ArraySlot') {
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
264
|
function isNestedHtmlTemplate(expr: ts.Expression): expr is ts.TaggedTemplateExpression {
|
|
282
265
|
return ts.isTaggedTemplateExpression(expr) && ts.isIdentifier(expr.tag) && expr.tag.text === COMPILER_ENTRYPOINT;
|
|
283
266
|
}
|
|
@@ -299,15 +282,7 @@ function rewriteExpression(ctx: CodegenContext, expr: ts.Expression): string {
|
|
|
299
282
|
}
|
|
300
283
|
|
|
301
284
|
|
|
302
|
-
const
|
|
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
|
-
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker): CodegenResult => {
|
|
285
|
+
const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFile: ts.SourceFile, checker?: ts.TypeChecker, existingAliases?: Map<string, string>): CodegenResult => {
|
|
311
286
|
if (templates.length === 0) {
|
|
312
287
|
return { changed: false, code: originalCode };
|
|
313
288
|
}
|
|
@@ -331,12 +306,13 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
331
306
|
|
|
332
307
|
let ctx: CodegenContext = {
|
|
333
308
|
checker,
|
|
334
|
-
|
|
335
|
-
|
|
309
|
+
templates: new Map(),
|
|
310
|
+
imports: existingAliases ?? new Map(),
|
|
336
311
|
printer,
|
|
337
312
|
sourceFile
|
|
338
313
|
},
|
|
339
|
-
replacements: Replacement[] = []
|
|
314
|
+
replacements: Replacement[] = [],
|
|
315
|
+
templateAlias = addImport(ctx, 'template');
|
|
340
316
|
|
|
341
317
|
for (let i = 0, n = rootTemplates.length; i < n; i++) {
|
|
342
318
|
let exprTexts: string[] = [],
|
|
@@ -380,20 +356,30 @@ const generateCode = (templates: TemplateInfo[], originalCode: string, sourceFil
|
|
|
380
356
|
let changed = replacements.length > 0,
|
|
381
357
|
code = c.replaceReverse(originalCode, replacements);
|
|
382
358
|
|
|
383
|
-
if (changed && ctx.
|
|
384
|
-
let
|
|
359
|
+
if (changed && ctx.templates.size > 0) {
|
|
360
|
+
let aliasedImports: string[] = [],
|
|
361
|
+
factories: string[] = [];
|
|
362
|
+
|
|
363
|
+
for (let [name, alias] of ctx.imports) {
|
|
364
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
365
|
+
}
|
|
385
366
|
|
|
386
|
-
for (let [
|
|
387
|
-
factories.push(`const ${id} = ${
|
|
367
|
+
for (let [html, id] of ctx.templates) {
|
|
368
|
+
factories.push(`const ${id} = ${templateAlias}(\`${html}\`);`);
|
|
388
369
|
}
|
|
389
370
|
|
|
390
|
-
|
|
371
|
+
// Remove html entrypoint and add aliased imports
|
|
372
|
+
code = imports.modify(code, sourceFile, PACKAGE, {
|
|
373
|
+
add: new Set(aliasedImports),
|
|
374
|
+
remove: [COMPILER_ENTRYPOINT]
|
|
375
|
+
});
|
|
376
|
+
code = factories.join('\n') + '\n\n' + code;
|
|
391
377
|
}
|
|
392
378
|
|
|
393
379
|
return { changed, code };
|
|
394
380
|
};
|
|
395
381
|
|
|
396
|
-
const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile): string => {
|
|
382
|
+
const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourceFile: ts.SourceFile, arraySlotAlias: string): string => {
|
|
397
383
|
if (calls.length === 0) {
|
|
398
384
|
return code;
|
|
399
385
|
}
|
|
@@ -405,7 +391,7 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
405
391
|
|
|
406
392
|
replacements.push({
|
|
407
393
|
end: call.end,
|
|
408
|
-
newText: `new ${
|
|
394
|
+
newText: `new ${arraySlotAlias}(
|
|
409
395
|
${printer.printNode(ts.EmitHint.Expression, call.arrayArg, sourceFile)},
|
|
410
396
|
${printer.printNode(ts.EmitHint.Expression, call.callbackArg, sourceFile)}
|
|
411
397
|
)`,
|
|
@@ -416,14 +402,5 @@ const generateReactiveInlining = (calls: ReactiveCallInfo[], code: string, sourc
|
|
|
416
402
|
return c.replaceReverse(code, replacements);
|
|
417
403
|
};
|
|
418
404
|
|
|
419
|
-
|
|
420
|
-
return ast.hasMatch(sourceFile, n =>
|
|
421
|
-
ts.isNewExpression(n) &&
|
|
422
|
-
ts.isPropertyAccessExpression(n.expression) &&
|
|
423
|
-
n.expression.name.text === 'ArraySlot'
|
|
424
|
-
) && !hasArraySlotImport(sourceFile);
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
export { addImport, generateCode, generateReactiveInlining, needsArraySlotImport };
|
|
405
|
+
export { generateCode, generateReactiveInlining };
|
|
429
406
|
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, uid } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_ENTRYPOINT_REACTIVITY, PACKAGE } from '~/constants';
|
|
4
|
+
import { generateCode, generateReactiveInlining } from './codegen';
|
|
5
|
+
import { findHtmlTemplates, findReactiveCalls } from './ts-parser';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
type TransformResult = {
|
|
@@ -39,22 +39,23 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
39
39
|
|
|
40
40
|
let changed = false,
|
|
41
41
|
codegenChanged = false,
|
|
42
|
-
|
|
43
|
-
reactiveCalls = findReactiveCalls(sourceFile),
|
|
42
|
+
existingAliases = new Map<string, string>(),
|
|
43
|
+
reactiveCalls = findReactiveCalls(sourceFile, checker),
|
|
44
44
|
result = code;
|
|
45
45
|
|
|
46
46
|
if (reactiveCalls.length > 0) {
|
|
47
|
+
let arraySlotAlias = uid('ArraySlot');
|
|
48
|
+
|
|
47
49
|
changed = true;
|
|
48
|
-
|
|
49
|
-
result = generateReactiveInlining(reactiveCalls, result, sourceFile);
|
|
50
|
+
existingAliases.set('ArraySlot', arraySlotAlias);
|
|
51
|
+
result = generateReactiveInlining(reactiveCalls, result, sourceFile, arraySlotAlias);
|
|
50
52
|
sourceFile = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
51
|
-
needsImport = needsArraySlotImport(sourceFile);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
let templates = findHtmlTemplates(sourceFile);
|
|
55
|
+
let templates = findHtmlTemplates(sourceFile, checker);
|
|
55
56
|
|
|
56
57
|
if (templates.length > 0) {
|
|
57
|
-
let codegenResult = generateCode(templates, result, sourceFile, checker);
|
|
58
|
+
let codegenResult = generateCode(templates, result, sourceFile, checker, existingAliases);
|
|
58
59
|
|
|
59
60
|
if (codegenResult.changed) {
|
|
60
61
|
changed = true;
|
|
@@ -63,8 +64,15 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
if
|
|
67
|
-
|
|
67
|
+
// Add aliased ArraySlot import if reactive calls were processed but codegen didn't run
|
|
68
|
+
if (existingAliases.size > 0 && !codegenChanged) {
|
|
69
|
+
let aliasedImports: string[] = [];
|
|
70
|
+
|
|
71
|
+
for (let [name, alias] of existingAliases) {
|
|
72
|
+
aliasedImports.push(`${name} as ${alias}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
result = imports.modify(result, sourceFile, PACKAGE, { add: aliasedImports });
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
if (changed) {
|
|
@@ -76,9 +84,3 @@ const transform = (sourceFile: ts.SourceFile, program: ts.Program): TransformRes
|
|
|
76
84
|
|
|
77
85
|
|
|
78
86
|
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
|
};
|