@astralsight/astroforge-rsbuild-plugin 0.0.6 → 0.0.7
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.d.mts +1 -0
- package/dist/index.mjs +302 -79
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -322,8 +322,9 @@ function extractPageModuleFromTsx(source, options) {
|
|
|
322
322
|
});
|
|
323
323
|
const bindings = collectAstroForgeImports(ast.program.body);
|
|
324
324
|
const pageFunction = findDefaultPageFunction(ast.program.body, options.filename);
|
|
325
|
-
const
|
|
326
|
-
const
|
|
325
|
+
const scriptExtraction = extractScript(source, ast.program.body, pageFunction.node, options.filename);
|
|
326
|
+
const script = scriptExtraction.script;
|
|
327
|
+
const template = templateFromRenderExpression(pageFunction.renderExpression, bindings, createTemplateContext(source, options.filename, scriptExtraction.context));
|
|
327
328
|
const components = extractLocalComponents(source, ast.program.body, pageFunction.node, bindings, options.filename);
|
|
328
329
|
const componentImports = collectComponentImports(ast.program.body, bindings, Object.keys(components));
|
|
329
330
|
const imports = importsFromTemplate(template, components);
|
|
@@ -346,16 +347,17 @@ function extractComponentFromTsx(source, options) {
|
|
|
346
347
|
});
|
|
347
348
|
const bindings = collectAstroForgeImports(ast.program.body);
|
|
348
349
|
const located = locateComponentFunction(ast.program.body, options.exportName, options.filename);
|
|
349
|
-
const
|
|
350
|
+
const script = {
|
|
351
|
+
...createEmptyScript(),
|
|
352
|
+
props: extractComponentProps(located.node, ast.program.body, options.filename)
|
|
353
|
+
};
|
|
354
|
+
const template = templateFromRenderExpression(located.renderExpression, bindings, createTemplateContext(source, options.filename));
|
|
350
355
|
const componentImports = collectComponentImports(ast.program.body, bindings, []);
|
|
351
356
|
return {
|
|
352
357
|
component: {
|
|
353
358
|
name: kebabCase(located.localName),
|
|
354
359
|
template,
|
|
355
|
-
script
|
|
356
|
-
...createEmptyScript(),
|
|
357
|
-
props: extractComponentProps(located.node, ast.program.body, options.filename)
|
|
358
|
-
},
|
|
360
|
+
script,
|
|
359
361
|
style: extractStyleTable(source, ast.program.body, options.filename, options.loadStyle)
|
|
360
362
|
},
|
|
361
363
|
componentImports
|
|
@@ -467,7 +469,7 @@ function extractLocalComponents(source, body, pageFunction, bindings, filename)
|
|
|
467
469
|
const name = kebabCase(candidate.name);
|
|
468
470
|
components[name] = {
|
|
469
471
|
name,
|
|
470
|
-
template: templateFromRenderExpression(renderExpressionFromFunction(fn, filename), bindings, filename),
|
|
472
|
+
template: templateFromRenderExpression(renderExpressionFromFunction(fn, filename), bindings, createTemplateContext(source, filename)),
|
|
471
473
|
script: {
|
|
472
474
|
...createEmptyScript(),
|
|
473
475
|
props: extractComponentProps(fn, body, filename)
|
|
@@ -655,13 +657,19 @@ function findDefaultPageFunction(body, filename) {
|
|
|
655
657
|
function extractScript(source, moduleBody, pageFunction, filename) {
|
|
656
658
|
const script = createEmptyScript();
|
|
657
659
|
const body = unwrapExpression(pageFunction.body);
|
|
658
|
-
if (body.type !== "BlockStatement") return script;
|
|
659
660
|
const context = createScriptContext(source, filename);
|
|
661
|
+
if (body.type !== "BlockStatement") return {
|
|
662
|
+
script,
|
|
663
|
+
context
|
|
664
|
+
};
|
|
660
665
|
for (const statement of body.body) collectUseStateDeclaration(script, context, statement);
|
|
661
666
|
for (const statement of body.body) collectMethod(script, context, statement);
|
|
662
667
|
collectPageLifecycle(script, context, moduleBody);
|
|
663
668
|
collectUseEffectCalls(script, context, body.body);
|
|
664
|
-
return
|
|
669
|
+
return {
|
|
670
|
+
script,
|
|
671
|
+
context
|
|
672
|
+
};
|
|
665
673
|
}
|
|
666
674
|
function createScriptContext(source, filename) {
|
|
667
675
|
return {
|
|
@@ -671,6 +679,22 @@ function createScriptContext(source, filename) {
|
|
|
671
679
|
stateSetters: /* @__PURE__ */ new Map()
|
|
672
680
|
};
|
|
673
681
|
}
|
|
682
|
+
function createTemplateContext(source, filename, scriptContext = createScriptContext(source, filename), aliases = /* @__PURE__ */ new Map()) {
|
|
683
|
+
return {
|
|
684
|
+
source,
|
|
685
|
+
filename,
|
|
686
|
+
scriptContext,
|
|
687
|
+
aliases
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function withTemplateAliases(context, aliases) {
|
|
691
|
+
const next = new Map(context.aliases);
|
|
692
|
+
for (const [source, target] of aliases) next.set(source, target);
|
|
693
|
+
return {
|
|
694
|
+
...context,
|
|
695
|
+
aliases: next
|
|
696
|
+
};
|
|
697
|
+
}
|
|
674
698
|
function collectUseStateDeclaration(script, context, statement) {
|
|
675
699
|
if (statement.type !== "VariableDeclaration") return;
|
|
676
700
|
for (const declarator of statement.declarations) {
|
|
@@ -881,6 +905,180 @@ function lowerStateSetterCall(context, stateName, node) {
|
|
|
881
905
|
}
|
|
882
906
|
return `this.${stateName} = ${lowerExpression(context, expression)}`;
|
|
883
907
|
}
|
|
908
|
+
function lowerTemplateEventHandler(context, node) {
|
|
909
|
+
const fn = unwrapExpression(node);
|
|
910
|
+
const firstParam = fn.params?.[0];
|
|
911
|
+
const aliases = [];
|
|
912
|
+
if (firstParam?.type === "Identifier") aliases.push([firstParam.name, "evt"]);
|
|
913
|
+
return `function(evt) ${lowerTemplateFunctionBody(withTemplateAliases(context, aliases), fn.body)}`;
|
|
914
|
+
}
|
|
915
|
+
function lowerTemplateFunctionBody(context, body) {
|
|
916
|
+
const node = unwrapExpression(body);
|
|
917
|
+
if (node.type !== "BlockStatement") return `{ return ${lowerTemplateExpression(context, node)}; }`;
|
|
918
|
+
const statements = node.body.map((statement) => lowerTemplateStatement(context, statement)).filter(Boolean);
|
|
919
|
+
if (statements.length === 0) return "{}";
|
|
920
|
+
return `{\n${statements.map((statement) => ` ${statement}`).join("\n")}\n}`;
|
|
921
|
+
}
|
|
922
|
+
function lowerTemplateStatement(context, statement) {
|
|
923
|
+
switch (statement.type) {
|
|
924
|
+
case "ExpressionStatement": return `${lowerTemplateExpression(context, statement.expression)};`;
|
|
925
|
+
case "ReturnStatement": return statement.argument ? `return ${lowerTemplateExpression(context, statement.argument)};` : "return;";
|
|
926
|
+
case "VariableDeclaration": return lowerTemplateVariableDeclaration(context, statement);
|
|
927
|
+
case "IfStatement": return lowerTemplateIfStatement(context, statement);
|
|
928
|
+
case "BlockStatement": {
|
|
929
|
+
const body = statement.body.map((item) => lowerTemplateStatement(context, item)).filter(Boolean).map((item) => ` ${item}`).join("\n");
|
|
930
|
+
return body ? `{\n${body}\n}` : "{}";
|
|
931
|
+
}
|
|
932
|
+
default: throw new Error(`${context.filename ?? "TSX"}: 内联事件暂不支持 ${statement.type}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function lowerTemplateVariableDeclaration(context, statement) {
|
|
936
|
+
const declarations = statement.declarations.map((declarator) => {
|
|
937
|
+
const id = sourceForNode(context.source, declarator.id);
|
|
938
|
+
if (!declarator.init) return id;
|
|
939
|
+
return `${id} = ${lowerTemplateExpression(context, declarator.init)}`;
|
|
940
|
+
});
|
|
941
|
+
return `${statement.kind} ${declarations.join(", ")};`;
|
|
942
|
+
}
|
|
943
|
+
function lowerTemplateIfStatement(context, statement) {
|
|
944
|
+
const test = lowerTemplateExpression(context, statement.test);
|
|
945
|
+
const consequent = lowerTemplateStatementAsBlock(context, statement.consequent);
|
|
946
|
+
if (!statement.alternate) return `if (${test}) ${consequent}`;
|
|
947
|
+
return `if (${test}) ${consequent} else ${statement.alternate.type === "IfStatement" ? lowerTemplateIfStatement(context, statement.alternate) : lowerTemplateStatementAsBlock(context, statement.alternate)}`;
|
|
948
|
+
}
|
|
949
|
+
function lowerTemplateStatementAsBlock(context, statement) {
|
|
950
|
+
if (statement.type === "BlockStatement") return lowerTemplateStatement(context, statement);
|
|
951
|
+
return `{\n ${lowerTemplateStatement(context, statement)}\n}`;
|
|
952
|
+
}
|
|
953
|
+
function lowerTemplateExpression(context, expression) {
|
|
954
|
+
const node = unwrapExpression(expression);
|
|
955
|
+
switch (node.type) {
|
|
956
|
+
case "Identifier": return lowerTemplateIdentifier(context, node.name);
|
|
957
|
+
case "StringLiteral": return JSON.stringify(node.value);
|
|
958
|
+
case "NumericLiteral": return String(node.value);
|
|
959
|
+
case "BooleanLiteral": return String(node.value);
|
|
960
|
+
case "NullLiteral": return "null";
|
|
961
|
+
case "TemplateLiteral": return lowerTemplateLiteral(context, node);
|
|
962
|
+
case "ConditionalExpression": return `${lowerTemplateExpression(context, node.test)} ? ${lowerTemplateExpression(context, node.consequent)} : ${lowerTemplateExpression(context, node.alternate)}`;
|
|
963
|
+
case "BinaryExpression":
|
|
964
|
+
case "LogicalExpression": return `${lowerTemplateExpression(context, node.left)} ${node.operator} ${lowerTemplateExpression(context, node.right)}`;
|
|
965
|
+
case "UnaryExpression": return `${node.operator}${lowerTemplateExpression(context, node.argument)}`;
|
|
966
|
+
case "AssignmentExpression": return `${lowerTemplateExpression(context, node.left)} ${node.operator} ${lowerTemplateExpression(context, node.right)}`;
|
|
967
|
+
case "UpdateExpression": {
|
|
968
|
+
const argument = lowerTemplateExpression(context, node.argument);
|
|
969
|
+
return node.prefix ? `${node.operator}${argument}` : `${argument}${node.operator}`;
|
|
970
|
+
}
|
|
971
|
+
case "MemberExpression":
|
|
972
|
+
case "OptionalMemberExpression": return lowerTemplateMemberExpression(context, node);
|
|
973
|
+
case "CallExpression":
|
|
974
|
+
case "OptionalCallExpression": return lowerTemplateCallExpression(context, node);
|
|
975
|
+
case "ArrayExpression": return `[${node.elements.map((item) => item ? item.type === "SpreadElement" ? `...${lowerTemplateExpression(context, item.argument)}` : lowerTemplateExpression(context, item) : "").join(", ")}]`;
|
|
976
|
+
case "ObjectExpression": return lowerTemplateObjectExpression(context, node);
|
|
977
|
+
case "ArrowFunctionExpression":
|
|
978
|
+
case "FunctionExpression": return lowerTemplateEventHandler(context, node);
|
|
979
|
+
default: throw new Error(`${context.filename ?? "TSX"}: 文本绑定暂不支持 ${node.type}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function lowerTemplateIdentifier(context, name) {
|
|
983
|
+
const alias = context.aliases.get(name);
|
|
984
|
+
if (alias) return alias;
|
|
985
|
+
if (TEMPLATE_GLOBALS.has(name)) return name;
|
|
986
|
+
return `_vm_.${name}`;
|
|
987
|
+
}
|
|
988
|
+
function lowerTemplateMemberExpression(context, node) {
|
|
989
|
+
const object = unwrapExpression(node.object);
|
|
990
|
+
let base;
|
|
991
|
+
if (object.type === "Identifier" && object.name === "props") base = "_vm_";
|
|
992
|
+
else base = lowerTemplateExpression(context, object);
|
|
993
|
+
const operator = node.optional ? "?." : ".";
|
|
994
|
+
if (node.computed) return `${base}${node.optional ? "?." : ""}[${lowerTemplateExpression(context, node.property)}]`;
|
|
995
|
+
const property = sourceForNode(context.source, node.property);
|
|
996
|
+
return `${base}${operator}${property}`;
|
|
997
|
+
}
|
|
998
|
+
function lowerTemplateCallExpression(context, node) {
|
|
999
|
+
const setterTarget = node.callee.type === "Identifier" ? context.scriptContext.stateSetters.get(node.callee.name) : void 0;
|
|
1000
|
+
if (setterTarget) return lowerTemplateStateSetterCall(context, setterTarget, node);
|
|
1001
|
+
const optional = node.optional ? "?." : "";
|
|
1002
|
+
return `${lowerTemplateExpression(context, node.callee)}${optional}(${node.arguments.map((arg) => {
|
|
1003
|
+
if (arg.type === "SpreadElement") return `...${lowerTemplateExpression(context, arg.argument)}`;
|
|
1004
|
+
return lowerTemplateExpression(context, arg);
|
|
1005
|
+
}).join(", ")})`;
|
|
1006
|
+
}
|
|
1007
|
+
function lowerTemplateStateSetterCall(context, stateName, node) {
|
|
1008
|
+
const next = node.arguments[0];
|
|
1009
|
+
if (!next) return `_vm_.${stateName} = null`;
|
|
1010
|
+
const expression = unwrapExpression(next);
|
|
1011
|
+
if (expression.type === "ArrowFunctionExpression") {
|
|
1012
|
+
const firstParam = expression.params[0];
|
|
1013
|
+
const aliases = firstParam?.type === "Identifier" ? [[firstParam.name, `_vm_.${stateName}`]] : [];
|
|
1014
|
+
if (expression.body.type === "BlockStatement") throw new Error(`${context.filename ?? "TSX"}: setState updater 暂不支持 block body`);
|
|
1015
|
+
return `_vm_.${stateName} = ${lowerTemplateExpression(withTemplateAliases(context, aliases), expression.body)}`;
|
|
1016
|
+
}
|
|
1017
|
+
return `_vm_.${stateName} = ${lowerTemplateExpression(context, expression)}`;
|
|
1018
|
+
}
|
|
1019
|
+
function lowerTemplateLiteral(context, node) {
|
|
1020
|
+
const parts = [];
|
|
1021
|
+
node.quasis.forEach((quasi, index) => {
|
|
1022
|
+
const text = quasi.value.cooked ?? quasi.value.raw ?? "";
|
|
1023
|
+
if (text) parts.push(JSON.stringify(text));
|
|
1024
|
+
const expression = node.expressions[index];
|
|
1025
|
+
if (expression) parts.push(`(${lowerTemplateExpression(context, expression)})`);
|
|
1026
|
+
});
|
|
1027
|
+
return parts.length > 0 ? parts.join(" + ") : "\"\"";
|
|
1028
|
+
}
|
|
1029
|
+
function lowerTemplateObjectExpression(context, node) {
|
|
1030
|
+
return `{ ${node.properties.map((property) => {
|
|
1031
|
+
if (property.type === "SpreadElement") return `...${lowerTemplateExpression(context, property.argument)}`;
|
|
1032
|
+
if (property.type !== "ObjectProperty") throw new Error(`${context.filename ?? "TSX"}: 文本绑定暂不支持对象方法`);
|
|
1033
|
+
return `${property.computed ? `[${lowerTemplateExpression(context, property.key)}]` : jsObjectKey(objectPropertyKey(property.key, context.filename))}: ${lowerTemplateExpression(context, property.value)}`;
|
|
1034
|
+
}).join(", ")} }`;
|
|
1035
|
+
}
|
|
1036
|
+
function jsObjectKey(key) {
|
|
1037
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
|
|
1038
|
+
}
|
|
1039
|
+
const TEMPLATE_GLOBALS = new Set([
|
|
1040
|
+
"Array",
|
|
1041
|
+
"Boolean",
|
|
1042
|
+
"Date",
|
|
1043
|
+
"Infinity",
|
|
1044
|
+
"JSON",
|
|
1045
|
+
"Math",
|
|
1046
|
+
"NaN",
|
|
1047
|
+
"Number",
|
|
1048
|
+
"Object",
|
|
1049
|
+
"Promise",
|
|
1050
|
+
"String",
|
|
1051
|
+
"console",
|
|
1052
|
+
"false",
|
|
1053
|
+
"isFinite",
|
|
1054
|
+
"isNaN",
|
|
1055
|
+
"null",
|
|
1056
|
+
"parseFloat",
|
|
1057
|
+
"parseInt",
|
|
1058
|
+
"network",
|
|
1059
|
+
"router",
|
|
1060
|
+
"storage",
|
|
1061
|
+
"true",
|
|
1062
|
+
"undefined",
|
|
1063
|
+
"velaBattery",
|
|
1064
|
+
"velaBluetoothBLE",
|
|
1065
|
+
"velaBrightness",
|
|
1066
|
+
"velaConfiguration",
|
|
1067
|
+
"velaCrypto",
|
|
1068
|
+
"velaDebug",
|
|
1069
|
+
"velaEvent",
|
|
1070
|
+
"velaExchange",
|
|
1071
|
+
"velaFolme",
|
|
1072
|
+
"velaInterconnect",
|
|
1073
|
+
"velaJumpApp",
|
|
1074
|
+
"velaLocale",
|
|
1075
|
+
"velaMediaSession",
|
|
1076
|
+
"velaMqttMessage",
|
|
1077
|
+
"velaProtobuf",
|
|
1078
|
+
"velaServiceClient",
|
|
1079
|
+
"velaVolume",
|
|
1080
|
+
"velaZlib"
|
|
1081
|
+
]);
|
|
884
1082
|
function findTopLevelBinding(body, name) {
|
|
885
1083
|
for (const statement of body) {
|
|
886
1084
|
if (statement.type === "FunctionDeclaration" && statement.id?.name === name) return statement;
|
|
@@ -899,50 +1097,50 @@ function renderExpressionFromFunction(node, filename) {
|
|
|
899
1097
|
}
|
|
900
1098
|
throw new Error(`${filename ?? "TSX"}: 页面函数缺少 return 语句`);
|
|
901
1099
|
}
|
|
902
|
-
function templateFromRenderExpression(expression, bindings,
|
|
903
|
-
const node = nodeFromJsx(expression, bindings,
|
|
1100
|
+
function templateFromRenderExpression(expression, bindings, context) {
|
|
1101
|
+
const node = nodeFromJsx(expression, bindings, context);
|
|
904
1102
|
if (node.kind === "fragment") return node.value;
|
|
905
1103
|
return [node];
|
|
906
1104
|
}
|
|
907
|
-
function nodeFromJsx(node, bindings,
|
|
1105
|
+
function nodeFromJsx(node, bindings, context) {
|
|
908
1106
|
switch (node.type) {
|
|
909
1107
|
case "JSXElement": return {
|
|
910
1108
|
kind: "element",
|
|
911
|
-
value: elementFromJsx(node, bindings,
|
|
1109
|
+
value: elementFromJsx(node, bindings, context)
|
|
912
1110
|
};
|
|
913
1111
|
case "JSXFragment": return {
|
|
914
1112
|
kind: "fragment",
|
|
915
|
-
value: childrenFromJsx(node.children, bindings,
|
|
1113
|
+
value: childrenFromJsx(node.children, bindings, context)
|
|
916
1114
|
};
|
|
917
|
-
default: throw new Error(`${filename ?? "TSX"}: 不支持的 JSX 根节点 ${node.type}`);
|
|
1115
|
+
default: throw new Error(`${context.filename ?? "TSX"}: 不支持的 JSX 根节点 ${node.type}`);
|
|
918
1116
|
}
|
|
919
1117
|
}
|
|
920
|
-
function elementFromJsx(node, bindings,
|
|
921
|
-
const name = jsxElementName(node.openingElement.name, filename);
|
|
1118
|
+
function elementFromJsx(node, bindings, context) {
|
|
1119
|
+
const name = jsxElementName(node.openingElement.name, context.filename);
|
|
922
1120
|
const tag = bindings.get(name) ?? name;
|
|
923
1121
|
const isComponent = !bindings.has(name) && /^[A-Z]/.test(name);
|
|
924
1122
|
const attrs = {};
|
|
925
1123
|
const events = {};
|
|
926
1124
|
for (const attr of node.openingElement.attributes) {
|
|
927
|
-
if (attr.type === "JSXSpreadAttribute") throw new Error(`${filename ?? "TSX"}: 暂不支持 JSX spread 属性`);
|
|
928
|
-
const attrName = jsxAttributeName(attr.name, filename);
|
|
1125
|
+
if (attr.type === "JSXSpreadAttribute") throw new Error(`${context.filename ?? "TSX"}: 暂不支持 JSX spread 属性`);
|
|
1126
|
+
const attrName = jsxAttributeName(attr.name, context.filename);
|
|
929
1127
|
if (attrName === "key") continue;
|
|
930
1128
|
if (isEventAttribute(attrName)) {
|
|
931
|
-
events[eventNameFromAttribute(attrName)] = bindingFromAttribute(attr.value, true,
|
|
1129
|
+
events[eventNameFromAttribute(attrName)] = bindingFromAttribute(attr.value, true, context);
|
|
932
1130
|
continue;
|
|
933
1131
|
}
|
|
934
1132
|
const normalizedName = normalizeAttributeName(attrName, isComponent);
|
|
935
|
-
attrs[normalizedName] = attrFromValue(normalizedName, attr.value,
|
|
1133
|
+
attrs[normalizedName] = attrFromValue(normalizedName, attr.value, context);
|
|
936
1134
|
}
|
|
937
1135
|
return {
|
|
938
1136
|
tag: isComponent ? kebabCase(tag) : tag,
|
|
939
1137
|
is_component: isComponent,
|
|
940
1138
|
attrs,
|
|
941
1139
|
events,
|
|
942
|
-
children: childrenFromJsx(node.children, bindings,
|
|
1140
|
+
children: childrenFromJsx(node.children, bindings, context)
|
|
943
1141
|
};
|
|
944
1142
|
}
|
|
945
|
-
function childrenFromJsx(children, bindings,
|
|
1143
|
+
function childrenFromJsx(children, bindings, context) {
|
|
946
1144
|
const out = [];
|
|
947
1145
|
for (const child of children) {
|
|
948
1146
|
if (child.type === "JSXText") {
|
|
@@ -954,7 +1152,7 @@ function childrenFromJsx(children, bindings, filename) {
|
|
|
954
1152
|
continue;
|
|
955
1153
|
}
|
|
956
1154
|
if (child.type === "JSXElement" || child.type === "JSXFragment") {
|
|
957
|
-
const node = nodeFromJsx(child, bindings,
|
|
1155
|
+
const node = nodeFromJsx(child, bindings, context);
|
|
958
1156
|
if (node.kind === "fragment") out.push(...node.value);
|
|
959
1157
|
else out.push(node);
|
|
960
1158
|
continue;
|
|
@@ -962,18 +1160,18 @@ function childrenFromJsx(children, bindings, filename) {
|
|
|
962
1160
|
if (child.type === "JSXExpressionContainer") {
|
|
963
1161
|
const expression = unwrapExpression(child.expression);
|
|
964
1162
|
if (expression.type === "JSXEmptyExpression") continue;
|
|
965
|
-
out.push(nodeFromExpression(expression, bindings,
|
|
1163
|
+
out.push(nodeFromExpression(expression, bindings, context));
|
|
966
1164
|
continue;
|
|
967
1165
|
}
|
|
968
|
-
throw new Error(`${filename ?? "TSX"}: 不支持的 JSX 子节点 ${child.type}`);
|
|
1166
|
+
throw new Error(`${context.filename ?? "TSX"}: 不支持的 JSX 子节点 ${child.type}`);
|
|
969
1167
|
}
|
|
970
1168
|
return out;
|
|
971
1169
|
}
|
|
972
|
-
function nodeFromExpression(expression, bindings,
|
|
973
|
-
const
|
|
974
|
-
if (conditional) return conditional;
|
|
975
|
-
const list = listFromExpression(expression, bindings, filename);
|
|
1170
|
+
function nodeFromExpression(expression, bindings, context) {
|
|
1171
|
+
const list = listFromExpression(expression, bindings, context);
|
|
976
1172
|
if (list) return list;
|
|
1173
|
+
const conditional = conditionalFromExpression(expression, bindings, context);
|
|
1174
|
+
if (conditional) return conditional;
|
|
977
1175
|
const value = literalValue(expression);
|
|
978
1176
|
if (value !== void 0) return {
|
|
979
1177
|
kind: "text",
|
|
@@ -981,65 +1179,80 @@ function nodeFromExpression(expression, bindings, filename) {
|
|
|
981
1179
|
};
|
|
982
1180
|
return {
|
|
983
1181
|
kind: "expression",
|
|
984
|
-
value: bindingFromExpression(expression, false,
|
|
1182
|
+
value: bindingFromExpression(expression, false, context)
|
|
985
1183
|
};
|
|
986
1184
|
}
|
|
987
|
-
function conditionalFromExpression(expression, bindings,
|
|
1185
|
+
function conditionalFromExpression(expression, bindings, context) {
|
|
988
1186
|
const node = unwrapExpression(expression);
|
|
989
|
-
if (node.type === "ConditionalExpression")
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1187
|
+
if (node.type === "ConditionalExpression") {
|
|
1188
|
+
if (!isControlFlowBranch(node.consequent) && !isControlFlowBranch(node.alternate)) return;
|
|
1189
|
+
return {
|
|
1190
|
+
kind: "conditional",
|
|
1191
|
+
value: { branches: [{
|
|
1192
|
+
guard: bindingFromExpression(node.test, false, context),
|
|
1193
|
+
body: nodesFromBranchExpression(node.consequent, bindings, context)
|
|
1194
|
+
}, ...alternateBranches(node.alternate, bindings, context)] }
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
if (node.type === "LogicalExpression" && node.operator === "&&") {
|
|
1198
|
+
if (!isControlFlowBranch(node.right)) return;
|
|
1199
|
+
return {
|
|
1200
|
+
kind: "conditional",
|
|
1201
|
+
value: { branches: [{
|
|
1202
|
+
guard: bindingFromExpression(node.left, false, context),
|
|
1203
|
+
body: nodesFromBranchExpression(node.right, bindings, context)
|
|
1204
|
+
}] }
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1003
1207
|
}
|
|
1004
|
-
function alternateBranches(expression, bindings,
|
|
1208
|
+
function alternateBranches(expression, bindings, context) {
|
|
1005
1209
|
const node = unwrapExpression(expression);
|
|
1006
1210
|
if (node.type === "ConditionalExpression") return [{
|
|
1007
|
-
guard: bindingFromExpression(node.test, false,
|
|
1008
|
-
body: nodesFromBranchExpression(node.consequent, bindings,
|
|
1009
|
-
}, ...alternateBranches(node.alternate, bindings,
|
|
1211
|
+
guard: bindingFromExpression(node.test, false, context),
|
|
1212
|
+
body: nodesFromBranchExpression(node.consequent, bindings, context)
|
|
1213
|
+
}, ...alternateBranches(node.alternate, bindings, context)];
|
|
1010
1214
|
return [{
|
|
1011
1215
|
guard: null,
|
|
1012
|
-
body: nodesFromBranchExpression(node, bindings,
|
|
1216
|
+
body: nodesFromBranchExpression(node, bindings, context)
|
|
1013
1217
|
}];
|
|
1014
1218
|
}
|
|
1015
|
-
function nodesFromBranchExpression(expression, bindings,
|
|
1219
|
+
function nodesFromBranchExpression(expression, bindings, context) {
|
|
1016
1220
|
const node = unwrapExpression(expression);
|
|
1017
1221
|
if (node.type === "NullLiteral") return [];
|
|
1018
1222
|
if (node.type === "BooleanLiteral" && node.value === false) return [];
|
|
1019
1223
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
1020
|
-
const result = nodeFromJsx(node, bindings,
|
|
1224
|
+
const result = nodeFromJsx(node, bindings, context);
|
|
1021
1225
|
return result.kind === "fragment" ? result.value : [result];
|
|
1022
1226
|
}
|
|
1023
|
-
return [nodeFromExpression(node, bindings,
|
|
1227
|
+
return [nodeFromExpression(node, bindings, context)];
|
|
1024
1228
|
}
|
|
1025
|
-
function
|
|
1229
|
+
function isControlFlowBranch(expression) {
|
|
1230
|
+
const node = unwrapExpression(expression);
|
|
1231
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
|
|
1232
|
+
if (node.type === "NullLiteral") return true;
|
|
1233
|
+
if (node.type === "BooleanLiteral" && node.value === false) return true;
|
|
1234
|
+
if (node.type === "ConditionalExpression") return isControlFlowBranch(node.consequent) || isControlFlowBranch(node.alternate);
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1237
|
+
function listFromExpression(expression, bindings, context) {
|
|
1026
1238
|
const node = unwrapExpression(expression);
|
|
1027
1239
|
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression" || node.callee.computed || node.callee.property.type !== "Identifier" || node.callee.property.name !== "map") return;
|
|
1028
1240
|
const callback = unwrapExpression(node.arguments[0]);
|
|
1029
|
-
if (!callback || !isFunctionLike(callback)) throw new Error(`${filename ?? "TSX"}: list render 的 map 参数必须是函数`);
|
|
1241
|
+
if (!callback || !isFunctionLike(callback)) throw new Error(`${context.filename ?? "TSX"}: list render 的 map 参数必须是函数`);
|
|
1030
1242
|
const itemParam = callback.params[0];
|
|
1031
|
-
if (itemParam?.type !== "Identifier") throw new Error(`${filename ?? "TSX"}: list render 必须声明 item 参数`);
|
|
1243
|
+
if (itemParam?.type !== "Identifier") throw new Error(`${context.filename ?? "TSX"}: list render 必须声明 item 参数`);
|
|
1032
1244
|
const indexParam = callback.params[1];
|
|
1033
|
-
if (indexParam && indexParam.type !== "Identifier") throw new Error(`${filename ?? "TSX"}: list render 的 index 参数必须是标识符`);
|
|
1034
|
-
const bodyExpression = renderExpressionFromMapCallback(callback, filename);
|
|
1245
|
+
if (indexParam && indexParam.type !== "Identifier") throw new Error(`${context.filename ?? "TSX"}: list render 的 index 参数必须是标识符`);
|
|
1246
|
+
const bodyExpression = renderExpressionFromMapCallback(callback, context.filename);
|
|
1247
|
+
const listContext = withTemplateAliases(context, [[itemParam.name, itemParam.name], ...indexParam ? [[indexParam.name, indexParam.name]] : []]);
|
|
1035
1248
|
return {
|
|
1036
1249
|
kind: "list",
|
|
1037
1250
|
value: {
|
|
1038
|
-
source: bindingFromExpression(node.callee.object, false,
|
|
1251
|
+
source: bindingFromExpression(node.callee.object, false, context),
|
|
1039
1252
|
item_var: itemParam.name,
|
|
1040
1253
|
index_var: indexParam?.name,
|
|
1041
|
-
key: keyBindingFromJsx(bodyExpression,
|
|
1042
|
-
body: nodesFromBranchExpression(bodyExpression, bindings,
|
|
1254
|
+
key: keyBindingFromJsx(bodyExpression, listContext),
|
|
1255
|
+
body: nodesFromBranchExpression(bodyExpression, bindings, listContext)
|
|
1043
1256
|
}
|
|
1044
1257
|
};
|
|
1045
1258
|
}
|
|
@@ -1053,14 +1266,14 @@ function renderExpressionFromMapCallback(callback, filename) {
|
|
|
1053
1266
|
}
|
|
1054
1267
|
throw new Error(`${filename ?? "TSX"}: list render 函数缺少 return 语句`);
|
|
1055
1268
|
}
|
|
1056
|
-
function keyBindingFromJsx(expression,
|
|
1269
|
+
function keyBindingFromJsx(expression, context) {
|
|
1057
1270
|
const node = unwrapExpression(expression);
|
|
1058
1271
|
if (node.type !== "JSXElement") return;
|
|
1059
1272
|
const keyAttr = node.openingElement.attributes.find((attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === "key");
|
|
1060
1273
|
if (!keyAttr) return;
|
|
1061
|
-
return bindingFromAttribute(keyAttr.value, false,
|
|
1274
|
+
return bindingFromAttribute(keyAttr.value, false, context);
|
|
1062
1275
|
}
|
|
1063
|
-
function attrFromValue(name, value,
|
|
1276
|
+
function attrFromValue(name, value, context) {
|
|
1064
1277
|
if (!value) return {
|
|
1065
1278
|
kind: "static",
|
|
1066
1279
|
value: true
|
|
@@ -1069,7 +1282,7 @@ function attrFromValue(name, value, filename) {
|
|
|
1069
1282
|
kind: "static",
|
|
1070
1283
|
value: value.value
|
|
1071
1284
|
};
|
|
1072
|
-
if (value.type !== "JSXExpressionContainer") throw new Error(`${filename ?? "TSX"}: 不支持的属性值 ${value.type}`);
|
|
1285
|
+
if (value.type !== "JSXExpressionContainer") throw new Error(`${context.filename ?? "TSX"}: 不支持的属性值 ${value.type}`);
|
|
1073
1286
|
const expression = unwrapExpression(value.expression);
|
|
1074
1287
|
const literal = literalValue(expression);
|
|
1075
1288
|
if (literal !== void 0) return {
|
|
@@ -1082,7 +1295,7 @@ function attrFromValue(name, value, filename) {
|
|
|
1082
1295
|
value: name === "style" ? normalizeStyleLiteral(staticObject) : staticObject
|
|
1083
1296
|
};
|
|
1084
1297
|
if (name === "style") {
|
|
1085
|
-
const styleObject = styleObjectAttr(expression,
|
|
1298
|
+
const styleObject = styleObjectAttr(expression, context);
|
|
1086
1299
|
if (styleObject) return {
|
|
1087
1300
|
kind: "style_object",
|
|
1088
1301
|
value: styleObject
|
|
@@ -1090,16 +1303,16 @@ function attrFromValue(name, value, filename) {
|
|
|
1090
1303
|
}
|
|
1091
1304
|
return {
|
|
1092
1305
|
kind: "dynamic",
|
|
1093
|
-
value: bindingFromExpression(expression, false,
|
|
1306
|
+
value: bindingFromExpression(expression, false, context)
|
|
1094
1307
|
};
|
|
1095
1308
|
}
|
|
1096
|
-
function styleObjectAttr(expression,
|
|
1309
|
+
function styleObjectAttr(expression, context) {
|
|
1097
1310
|
const node = unwrapExpression(expression);
|
|
1098
1311
|
if (node.type !== "ObjectExpression") return void 0;
|
|
1099
1312
|
const slots = [];
|
|
1100
1313
|
for (const property of node.properties) {
|
|
1101
|
-
if (property.type !== "ObjectProperty" || property.computed) throw new Error(`${filename ?? "TSX"}: style 对象暂不支持展开、方法或计算键`);
|
|
1102
|
-
const key = kebabToCamel(objectPropertyKey(property.key, filename));
|
|
1314
|
+
if (property.type !== "ObjectProperty" || property.computed) throw new Error(`${context.filename ?? "TSX"}: style 对象暂不支持展开、方法或计算键`);
|
|
1315
|
+
const key = kebabToCamel(objectPropertyKey(property.key, context.filename));
|
|
1103
1316
|
const value = staticAttrLiteral(property.value);
|
|
1104
1317
|
if (value !== void 0) {
|
|
1105
1318
|
slots.push({
|
|
@@ -1115,7 +1328,7 @@ function styleObjectAttr(expression, filename) {
|
|
|
1115
1328
|
name: key,
|
|
1116
1329
|
value: {
|
|
1117
1330
|
kind: "dynamic",
|
|
1118
|
-
value: bindingFromExpression(property.value, false,
|
|
1331
|
+
value: bindingFromExpression(property.value, false, context)
|
|
1119
1332
|
}
|
|
1120
1333
|
});
|
|
1121
1334
|
}
|
|
@@ -1160,17 +1373,27 @@ function staticAttrLiteral(expression) {
|
|
|
1160
1373
|
return out;
|
|
1161
1374
|
}
|
|
1162
1375
|
}
|
|
1163
|
-
function bindingFromAttribute(value, callable,
|
|
1164
|
-
if (!value || value.type !== "JSXExpressionContainer") throw new Error(`${filename ?? "TSX"}: 事件属性必须使用表达式绑定`);
|
|
1165
|
-
return bindingFromExpression(unwrapExpression(value.expression), callable,
|
|
1376
|
+
function bindingFromAttribute(value, callable, context) {
|
|
1377
|
+
if (!value || value.type !== "JSXExpressionContainer") throw new Error(`${context.filename ?? "TSX"}: 事件属性必须使用表达式绑定`);
|
|
1378
|
+
return bindingFromExpression(unwrapExpression(value.expression), callable, context);
|
|
1166
1379
|
}
|
|
1167
|
-
function bindingFromExpression(expression, callable,
|
|
1380
|
+
function bindingFromExpression(expression, callable, context) {
|
|
1381
|
+
const node = unwrapExpression(expression);
|
|
1382
|
+
if (callable && isFunctionLike(node)) return {
|
|
1383
|
+
path: sourceForNode(context.source, node),
|
|
1384
|
+
expr: lowerTemplateEventHandler(context, node),
|
|
1385
|
+
is_callable: true
|
|
1386
|
+
};
|
|
1168
1387
|
const path = bindingPath(expression);
|
|
1169
|
-
if (
|
|
1170
|
-
return {
|
|
1388
|
+
if (path) return {
|
|
1171
1389
|
path,
|
|
1172
1390
|
is_callable: callable
|
|
1173
1391
|
};
|
|
1392
|
+
return {
|
|
1393
|
+
path: sourceForNode(context.source, node),
|
|
1394
|
+
expr: lowerTemplateExpression(context, node),
|
|
1395
|
+
is_callable: callable
|
|
1396
|
+
};
|
|
1174
1397
|
}
|
|
1175
1398
|
function bindingPath(expression) {
|
|
1176
1399
|
const node = unwrapExpression(expression);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astralsight/astroforge-rsbuild-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "AstroForge Plugin for RsBuild",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@babel/parser": "^7.29.3",
|
|
41
|
-
"@astralsight/astroforge-core": "^0.0.
|
|
41
|
+
"@astralsight/astroforge-core": "^0.0.7"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsdown"
|