diff --git a/src/utils/transform-jsx-to-reactive/index.test.ts b/src/utils/transform-jsx-to-reactive/index.test.ts
index 646d738d1..b65969800 100644
--- a/src/utils/transform-jsx-to-reactive/index.test.ts
+++ b/src/utils/transform-jsx-to-reactive/index.test.ts
@@ -713,28 +713,47 @@ describe("utils", () => {
expect(output).toBe(expected);
});
- it.todo(
- "should work with a component as an arrow function with blockstatement and the default export on a different line",
- () => {
- const input = `
+ it("should work with a component as an arrow function with blockstatement and the default export on a different line", () => {
+ const input = `
const MyComponent = (props) =>
{props.foo}
export default MyComponent
`;
- const output = toInline(
- transformJSXToReactive(input, "src/web-components/my-component.tsx"),
- );
+ const output = toInline(
+ transformJSXToReactive(input, "src/web-components/my-component.tsx"),
+ );
- const expected = toInline(`
+ const expected = toInline(`
import {brisaElement} from "brisa/client";
- const MyComponent = (props, {h}) => h('div', {}, () => props.foo.value);
- export default brisaElement(MyComponent, ['foo']);
+ export default brisaElement(function (props, {h}) {return h('div', {}, () => props.foo.value);}, ['foo']);
`);
- expect(output).toBe(expected);
- },
- );
+ expect(output).toBe(expected);
+ });
+
+ it("should work default export on a different line in a function declaration", () => {
+ const input = `
+ function MyComponent(props) {
+ return {props.foo}
+ }
+ export default MyComponent
+ `;
+
+ const output = toInline(
+ transformJSXToReactive(input, "src/web-components/my-component.tsx"),
+ );
+
+ const expected = toInline(`
+ import {brisaElement} from "brisa/client";
+
+ export default brisaElement(function (props, {h}) {
+ return h('div', {}, () => props.foo.value);
+ }, ['foo']);
+ `);
+
+ expect(output).toBe(expected);
+ });
it.todo(
"should wrap conditional renders in different returns inside an hyperScript function",
diff --git a/src/utils/transform-jsx-to-reactive/index.ts b/src/utils/transform-jsx-to-reactive/index.ts
index c86dd2501..7deb33e8f 100644
--- a/src/utils/transform-jsx-to-reactive/index.ts
+++ b/src/utils/transform-jsx-to-reactive/index.ts
@@ -6,6 +6,7 @@ import defineBrisaElement from "./define-brisa-element";
import getComponentVariableNames from "./get-component-variable-names";
import { ALTERNATIVE_FOLDER_REGEX, WEB_COMPONENT_REGEX } from "./constants";
import transformToReactiveProps from "./transform-to-reactive-props";
+import transformToDirectExport from "./transform-to-direct-export";
const { parseCodeToAST, generateCodeFromAST } = AST("tsx");
@@ -13,7 +14,9 @@ export default function transformJSXToReactive(code: string, path: string) {
if (path.match(ALTERNATIVE_FOLDER_REGEX)) return code;
const ast = parseCodeToAST(code);
- const [astWithPropsDotValue, propNames] = transformToReactiveProps(ast);
+ const astWithDirectExport = transformToDirectExport(ast);
+ const [astWithPropsDotValue, propNames] =
+ transformToReactiveProps(astWithDirectExport);
const reactiveAst = transformToReactiveArrays(astWithPropsDotValue);
const [componentBranch, index] = getWebComponentAst(reactiveAst) as [
ESTree.FunctionDeclaration,
diff --git a/src/utils/transform-jsx-to-reactive/transform-to-direct-export/index.test.ts b/src/utils/transform-jsx-to-reactive/transform-to-direct-export/index.test.ts
new file mode 100644
index 000000000..925ae87ed
--- /dev/null
+++ b/src/utils/transform-jsx-to-reactive/transform-to-direct-export/index.test.ts
@@ -0,0 +1,101 @@
+import { describe, expect, it } from "bun:test";
+import AST from "../../ast";
+import transformToDirectExport from ".";
+
+const { parseCodeToAST, generateCodeFromAST } = AST();
+const toInline = (s: string) => s.replace(/\s*\n\s*/g, "").replaceAll("'", '"');
+
+describe("utils", () => {
+ describe("transform-jsx-to-reactive", () => {
+ describe("transform-to-direct-export", () => {
+ it("should transform the web-component to a direct export if the component is a variable declaration", () => {
+ const ast = parseCodeToAST(`
+ const MyComponent = (props) => {props.foo}
;
+ export default MyComponent;
+ `);
+ const outputAst = transformToDirectExport(ast);
+ const outputCode = toInline(generateCodeFromAST(outputAst));
+ const expectedCode = toInline(`
+ export default props => jsxDEV("div", {children: props.foo}, undefined, false, undefined, this);
+ `);
+
+ expect(outputCode).toBe(expectedCode);
+ });
+
+ it("should transform the web-component to a direct export if the component is a function declaration", () => {
+ const ast = parseCodeToAST(`
+ function MyComponent(props) {
+ return {props.foo}
;
+ }
+ export default MyComponent;
+ `);
+ const outputAst = transformToDirectExport(ast);
+ const outputCode = toInline(generateCodeFromAST(outputAst));
+ const expectedCode = toInline(`
+ export default function (props) {return jsxDEV("div", {children: props.foo}, undefined, false, undefined, this);}
+ `);
+
+ expect(outputCode).toBe(expectedCode);
+ });
+
+ it("should transform the web-component to a direct export if the component is an arrow function with block statement declaration", () => {
+ const ast = parseCodeToAST(`
+ const MyComponent = (props) => {
+ return {props.foo}
;
+ }
+ export default MyComponent;
+ `);
+ const outputAst = transformToDirectExport(ast);
+ const outputCode = toInline(generateCodeFromAST(outputAst));
+ const expectedCode = toInline(`
+ export default props => {return jsxDEV("div", {children: props.foo}, undefined, false, undefined, this);};
+ `);
+
+ expect(outputCode).toBe(expectedCode);
+ });
+
+ it("should not transform the web-component to a direct export if the component is a direct export", () => {
+ const ast = parseCodeToAST(`
+ export default (props) => {props.foo}
;
+ `);
+ const outputAst = transformToDirectExport(ast);
+ const outputCode = toInline(generateCodeFromAST(outputAst));
+ const expectedCode = toInline(`
+ export default props => jsxDEV("div", {children: props.foo}, undefined, false, undefined, this);
+ `);
+
+ expect(outputCode).toBe(expectedCode);
+ });
+
+ it("should not transform the web-component to a direct export if the component is a direct export with block statement", () => {
+ const ast = parseCodeToAST(`
+ export default (props) => {
+ return {props.foo}
;
+ }
+ `);
+ const outputAst = transformToDirectExport(ast);
+ const outputCode = toInline(generateCodeFromAST(outputAst));
+ const expectedCode = toInline(`
+ export default props => {return jsxDEV("div", {children: props.foo}, undefined, false, undefined, this);};
+ `);
+
+ expect(outputCode).toBe(expectedCode);
+ });
+
+ it("should not transform the web-component to a direct export if the component is a direct export with a function declaration", () => {
+ const ast = parseCodeToAST(`
+ export default function MyComponent(props) {
+ return {props.foo}
;
+ }
+ `);
+ const outputAst = transformToDirectExport(ast);
+ const outputCode = toInline(generateCodeFromAST(outputAst));
+ const expectedCode = toInline(`
+ export default function MyComponent(props) {return jsxDEV("div", {children: props.foo}, undefined, false, undefined, this);}
+ `);
+
+ expect(outputCode).toBe(expectedCode);
+ });
+ });
+ });
+});
diff --git a/src/utils/transform-jsx-to-reactive/transform-to-direct-export/index.ts b/src/utils/transform-jsx-to-reactive/transform-to-direct-export/index.ts
new file mode 100644
index 000000000..a9b47265a
--- /dev/null
+++ b/src/utils/transform-jsx-to-reactive/transform-to-direct-export/index.ts
@@ -0,0 +1,72 @@
+import { ESTree } from "meriyah";
+
+const DIRECT_TYPES = new Set([
+ "ArrowFunctionExpression",
+ "FunctionExpression",
+ "VariableDeclaration",
+]);
+
+/**
+ * transformToDirectExport
+ *
+ * @description Transform no-direct default export to a direct default export
+ * @example
+ * Input:
+ * const MyComponent = (props) => {props.foo}
;
+ * export default MyComponent;
+ *
+ * Output:
+ * export default (props) => {props.foo}
;
+ *
+ * @param {ESTree.Program} ast
+ * @returns {ESTree.Program}
+ */
+export default function transformToDirectExport(
+ ast: ESTree.Program,
+): ESTree.Program {
+ const defaultExportIndex = ast.body.findIndex(
+ (node) => node.type === "ExportDefaultDeclaration",
+ );
+
+ if (defaultExportIndex === -1) return ast;
+
+ const defaultExportNode = ast.body[defaultExportIndex] as any;
+
+ if (DIRECT_TYPES.has(defaultExportNode.declaration.type)) return ast;
+
+ const astWithoutDefaultExport = {
+ ...ast,
+ body: ast.body.filter((node, index) => index !== defaultExportIndex),
+ };
+
+ const componentDeclarationIndex = ast.body.findIndex((node: any) => {
+ const name =
+ node.id?.name ??
+ node.declaration?.name ??
+ node?.declarations?.[0]?.id?.name;
+ return (
+ DIRECT_TYPES.has(node.type) && name === defaultExportNode.declaration.name
+ );
+ });
+
+ if (componentDeclarationIndex === -1) return ast;
+
+ const componentDeclaration = ast.body[componentDeclarationIndex];
+
+ if (componentDeclaration.type === "VariableDeclaration") {
+ const updatedBody = astWithoutDefaultExport.body.map((node, index) => {
+ if (index === componentDeclarationIndex) {
+ return {
+ ...node,
+ type: "ExportDefaultDeclaration",
+ declaration: componentDeclaration.declarations[0].init,
+ };
+ }
+ return node;
+ });
+
+ return { ...astWithoutDefaultExport, body: updatedBody } as ESTree.Program;
+ }
+
+ return ast;
+}
diff --git a/src/utils/transform-jsx-to-reactive/transform-to-reactive-props/index.test.ts b/src/utils/transform-jsx-to-reactive/transform-to-reactive-props/index.test.ts
index e69778fc4..17f64bbf0 100644
--- a/src/utils/transform-jsx-to-reactive/transform-to-reactive-props/index.test.ts
+++ b/src/utils/transform-jsx-to-reactive/transform-to-reactive-props/index.test.ts
@@ -190,6 +190,38 @@ describe("utils", () => {
expect(outputCode).toBe(expectedCode);
expect(propNames).toEqual(["foo", "bar", "baz"]);
});
+
+ it("should work consuming a property of some props", () => {
+ const code = `
+ const outsideComponent = (props) => {
+ console.log(props.foo.name);
+ if(props.bar.name) return props.baz.name;
+ }
+
+ export default function InsideWebComoponent(props) {
+ console.log(props.foo.name);
+ if(props.bar?.name) return {props.baz.name}
;
+ }
+ `;
+ const ast = parseCodeToAST(code);
+ const [outputAst, propNames] = transformToReactiveProps(ast);
+ const outputCode = toInline(generateCodeFromAST(outputAst));
+
+ const expectedCode = toInline(`
+ const outsideComponent = props => {
+ console.log(props.foo.name);
+ if (props.bar.name) return props.baz.name;
+ };
+
+ export default function InsideWebComoponent(props) {
+ console.log(props.foo.value.name);
+ if (props.bar.value?.name) return jsxDEV("div", {children: props.baz.value.name}, undefined, false, undefined, this);
+ }
+ `);
+
+ expect(outputCode).toBe(expectedCode);
+ expect(propNames).toEqual(["foo", "bar", "baz"]);
+ });
});
});
});