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,78 @@
|
|
|
1
|
+
import { extractJSXAttributes, getJSXChildren, isJSXExpressionContainer } from "../parser.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transform <Show> components into showBlock() calls
|
|
5
|
+
*
|
|
6
|
+
* Input:
|
|
7
|
+
* <Show when={condition}><div>...</div></Show>
|
|
8
|
+
*
|
|
9
|
+
* Output:
|
|
10
|
+
* showBlock(container, condition, (anchor) => {
|
|
11
|
+
* // template creation and bindings
|
|
12
|
+
* return { start, end };
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} ShowTransformResult
|
|
18
|
+
* @property {string} containerVar - Variable name of the container element
|
|
19
|
+
* @property {import("@babel/types").Node} conditionExpression - The `when` prop expression
|
|
20
|
+
* @property {import("@babel/types").JSXElement} bodyJSX - The JSX child to render conditionally
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extract information from a <Show> component for transformation
|
|
25
|
+
* @param {import("@babel/types").JSXElement} node - The <Show> JSX element
|
|
26
|
+
* @param {string} containerVar - Variable name of the parent container
|
|
27
|
+
* @returns {ShowTransformResult}
|
|
28
|
+
*/
|
|
29
|
+
export function extractShowInfo(node, containerVar) {
|
|
30
|
+
const attrs = extractJSXAttributes(node.openingElement);
|
|
31
|
+
|
|
32
|
+
// Get the `when` prop
|
|
33
|
+
const whenValue = attrs.get("when");
|
|
34
|
+
if (!whenValue || !isJSXExpressionContainer(whenValue)) {
|
|
35
|
+
throw createShowError(node, "Missing required 'when' prop on <Show> component");
|
|
36
|
+
}
|
|
37
|
+
const conditionExpression = whenValue.expression;
|
|
38
|
+
|
|
39
|
+
// Get the children - should have at least one child element
|
|
40
|
+
const children = getJSXChildren(node);
|
|
41
|
+
if (children.length === 0) {
|
|
42
|
+
throw createShowError(node, "<Show> must have at least one child element");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get the first JSX element child
|
|
46
|
+
let bodyJSX = null;
|
|
47
|
+
for (const child of children) {
|
|
48
|
+
if (child.type === "JSXElement") {
|
|
49
|
+
bodyJSX = child;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
// Support expression container with JSX: {<div>...</div>}
|
|
53
|
+
if (isJSXExpressionContainer(child) && child.expression.type === "JSXElement") {
|
|
54
|
+
bodyJSX = child.expression;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!bodyJSX) {
|
|
60
|
+
throw createShowError(node, "<Show> must have a JSX element as child");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
containerVar,
|
|
65
|
+
conditionExpression,
|
|
66
|
+
bodyJSX,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a formatted error for <Show> component issues
|
|
72
|
+
*/
|
|
73
|
+
function createShowError(node, message) {
|
|
74
|
+
const error = new Error(message);
|
|
75
|
+
error.code = "SHOW_COMPONENT_ERROR";
|
|
76
|
+
error.loc = node.loc;
|
|
77
|
+
return error;
|
|
78
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { traverse } from "../utils.js";
|
|
2
|
+
import { getJSXElementName, isPascalCase, isControlFlowComponent } from "../parser.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validate that no unsupported PascalCase components remain in the AST
|
|
6
|
+
* This runs AFTER control flow transformers (For, Show, Switch) have processed their nodes
|
|
7
|
+
*
|
|
8
|
+
* Throws helpful error messages with suggestions for alternatives
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate the AST for unsupported components
|
|
13
|
+
* @param {import("@babel/types").File} ast - The Babel AST
|
|
14
|
+
* @throws {Error} If unsupported PascalCase components are found
|
|
15
|
+
*/
|
|
16
|
+
export function validateNoCustomComponents(ast) {
|
|
17
|
+
const errors = [];
|
|
18
|
+
|
|
19
|
+
traverse(ast, {
|
|
20
|
+
JSXElement(path) {
|
|
21
|
+
const node = path.node;
|
|
22
|
+
const name = getJSXElementName(node);
|
|
23
|
+
|
|
24
|
+
if (!name) return;
|
|
25
|
+
|
|
26
|
+
// Skip control flow components (they should be transformed, but check anyway)
|
|
27
|
+
if (isControlFlowComponent(node)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check for PascalCase (custom component)
|
|
32
|
+
if (isPascalCase(name)) {
|
|
33
|
+
errors.push(createComponentError(name, node));
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
noScope: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (errors.length > 0) {
|
|
40
|
+
// Throw the first error (could aggregate, but one at a time is clearer)
|
|
41
|
+
throw errors[0];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a detailed error for an unsupported component
|
|
47
|
+
* @param {string} name - Component name
|
|
48
|
+
* @param {import("@babel/types").JSXElement} node - The JSX node
|
|
49
|
+
* @returns {Error}
|
|
50
|
+
*/
|
|
51
|
+
function createComponentError(name, node) {
|
|
52
|
+
const kebabName = toKebabCase(name);
|
|
53
|
+
|
|
54
|
+
const error = new Error(
|
|
55
|
+
`Unsupported component '${name}'.\n` + `PascalCase components are not supported in Roqa.\n`,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
error.code = "UNSUPPORTED_COMPONENT";
|
|
59
|
+
error.componentName = name;
|
|
60
|
+
error.loc = node.loc;
|
|
61
|
+
error.suggestions = [
|
|
62
|
+
`Use web components: defineComponent("${kebabName}", ${name})`,
|
|
63
|
+
`Use control flow: <For each={items}>`,
|
|
64
|
+
`Use lowercase HTML elements: <div>, <span>, <button>`,
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert PascalCase to kebab-case
|
|
72
|
+
* @param {string} str
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function toKebabCase(str) {
|
|
76
|
+
return str
|
|
77
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
78
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
|
79
|
+
.toLowerCase();
|
|
80
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import _traverse from "@babel/traverse";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared utilities for the Roqa compiler
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Handle CJS/ESM interop for @babel/traverse
|
|
8
|
+
export const traverse = _traverse.default || _traverse;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Compiler constants to avoid magic strings
|
|
12
|
+
*/
|
|
13
|
+
export const CONSTANTS = {
|
|
14
|
+
/** Prefix for delegated event handlers (e.g., __click) */
|
|
15
|
+
EVENT_PREFIX: "__",
|
|
16
|
+
/** Prefix for cell refs used in inline updates (e.g., ref_1) */
|
|
17
|
+
REF_PREFIX: "ref_",
|
|
18
|
+
/** Prefix for template variables (e.g., $tmpl_1) */
|
|
19
|
+
TEMPLATE_PREFIX: "$tmpl_",
|
|
20
|
+
/** Prefix for root variables (e.g., $root_1) */
|
|
21
|
+
ROOT_PREFIX: "$root_",
|
|
22
|
+
/** Internal key used for root variable counter */
|
|
23
|
+
ROOT_COUNTER_KEY: "$root",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Escape single quotes and backslashes in template strings
|
|
28
|
+
* @param {string} str
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function escapeTemplateString(str) {
|
|
32
|
+
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Escape special characters in a string literal
|
|
37
|
+
* @param {string} str
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function escapeStringLiteral(str) {
|
|
41
|
+
return str
|
|
42
|
+
.replace(/\\/g, "\\\\")
|
|
43
|
+
.replace(/"/g, '\\"')
|
|
44
|
+
.replace(/\n/g, "\\n")
|
|
45
|
+
.replace(/\r/g, "\\r")
|
|
46
|
+
.replace(/\t/g, "\\t");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Escape HTML special characters
|
|
51
|
+
* @param {string} str
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
export function escapeHtml(str) {
|
|
55
|
+
return str
|
|
56
|
+
.replace(/&/g, "&")
|
|
57
|
+
.replace(/</g, "<")
|
|
58
|
+
.replace(/>/g, ">")
|
|
59
|
+
.replace(/"/g, """);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Escape attribute values
|
|
64
|
+
* @param {string} str
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
export function escapeAttr(str) {
|
|
68
|
+
return str.replace(/"/g, """).replace(/'/g, "'");
|
|
69
|
+
}
|