@geajs/vite-plugin 1.4.0 → 1.4.2
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/dist/index.mjs +507 -207
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
1
2
|
import _traverse from "@babel/traverse";
|
|
2
3
|
import _generate from "@babel/generator";
|
|
3
4
|
import * as t from "@babel/types";
|
|
4
|
-
import { parse } from "@babel/parser";
|
|
5
5
|
import { id, js, jsExpr, jsImport } from "eszter";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { dirname, relative, resolve } from "node:path";
|
|
7
|
+
import { dirname, posix, relative, resolve } from "node:path";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
//#region src/utils/babel-interop.ts
|
|
10
11
|
/**
|
|
@@ -277,215 +278,59 @@ function collectBindings(stmts, bindings) {
|
|
|
277
278
|
let localName;
|
|
278
279
|
if (t.isIdentifier(prop.value)) localName = prop.value.name;
|
|
279
280
|
else continue;
|
|
280
|
-
const mem = t.memberExpression(cloneExpr$
|
|
281
|
+
const mem = t.memberExpression(cloneExpr$1(decl.init), t.identifier(sourceKey));
|
|
281
282
|
bindings.set(localName, mem);
|
|
282
283
|
}
|
|
283
|
-
else if (t.isIdentifier(decl.id)) bindings.set(decl.id.name, cloneExpr$
|
|
284
|
+
else if (t.isIdentifier(decl.id)) bindings.set(decl.id.name, cloneExpr$1(decl.init));
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
287
|
}
|
|
287
288
|
/** Shallow clone to avoid AST aliasing hazards when substituting. */
|
|
288
|
-
function cloneExpr$
|
|
289
|
+
function cloneExpr$1(expr) {
|
|
289
290
|
return t.cloneNode(expr);
|
|
290
291
|
}
|
|
291
292
|
//#endregion
|
|
292
293
|
//#region src/closure-codegen/emit/emit-substitution.ts
|
|
293
|
-
|
|
294
|
-
return t.cloneNode(expr);
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Walk an expression tree and replace Identifier references that match a
|
|
298
|
-
* binding with the bound expression (also recursively). Used to rewrite
|
|
299
|
-
* JSX-expression member chains so their root is the real reactive source.
|
|
300
|
-
* Returns a new expression; does not mutate input.
|
|
301
|
-
*/
|
|
294
|
+
/** Resolve template aliases at free read/write sites, preserving lexical scope. */
|
|
302
295
|
function substituteBindings(expr, bindings) {
|
|
303
296
|
if (!expr) return expr;
|
|
304
297
|
if (t.isTSAsExpression(expr) || t.isTSTypeAssertion(expr) || t.isTSNonNullExpression(expr) || t.isTSInstantiationExpression(expr)) return substituteBindings(expr.expression, bindings);
|
|
305
298
|
if (bindings.size === 0) return expr;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
299
|
+
const node = t.cloneNode(expr, true);
|
|
300
|
+
const statement = t.isStatement(node);
|
|
301
|
+
const container = t.isJSXExpressionContainer(node);
|
|
302
|
+
if (container && t.isJSXEmptyExpression(node.expression)) return node;
|
|
303
|
+
if (!statement && !container && !t.isExpression(node)) return node;
|
|
304
|
+
const file = t.file(t.program([statement ? node : t.expressionStatement(container ? node.expression : node)]));
|
|
305
|
+
traverse(file, { Identifier(path) {
|
|
306
|
+
const name = path.node.name;
|
|
307
|
+
if (!bindings.has(name) || path.scope.getBinding(name)) return;
|
|
308
|
+
if (!path.isReferencedIdentifier() && !isAssignmentTarget(path)) return;
|
|
309
|
+
const remaining = new Map(bindings);
|
|
310
|
+
remaining.delete(name);
|
|
311
|
+
const replacement = substituteBindings(bindings.get(name), remaining);
|
|
312
|
+
path.replaceWith(t.cloneNode(replacement, true));
|
|
313
|
+
path.skip();
|
|
314
|
+
} });
|
|
315
|
+
const result = statement ? file.program.body[0] : file.program.body[0].expression;
|
|
316
|
+
return container ? {
|
|
317
|
+
...node,
|
|
318
|
+
expression: result
|
|
319
|
+
} : result;
|
|
320
|
+
}
|
|
321
|
+
/** Assignment patterns write references; labels and declarations do not. */
|
|
322
|
+
function isAssignmentTarget(path) {
|
|
323
|
+
let target = path;
|
|
324
|
+
while (target.parentPath) {
|
|
325
|
+
const parent = target.parentPath;
|
|
326
|
+
if (parent.isObjectProperty() && target.key === "value" || parent.isObjectPattern() || parent.isArrayPattern() || parent.isRestElement() && target.key === "argument" || parent.isAssignmentPattern() && target.key === "left") {
|
|
327
|
+
target = parent;
|
|
328
|
+
continue;
|
|
312
329
|
}
|
|
313
|
-
return
|
|
314
|
-
}
|
|
315
|
-
if (t.isMemberExpression(expr)) return {
|
|
316
|
-
...expr,
|
|
317
|
-
object: substituteBindings(expr.object, bindings),
|
|
318
|
-
property: expr.computed ? substituteBindings(expr.property, bindings) : expr.property
|
|
319
|
-
};
|
|
320
|
-
if (t.isCallExpression(expr)) return {
|
|
321
|
-
...expr,
|
|
322
|
-
callee: substituteBindings(expr.callee, bindings),
|
|
323
|
-
arguments: expr.arguments.map((a) => substituteBindings(a, bindings))
|
|
324
|
-
};
|
|
325
|
-
if (t.isBinaryExpression(expr) || t.isLogicalExpression(expr)) return {
|
|
326
|
-
...expr,
|
|
327
|
-
left: substituteBindings(expr.left, bindings),
|
|
328
|
-
right: substituteBindings(expr.right, bindings)
|
|
329
|
-
};
|
|
330
|
-
if (t.isConditionalExpression(expr)) return {
|
|
331
|
-
...expr,
|
|
332
|
-
test: substituteBindings(expr.test, bindings),
|
|
333
|
-
consequent: substituteBindings(expr.consequent, bindings),
|
|
334
|
-
alternate: substituteBindings(expr.alternate, bindings)
|
|
335
|
-
};
|
|
336
|
-
if (t.isUnaryExpression(expr) || t.isUpdateExpression(expr)) return {
|
|
337
|
-
...expr,
|
|
338
|
-
argument: substituteBindings(expr.argument, bindings)
|
|
339
|
-
};
|
|
340
|
-
if (t.isTemplateLiteral(expr)) return {
|
|
341
|
-
...expr,
|
|
342
|
-
expressions: expr.expressions.map((e) => substituteBindings(e, bindings))
|
|
343
|
-
};
|
|
344
|
-
if (t.isOptionalMemberExpression(expr)) return {
|
|
345
|
-
...expr,
|
|
346
|
-
object: substituteBindings(expr.object, bindings),
|
|
347
|
-
property: expr.computed ? substituteBindings(expr.property, bindings) : expr.property
|
|
348
|
-
};
|
|
349
|
-
if (t.isOptionalCallExpression(expr)) return {
|
|
350
|
-
...expr,
|
|
351
|
-
callee: substituteBindings(expr.callee, bindings),
|
|
352
|
-
arguments: expr.arguments.map((a) => substituteBindings(a, bindings))
|
|
353
|
-
};
|
|
354
|
-
if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
|
|
355
|
-
const paramNames = /* @__PURE__ */ new Set();
|
|
356
|
-
for (const p of expr.params) if (t.isIdentifier(p)) paramNames.add(p.name);
|
|
357
|
-
else if (t.isObjectPattern(p)) {
|
|
358
|
-
for (const prop of p.properties) if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) paramNames.add(prop.value.name);
|
|
359
|
-
}
|
|
360
|
-
const shadowed = new Map(bindings);
|
|
361
|
-
for (const k of paramNames) shadowed.delete(k);
|
|
362
|
-
if (shadowed.size === 0) return expr;
|
|
363
|
-
return {
|
|
364
|
-
...expr,
|
|
365
|
-
body: substituteBindings(expr.body, shadowed)
|
|
366
|
-
};
|
|
330
|
+
return (parent.isAssignmentExpression() || parent.isForInStatement() || parent.isForOfStatement()) && target.key === "left" || parent.isUpdateExpression() && target.key === "argument";
|
|
367
331
|
}
|
|
368
|
-
|
|
369
|
-
...expr,
|
|
370
|
-
body: expr.body.map((s) => substituteBindings(s, bindings))
|
|
371
|
-
};
|
|
372
|
-
if (t.isExpressionStatement(expr)) return {
|
|
373
|
-
...expr,
|
|
374
|
-
expression: substituteBindings(expr.expression, bindings)
|
|
375
|
-
};
|
|
376
|
-
if (t.isReturnStatement(expr)) return expr.argument ? {
|
|
377
|
-
...expr,
|
|
378
|
-
argument: substituteBindings(expr.argument, bindings)
|
|
379
|
-
} : expr;
|
|
380
|
-
if (t.isIfStatement(expr)) return {
|
|
381
|
-
...expr,
|
|
382
|
-
test: substituteBindings(expr.test, bindings),
|
|
383
|
-
consequent: substituteBindings(expr.consequent, bindings),
|
|
384
|
-
alternate: expr.alternate ? substituteBindings(expr.alternate, bindings) : null
|
|
385
|
-
};
|
|
386
|
-
if (t.isVariableDeclaration(expr)) return {
|
|
387
|
-
...expr,
|
|
388
|
-
declarations: expr.declarations.map((d) => ({
|
|
389
|
-
...d,
|
|
390
|
-
init: d.init ? substituteBindings(d.init, bindings) : null
|
|
391
|
-
}))
|
|
392
|
-
};
|
|
393
|
-
if (t.isTryStatement(expr)) return {
|
|
394
|
-
...expr,
|
|
395
|
-
block: substituteBindings(expr.block, bindings),
|
|
396
|
-
handler: expr.handler ? {
|
|
397
|
-
...expr.handler,
|
|
398
|
-
body: substituteBindings(expr.handler.body, bindings)
|
|
399
|
-
} : null,
|
|
400
|
-
finalizer: expr.finalizer ? substituteBindings(expr.finalizer, bindings) : null
|
|
401
|
-
};
|
|
402
|
-
if (t.isForStatement(expr) || t.isForInStatement(expr) || t.isForOfStatement(expr)) return {
|
|
403
|
-
...expr,
|
|
404
|
-
body: substituteBindings(expr.body, bindings)
|
|
405
|
-
};
|
|
406
|
-
if (t.isWhileStatement(expr) || t.isDoWhileStatement(expr)) return {
|
|
407
|
-
...expr,
|
|
408
|
-
test: substituteBindings(expr.test, bindings),
|
|
409
|
-
body: substituteBindings(expr.body, bindings)
|
|
410
|
-
};
|
|
411
|
-
if (t.isSwitchStatement(expr)) return {
|
|
412
|
-
...expr,
|
|
413
|
-
discriminant: substituteBindings(expr.discriminant, bindings),
|
|
414
|
-
cases: expr.cases.map((c) => ({
|
|
415
|
-
...c,
|
|
416
|
-
consequent: c.consequent.map((s) => substituteBindings(s, bindings))
|
|
417
|
-
}))
|
|
418
|
-
};
|
|
419
|
-
if (t.isArrayExpression(expr)) return {
|
|
420
|
-
...expr,
|
|
421
|
-
elements: expr.elements.map((e) => e ? substituteBindings(e, bindings) : e)
|
|
422
|
-
};
|
|
423
|
-
if (t.isObjectExpression(expr)) return {
|
|
424
|
-
...expr,
|
|
425
|
-
properties: expr.properties.map((p) => t.isObjectProperty(p) ? {
|
|
426
|
-
...p,
|
|
427
|
-
value: substituteBindings(p.value, bindings)
|
|
428
|
-
} : p)
|
|
429
|
-
};
|
|
430
|
-
if (t.isSpreadElement(expr)) return {
|
|
431
|
-
...expr,
|
|
432
|
-
argument: substituteBindings(expr.argument, bindings)
|
|
433
|
-
};
|
|
434
|
-
if (t.isNewExpression(expr)) return {
|
|
435
|
-
...expr,
|
|
436
|
-
callee: substituteBindings(expr.callee, bindings),
|
|
437
|
-
arguments: expr.arguments.map((a) => substituteBindings(a, bindings))
|
|
438
|
-
};
|
|
439
|
-
if (t.isSequenceExpression(expr)) return {
|
|
440
|
-
...expr,
|
|
441
|
-
expressions: expr.expressions.map((e) => substituteBindings(e, bindings))
|
|
442
|
-
};
|
|
443
|
-
if (t.isAssignmentExpression(expr)) return {
|
|
444
|
-
...expr,
|
|
445
|
-
right: substituteBindings(expr.right, bindings)
|
|
446
|
-
};
|
|
447
|
-
if (t.isJSXElement(expr)) {
|
|
448
|
-
const newOpening = {
|
|
449
|
-
...expr.openingElement,
|
|
450
|
-
attributes: expr.openingElement.attributes.map((a) => {
|
|
451
|
-
if (t.isJSXAttribute(a) && a.value && t.isJSXExpressionContainer(a.value) && !t.isJSXEmptyExpression(a.value.expression)) return {
|
|
452
|
-
...a,
|
|
453
|
-
value: {
|
|
454
|
-
...a.value,
|
|
455
|
-
expression: substituteBindings(a.value.expression, bindings)
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
return a;
|
|
459
|
-
})
|
|
460
|
-
};
|
|
461
|
-
const newChildren = expr.children.map((c) => substituteBindings(c, bindings));
|
|
462
|
-
return {
|
|
463
|
-
...expr,
|
|
464
|
-
openingElement: newOpening,
|
|
465
|
-
children: newChildren
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
if (t.isJSXFragment(expr)) return {
|
|
469
|
-
...expr,
|
|
470
|
-
children: expr.children.map((c) => substituteBindings(c, bindings))
|
|
471
|
-
};
|
|
472
|
-
if (t.isJSXExpressionContainer(expr) && !t.isJSXEmptyExpression(expr.expression)) return {
|
|
473
|
-
...expr,
|
|
474
|
-
expression: substituteBindings(expr.expression, bindings)
|
|
475
|
-
};
|
|
476
|
-
if (t.isJSXText(expr) || t.isJSXSpreadChild(expr) || t.isJSXEmptyExpression(expr)) return expr;
|
|
477
|
-
return expr;
|
|
332
|
+
return false;
|
|
478
333
|
}
|
|
479
|
-
/**
|
|
480
|
-
* Compile a JSX element/fragment into a clone-and-wire expression.
|
|
481
|
-
*
|
|
482
|
-
* Returns a BlockStatement that:
|
|
483
|
-
* - declares `const root = (_tpl<N>_root || (_tpl<N>_root = _tpl<N>_create())).cloneNode(true)`
|
|
484
|
-
* - wires all slots
|
|
485
|
-
* - returns `root`
|
|
486
|
-
*
|
|
487
|
-
* Side effect: adds the lazy template root cache to `ctx.templateDecls`.
|
|
488
|
-
*/
|
|
489
334
|
//#endregion
|
|
490
335
|
//#region src/closure-codegen/emit/template-decl.ts
|
|
491
336
|
const SVG_TEMPLATE_ROOT_TAGS = new Set([
|
|
@@ -621,6 +466,27 @@ function emitWalkExpr(root, walk, walkKinds) {
|
|
|
621
466
|
return out;
|
|
622
467
|
}
|
|
623
468
|
//#endregion
|
|
469
|
+
//#region src/closure-codegen/keyed-list/keyed-list-item-reader.ts
|
|
470
|
+
/** Read through the row observable so primitive replacements remain reactive too. */
|
|
471
|
+
function useObservableItem(createItem, importsNeeded) {
|
|
472
|
+
if (!t.isArrowFunctionExpression(createItem)) throw new Error("Expected a keyed-list row factory");
|
|
473
|
+
const item = createItem.params[0];
|
|
474
|
+
const names = /* @__PURE__ */ new Set();
|
|
475
|
+
t.traverseFast(createItem, (node) => {
|
|
476
|
+
if (t.isIdentifier(node)) names.add(node.name);
|
|
477
|
+
});
|
|
478
|
+
let name = "__geaItemObservable";
|
|
479
|
+
while (names.has(name)) name += "_";
|
|
480
|
+
const observable = t.identifier(name);
|
|
481
|
+
const read = t.callExpression(t.identifier("readItem"), [observable]);
|
|
482
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
483
|
+
if (t.isIdentifier(item)) bindings.set(item.name, read);
|
|
484
|
+
else for (const name of Object.keys(t.getBindingIdentifiers(item))) bindings.set(name, t.callExpression(t.arrowFunctionExpression([t.cloneNode(item, true)], t.identifier(name)), [t.cloneNode(read, true)]));
|
|
485
|
+
createItem.body = substituteBindings(createItem.body, bindings);
|
|
486
|
+
createItem.params[0] = observable;
|
|
487
|
+
importsNeeded.add("readItem");
|
|
488
|
+
}
|
|
489
|
+
//#endregion
|
|
624
490
|
//#region src/closure-codegen/emit/emit-map-branch.ts
|
|
625
491
|
function buildMapBranchFn(mapExpr, ctx) {
|
|
626
492
|
const arrowExpr = mapExpr.arguments[0];
|
|
@@ -644,7 +510,7 @@ function buildMapBranchFn(mapExpr, ctx) {
|
|
|
644
510
|
t.identifier("d")
|
|
645
511
|
], createItemBlock);
|
|
646
512
|
ctx.importsNeeded.add("createItemObservable");
|
|
647
|
-
ctx.importsNeeded
|
|
513
|
+
useObservableItem(createItemFn, ctx.importsNeeded);
|
|
648
514
|
ctx.importsNeeded.add("_rescue");
|
|
649
515
|
ctx.importsNeeded.add("GEA_PROXY_RAW");
|
|
650
516
|
const listId = "L" + ctx.listCounter++;
|
|
@@ -658,7 +524,7 @@ function buildMapBranchFn(mapExpr, ctx) {
|
|
|
658
524
|
t.identifier(rqName),
|
|
659
525
|
t.callExpression(t.identifier("String"), [t.identifier("__k")]),
|
|
660
526
|
t.identifier(itemParam)
|
|
661
|
-
]))]), t.ifStatement(t.identifier("__r"), t.blockStatement([t.returnStatement(t.identifier("__r"))])), t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__rd"), t.callExpression(t.memberExpression(t.identifier("d"), t.identifier("child")), []))]), t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__obs"), t.callExpression(t.identifier("createItemObservable"), [t.identifier(itemParam)]))]), t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__li"), t.
|
|
527
|
+
]))]), t.ifStatement(t.identifier("__r"), t.blockStatement([t.returnStatement(t.identifier("__r"))])), t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__rd"), t.callExpression(t.memberExpression(t.identifier("d"), t.identifier("child")), []))]), t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__obs"), t.callExpression(t.identifier("createItemObservable"), [t.identifier(itemParam)]))]), t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__li"), t.identifier("__obs"))]), t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__el"), t.callExpression(t.identifier(ciName), [
|
|
662
528
|
t.identifier("__li"),
|
|
663
529
|
t.identifier(idxParam),
|
|
664
530
|
t.identifier("__rd")
|
|
@@ -2125,7 +1991,7 @@ function buildCreateEntryArrow(options, itemId, idxId) {
|
|
|
2125
1991
|
}
|
|
2126
1992
|
const obsId = t.identifier("__obs");
|
|
2127
1993
|
const liveItemId = t.identifier("__li");
|
|
2128
|
-
if (needsItemProxy) createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(obsId, t.callExpression(t.identifier("createItemObservable"), [itemId]))]), t.variableDeclaration("const", [t.variableDeclarator(liveItemId,
|
|
1994
|
+
if (needsItemProxy) createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(obsId, t.callExpression(t.identifier("createItemObservable"), [itemId]))]), t.variableDeclaration("const", [t.variableDeclarator(liveItemId, obsId)]));
|
|
2129
1995
|
else createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(obsId, t.nullLiteral())]), t.variableDeclaration("const", [t.variableDeclarator(liveItemId, itemId)]));
|
|
2130
1996
|
const elementId = t.identifier("__el");
|
|
2131
1997
|
createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(elementId, t.callExpression(t.identifier(ciName), [
|
|
@@ -2150,7 +2016,7 @@ function buildSimpleCreateEntryArrow(options, itemId, idxId) {
|
|
|
2150
2016
|
const liveItemId = t.identifier("__li");
|
|
2151
2017
|
const createEntryStmts = [t.variableDeclaration("const", [t.variableDeclarator(kId, keyExprWith(options, itemId, idxId))])];
|
|
2152
2018
|
if (needsRowDisposer) createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(rowDId, t.callExpression(t.memberExpression(t.identifier("d"), t.identifier("child")), []))]));
|
|
2153
|
-
if (needsItemProxy) createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(obsId, t.callExpression(t.identifier("createItemObservable"), [itemId]))]), t.variableDeclaration("const", [t.variableDeclarator(liveItemId,
|
|
2019
|
+
if (needsItemProxy) createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(obsId, t.callExpression(t.identifier("createItemObservable"), [itemId]))]), t.variableDeclaration("const", [t.variableDeclarator(liveItemId, obsId)]));
|
|
2154
2020
|
const createArgs = [needsItemProxy ? liveItemId : itemId, idxId];
|
|
2155
2021
|
if (needsRowDisposer) createArgs.push(rowDId);
|
|
2156
2022
|
createEntryStmts.push(t.variableDeclaration("const", [t.variableDeclarator(elementId, t.callExpression(t.identifier(ciName), createArgs))]));
|
|
@@ -3008,8 +2874,21 @@ function buildInlinePropKeyedListBlock(options) {
|
|
|
3008
2874
|
__PATCH__: options.patchEntryArrow
|
|
3009
2875
|
});
|
|
3010
2876
|
replaceByKeyMarker(block, options.relMatches);
|
|
2877
|
+
if (options.ctx.embedded) rewriteObserveReconcileForEmbedded(block);
|
|
3011
2878
|
return block;
|
|
3012
2879
|
}
|
|
2880
|
+
function rewriteObserveReconcileForEmbedded(node) {
|
|
2881
|
+
if (!node || typeof node !== "object") return;
|
|
2882
|
+
if (Array.isArray(node)) {
|
|
2883
|
+
for (const value of node) rewriteObserveReconcileForEmbedded(value);
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
if (node.type === "CallExpression" && t.isIdentifier(node.callee, { name: "__kl_reconcile" }) && node.arguments.length >= 1 && t.isIdentifier(node.arguments[0], { name: "_value" })) node.arguments[0] = t.callExpression(t.identifier("__kl_resolve"), []);
|
|
2887
|
+
for (const key of Object.keys(node)) {
|
|
2888
|
+
if (key === "loc" || key === "start" || key === "end" || key === "type") continue;
|
|
2889
|
+
rewriteObserveReconcileForEmbedded(node[key]);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
3013
2892
|
function isDirectIdKey(expr) {
|
|
3014
2893
|
if (!t.isArrowFunctionExpression(expr) || expr.params.length === 0) return false;
|
|
3015
2894
|
const firstParam = expr.params[0];
|
|
@@ -3150,7 +3029,7 @@ function emitKeyedListSlot(slot, stmts, ctx) {
|
|
|
3150
3029
|
const needsRowDisposer = !(patchRowExpr && t.isIdentifier(itemParam) && !createItemBodyReferencesRowDisposer(createItem));
|
|
3151
3030
|
if (needsItemProxy) {
|
|
3152
3031
|
ctx.importsNeeded.add("createItemObservable");
|
|
3153
|
-
ctx.importsNeeded
|
|
3032
|
+
useObservableItem(createItem, ctx.importsNeeded);
|
|
3154
3033
|
}
|
|
3155
3034
|
const ciName = "__ki_" + listId;
|
|
3156
3035
|
const prName = "__kp_" + listId;
|
|
@@ -5433,6 +5312,185 @@ function rewriteFnComponent(fnDecl, parentCtx) {
|
|
|
5433
5312
|
else fnDecl.params[1] = t.identifier("d");
|
|
5434
5313
|
fnDecl.body.body = newBody;
|
|
5435
5314
|
}
|
|
5315
|
+
//#endregion
|
|
5316
|
+
//#region src/closure-codegen/transform/transform-component-props.ts
|
|
5317
|
+
/**
|
|
5318
|
+
* Best-effort props-shape inference for compiled component classes.
|
|
5319
|
+
*
|
|
5320
|
+
* PROBLEM: the codegen rewrites `class HomeView extends Component { ... }`
|
|
5321
|
+
* into `class HomeView extends CompiledComponent { ... }` with no type
|
|
5322
|
+
* argument. `CompiledComponent<P extends Record<string, any> = Record<string,
|
|
5323
|
+
* any>>` then defaults `P` to `Record<string, any>`, so every
|
|
5324
|
+
* `this.props.x` read inside the class infers as `any` — even though the
|
|
5325
|
+
* original JSX call site (`<HomeView app={this} />`) fully determines the
|
|
5326
|
+
* real shape.
|
|
5327
|
+
*
|
|
5328
|
+
* FIX: scan the module for JSX usages of each locally-declared component and
|
|
5329
|
+
* synthesize a `{ attr: Type; ... }` literal from what's actually passed, so
|
|
5330
|
+
* callers can thread it onto the rewritten `extends CompiledXxx<...>` clause.
|
|
5331
|
+
*
|
|
5332
|
+
* This has no type checker to lean on — only the attribute value's own AST
|
|
5333
|
+
* shape. Only a few forms are classified:
|
|
5334
|
+
* - `this` → the name of the enclosing class
|
|
5335
|
+
* - string / template (static) → `string`
|
|
5336
|
+
* - number / boolean / null literal → their primitive type
|
|
5337
|
+
* Anything else (a spread attribute, JSX children, an arbitrary expression)
|
|
5338
|
+
* makes that JSX usage "unresolvable", and the whole component is skipped —
|
|
5339
|
+
* emitting a narrower-than-reality type would be worse than emitting none,
|
|
5340
|
+
* since geatsc has no boxing to fall back on.
|
|
5341
|
+
*/
|
|
5342
|
+
/**
|
|
5343
|
+
* Names among `componentNames` that appear as a JSX opening-element tag
|
|
5344
|
+
* anywhere in this module — regardless of whether the usage is resolvable
|
|
5345
|
+
* into a props shape (unlike `inferComponentPropsTypes`, which drops
|
|
5346
|
+
* unresolvable or props-less usages entirely).
|
|
5347
|
+
*
|
|
5348
|
+
* Used to distinguish "used here, but with a shape we couldn't infer" from
|
|
5349
|
+
* "never used as JSX in this file at all" — only the latter is safe grounds
|
|
5350
|
+
* for treating a class as provably propless. See `applyPropsTypeArgument`
|
|
5351
|
+
* in transform.ts for how this combines with `classPropsReadsAreCovered`.
|
|
5352
|
+
*/
|
|
5353
|
+
function collectComponentsUsedAsJsx(ast, componentNames) {
|
|
5354
|
+
const used = /* @__PURE__ */ new Set();
|
|
5355
|
+
if (componentNames.size === 0) return used;
|
|
5356
|
+
traverse(ast, {
|
|
5357
|
+
noScope: true,
|
|
5358
|
+
JSXElement(path) {
|
|
5359
|
+
const name = path.node.openingElement.name;
|
|
5360
|
+
if (t.isJSXIdentifier(name) && componentNames.has(name.name)) used.add(name.name);
|
|
5361
|
+
}
|
|
5362
|
+
});
|
|
5363
|
+
return used;
|
|
5364
|
+
}
|
|
5365
|
+
function inferComponentPropsTypes(ast, componentNames) {
|
|
5366
|
+
if (componentNames.size === 0) return /* @__PURE__ */ new Map();
|
|
5367
|
+
const usages = /* @__PURE__ */ new Map();
|
|
5368
|
+
const getUsage = (name) => {
|
|
5369
|
+
let usage = usages.get(name);
|
|
5370
|
+
if (!usage) {
|
|
5371
|
+
usage = {
|
|
5372
|
+
attrs: /* @__PURE__ */ new Map(),
|
|
5373
|
+
siteCount: 0,
|
|
5374
|
+
unresolvable: false
|
|
5375
|
+
};
|
|
5376
|
+
usages.set(name, usage);
|
|
5377
|
+
}
|
|
5378
|
+
return usage;
|
|
5379
|
+
};
|
|
5380
|
+
traverse(ast, {
|
|
5381
|
+
noScope: true,
|
|
5382
|
+
JSXElement(path) {
|
|
5383
|
+
const opening = path.node.openingElement;
|
|
5384
|
+
const name = opening.name;
|
|
5385
|
+
if (!t.isJSXIdentifier(name) || !componentNames.has(name.name)) return;
|
|
5386
|
+
const usage = getUsage(name.name);
|
|
5387
|
+
usage.siteCount++;
|
|
5388
|
+
if ((path.node.children ?? []).filter((child) => !(t.isJSXText(child) && /^\s*$/.test(child.value))).length > 0) usage.unresolvable = true;
|
|
5389
|
+
const enclosingClassName = findEnclosingClassName(path);
|
|
5390
|
+
for (const attr of opening.attributes) {
|
|
5391
|
+
if (!t.isJSXAttribute(attr) || !t.isJSXIdentifier(attr.name)) {
|
|
5392
|
+
usage.unresolvable = true;
|
|
5393
|
+
continue;
|
|
5394
|
+
}
|
|
5395
|
+
const attrName = attr.name.name;
|
|
5396
|
+
const typeStr = classifyAttrValue(attr.value, enclosingClassName);
|
|
5397
|
+
if (typeStr == null) {
|
|
5398
|
+
usage.unresolvable = true;
|
|
5399
|
+
continue;
|
|
5400
|
+
}
|
|
5401
|
+
let observation = usage.attrs.get(attrName);
|
|
5402
|
+
if (!observation) {
|
|
5403
|
+
observation = {
|
|
5404
|
+
types: /* @__PURE__ */ new Set(),
|
|
5405
|
+
seenCount: 0
|
|
5406
|
+
};
|
|
5407
|
+
usage.attrs.set(attrName, observation);
|
|
5408
|
+
}
|
|
5409
|
+
observation.types.add(typeStr);
|
|
5410
|
+
observation.seenCount++;
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
});
|
|
5414
|
+
const result = /* @__PURE__ */ new Map();
|
|
5415
|
+
for (const [name, usage] of usages) {
|
|
5416
|
+
if (usage.unresolvable || usage.siteCount === 0 || usage.attrs.size === 0) continue;
|
|
5417
|
+
const members = [...usage.attrs.entries()].map(([attrName, observation]) => {
|
|
5418
|
+
const required = observation.seenCount === usage.siteCount;
|
|
5419
|
+
const member = t.tsPropertySignature(t.identifier(attrName), t.tsTypeAnnotation(unionOfTypeStrings(observation.types)));
|
|
5420
|
+
member.optional = !required;
|
|
5421
|
+
return member;
|
|
5422
|
+
});
|
|
5423
|
+
result.set(name, t.tsTypeLiteral(members));
|
|
5424
|
+
}
|
|
5425
|
+
return result;
|
|
5426
|
+
}
|
|
5427
|
+
/**
|
|
5428
|
+
* Guard against emitting a props type narrower than what the class body
|
|
5429
|
+
* actually reads. `classDecl` must already reflect the fully-lowered body
|
|
5430
|
+
* (template method compiled in place) — this scans every `this.props.<key>`
|
|
5431
|
+
* read in the class and requires each key to be present in `propsType`.
|
|
5432
|
+
* Only guards against the codegen NARROWING props out from under real reads;
|
|
5433
|
+
* it does not otherwise validate the inferred shape.
|
|
5434
|
+
*/
|
|
5435
|
+
function classPropsReadsAreCovered(classDecl, propsType) {
|
|
5436
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
5437
|
+
for (const member of propsType.members) if (t.isTSPropertySignature(member) && t.isIdentifier(member.key)) allowed.add(member.key.name);
|
|
5438
|
+
let covered = true;
|
|
5439
|
+
const isPropsMember = (node) => (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) && !node.computed && t.isIdentifier(node.property, { name: "props" }) && t.isThisExpression(node.object);
|
|
5440
|
+
const visit = (node) => {
|
|
5441
|
+
if (!covered || !node || typeof node !== "object") return;
|
|
5442
|
+
if (Array.isArray(node)) {
|
|
5443
|
+
for (const child of node) visit(child);
|
|
5444
|
+
return;
|
|
5445
|
+
}
|
|
5446
|
+
if ((t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) && !node.computed && t.isIdentifier(node.property) && isPropsMember(node.object)) {
|
|
5447
|
+
if (!allowed.has(node.property.name)) covered = false;
|
|
5448
|
+
return;
|
|
5449
|
+
}
|
|
5450
|
+
for (const key of Object.keys(node)) {
|
|
5451
|
+
if (key === "loc" || key === "start" || key === "end" || key === "type") continue;
|
|
5452
|
+
visit(node[key]);
|
|
5453
|
+
}
|
|
5454
|
+
};
|
|
5455
|
+
visit(classDecl.body.body);
|
|
5456
|
+
return covered;
|
|
5457
|
+
}
|
|
5458
|
+
function findEnclosingClassName(path) {
|
|
5459
|
+
let current = path.parentPath;
|
|
5460
|
+
while (current) {
|
|
5461
|
+
if (current.isFunctionExpression() || current.isFunctionDeclaration()) return null;
|
|
5462
|
+
if (current.isClassDeclaration()) return current.node.id ? current.node.id.name : null;
|
|
5463
|
+
current = current.parentPath;
|
|
5464
|
+
}
|
|
5465
|
+
return null;
|
|
5466
|
+
}
|
|
5467
|
+
function classifyAttrValue(value, enclosingClassName) {
|
|
5468
|
+
if (value == null) return "boolean";
|
|
5469
|
+
if (t.isStringLiteral(value)) return "string";
|
|
5470
|
+
const expr = t.isJSXExpressionContainer(value) ? value.expression : null;
|
|
5471
|
+
if (!expr || t.isJSXEmptyExpression(expr)) return null;
|
|
5472
|
+
if (t.isThisExpression(expr)) return enclosingClassName;
|
|
5473
|
+
if (t.isStringLiteral(expr)) return "string";
|
|
5474
|
+
if (t.isNumericLiteral(expr)) return "number";
|
|
5475
|
+
if (t.isBooleanLiteral(expr)) return "boolean";
|
|
5476
|
+
if (t.isNullLiteral(expr)) return "null";
|
|
5477
|
+
if (t.isTemplateLiteral(expr) && expr.expressions.length === 0) return "string";
|
|
5478
|
+
return null;
|
|
5479
|
+
}
|
|
5480
|
+
function unionOfTypeStrings(types) {
|
|
5481
|
+
const nodes = [...types].sort().map(typeStringToNode);
|
|
5482
|
+
if (nodes.length === 1) return nodes[0];
|
|
5483
|
+
return t.tsUnionType(nodes);
|
|
5484
|
+
}
|
|
5485
|
+
function typeStringToNode(typeStr) {
|
|
5486
|
+
switch (typeStr) {
|
|
5487
|
+
case "string": return t.tsStringKeyword();
|
|
5488
|
+
case "number": return t.tsNumberKeyword();
|
|
5489
|
+
case "boolean": return t.tsBooleanKeyword();
|
|
5490
|
+
case "null": return t.tsNullKeyword();
|
|
5491
|
+
default: return t.tsTypeReference(t.identifier(typeStr));
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5436
5494
|
const RESOLVED_RECONCILE_ID = "\0virtual:gea-reconcile";
|
|
5437
5495
|
const HMR_RUNTIME_ID = "virtual:gea-hmr";
|
|
5438
5496
|
const RESOLVED_HMR_RUNTIME_ID = "\0" + HMR_RUNTIME_ID;
|
|
@@ -5811,13 +5869,17 @@ function transformFile(source, _filename, options = {}) {
|
|
|
5811
5869
|
}
|
|
5812
5870
|
const ctx = createEmitContext();
|
|
5813
5871
|
ctx.irTemplates = [];
|
|
5872
|
+
ctx.embedded = options.embedded;
|
|
5814
5873
|
ctx.directFnComponents = collectDirectFnComponents(ast);
|
|
5815
5874
|
ctx.directFnComponentParams = collectDirectFnComponentParams(ast, ctx.directFnComponents);
|
|
5816
5875
|
ctx.directFnStringProps = collectDirectFnStringProps(ast, ctx.directFnComponents);
|
|
5817
5876
|
ctx.directFnNoDisposer = /* @__PURE__ */ new Set();
|
|
5818
|
-
|
|
5877
|
+
const localComponentNames = collectLocalClassComponents(ast);
|
|
5878
|
+
ctx.directClassComponents = new Set(localComponentNames);
|
|
5819
5879
|
for (const name of options.directClassComponents ?? []) ctx.directClassComponents.add(name);
|
|
5820
5880
|
ctx.directFactoryComponents = new Set(options.directFactoryComponents);
|
|
5881
|
+
const componentPropsShapes = inferComponentPropsTypes(ast, localComponentNames);
|
|
5882
|
+
const componentsUsedAsJsx = collectComponentsUsedAsJsx(ast, localComponentNames);
|
|
5821
5883
|
const rewritten = [];
|
|
5822
5884
|
const reactiveComponentNames = /* @__PURE__ */ new Set();
|
|
5823
5885
|
let firstClassIdx = -1;
|
|
@@ -5920,18 +5982,22 @@ function transformFile(source, _filename, options = {}) {
|
|
|
5920
5982
|
ctx.importsNeeded.add("CompiledComponent");
|
|
5921
5983
|
classDecl.superClass = t.identifier("CompiledComponent");
|
|
5922
5984
|
usesCompiledRuntimeBase = true;
|
|
5985
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5923
5986
|
} else if (useTinyReactiveComponent) {
|
|
5924
5987
|
ctx.importsNeeded.add("CompiledTinyReactiveComponent");
|
|
5925
5988
|
classDecl.superClass = t.identifier("CompiledTinyReactiveComponent");
|
|
5926
5989
|
usesCompiledRuntimeBase = true;
|
|
5990
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5927
5991
|
} else if (useLeanReactiveComponent) {
|
|
5928
5992
|
ctx.importsNeeded.add("CompiledLeanReactiveComponent");
|
|
5929
5993
|
classDecl.superClass = t.identifier("CompiledLeanReactiveComponent");
|
|
5930
5994
|
usesCompiledRuntimeBase = true;
|
|
5995
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5931
5996
|
} else if (t.isIdentifier(classDecl.superClass, { name: "Component" })) {
|
|
5932
5997
|
ctx.importsNeeded.add("CompiledReactiveComponent");
|
|
5933
5998
|
classDecl.superClass = t.identifier("CompiledReactiveComponent");
|
|
5934
5999
|
usesCompiledRuntimeBase = true;
|
|
6000
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5935
6001
|
}
|
|
5936
6002
|
if (usesCompiledRuntimeBase && hasAfterRenderAsyncHook && !hasOwnInstanceMethod(classDecl, "render")) {
|
|
5937
6003
|
ctx.importsNeeded.add("scheduleAfterRenderAsync");
|
|
@@ -6050,6 +6116,70 @@ function findClassDeclarationByName(ast, name) {
|
|
|
6050
6116
|
}
|
|
6051
6117
|
return null;
|
|
6052
6118
|
}
|
|
6119
|
+
/**
|
|
6120
|
+
* Thread an inferred props shape onto the rewritten `extends CompiledXxx`
|
|
6121
|
+
* clause as an explicit type argument, e.g. `CompiledComponent<{ app: App }>`.
|
|
6122
|
+
*
|
|
6123
|
+
* Two guards keep this from ever emitting a WRONG type (never just an
|
|
6124
|
+
* incomplete one):
|
|
6125
|
+
* - If the hand-written source already annotated `extends Component<...>`,
|
|
6126
|
+
* that `superTypeParameters` node survives the `superClass` identifier
|
|
6127
|
+
* swap above untouched — do not override the author's own type.
|
|
6128
|
+
* - If the class body reads a `this.props.<key>` that the inferred shape
|
|
6129
|
+
* doesn't cover (e.g. a caller in another file passes it, invisible to
|
|
6130
|
+
* this per-file scan), leave the class with no type argument so it keeps
|
|
6131
|
+
* the permissive `Record<string, any>` default rather than breaking.
|
|
6132
|
+
*
|
|
6133
|
+
* A class with NO JSX usage site in this file at all (a root component,
|
|
6134
|
+
* mounted by the runtime rather than written as `<App/>` anywhere) gets no
|
|
6135
|
+
* entry in `componentPropsShapes` — inference only ever runs over observed
|
|
6136
|
+
* call sites, so an unobserved component produces no shape, resolvable or
|
|
6137
|
+
* not. Falling back to the permissive `Record<string, any>` default in that
|
|
6138
|
+
* case isn't merely imprecise, it's backwards: silence at every call site is
|
|
6139
|
+
* exactly the evidence that the class takes no props, so the honest emission
|
|
6140
|
+
* is an explicit empty type — `{}` — not the default's promise that it might
|
|
6141
|
+
* accept arbitrary ones.
|
|
6142
|
+
*
|
|
6143
|
+
* Emitting that empty type is gated the same way as the shape-inference path
|
|
6144
|
+
* above, by the SAME two-guard standard, instantiated for the "no props"
|
|
6145
|
+
* claim instead of an inferred one:
|
|
6146
|
+
* - `!componentsUsedAsJsx.has(className)` — the per-file JSX-usage guard.
|
|
6147
|
+
* This is a per-file scan, so it cannot see a caller in ANOTHER file
|
|
6148
|
+
* that writes `<App foo={x}/>`. That is sound here for a reason the
|
|
6149
|
+
* general shape-inference path above does not get to rely on: the guard
|
|
6150
|
+
* below additionally requires the class to read NO `this.props.<key>` at
|
|
6151
|
+
* all, in its own body. A component that genuinely uses props always
|
|
6152
|
+
* reads at least one, so a cross-file caller passing real data to a
|
|
6153
|
+
* class that reads none of it would itself be dead-argument surface on
|
|
6154
|
+
* the CALLER, not a case this class's compiled shape needs to accept.
|
|
6155
|
+
* Nothing here can silently misbehave: the only externally-visible
|
|
6156
|
+
* effect of a stricter-than-{} … Record<string, any> narrowing is that
|
|
6157
|
+
* TypeScript itself would flag an excess/unknown prop at that other JSX
|
|
6158
|
+
* call site — a loud compile error, not silent wrong behavior, and
|
|
6159
|
+
* exactly the category of bug an empty props type exists to catch.
|
|
6160
|
+
* - `classPropsReadsAreCovered(classDecl, <empty type>)` — reuses the
|
|
6161
|
+
* existing reads-coverage guard against a zero-member shape, i.e. "this
|
|
6162
|
+
* class's body contains no `this.props.<key>` read whatsoever." This is
|
|
6163
|
+
* strictly stronger than the non-empty-shape case (which only requires
|
|
6164
|
+
* coverage of the OBSERVED keys) — here NO key may be read, observed or
|
|
6165
|
+
* not — so it still catches the shape-inference guard's original worry
|
|
6166
|
+
* (a real prop consumed only via a cross-file call site) as a special
|
|
6167
|
+
* case: if the body read that prop, this guard fails and the permissive
|
|
6168
|
+
* default is kept, exactly as before.
|
|
6169
|
+
*/
|
|
6170
|
+
function applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx) {
|
|
6171
|
+
if (classDecl.superTypeParameters) return;
|
|
6172
|
+
const propsType = componentPropsShapes.get(className);
|
|
6173
|
+
if (propsType) {
|
|
6174
|
+
if (!classPropsReadsAreCovered(classDecl, propsType)) return;
|
|
6175
|
+
classDecl.superTypeParameters = t.tsTypeParameterInstantiation([propsType]);
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
if (componentsUsedAsJsx.has(className)) return;
|
|
6179
|
+
const emptyPropsType = t.tsTypeLiteral([]);
|
|
6180
|
+
if (!classPropsReadsAreCovered(classDecl, emptyPropsType)) return;
|
|
6181
|
+
classDecl.superTypeParameters = t.tsTypeParameterInstantiation([emptyPropsType]);
|
|
6182
|
+
}
|
|
6053
6183
|
function collectLocalClassComponents(ast) {
|
|
6054
6184
|
const names = /* @__PURE__ */ new Set();
|
|
6055
6185
|
for (const node of ast.program.body) {
|
|
@@ -6370,10 +6500,12 @@ function injectHMR(ast, componentClassNames, defaultExportClassName, componentIm
|
|
|
6370
6500
|
hmrStmts.push(js`const __moduleExports = ${t.objectExpression(exportProperties)};`);
|
|
6371
6501
|
hmrStmts.push(js`registerHotModule(${jsExpr`${importMeta()}.url`}, __moduleExports);`);
|
|
6372
6502
|
const acceptBody = [t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__updatedModule"), t.logicalExpression("||", t.identifier("newModule"), t.identifier("__moduleExports")))]), t.expressionStatement(t.callExpression(t.identifier("registerHotModule"), [t.memberExpression(importMeta(), t.identifier("url")), t.identifier("__updatedModule")]))];
|
|
6503
|
+
acceptBody.push(js`let __patched = false;`);
|
|
6373
6504
|
for (const cn of componentClassNames) {
|
|
6374
6505
|
const key = cn === defaultExportClassName ? "default" : cn;
|
|
6375
|
-
acceptBody.push(t.expressionStatement(t.callExpression(t.identifier("handleComponentUpdate"), [t.memberExpression(importMeta(), t.identifier("url")), t.objectExpression([t.objectProperty(t.identifier("default"), t.memberExpression(t.identifier("__updatedModule"), t.identifier(key)))])])));
|
|
6506
|
+
acceptBody.push(t.expressionStatement(t.assignmentExpression("=", t.identifier("__patched"), t.logicalExpression("||", t.callExpression(t.identifier("handleComponentUpdate"), [t.memberExpression(importMeta(), t.identifier("url")), t.objectExpression([t.objectProperty(t.identifier("default"), t.memberExpression(t.identifier("__updatedModule"), t.identifier(key)))])]), t.identifier("__patched")))));
|
|
6376
6507
|
}
|
|
6508
|
+
acceptBody.push(t.ifStatement(t.unaryExpression("!", t.identifier("__patched")), t.expressionStatement(t.callExpression(t.memberExpression(hot(), t.identifier("invalidate")), []))));
|
|
6377
6509
|
hmrStmts.push(t.expressionStatement(t.callExpression(t.memberExpression(hot(), t.identifier("accept")), [t.arrowFunctionExpression([t.identifier("newModule")], t.blockStatement(acceptBody))])));
|
|
6378
6510
|
hmrStmts.push(...createAccepts(componentImports, proxyDep, shouldSkipDepAccept));
|
|
6379
6511
|
for (const cn of componentClassNames) {
|
|
@@ -6584,7 +6716,8 @@ function transform(ctx) {
|
|
|
6584
6716
|
const emitted = transformFile(code, sourceFile, {
|
|
6585
6717
|
directClassComponents: knownClassComponentImports,
|
|
6586
6718
|
directFactoryComponents: knownFactoryComponentImports,
|
|
6587
|
-
enableTinyReactiveComponents: !isServe
|
|
6719
|
+
enableTinyReactiveComponents: !isServe,
|
|
6720
|
+
embedded: ctx.embedded
|
|
6588
6721
|
});
|
|
6589
6722
|
if (emitted.changed) {
|
|
6590
6723
|
ir = emitted.ir;
|
|
@@ -6674,9 +6807,10 @@ function transformCompiledStoreModule(source, moduleId = "<unknown>", resolveImp
|
|
|
6674
6807
|
if (classDecls.length === 0) return null;
|
|
6675
6808
|
const constants = collectImportedLiteralConstants(ast, moduleId, resolveImportPath);
|
|
6676
6809
|
const fallbackIrs = classDecls.map((classDecl) => buildStoreIr(classDecl, moduleId, "compiled", constants, ast));
|
|
6810
|
+
const fallbackKeepAlive = storeMethodFreeFunctionKeepAlive(ast, classDecls);
|
|
6677
6811
|
const fallback = {
|
|
6678
|
-
code: source,
|
|
6679
|
-
changed:
|
|
6812
|
+
code: fallbackKeepAlive ? `${source}${fallbackKeepAlive}` : source,
|
|
6813
|
+
changed: !!fallbackKeepAlive,
|
|
6680
6814
|
ir: fallbackIrs[0],
|
|
6681
6815
|
irs: fallbackIrs
|
|
6682
6816
|
};
|
|
@@ -6943,6 +7077,34 @@ function literalConstantsFromFile(file, names) {
|
|
|
6943
7077
|
}
|
|
6944
7078
|
return constants;
|
|
6945
7079
|
}
|
|
7080
|
+
function moduleFreeFunctionNames(ast) {
|
|
7081
|
+
const names = /* @__PURE__ */ new Set();
|
|
7082
|
+
for (const node of ast.program.body) if (t.isFunctionDeclaration(node) && node.id) names.add(node.id.name);
|
|
7083
|
+
else if (t.isExportNamedDeclaration(node) && node.declaration && t.isFunctionDeclaration(node.declaration) && node.declaration.id) names.add(node.declaration.id.name);
|
|
7084
|
+
return names;
|
|
7085
|
+
}
|
|
7086
|
+
function nodeReferencesIdentifier(node, name) {
|
|
7087
|
+
if (!node || typeof node !== "object") return false;
|
|
7088
|
+
if (Array.isArray(node)) {
|
|
7089
|
+
for (const child of node) if (nodeReferencesIdentifier(child, name)) return true;
|
|
7090
|
+
return false;
|
|
7091
|
+
}
|
|
7092
|
+
const record = node;
|
|
7093
|
+
if (record.type === "Identifier" && record.name === name) return true;
|
|
7094
|
+
for (const key of Object.keys(record)) {
|
|
7095
|
+
if (key === "type" || key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments") continue;
|
|
7096
|
+
if (nodeReferencesIdentifier(record[key], name)) return true;
|
|
7097
|
+
}
|
|
7098
|
+
return false;
|
|
7099
|
+
}
|
|
7100
|
+
function storeMethodFreeFunctionKeepAlive(ast, classDecls) {
|
|
7101
|
+
const freeFns = moduleFreeFunctionNames(ast);
|
|
7102
|
+
if (freeFns.size === 0) return "";
|
|
7103
|
+
const kept = [];
|
|
7104
|
+
for (const name of freeFns) if (classDecls.some((classDecl) => classDecl.body.body.some((member) => t.isClassMethod(member) && nodeReferencesIdentifier(member.body, name)))) kept.push(name);
|
|
7105
|
+
if (kept.length === 0) return "";
|
|
7106
|
+
return `\n;(globalThis.__GEA_IR_KEEP__ ||= []).push(${kept.join(", ")});\n`;
|
|
7107
|
+
}
|
|
6946
7108
|
function literalConstant(name, value) {
|
|
6947
7109
|
if (t.isStringLiteral(value)) return {
|
|
6948
7110
|
name,
|
|
@@ -8080,6 +8242,7 @@ function geaPlugin(options = {}) {
|
|
|
8080
8242
|
const irComponents = /* @__PURE__ */ new Map();
|
|
8081
8243
|
const irStores = /* @__PURE__ */ new Map();
|
|
8082
8244
|
const hostCapabilities = /* @__PURE__ */ new Set();
|
|
8245
|
+
const staticModuleShapes = /* @__PURE__ */ new Map();
|
|
8083
8246
|
const storeRegistry = /* @__PURE__ */ new Map();
|
|
8084
8247
|
const resolveImportPath = (importer, source) => {
|
|
8085
8248
|
const base = resolve(dirname(importer), source);
|
|
@@ -8237,7 +8400,10 @@ function geaPlugin(options = {}) {
|
|
|
8237
8400
|
if (!cleanId.match(/\.(js|jsx|ts|tsx)$/) || cleanId.includes("node_modules")) return null;
|
|
8238
8401
|
let transformedCode = code;
|
|
8239
8402
|
let changed = false;
|
|
8240
|
-
if (irOptions?.enabled)
|
|
8403
|
+
if (irOptions?.enabled) {
|
|
8404
|
+
recordHostCapabilities(code);
|
|
8405
|
+
staticModuleShapes.set(cleanId, collectStaticModuleShape(code));
|
|
8406
|
+
}
|
|
8241
8407
|
if (code.includes("extends Store") || code.includes("new Store(")) {
|
|
8242
8408
|
storeModules.add(cleanId);
|
|
8243
8409
|
const storeClassName = extractStoreClassName(code);
|
|
@@ -8280,6 +8446,7 @@ function geaPlugin(options = {}) {
|
|
|
8280
8446
|
code: transformedCode,
|
|
8281
8447
|
isServe: isServeCommand,
|
|
8282
8448
|
isSSR,
|
|
8449
|
+
embedded: !!irOptions?.enabled,
|
|
8283
8450
|
hmrImportSource: HMR_RUNTIME_ID,
|
|
8284
8451
|
isStoreModule,
|
|
8285
8452
|
isComponentModule,
|
|
@@ -8322,7 +8489,7 @@ function geaPlugin(options = {}) {
|
|
|
8322
8489
|
map: null
|
|
8323
8490
|
};
|
|
8324
8491
|
},
|
|
8325
|
-
generateBundle(_options, bundle) {
|
|
8492
|
+
async generateBundle(_options, bundle) {
|
|
8326
8493
|
if (!irOptions?.enabled) return;
|
|
8327
8494
|
const renderedIds = renderedModuleIds(bundle);
|
|
8328
8495
|
const modules = Array.from(irModules.values()).filter((module) => {
|
|
@@ -8344,12 +8511,23 @@ function geaPlugin(options = {}) {
|
|
|
8344
8511
|
if (candidateModule && !modules.includes(candidateModule)) modules.push(candidateModule);
|
|
8345
8512
|
}
|
|
8346
8513
|
}
|
|
8514
|
+
const liveComponents = Array.from(irComponents.values()).filter((component) => componentIds.has(component.id));
|
|
8515
|
+
const logicalRoutes = await collectLogicalModuleRoutes({
|
|
8516
|
+
entries: entryFacadeModuleIds(bundle),
|
|
8517
|
+
root: resolvedConfig?.root,
|
|
8518
|
+
moduleShapes: staticModuleShapes,
|
|
8519
|
+
resolveModule: async (specifier, importer) => {
|
|
8520
|
+
const resolved = await this.resolve(specifier, importer, { skipSelf: true });
|
|
8521
|
+
return resolved && !resolved.external ? cleanRollupModuleId(resolved.id) : null;
|
|
8522
|
+
}
|
|
8523
|
+
});
|
|
8524
|
+
const components = liveComponents.map((component) => attachRootRendererAuthority(component, logicalRoutes, staticModuleShapes));
|
|
8347
8525
|
const irBundle = {
|
|
8348
8526
|
schema: "gea-ir",
|
|
8349
8527
|
version: 1,
|
|
8350
8528
|
entry: geaIrEntryFromBundle(bundle) ?? geaIrConfiguredEntry(resolvedConfig),
|
|
8351
8529
|
modules,
|
|
8352
|
-
components
|
|
8530
|
+
components,
|
|
8353
8531
|
stores: Array.from(irStores.values()).filter((store) => storeIds.has(store.id)),
|
|
8354
8532
|
hostCapabilities: Array.from(hostCapabilities).sort()
|
|
8355
8533
|
};
|
|
@@ -8427,6 +8605,105 @@ function geaPlugin(options = {}) {
|
|
|
8427
8605
|
if (/\bdocument\s*\./.test(source)) hostCapabilities.add("dom");
|
|
8428
8606
|
}
|
|
8429
8607
|
}
|
|
8608
|
+
function collectStaticModuleShape(source) {
|
|
8609
|
+
const specifiers = /* @__PURE__ */ new Set();
|
|
8610
|
+
const exportsByLocalName = /* @__PURE__ */ new Map();
|
|
8611
|
+
const addExport = (localName, exportName) => {
|
|
8612
|
+
const names = exportsByLocalName.get(localName) ?? /* @__PURE__ */ new Set();
|
|
8613
|
+
names.add(exportName);
|
|
8614
|
+
exportsByLocalName.set(localName, names);
|
|
8615
|
+
};
|
|
8616
|
+
try {
|
|
8617
|
+
const ast = parse(source, {
|
|
8618
|
+
sourceType: "module",
|
|
8619
|
+
plugins: [
|
|
8620
|
+
"typescript",
|
|
8621
|
+
"jsx",
|
|
8622
|
+
"classProperties",
|
|
8623
|
+
"classPrivateProperties",
|
|
8624
|
+
"classPrivateMethods"
|
|
8625
|
+
]
|
|
8626
|
+
});
|
|
8627
|
+
for (const statement of ast.program.body) {
|
|
8628
|
+
if ((statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") && statement.source?.value) specifiers.add(statement.source.value);
|
|
8629
|
+
if (statement.type === "ExportNamedDeclaration") {
|
|
8630
|
+
const declaration = statement.declaration;
|
|
8631
|
+
if ((declaration?.type === "ClassDeclaration" || declaration?.type === "FunctionDeclaration") && declaration.id) addExport(declaration.id.name, declaration.id.name);
|
|
8632
|
+
else if (declaration?.type === "VariableDeclaration") {
|
|
8633
|
+
for (const declarator of declaration.declarations) if (declarator.id.type === "Identifier") addExport(declarator.id.name, declarator.id.name);
|
|
8634
|
+
}
|
|
8635
|
+
if (!statement.source) for (const specifier of statement.specifiers) {
|
|
8636
|
+
if (specifier.type !== "ExportSpecifier") continue;
|
|
8637
|
+
addExport(specifier.local.type === "Identifier" ? specifier.local.name : specifier.local.value, specifier.exported.type === "Identifier" ? specifier.exported.name : specifier.exported.value);
|
|
8638
|
+
}
|
|
8639
|
+
} else if (statement.type === "ExportDefaultDeclaration") {
|
|
8640
|
+
const declaration = statement.declaration;
|
|
8641
|
+
if ((declaration.type === "ClassDeclaration" || declaration.type === "FunctionDeclaration") && declaration.id) addExport(declaration.id.name, "default");
|
|
8642
|
+
else if (declaration.type === "Identifier") addExport(declaration.name, "default");
|
|
8643
|
+
}
|
|
8644
|
+
}
|
|
8645
|
+
} catch {}
|
|
8646
|
+
return {
|
|
8647
|
+
specifiers: [...specifiers].sort(),
|
|
8648
|
+
exportsByLocalName: new Map([...exportsByLocalName].map(([localName, exportNames]) => [localName, new Set([...exportNames].sort())]))
|
|
8649
|
+
};
|
|
8650
|
+
}
|
|
8651
|
+
/** Logical source coordinates are relative to the project, never to its checkout. */
|
|
8652
|
+
async function collectLogicalModuleRoutes(input) {
|
|
8653
|
+
let root = input.root ?? (input.entries[0] ? dirname(input.entries[0]) : "/");
|
|
8654
|
+
if (!input.root) while (input.entries.some((entry) => relative(root, entry).startsWith("../"))) root = dirname(root);
|
|
8655
|
+
const routes = /* @__PURE__ */ new Map();
|
|
8656
|
+
const owners = /* @__PURE__ */ new Map();
|
|
8657
|
+
const pending = [];
|
|
8658
|
+
const add = (moduleId, fallback) => {
|
|
8659
|
+
moduleId = cleanRollupModuleId(moduleId);
|
|
8660
|
+
if (routes.has(moduleId)) return;
|
|
8661
|
+
const local = relative(root, moduleId).replace(/\\/g, "/");
|
|
8662
|
+
const route = !local.startsWith("../") && !moduleId.startsWith("\0") ? `/${local}` : fallback;
|
|
8663
|
+
if (!route) return;
|
|
8664
|
+
const owner = owners.get(route);
|
|
8665
|
+
if (owner && owner !== moduleId) throw new Error(`Ambiguous Gea renderer module coordinate: ${route}`);
|
|
8666
|
+
owners.set(route, moduleId);
|
|
8667
|
+
routes.set(moduleId, new Set([route]));
|
|
8668
|
+
pending.push(moduleId);
|
|
8669
|
+
};
|
|
8670
|
+
for (const entry of input.entries) add(entry, `/${relative(root, entry).replace(/\\/g, "/")}`);
|
|
8671
|
+
while (pending.length > 0) {
|
|
8672
|
+
const moduleId = pending.shift();
|
|
8673
|
+
const route = [...routes.get(moduleId)][0];
|
|
8674
|
+
for (const specifier of input.moduleShapes.get(moduleId)?.specifiers ?? []) {
|
|
8675
|
+
const target = await input.resolveModule(specifier, moduleId);
|
|
8676
|
+
if (!target) continue;
|
|
8677
|
+
add(target, specifier.startsWith("./") || specifier.startsWith("../") ? posix.join(posix.dirname(route), specifier) : specifier.startsWith("/") ? null : `/@modules/${specifier}`);
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
return routes;
|
|
8681
|
+
}
|
|
8682
|
+
function attachRootRendererAuthority(component, logicalRoutes, moduleShapes) {
|
|
8683
|
+
const moduleId = cleanRollupModuleId(component.module);
|
|
8684
|
+
const routes = logicalRoutes.get(moduleId);
|
|
8685
|
+
const exportNames = moduleShapes.get(moduleId)?.exportsByLocalName.get(component.exportName);
|
|
8686
|
+
if (!routes?.size || !exportNames?.size) return component;
|
|
8687
|
+
const candidates = [...routes].flatMap((moduleSpecifier) => [...exportNames].map((exportName) => ({
|
|
8688
|
+
moduleSpecifier,
|
|
8689
|
+
exportName
|
|
8690
|
+
})));
|
|
8691
|
+
candidates.sort((left, right) => {
|
|
8692
|
+
return (left.exportName === component.exportName ? 0 : 1) - (right.exportName === component.exportName ? 0 : 1) || left.moduleSpecifier.length - right.moduleSpecifier.length || `${left.moduleSpecifier}#${left.exportName}`.localeCompare(`${right.moduleSpecifier}#${right.exportName}`);
|
|
8693
|
+
});
|
|
8694
|
+
const coordinate = candidates[0];
|
|
8695
|
+
const digest = createHash("sha256").update(`${coordinate.moduleSpecifier}\u0000${coordinate.exportName}`).digest("hex");
|
|
8696
|
+
return {
|
|
8697
|
+
...component,
|
|
8698
|
+
rootRendererAuthority: {
|
|
8699
|
+
component: coordinate,
|
|
8700
|
+
rendererResourceId: `gea-renderer:v1:${digest}`
|
|
8701
|
+
}
|
|
8702
|
+
};
|
|
8703
|
+
}
|
|
8704
|
+
function entryFacadeModuleIds(bundle) {
|
|
8705
|
+
return Object.values(bundle).filter((item) => item.type === "chunk" && item.isEntry && item.facadeModuleId).map((item) => cleanRollupModuleId(item.facadeModuleId)).sort();
|
|
8706
|
+
}
|
|
8430
8707
|
function renderedModuleIds(bundle) {
|
|
8431
8708
|
const ids = /* @__PURE__ */ new Set();
|
|
8432
8709
|
for (const item of Object.values(bundle)) {
|
|
@@ -8479,7 +8756,29 @@ function compilerRuntimePathFromCoreEntry(entry) {
|
|
|
8479
8756
|
if (clean.endsWith("/dist/index.mjs")) return clean.slice(0, -15) + "/dist/compiler-runtime.mjs";
|
|
8480
8757
|
return null;
|
|
8481
8758
|
}
|
|
8759
|
+
function compilerRuntimeExportLines(runtimePath) {
|
|
8760
|
+
if (!runtimePath.endsWith(".ts")) return null;
|
|
8761
|
+
let source;
|
|
8762
|
+
try {
|
|
8763
|
+
source = readFileSync(runtimePath, "utf8");
|
|
8764
|
+
} catch {
|
|
8765
|
+
return null;
|
|
8766
|
+
}
|
|
8767
|
+
const dir = dirname(runtimePath);
|
|
8768
|
+
const lines = [];
|
|
8769
|
+
const EXPORT_FROM_RE = /export\s*\{([^}]*)\}\s*from\s*['"](\.[^'"]+)['"]/g;
|
|
8770
|
+
let match;
|
|
8771
|
+
while (match = EXPORT_FROM_RE.exec(source)) {
|
|
8772
|
+
const names = match[1];
|
|
8773
|
+
const relativeSpecifier = match[2];
|
|
8774
|
+
const absoluteSpecifier = normalizeImportPath(resolve(dir, relativeSpecifier));
|
|
8775
|
+
lines.push(`export {${names}} from ${JSON.stringify(absoluteSpecifier)}`);
|
|
8776
|
+
}
|
|
8777
|
+
return lines.length > 0 ? lines : null;
|
|
8778
|
+
}
|
|
8482
8779
|
function compilerRuntimeSource(runtimePath) {
|
|
8780
|
+
const perSubmoduleLines = compilerRuntimeExportLines(runtimePath);
|
|
8781
|
+
if (perSubmoduleLines) return `${perSubmoduleLines.join("\n")}\n`;
|
|
8483
8782
|
const path = normalizeImportPath(runtimePath);
|
|
8484
8783
|
return `export {
|
|
8485
8784
|
NOOP_DISPOSER,
|
|
@@ -8522,6 +8821,7 @@ function compilerRuntimeSource(runtimePath) {
|
|
|
8522
8821
|
GEA_DIRTY_PROPS,
|
|
8523
8822
|
createItemObservable,
|
|
8524
8823
|
createItemProxy,
|
|
8824
|
+
readItem,
|
|
8525
8825
|
_rescue,
|
|
8526
8826
|
GEA_CREATE_TEMPLATE,
|
|
8527
8827
|
GEA_PARENT_COMPONENT,
|