Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b954aa6
React properties vite plugin
abdullahtellioglu Sep 1, 2025
0bb11a2
Merge branch 'main' into feat/react-component-properties-plugin
abdullahtellioglu Sep 1, 2025
a04b0bc
Formatter
abdullahtellioglu Sep 1, 2025
16c0027
Merge remote-tracking branch 'origin/feat/react-component-properties-…
abdullahtellioglu Sep 1, 2025
e041599
Merge branch 'main' into feat/react-component-properties-plugin
abdullahtellioglu Sep 8, 2025
25cc78f
Merge branch 'main' into feat/react-component-properties-plugin
abdullahtellioglu Sep 8, 2025
ab9adfc
Added test with alias import
abdullahtellioglu Sep 8, 2025
67b99ec
Merge branch 'main' into feat/react-component-properties-plugin
abdullahtellioglu Sep 8, 2025
5150182
Formatter
abdullahtellioglu Sep 8, 2025
9acfbca
Broken as JSX cannot be resolved
abdullahtellioglu Sep 9, 2025
c4b56b6
Revert "Broken as JSX cannot be resolved"
Artur- Sep 12, 2025
0ee8c4f
fix: resolve JSX namespace for all React import patterns
Artur- Sep 12, 2025
9d7f451
Merge branch 'refs/heads/main' into feat/react-component-properties-p…
abdullahtellioglu Sep 12, 2025
b83f59e
Fixed intrinsic elements, updated tests
abdullahtellioglu Sep 13, 2025
c9918ce
Formatter
abdullahtellioglu Sep 13, 2025
bdc6a2b
Improve performance by providing old program
abdullahtellioglu Sep 17, 2025
0cb7ee5
Logs are updated
abdullahtellioglu Sep 17, 2025
2fc4004
Merge branch 'main' into feat/react-component-properties-plugin
abdullahtellioglu Sep 17, 2025
e15e541
replaced console.log with vite internal loggers
abdullahtellioglu Sep 17, 2025
3302475
Merge remote-tracking branch 'origin/feat/react-component-properties-…
abdullahtellioglu Sep 17, 2025
c6a03d4
reverted this.debug
abdullahtellioglu Sep 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion flow-server/src/main/resources/plugins/plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"rollup-plugin-postcss-lit-custom",
"react-function-location-plugin",
"rollup-plugin-vaadin-i18n",
"vite-plugin-service-worker"
"vite-plugin-service-worker",
"react-component-properties-plugin"
]
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"description": "React component properties finds the JSX element properties",
"keywords": [
"plugin",
"copilot"
],
"name": "@vaadin/react-component-properties-plugin",
"version": "0.0.4",
"main": "index.ts",
"private": true,
"author": "Vaadin Ltd",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/vaadin/copilot/issues"
},
"files": [
"index.ts"
],
"peerDependencies": {
"vite": "6.x"
},
"devDependencies": {
"@types/node": "22.x"
}
}
5 changes: 3 additions & 2 deletions flow-server/src/main/resources/vite.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import checker from 'vite-plugin-checker';
import postcssLit from '#buildFolder#/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js';
import vaadinI18n from '#buildFolder#/plugins/rollup-plugin-vaadin-i18n/rollup-plugin-vaadin-i18n.js';
import serviceWorkerPlugin from '#buildFolder#/plugins/vite-plugin-service-worker';

import reactComponentPropertiesPlugin from '#buildFolder#/plugins/react-component-properties-plugin';
import { createRequire } from 'module';

import { visualizer } from 'rollup-plugin-visualizer';
Expand Down Expand Up @@ -799,7 +799,8 @@ export const vaadinConfig: UserConfigFn = (env) => {
checker({
typescript: true
}),
productionMode && visualizer({ brotliSize: true, filename: bundleSizeFile })
productionMode && visualizer({ brotliSize: true, filename: bundleSizeFile }),
devMode && reactComponentPropertiesPlugin()
]
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public void getPluginsReturnsExpectedList() {
"theme-loader", "theme-live-reload-plugin",
"build-status-plugin", "rollup-plugin-postcss-lit-custom",
"react-function-location-plugin", "rollup-plugin-vaadin-i18n",
"vite-plugin-service-worker" };
"vite-plugin-service-worker",
"react-component-properties-plugin" };
final List<String> plugins = FrontendPluginsUtil.getPlugins();
Assert.assertEquals("Unexpected number of plugins in 'plugins.json'",
expectedPlugins.length, plugins.size());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Sample of IntrinsicElements to mimic the react-types loaded
// via node modules. Used by ReactPropertiesPlugin

declare namespace JSX {
interface IntrinsicElements {
h1: { text: string };
div: { [key: string]: any, "aria-label": string };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@


export default function ReactComponentProperties(){
return (
<div data-test-id="react-component-properties">
<ComponentWithInlineProps name={'fooName'} surname={'fooSurname'} />
<ComponentWithoutProps></ComponentWithoutProps>
<h1 data-test-id="simple-header">Header 1</h1>
<ComponentWithInterfaceProps label={"fooLabel"}></ComponentWithInterfaceProps>
<ComponentWithAnyProps />
</div>
)
}

const ComponentWithoutProps = () => {
return <span data-test-id="component-without-props"></span>
}
const ComponentWithInlineProps = (props: { name: string, surname: string, sex?: 'M' | 'F'}) => {
const { name, surname, sex } = props;
return (
<div data-test-id="component-with-inline-props">
<h4>{name} - ${surname}</h4>
<h5>{sex ?? 'Unknown'}</h5>
</div>
)
}
interface ComponentWithInterfaceProperties {
label: string;
}
const ComponentWithInterfaceProps = (props: ComponentWithInterfaceProperties) => {
return (
<div data-test-id="component-with-interface-props">
{props.label}
</div>
)
}

const ComponentWithAnyProps = (props: any) => {
return (
<h5 data-test-id="component-with-any-props">This component has any typed props</h5>
)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createBrowserRouter, RouteObject } from 'react-router';
import { serverSideRoutes } from 'Frontend/generated/flow/Flow';
import ReactComponents from './ReactComponents';

import ReactComponentProperties from "./ReactComponentProperties";
function build() {
const routes = [
{ path: 'react-components', element: <ReactComponents/> },
{ path: 'react-component-properties', element: <ReactComponentProperties />},
...serverSideRoutes
] as RouteObject[];
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.vaadin.viteapp;

import com.vaadin.flow.testutil.ChromeBrowserTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebElement;

import java.util.List;
import java.util.Map;

public class ReactComponentPropertiesIT extends ChromeBrowserTest {

private static final String DATA_TEST_ID = "data-test-id";

@Before
public void openView() {
getDriver().get(getTestURL());
waitForDevServer();
}

@Override
protected String getTestPath() {
return "/react-component-properties";
}

@Test
public void inlinePropsCanFound() {
var componentWithInlinePropsElement = $("*")
.withAttribute(DATA_TEST_ID, "component-with-inline-props")
.waitForFirst();
Map<String, Object> debugPropertiesFromFiberNode = getDebugPropertiesFromFiberNode(
componentWithInlinePropsElement);
var error = (Boolean) debugPropertiesFromFiberNode.get("error");
Assert.assertFalse(error);
debugPropertiesFromFiberNode.get("properties");
List<?> properties = (List<?>) debugPropertiesFromFiberNode
.get("properties");
Assert.assertFalse(properties.isEmpty());
}

@Test
public void intrinsicPropsCanFoundInWindow() {
var h1 = $("*").withAttribute(DATA_TEST_ID, "simple-header")
.waitForFirst();
Map<String, Object> debugPropertiesFromFiberNode = getDebugPropertiesFromFiberNode(
h1);
Assert.assertNull(debugPropertiesFromFiberNode);
Map<String, Object> debugPropertiesFromWindow = getDebugPropertiesFromWindow(
h1);
Assert.assertNotNull(debugPropertiesFromWindow);
Assert.assertFalse((Boolean) debugPropertiesFromWindow.get("error"));
Assert.assertFalse(
((List<?>) debugPropertiesFromWindow.get("properties"))
.isEmpty());
}

@Test
public void interfacePropsCanFound() {
var compWithInterfaceProps = $("*")
.withAttribute(DATA_TEST_ID, "component-with-interface-props")
.waitForFirst();
Map<String, Object> debugPropertiesFromFiberNode = getDebugPropertiesFromFiberNode(
compWithInterfaceProps);
Assert.assertNotNull(debugPropertiesFromFiberNode);
assertError(debugPropertiesFromFiberNode, false);
}

@Test
public void componentWithoutPropsShouldHaveAnError() {
var element = $("*")
.withAttribute(DATA_TEST_ID, "component-with-any-props")
.waitForFirst();
Assert.assertNull(getDebugPropertiesFromFiberNode(element));
Map<String, Object> debugPropertiesFromWindow = getDebugPropertiesFromWindow(
element);
assertError(debugPropertiesFromWindow, true);
}

@Test
public void errorShouldBeRegisteredWhenIntrinsicElementsNotLoaded() {
var anySpan = $("span").waitForFirst();
Map<String, Object> spanProps = getDebugPropertiesFromWindow(anySpan);
assertError(spanProps, true);

var anyDiv = $("div").waitForFirst();
Map<String, Object> divProperties = getDebugPropertiesFromWindow(
anyDiv);
assertError(divProperties, false);

}

private void assertError(Map<String, Object> propObj, boolean expected) {
var error = (Boolean) propObj.get("error");
Assert.assertEquals(expected, error);
}

private Map<String, Object> getDebugPropertiesFromWindow(
WebElement element) {
String tagName = element.getTagName();
return (Map<String, Object>) executeScript(
"""
return window.Vaadin.copilot.ReactProperties.properties[arguments[0]];
""",
tagName);
}

private Map<String, Object> getDebugPropertiesFromFiberNode(
WebElement element) {
return (Map<String, Object>) executeScript(
"""
const key = Object.keys(arguments[0]).filter(a => a.startsWith("__reactFiber"))[0];
const fiber = arguments[0][key];
return fiber.return.type?.__debugProperties;
""",
element);
}

}
Loading