@geajs/vite-plugin 1.4.1 → 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 +459 -204
- 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
|
|
330
|
+
return (parent.isAssignmentExpression() || parent.isForInStatement() || parent.isForOfStatement()) && target.key === "left" || parent.isUpdateExpression() && target.key === "argument";
|
|
314
331
|
}
|
|
315
|
-
|
|
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
|
-
};
|
|
367
|
-
}
|
|
368
|
-
if (t.isBlockStatement(expr)) return {
|
|
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))]));
|
|
@@ -3163,7 +3029,7 @@ function emitKeyedListSlot(slot, stmts, ctx) {
|
|
|
3163
3029
|
const needsRowDisposer = !(patchRowExpr && t.isIdentifier(itemParam) && !createItemBodyReferencesRowDisposer(createItem));
|
|
3164
3030
|
if (needsItemProxy) {
|
|
3165
3031
|
ctx.importsNeeded.add("createItemObservable");
|
|
3166
|
-
ctx.importsNeeded
|
|
3032
|
+
useObservableItem(createItem, ctx.importsNeeded);
|
|
3167
3033
|
}
|
|
3168
3034
|
const ciName = "__ki_" + listId;
|
|
3169
3035
|
const prName = "__kp_" + listId;
|
|
@@ -5446,6 +5312,185 @@ function rewriteFnComponent(fnDecl, parentCtx) {
|
|
|
5446
5312
|
else fnDecl.params[1] = t.identifier("d");
|
|
5447
5313
|
fnDecl.body.body = newBody;
|
|
5448
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
|
+
}
|
|
5449
5494
|
const RESOLVED_RECONCILE_ID = "\0virtual:gea-reconcile";
|
|
5450
5495
|
const HMR_RUNTIME_ID = "virtual:gea-hmr";
|
|
5451
5496
|
const RESOLVED_HMR_RUNTIME_ID = "\0" + HMR_RUNTIME_ID;
|
|
@@ -5829,9 +5874,12 @@ function transformFile(source, _filename, options = {}) {
|
|
|
5829
5874
|
ctx.directFnComponentParams = collectDirectFnComponentParams(ast, ctx.directFnComponents);
|
|
5830
5875
|
ctx.directFnStringProps = collectDirectFnStringProps(ast, ctx.directFnComponents);
|
|
5831
5876
|
ctx.directFnNoDisposer = /* @__PURE__ */ new Set();
|
|
5832
|
-
|
|
5877
|
+
const localComponentNames = collectLocalClassComponents(ast);
|
|
5878
|
+
ctx.directClassComponents = new Set(localComponentNames);
|
|
5833
5879
|
for (const name of options.directClassComponents ?? []) ctx.directClassComponents.add(name);
|
|
5834
5880
|
ctx.directFactoryComponents = new Set(options.directFactoryComponents);
|
|
5881
|
+
const componentPropsShapes = inferComponentPropsTypes(ast, localComponentNames);
|
|
5882
|
+
const componentsUsedAsJsx = collectComponentsUsedAsJsx(ast, localComponentNames);
|
|
5835
5883
|
const rewritten = [];
|
|
5836
5884
|
const reactiveComponentNames = /* @__PURE__ */ new Set();
|
|
5837
5885
|
let firstClassIdx = -1;
|
|
@@ -5934,18 +5982,22 @@ function transformFile(source, _filename, options = {}) {
|
|
|
5934
5982
|
ctx.importsNeeded.add("CompiledComponent");
|
|
5935
5983
|
classDecl.superClass = t.identifier("CompiledComponent");
|
|
5936
5984
|
usesCompiledRuntimeBase = true;
|
|
5985
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5937
5986
|
} else if (useTinyReactiveComponent) {
|
|
5938
5987
|
ctx.importsNeeded.add("CompiledTinyReactiveComponent");
|
|
5939
5988
|
classDecl.superClass = t.identifier("CompiledTinyReactiveComponent");
|
|
5940
5989
|
usesCompiledRuntimeBase = true;
|
|
5990
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5941
5991
|
} else if (useLeanReactiveComponent) {
|
|
5942
5992
|
ctx.importsNeeded.add("CompiledLeanReactiveComponent");
|
|
5943
5993
|
classDecl.superClass = t.identifier("CompiledLeanReactiveComponent");
|
|
5944
5994
|
usesCompiledRuntimeBase = true;
|
|
5995
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5945
5996
|
} else if (t.isIdentifier(classDecl.superClass, { name: "Component" })) {
|
|
5946
5997
|
ctx.importsNeeded.add("CompiledReactiveComponent");
|
|
5947
5998
|
classDecl.superClass = t.identifier("CompiledReactiveComponent");
|
|
5948
5999
|
usesCompiledRuntimeBase = true;
|
|
6000
|
+
applyPropsTypeArgument(classDecl, className, componentPropsShapes, componentsUsedAsJsx);
|
|
5949
6001
|
}
|
|
5950
6002
|
if (usesCompiledRuntimeBase && hasAfterRenderAsyncHook && !hasOwnInstanceMethod(classDecl, "render")) {
|
|
5951
6003
|
ctx.importsNeeded.add("scheduleAfterRenderAsync");
|
|
@@ -6064,6 +6116,70 @@ function findClassDeclarationByName(ast, name) {
|
|
|
6064
6116
|
}
|
|
6065
6117
|
return null;
|
|
6066
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
|
+
}
|
|
6067
6183
|
function collectLocalClassComponents(ast) {
|
|
6068
6184
|
const names = /* @__PURE__ */ new Set();
|
|
6069
6185
|
for (const node of ast.program.body) {
|
|
@@ -6384,10 +6500,12 @@ function injectHMR(ast, componentClassNames, defaultExportClassName, componentIm
|
|
|
6384
6500
|
hmrStmts.push(js`const __moduleExports = ${t.objectExpression(exportProperties)};`);
|
|
6385
6501
|
hmrStmts.push(js`registerHotModule(${jsExpr`${importMeta()}.url`}, __moduleExports);`);
|
|
6386
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;`);
|
|
6387
6504
|
for (const cn of componentClassNames) {
|
|
6388
6505
|
const key = cn === defaultExportClassName ? "default" : cn;
|
|
6389
|
-
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")))));
|
|
6390
6507
|
}
|
|
6508
|
+
acceptBody.push(t.ifStatement(t.unaryExpression("!", t.identifier("__patched")), t.expressionStatement(t.callExpression(t.memberExpression(hot(), t.identifier("invalidate")), []))));
|
|
6391
6509
|
hmrStmts.push(t.expressionStatement(t.callExpression(t.memberExpression(hot(), t.identifier("accept")), [t.arrowFunctionExpression([t.identifier("newModule")], t.blockStatement(acceptBody))])));
|
|
6392
6510
|
hmrStmts.push(...createAccepts(componentImports, proxyDep, shouldSkipDepAccept));
|
|
6393
6511
|
for (const cn of componentClassNames) {
|
|
@@ -8124,6 +8242,7 @@ function geaPlugin(options = {}) {
|
|
|
8124
8242
|
const irComponents = /* @__PURE__ */ new Map();
|
|
8125
8243
|
const irStores = /* @__PURE__ */ new Map();
|
|
8126
8244
|
const hostCapabilities = /* @__PURE__ */ new Set();
|
|
8245
|
+
const staticModuleShapes = /* @__PURE__ */ new Map();
|
|
8127
8246
|
const storeRegistry = /* @__PURE__ */ new Map();
|
|
8128
8247
|
const resolveImportPath = (importer, source) => {
|
|
8129
8248
|
const base = resolve(dirname(importer), source);
|
|
@@ -8281,7 +8400,10 @@ function geaPlugin(options = {}) {
|
|
|
8281
8400
|
if (!cleanId.match(/\.(js|jsx|ts|tsx)$/) || cleanId.includes("node_modules")) return null;
|
|
8282
8401
|
let transformedCode = code;
|
|
8283
8402
|
let changed = false;
|
|
8284
|
-
if (irOptions?.enabled)
|
|
8403
|
+
if (irOptions?.enabled) {
|
|
8404
|
+
recordHostCapabilities(code);
|
|
8405
|
+
staticModuleShapes.set(cleanId, collectStaticModuleShape(code));
|
|
8406
|
+
}
|
|
8285
8407
|
if (code.includes("extends Store") || code.includes("new Store(")) {
|
|
8286
8408
|
storeModules.add(cleanId);
|
|
8287
8409
|
const storeClassName = extractStoreClassName(code);
|
|
@@ -8367,7 +8489,7 @@ function geaPlugin(options = {}) {
|
|
|
8367
8489
|
map: null
|
|
8368
8490
|
};
|
|
8369
8491
|
},
|
|
8370
|
-
generateBundle(_options, bundle) {
|
|
8492
|
+
async generateBundle(_options, bundle) {
|
|
8371
8493
|
if (!irOptions?.enabled) return;
|
|
8372
8494
|
const renderedIds = renderedModuleIds(bundle);
|
|
8373
8495
|
const modules = Array.from(irModules.values()).filter((module) => {
|
|
@@ -8389,12 +8511,23 @@ function geaPlugin(options = {}) {
|
|
|
8389
8511
|
if (candidateModule && !modules.includes(candidateModule)) modules.push(candidateModule);
|
|
8390
8512
|
}
|
|
8391
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));
|
|
8392
8525
|
const irBundle = {
|
|
8393
8526
|
schema: "gea-ir",
|
|
8394
8527
|
version: 1,
|
|
8395
8528
|
entry: geaIrEntryFromBundle(bundle) ?? geaIrConfiguredEntry(resolvedConfig),
|
|
8396
8529
|
modules,
|
|
8397
|
-
components
|
|
8530
|
+
components,
|
|
8398
8531
|
stores: Array.from(irStores.values()).filter((store) => storeIds.has(store.id)),
|
|
8399
8532
|
hostCapabilities: Array.from(hostCapabilities).sort()
|
|
8400
8533
|
};
|
|
@@ -8472,6 +8605,105 @@ function geaPlugin(options = {}) {
|
|
|
8472
8605
|
if (/\bdocument\s*\./.test(source)) hostCapabilities.add("dom");
|
|
8473
8606
|
}
|
|
8474
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
|
+
}
|
|
8475
8707
|
function renderedModuleIds(bundle) {
|
|
8476
8708
|
const ids = /* @__PURE__ */ new Set();
|
|
8477
8709
|
for (const item of Object.values(bundle)) {
|
|
@@ -8524,7 +8756,29 @@ function compilerRuntimePathFromCoreEntry(entry) {
|
|
|
8524
8756
|
if (clean.endsWith("/dist/index.mjs")) return clean.slice(0, -15) + "/dist/compiler-runtime.mjs";
|
|
8525
8757
|
return null;
|
|
8526
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
|
+
}
|
|
8527
8779
|
function compilerRuntimeSource(runtimePath) {
|
|
8780
|
+
const perSubmoduleLines = compilerRuntimeExportLines(runtimePath);
|
|
8781
|
+
if (perSubmoduleLines) return `${perSubmoduleLines.join("\n")}\n`;
|
|
8528
8782
|
const path = normalizeImportPath(runtimePath);
|
|
8529
8783
|
return `export {
|
|
8530
8784
|
NOOP_DISPOSER,
|
|
@@ -8567,6 +8821,7 @@ function compilerRuntimeSource(runtimePath) {
|
|
|
8567
8821
|
GEA_DIRTY_PROPS,
|
|
8568
8822
|
createItemObservable,
|
|
8569
8823
|
createItemProxy,
|
|
8824
|
+
readItem,
|
|
8570
8825
|
_rescue,
|
|
8571
8826
|
GEA_CREATE_TEMPLATE,
|
|
8572
8827
|
GEA_PARENT_COMPONENT,
|