roqa 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/package.json +77 -0
- package/src/compiler/codegen.js +1217 -0
- package/src/compiler/index.js +47 -0
- package/src/compiler/parser.js +197 -0
- package/src/compiler/transforms/bind-detector.js +264 -0
- package/src/compiler/transforms/events.js +246 -0
- package/src/compiler/transforms/for-transform.js +164 -0
- package/src/compiler/transforms/inline-get.js +1049 -0
- package/src/compiler/transforms/jsx-to-template.js +871 -0
- package/src/compiler/transforms/show-transform.js +78 -0
- package/src/compiler/transforms/validate.js +80 -0
- package/src/compiler/utils.js +69 -0
- package/src/jsx-runtime.d.ts +640 -0
- package/src/jsx-runtime.js +73 -0
- package/src/runtime/cell.js +37 -0
- package/src/runtime/component.js +241 -0
- package/src/runtime/events.js +156 -0
- package/src/runtime/for-block.js +374 -0
- package/src/runtime/index.js +17 -0
- package/src/runtime/show-block.js +115 -0
- package/src/runtime/template.js +32 -0
- package/types/compiler.d.ts +9 -0
- package/types/index.d.ts +433 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { isJSXExpressionContainer } from "../parser.js";
|
|
2
|
+
import { CONSTANTS } from "../utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Transform event handlers from JSX attributes to inline assignments
|
|
6
|
+
*
|
|
7
|
+
* Input JSX:
|
|
8
|
+
* <button onclick={run}>Click</button>
|
|
9
|
+
* <a onclick={() => select(row)}>Select</a>
|
|
10
|
+
*
|
|
11
|
+
* Output:
|
|
12
|
+
* button_1.__click = run;
|
|
13
|
+
* a_1.__click = [select, row]; // For parameterized handlers
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} EventBinding
|
|
18
|
+
* @property {string} varName - Variable name of the element
|
|
19
|
+
* @property {string} eventName - Event name (click, input, etc.)
|
|
20
|
+
* @property {import("@babel/types").Node} handler - The handler expression
|
|
21
|
+
* @property {number[]} path - Path in the JSX tree
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} ProcessedEvent
|
|
26
|
+
* @property {string} varName - Variable name of the element
|
|
27
|
+
* @property {string} eventName - Event name
|
|
28
|
+
* @property {import("@babel/types").Node} handlerExpression - The handler (identifier or array expression)
|
|
29
|
+
* @property {boolean} isParameterized - Whether handler needs item parameter
|
|
30
|
+
* @property {import("@babel/types").Node[]} params - Additional parameters for the handler
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Process event bindings and determine handler type
|
|
35
|
+
* @param {EventBinding[]} events
|
|
36
|
+
* @param {string|null} itemParam - Current loop item parameter name (for parameterized handlers)
|
|
37
|
+
* @returns {ProcessedEvent[]}
|
|
38
|
+
*/
|
|
39
|
+
export function processEvents(events, itemParam = null) {
|
|
40
|
+
const processed = [];
|
|
41
|
+
|
|
42
|
+
for (const event of events) {
|
|
43
|
+
const { varName, eventName, handler } = event;
|
|
44
|
+
|
|
45
|
+
if (!handler) {
|
|
46
|
+
// Boolean attribute like onclick without value - skip
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extract the actual expression from JSXExpressionContainer
|
|
51
|
+
const expression = isJSXExpressionContainer(handler) ? handler.expression : handler;
|
|
52
|
+
|
|
53
|
+
const processedEvent = analyzeHandler(expression, varName, eventName, itemParam);
|
|
54
|
+
processed.push(processedEvent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return processed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Analyze a handler expression to determine its type
|
|
62
|
+
* @param {import("@babel/types").Node} expression
|
|
63
|
+
* @param {string} varName
|
|
64
|
+
* @param {string} eventName
|
|
65
|
+
* @param {string|null} itemParam
|
|
66
|
+
* @returns {ProcessedEvent}
|
|
67
|
+
*/
|
|
68
|
+
function analyzeHandler(expression, varName, eventName, itemParam) {
|
|
69
|
+
// Case 1: Simple identifier - onclick={run}
|
|
70
|
+
if (expression.type === "Identifier") {
|
|
71
|
+
return {
|
|
72
|
+
varName,
|
|
73
|
+
eventName,
|
|
74
|
+
handlerExpression: expression,
|
|
75
|
+
isParameterized: false,
|
|
76
|
+
params: [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Case 2: Arrow function - onclick={() => select(row)}
|
|
81
|
+
if (expression.type === "ArrowFunctionExpression" || expression.type === "FunctionExpression") {
|
|
82
|
+
return analyzeArrowHandler(expression, varName, eventName, itemParam);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Case 3: Call expression directly - onclick={handler(item)}
|
|
86
|
+
// This is unusual but handle it
|
|
87
|
+
if (expression.type === "CallExpression") {
|
|
88
|
+
// Wrap in array format: [handler, ...args]
|
|
89
|
+
const args = expression.arguments;
|
|
90
|
+
return {
|
|
91
|
+
varName,
|
|
92
|
+
eventName,
|
|
93
|
+
handlerExpression: expression.callee,
|
|
94
|
+
isParameterized: true,
|
|
95
|
+
params: args,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Default: treat as simple handler
|
|
100
|
+
return {
|
|
101
|
+
varName,
|
|
102
|
+
eventName,
|
|
103
|
+
handlerExpression: expression,
|
|
104
|
+
isParameterized: false,
|
|
105
|
+
params: [],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if an expression is "static" - safe to extract as a parameter
|
|
111
|
+
* that can be evaluated at setup time rather than click time.
|
|
112
|
+
* @param {import("@babel/types").Node} node
|
|
113
|
+
* @param {Set<string>} boundParams - Parameter names bound by the arrow function
|
|
114
|
+
* @returns {boolean}
|
|
115
|
+
*/
|
|
116
|
+
function isStaticExpression(node, boundParams = new Set()) {
|
|
117
|
+
if (!node) return true;
|
|
118
|
+
|
|
119
|
+
switch (node.type) {
|
|
120
|
+
// Identifiers are static ONLY if they are not bound parameters
|
|
121
|
+
case "Identifier":
|
|
122
|
+
return !boundParams.has(node.name);
|
|
123
|
+
|
|
124
|
+
// Literals are always static
|
|
125
|
+
case "NumericLiteral":
|
|
126
|
+
case "StringLiteral":
|
|
127
|
+
case "BooleanLiteral":
|
|
128
|
+
case "NullLiteral":
|
|
129
|
+
return true;
|
|
130
|
+
|
|
131
|
+
// Member expressions like obj.prop are static only if obj is static
|
|
132
|
+
case "MemberExpression":
|
|
133
|
+
return (
|
|
134
|
+
isStaticExpression(node.object, boundParams) &&
|
|
135
|
+
isStaticExpression(node.property, boundParams)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Function calls are NOT static - they need to be evaluated at click time
|
|
139
|
+
case "CallExpression":
|
|
140
|
+
return false;
|
|
141
|
+
|
|
142
|
+
// Binary/unary expressions containing calls are not static
|
|
143
|
+
case "BinaryExpression":
|
|
144
|
+
case "LogicalExpression":
|
|
145
|
+
return (
|
|
146
|
+
isStaticExpression(node.left, boundParams) && isStaticExpression(node.right, boundParams)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
case "UnaryExpression":
|
|
150
|
+
return isStaticExpression(node.argument, boundParams);
|
|
151
|
+
|
|
152
|
+
// Conditional expressions need all parts to be static
|
|
153
|
+
case "ConditionalExpression":
|
|
154
|
+
return (
|
|
155
|
+
isStaticExpression(node.test, boundParams) &&
|
|
156
|
+
isStaticExpression(node.consequent, boundParams) &&
|
|
157
|
+
isStaticExpression(node.alternate, boundParams)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Array/object literals - check all elements
|
|
161
|
+
case "ArrayExpression":
|
|
162
|
+
return node.elements.every((el) => el === null || isStaticExpression(el, boundParams));
|
|
163
|
+
|
|
164
|
+
case "ObjectExpression":
|
|
165
|
+
return node.properties.every(
|
|
166
|
+
(prop) =>
|
|
167
|
+
prop.type === "ObjectProperty" &&
|
|
168
|
+
isStaticExpression(prop.key, boundParams) &&
|
|
169
|
+
isStaticExpression(prop.value, boundParams),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Default: not static (be conservative)
|
|
173
|
+
default:
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Analyze an arrow function handler
|
|
180
|
+
* @param {import("@babel/types").ArrowFunctionExpression} arrow
|
|
181
|
+
* @param {string} varName
|
|
182
|
+
* @param {string} eventName
|
|
183
|
+
* @param {string|null} _itemParam
|
|
184
|
+
* @returns {ProcessedEvent}
|
|
185
|
+
*/
|
|
186
|
+
function analyzeArrowHandler(arrow, varName, eventName, _itemParam) {
|
|
187
|
+
const body = arrow.body;
|
|
188
|
+
|
|
189
|
+
// Collect parameter names that are bound by this arrow function
|
|
190
|
+
const boundParams = new Set();
|
|
191
|
+
for (const param of arrow.params) {
|
|
192
|
+
if (param.type === "Identifier") {
|
|
193
|
+
boundParams.add(param.name);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check if it's a simple call expression: () => fn(args)
|
|
198
|
+
// Transform to array format: [fn, ...args] ONLY if all args are static
|
|
199
|
+
// (don't reference arrow function parameters)
|
|
200
|
+
if (body.type === "CallExpression" && body.callee.type === "Identifier") {
|
|
201
|
+
const allArgsStatic = body.arguments.every((arg) => isStaticExpression(arg, boundParams));
|
|
202
|
+
|
|
203
|
+
if (allArgsStatic) {
|
|
204
|
+
return {
|
|
205
|
+
varName,
|
|
206
|
+
eventName,
|
|
207
|
+
handlerExpression: body.callee,
|
|
208
|
+
isParameterized: body.arguments.length > 0,
|
|
209
|
+
params: body.arguments,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// For more complex arrow functions, keep them as-is
|
|
215
|
+
// e.g., () => { multiple; statements; } or () => obj.method()
|
|
216
|
+
// or when arguments contain dynamic expressions like get(count) + 1
|
|
217
|
+
// or when arguments reference arrow function parameters like e.target.value
|
|
218
|
+
return {
|
|
219
|
+
varName,
|
|
220
|
+
eventName,
|
|
221
|
+
handlerExpression: arrow,
|
|
222
|
+
isParameterized: false,
|
|
223
|
+
params: [],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Generate event handler assignment code
|
|
229
|
+
* @param {ProcessedEvent} event
|
|
230
|
+
* @param {(node: import("@babel/types").Node) => string} generateExpr - Function to generate expression code
|
|
231
|
+
* @returns {string}
|
|
232
|
+
*/
|
|
233
|
+
export function generateEventAssignment(event, generateExpr) {
|
|
234
|
+
const { varName, eventName, handlerExpression, isParameterized, params } = event;
|
|
235
|
+
|
|
236
|
+
if (isParameterized) {
|
|
237
|
+
// Array format: element.__click = [handler, arg1, arg2]
|
|
238
|
+
const handlerCode = generateExpr(handlerExpression);
|
|
239
|
+
const paramsCode = params.map((p) => generateExpr(p)).join(", ");
|
|
240
|
+
return `${varName}.${CONSTANTS.EVENT_PREFIX}${eventName} = [${handlerCode}, ${paramsCode}];`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Simple assignment: element.__click = handler
|
|
244
|
+
const handlerCode = generateExpr(handlerExpression);
|
|
245
|
+
return `${varName}.${CONSTANTS.EVENT_PREFIX}${eventName} = ${handlerCode};`;
|
|
246
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { extractJSXAttributes, getJSXChildren, isJSXExpressionContainer } from "../parser.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transform <For> components into forBlock() calls
|
|
5
|
+
*
|
|
6
|
+
* Input:
|
|
7
|
+
* <For each={items}>{(row) => <tr>...</tr>}</For>
|
|
8
|
+
*
|
|
9
|
+
* Output:
|
|
10
|
+
* forBlock(container, items, (anchor, item, index) => {
|
|
11
|
+
* // template creation and bindings
|
|
12
|
+
* return { start, end };
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} ForTransformResult
|
|
18
|
+
* @property {string} containerVar - Variable name of the container element
|
|
19
|
+
* @property {import("@babel/types").Node} itemsExpression - The `each` prop expression
|
|
20
|
+
* @property {string} itemParam - The callback's item parameter name
|
|
21
|
+
* @property {string|null} indexParam - The callback's index parameter name (if present)
|
|
22
|
+
* @property {import("@babel/types").JSXElement} bodyJSX - The JSX returned by the callback
|
|
23
|
+
* @property {import("@babel/types").Node} originalCallback - The original callback for preserving other code
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract information from a <For> component for transformation
|
|
28
|
+
* @param {import("@babel/types").JSXElement} node - The <For> JSX element
|
|
29
|
+
* @param {string} containerVar - Variable name of the parent container
|
|
30
|
+
* @returns {ForTransformResult}
|
|
31
|
+
*/
|
|
32
|
+
export function extractForInfo(node, containerVar) {
|
|
33
|
+
const attrs = extractJSXAttributes(node.openingElement);
|
|
34
|
+
|
|
35
|
+
// Get the `each` prop
|
|
36
|
+
const eachValue = attrs.get("each");
|
|
37
|
+
if (!eachValue || !isJSXExpressionContainer(eachValue)) {
|
|
38
|
+
throw createForError(node, "Missing required 'each' prop on <For> component");
|
|
39
|
+
}
|
|
40
|
+
const itemsExpression = eachValue.expression;
|
|
41
|
+
|
|
42
|
+
// Get the children - should be a single expression container with arrow function
|
|
43
|
+
const children = getJSXChildren(node);
|
|
44
|
+
if (children.length !== 1) {
|
|
45
|
+
throw createForError(node, "<For> must have exactly one child (render callback)");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const child = children[0];
|
|
49
|
+
if (!isJSXExpressionContainer(child)) {
|
|
50
|
+
throw createForError(node, "<For> child must be an expression: {(item) => <element>}");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const callback = child.expression;
|
|
54
|
+
if (callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression") {
|
|
55
|
+
throw createForError(node, "<For> child must be an arrow function or function expression");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Extract callback parameters
|
|
59
|
+
const params = callback.params;
|
|
60
|
+
if (params.length < 1) {
|
|
61
|
+
throw createForError(node, "<For> callback must have at least one parameter (item)");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const itemParam = extractParamName(params[0]);
|
|
65
|
+
const indexParam = params.length > 1 ? extractParamName(params[1]) : null;
|
|
66
|
+
|
|
67
|
+
// Extract the JSX from the callback body
|
|
68
|
+
const bodyJSX = extractCallbackJSX(callback);
|
|
69
|
+
if (!bodyJSX) {
|
|
70
|
+
throw createForError(node, "<For> callback must return JSX");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
containerVar,
|
|
75
|
+
itemsExpression,
|
|
76
|
+
itemParam,
|
|
77
|
+
indexParam,
|
|
78
|
+
bodyJSX,
|
|
79
|
+
originalCallback: callback,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract parameter name from various parameter types
|
|
85
|
+
* @param {import("@babel/types").Node} param
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
function extractParamName(param) {
|
|
89
|
+
if (param.type === "Identifier") {
|
|
90
|
+
return param.name;
|
|
91
|
+
}
|
|
92
|
+
if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
|
|
93
|
+
return param.left.name;
|
|
94
|
+
}
|
|
95
|
+
throw new Error("Unsupported parameter type in <For> callback");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Extract JSX from callback body
|
|
100
|
+
* @param {import("@babel/types").ArrowFunctionExpression|import("@babel/types").FunctionExpression} callback
|
|
101
|
+
* @returns {import("@babel/types").JSXElement|null}
|
|
102
|
+
*/
|
|
103
|
+
function extractCallbackJSX(callback) {
|
|
104
|
+
const body = callback.body;
|
|
105
|
+
|
|
106
|
+
// Arrow function with expression body: (item) => <tr>...</tr>
|
|
107
|
+
if (body.type === "JSXElement") {
|
|
108
|
+
return body;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Block body: (item) => { return <tr>...</tr>; }
|
|
112
|
+
if (body.type === "BlockStatement") {
|
|
113
|
+
// Look for return statement with JSX
|
|
114
|
+
for (const stmt of body.body) {
|
|
115
|
+
if (stmt.type === "ReturnStatement" && stmt.argument) {
|
|
116
|
+
if (stmt.argument.type === "JSXElement") {
|
|
117
|
+
return stmt.argument;
|
|
118
|
+
}
|
|
119
|
+
// Handle parenthesized JSX: return (<tr>...</tr>)
|
|
120
|
+
if (stmt.argument.type === "ParenthesizedExpression") {
|
|
121
|
+
if (stmt.argument.expression.type === "JSXElement") {
|
|
122
|
+
return stmt.argument.expression;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get any statements before the return in a callback body
|
|
134
|
+
* These need to be preserved in the generated forBlock callback
|
|
135
|
+
* @param {import("@babel/types").ArrowFunctionExpression|import("@babel/types").FunctionExpression} callback
|
|
136
|
+
* @returns {import("@babel/types").Statement[]}
|
|
137
|
+
*/
|
|
138
|
+
export function getCallbackPreamble(callback) {
|
|
139
|
+
const body = callback.body;
|
|
140
|
+
|
|
141
|
+
if (body.type !== "BlockStatement") {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const preamble = [];
|
|
146
|
+
for (const stmt of body.body) {
|
|
147
|
+
if (stmt.type === "ReturnStatement") {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
preamble.push(stmt);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return preamble;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a formatted error for <For> component issues
|
|
158
|
+
*/
|
|
159
|
+
function createForError(node, message) {
|
|
160
|
+
const error = new Error(message);
|
|
161
|
+
error.code = "FOR_COMPONENT_ERROR";
|
|
162
|
+
error.loc = node.loc;
|
|
163
|
+
return error;
|
|
164
|
+
}
|