-
Notifications
You must be signed in to change notification settings - Fork 167
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Write source information for React components defined in the pr…
…oject (#18470) * feat: Write source information for React components defined in the project When transpiling tsx/jsx to ts/js, Babel writes the source information for where components are used. This adds the information about where components are defined. * Test * format * Fix review comments
- Loading branch information
Showing
8 changed files
with
190 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
flow-server/src/main/resources/plugins/react-function-location-plugin/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"name": "react-function-location-plugin", | ||
"version": "1.0.0", | ||
"description": "A Vite plugin for gather development information about source location of React functions", | ||
"main": "react-function-location-plugin.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"author": "Vaadin Ltd", | ||
"license": "Apache-2.0", | ||
"files": [ | ||
"react-function-location-plugin.js" | ||
] | ||
} |
73 changes: 73 additions & 0 deletions
73
...c/main/resources/plugins/react-function-location-plugin/react-function-location-plugin.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import * as t from '@babel/types'; | ||
|
||
export function addFunctionComponentSourceLocationBabel() { | ||
function isReactFunctionName(name) { | ||
// A React component function always starts with a Capital letter | ||
return name && name.match(/^[A-Z].*/); | ||
} | ||
|
||
/** | ||
* Writes debug info as Name.__debugSourceDefine={...} after the given statement ("path"). | ||
* This is used to make the source location of the function (defined by the loc parameter) available in the browser in development mode. | ||
* The name __debugSourceDefine is prefixed by __ to mark this is not a public API. | ||
*/ | ||
function addDebugInfo(path, name, filename, loc) { | ||
const lineNumber = loc.start.line; | ||
const columnNumber = loc.start.column + 1; | ||
const debugSourceMember = t.memberExpression(t.identifier(name), t.identifier('__debugSourceDefine')); | ||
const debugSourceDefine = t.objectExpression([ | ||
t.objectProperty(t.identifier('fileName'), t.stringLiteral(filename)), | ||
t.objectProperty(t.identifier('lineNumber'), t.numericLiteral(lineNumber)), | ||
t.objectProperty(t.identifier('columnNumber'), t.numericLiteral(columnNumber)) | ||
]); | ||
const assignment = t.expressionStatement(t.assignmentExpression('=', debugSourceMember, debugSourceDefine)); | ||
const condition = t.binaryExpression( | ||
'===', | ||
t.unaryExpression('typeof', t.identifier(name)), | ||
t.stringLiteral('function') | ||
); | ||
const ifFunction = t.ifStatement(condition, t.blockStatement([assignment])); | ||
path.insertAfter(ifFunction); | ||
} | ||
|
||
return { | ||
visitor: { | ||
VariableDeclaration(path, state) { | ||
// Finds declarations such as | ||
// const Foo = () => <div/> | ||
// export const Bar = () => <span/> | ||
|
||
// and writes a Foo.__debugSourceDefine= {..} after it, referring to the start of the function body | ||
path.node.declarations.forEach((declaration) => { | ||
if (declaration.id.type !== 'Identifier') { | ||
return; | ||
} | ||
const name = declaration?.id?.name; | ||
if (!isReactFunctionName(name)) { | ||
return; | ||
} | ||
|
||
const filename = state.file.opts.filename; | ||
if (declaration?.init?.body?.loc) { | ||
addDebugInfo(path, name, filename, declaration.init.body.loc); | ||
} | ||
}); | ||
}, | ||
|
||
FunctionDeclaration(path, state) { | ||
// Finds declarations such as | ||
// functio Foo() { return <div/>; } | ||
// export function Bar() { return <span>Hello</span>;} | ||
|
||
// and writes a Foo.__debugSourceDefine= {..} after it, referring to the start of the function body | ||
const node = path.node; | ||
const name = node?.id?.name; | ||
if (!isReactFunctionName(name)) { | ||
return; | ||
} | ||
const filename = state.file.opts.filename; | ||
addDebugInfo(path, name, filename, node.body.loc); | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
flow-tests/test-frontend/vite-basics/frontend/ReactComponents.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
function MyComp() { | ||
return <div data-expected="1_19">A component defined in a function</div>; | ||
} | ||
export function MyCompExport() { | ||
return ( | ||
<> | ||
<div data-expected="4_34">A component defined in an exported function</div> | ||
</> | ||
); | ||
} | ||
const MyCompConst = () => <div data-expected="11_29">A component defined in a const</div>; | ||
export const MyCompConstExport = () => <div data-expected="12_42">A component defined in an exported const</div>; | ||
|
||
const NotAComp = undefined; | ||
|
||
export default function InProjectComponentView() { | ||
function Inner() { | ||
return <div data-expected="17_22">A component defined using a function inside another component</div>; | ||
} | ||
const InnerConst = () => <div data-expected="20_30">A component defined using a const inside another component</div>; | ||
|
||
return ( | ||
<div data-expected="16_52"> | ||
default | ||
<Inner></Inner> | ||
<InnerConst /> | ||
<MyComp /> | ||
<MyCompExport></MyCompExport> | ||
<MyCompConstExport></MyCompConstExport> | ||
<MyCompConst /> | ||
</div> | ||
); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { createBrowserRouter, RouteObject } from 'react-router-dom'; | ||
import { serverSideRoutes } from 'Frontend/generated/flow/Flow'; | ||
import ReactComponents from './ReactComponents'; | ||
|
||
export const routes = [ | ||
{ path: 'react-components', element: <ReactComponents/> }, | ||
...serverSideRoutes | ||
] as RouteObject[]; | ||
|
||
export default createBrowserRouter(routes); |
51 changes: 51 additions & 0 deletions
51
flow-tests/test-frontend/vite-basics/src/test/java/com/vaadin/viteapp/ReactComponentsIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.vaadin.viteapp; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
import com.vaadin.flow.testutil.ChromeBrowserTest; | ||
import com.vaadin.testbench.TestBenchElement; | ||
|
||
public class ReactComponentsIT extends ChromeBrowserTest { | ||
|
||
@Before | ||
public void openView() { | ||
getDriver().get(getTestURL()); | ||
waitForDevServer(); | ||
} | ||
|
||
@Override | ||
protected String getTestPath() { | ||
return "/react-components"; | ||
} | ||
|
||
@Test | ||
public void functionLocationsAvailable() { | ||
List<TestBenchElement> elements = $("*").hasAttribute("data-expected") | ||
.all(); | ||
Assert.assertTrue(elements.size() > 5); | ||
for (TestBenchElement element : elements) { | ||
String expected = element.getAttribute("data-expected"); | ||
Long line = Long.parseLong(expected.split("_")[0]); | ||
Long column = Long.parseLong(expected.split("_")[1]); | ||
String filenameEnd = "vite-basics/frontend/ReactComponents.tsx"; | ||
|
||
Map<String, Object> result = (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.__debugSourceDefine; | ||
""", | ||
element); | ||
|
||
Assert.assertTrue( | ||
result.get("fileName").toString().endsWith(filenameEnd)); | ||
Assert.assertSame(line, result.get("lineNumber")); | ||
Assert.assertSame(column, result.get("columnNumber")); | ||
} | ||
} | ||
} |