Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use a new RouterBuilder API for routes.tsx #19020

Merged
merged 36 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8a3dd4c
feat: Use a new RouterBuilder API for routes.tsx
mshabarov Mar 22, 2024
ad8f919
Merge branch 'main' into new-route-builder-api
mshabarov Mar 22, 2024
29de181
fix import
mshabarov Mar 22, 2024
624ca16
Merge remote-tracking branch 'origin/new-route-builder-api' into new-…
mshabarov Mar 22, 2024
af29624
fix import
mshabarov Mar 22, 2024
7d3ce09
fix tests
mshabarov Mar 22, 2024
9bd57e6
fix tests
mshabarov Mar 22, 2024
1fc450d
update routes.tsx in vite-basics
mshabarov Mar 25, 2024
8ce13f7
Merge branch 'main' into new-route-builder-api
mshabarov Mar 25, 2024
e74e612
address review comments
mshabarov Mar 25, 2024
8990d9f
Merge remote-tracking branch 'origin/new-route-builder-api' into new-…
mshabarov Mar 25, 2024
9b218f8
Merge branch 'main' into new-route-builder-api
mshabarov Mar 25, 2024
5ebec47
revert changes in BuildFrontendMojoTest
mshabarov Mar 26, 2024
cb37a19
Merge remote-tracking branch 'origin/new-route-builder-api' into new-…
mshabarov Mar 26, 2024
66f9916
Merge branch 'main' into new-route-builder-api
mshabarov Mar 26, 2024
4c7eaee
Merge remote-tracking branch 'origin/main' into new-route-builder-api
mshabarov Mar 28, 2024
f039652
Merge remote-tracking branch 'origin/new-route-builder-api' into new-…
mshabarov Mar 28, 2024
8cdd04b
Rename to withFallbackComponent, more docs for protect, extra pattern…
mshabarov Apr 2, 2024
1e2d831
Merge branch 'main' into new-route-builder-api
mshabarov Apr 2, 2024
50031dc
Simplifies missing route logic, more clarifications to routes.tsx tem…
mshabarov Apr 2, 2024
90d69db
Merge remote-tracking branch 'origin/new-route-builder-api' into new-…
mshabarov Apr 2, 2024
41afd50
Merge branch 'main' into new-route-builder-api
mshabarov Apr 2, 2024
760f5dd
Merge branch 'main' into new-route-builder-api
mshabarov Apr 2, 2024
fee3c8f
fix test
mshabarov Apr 2, 2024
956caf6
Merge remote-tracking branch 'origin/new-route-builder-api' into new-…
mshabarov Apr 2, 2024
a0f4f92
Merge branch 'main' into new-route-builder-api
mshabarov Apr 3, 2024
47cd498
adapt to a new changes in Hilla
mshabarov Apr 4, 2024
b455eec
Merge branch 'main' into new-route-builder-api
mshabarov Apr 5, 2024
14eede8
formatting
mshabarov Apr 5, 2024
08c75f4
Merge remote-tracking branch 'origin/new-route-builder-api' into new-…
mshabarov Apr 5, 2024
d5f6ab0
Merge branch 'main' into new-route-builder-api
mshabarov Apr 5, 2024
c45e157
Merge branch 'main' into new-route-builder-api
mshabarov Apr 8, 2024
1238e1c
changed import for router
mshabarov Apr 8, 2024
d4c4489
export router properly and update builder name
mshabarov Apr 9, 2024
b021791
fix routes config in test module
mshabarov Apr 9, 2024
b668bfd
fix vite-basics module
mshabarov Apr 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -528,16 +528,20 @@ public void mavenGoal_generateOpenApiJson_when_itIsInClientSideMode()
// Enable Hilla to generate openApi
FileUtils.fileWrite(new File(frontendDirectory, "routes.tsx"), "UTF-8",
"""
import { serverSideRoutes } from "Frontend/generated/flow/Flow";
export const routes = [
{
element: <MainLayout />,
handle: { title: 'Main' }
}
] as RouteObject[];


export const router = createBrowserRouter(...routes]);
import { RouterBuilder } from '@vaadin/hilla-file-router/runtime.js';
import { serverRoute } from 'Frontend/generated/flow/server-route';

const routerBuilder = new RouterBuilder()
.withReactRoutes([
{
element: <MainLayout />,
handle: { title: 'Main' }
}
])
.withServerFallback(serverRoute);

export const routes = routerBuilder.routes;
export default routerBuilder.build();
""");

Assert.assertFalse(
Expand All @@ -552,16 +556,21 @@ public void mavenGoal_generateTsFiles_when_enabled() throws Exception {
// Enable Hilla to generate ts files
FileUtils.fileWrite(new File(frontendDirectory, "routes.tsx"), "UTF-8",
"""
import { serverSideRoutes } from "Frontend/generated/flow/Flow";
export const routes = [
{
element: <MainLayout />,
handle: { title: 'Main' }
}
] as RouteObject[];


export const router = createBrowserRouter(...routes]);
import { RouterBuilder } from '@vaadin/hilla-file-router/runtime.js';
import { serverRoute } from 'Frontend/generated/flow/server-route';
import fileRoutes from 'Frontend/generated/file-routes';

const routerBuilder = new RouterBuilder()
.withReactRoutes([
{
element: <MainLayout />,
handle: { title: 'Main' }
}
])
.withServerFallback(serverRoute);

export const routes = routerBuilder.routes;
export default routerBuilder.build();
""");

File connectClientApi = new File(generatedTsFolder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,11 @@ public class FrontendUtils {

public static final String ROUTES_TSX = "routes.tsx";

public static final String ROUTES_FLOW_TSX = "routes-flow.tsx";

public static final String ROUTES_JS = "routes.js";

public static final String VIEWS_TS = "views.ts";
public static final String FILE_ROUTES_TSX = "file-routes.ts";

/**
* Default generated path for generated frontend files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,37 +54,25 @@ public class TaskGenerateReactFiles implements FallibleCommand {
public static final String CLASS_PACKAGE = "com/vaadin/flow/server/frontend/%s";
private Options options;
protected static String NO_IMPORT = """
Faulty configuration of serverSideRoutes.
Faulty configuration of server-side routes.
The server route definition is missing from the '%1$s' file

To have working Flow routes add the following to the '%1$s' file:
- import { buildRoute } from "Frontend/generated/flow/Flow";
- call buildRoute optionally with routes and position for server side routes as shown below:
- import { serverRoute } from 'Frontend/generated/flow/server-route';
- call 'withServerFallback' method of 'RouterBuilder' as shown below:

let routing = [
{
element: <MainLayout />,
handle: { title: 'Main' },
children: [
{ path: '/hilla', element: <HillaView />, handle: { title: 'Hilla' } }
],
},
] as RouteObject[];
export const routes = buildRoute(routing, routing[0].children);
OR
- import { serverSideRoutes } from "Frontend/generated/flow/Flow";
- route '...serverSideRoutes' into the routes definition as shown below:
const routerBuilder = new RouterBuilder()
.withServerFallback(serverRoute)
// .withFileRoutes() or .withReactRoutes()
// ...
export const routes = routerBuilder.routes;

OR

export const routes = [
{
element: <MainLayout />,
handle: { title: 'Main' },
children: [
{ path: '/', element: <HelloWorldView />, handle: { title: 'Hello World' } },
...serverSideRoutes
],
},
...serverRoute
] as RouteObject[];

""";
protected static String MISSING_ROUTES_EXPORT = """
Routes need to be exported as 'routes' for server navigation handling.
Expand All @@ -96,15 +84,22 @@ public class TaskGenerateReactFiles implements FallibleCommand {
private static final String FLOW_TSX = "Flow.tsx";
private static final String REACT_ADAPTER_TSX = "ReactAdapter.tsx";
static final String FLOW_FLOW_TSX = "flow/" + FLOW_TSX;

static final String FLOW_SERVER_ROUTE_TSX = "flow/server-route.tsx";
static final String FLOW_REACT_ADAPTER_TSX = "flow/" + REACT_ADAPTER_TSX;
private static final String ROUTES_JS_IMPORT_PATH_TOKEN = "%routesJsImportPath%";
static final String VIEWS_TS_FALLBACK = """
const routes = { path: "", module: undefined, children: [] };
export default routes;
static final String FILE_ROUTES_TS_FALLBACK = """
const fileRoutes = { path: "", module: undefined, children: [] };
export default fileRoutes;
""";

static final String SERVER_ROUTES_TS = """
import { serverSideRoutes } from "Frontend/generated/flow/Flow";
export const serverRoute = serverSideRoutes;
caalador marked this conversation as resolved.
Show resolved Hide resolved
""";

private static Pattern SERVER_ROUTE_PATTERN = Pattern.compile(
"import[\\s\\S]?\\{[\\s\\S]*(?:serverSideRoutes|buildRoute)+[\\s\\S]*\\}[\\s\\S]?from[\\s\\S]?(\"|'|`)Frontend\\/generated\\/flow\\/Flow(\\.js)?\\1;");
"import[\\s\\S]?\\{[\\s\\S]*serverRoute[\\s\\S]*\\}[\\s\\S]?from[\\s\\S]?(\"|'|`)Frontend\\/generated\\/flow\\/server-route(\\.js)?\\1;");

/**
* Create a task to generate <code>index.js</code> if necessary.
Expand All @@ -129,8 +124,10 @@ private void doExecute() throws ExecutionFailedException {
File frontendDirectory = options.getFrontendDirectory();
File frontendGeneratedFolder = options.getFrontendGeneratedFolder();
File flowTsx = new File(frontendGeneratedFolder, FLOW_FLOW_TSX);
File viewsTs = new File(frontendGeneratedFolder,
FrontendUtils.VIEWS_TS);
File fileRoutesTs = new File(frontendGeneratedFolder,
FrontendUtils.FILE_ROUTES_TSX);
File serverRouteTs = new File(frontendGeneratedFolder,
FLOW_SERVER_ROUTE_TSX);
File reactAdapterTsx = new File(frontendGeneratedFolder,
FLOW_REACT_ADAPTER_TSX);
File routesTsx = new File(frontendDirectory, FrontendUtils.ROUTES_TSX);
Expand All @@ -141,12 +138,18 @@ private void doExecute() throws ExecutionFailedException {
if (fileAvailable(REACT_ADAPTER_TSX)) {
writeFile(reactAdapterTsx, getFileContent(REACT_ADAPTER_TSX));
}
if (!viewsTs.exists()) {
writeFile(viewsTs, VIEWS_TS_FALLBACK);
if (!fileRoutesTs.exists()) {
writeFile(fileRoutesTs, FILE_ROUTES_TS_FALLBACK);
}
caalador marked this conversation as resolved.
Show resolved Hide resolved
if (!serverRouteTs.exists()) {
writeFile(serverRouteTs, SERVER_ROUTES_TS);
}
if (!routesTsx.exists()) {
boolean isHillaUsed = FrontendUtils.isHillaUsed(
frontendDirectory, options.getClassFinder());
writeFile(frontendGeneratedFolderRoutesTsx,
getFileContent(FrontendUtils.ROUTES_TSX));
getFileContent(isHillaUsed ? FrontendUtils.ROUTES_TSX
: FrontendUtils.ROUTES_FLOW_TSX));
} else {
String routesContent = FileUtils.readFileToString(routesTsx,
UTF_8);
Expand Down Expand Up @@ -209,38 +212,13 @@ private void cleanup() throws ExecutionFailedException {

private String getFlowTsxFileContent(boolean frontendRoutesTsExists)
throws IOException {
String content = getFileContent(FLOW_TSX).replace(
ROUTES_JS_IMPORT_PATH_TOKEN,
return getFileContent(FLOW_TSX).replace(ROUTES_JS_IMPORT_PATH_TOKEN,
(frontendRoutesTsExists)
? FrontendUtils.FRONTEND_FOLDER_ALIAS
+ FrontendUtils.ROUTES_JS
: FrontendUtils.FRONTEND_FOLDER_ALIAS
+ FrontendUtils.GENERATED
+ FrontendUtils.ROUTES_JS);
;
if (FrontendUtils.isHillaUsed(options.getFrontendDirectory(),
options.getClassFinder())) {
return content.replace("//%toReactRouterImport%",
"import { toReactRouter } from '@vaadin/hilla-file-router/runtime.js';")
.replace("//%viewsJsImport%",
"import views from 'Frontend/generated/views.js';")
.replace("//%buildRouteFunction%",
"""
if(!routes) {
// @ts-ignore
const route: RouteObject = toReactRouter(views);
if(route.children && route.children.length > 0) {
serverSidePosition = route.children;
if (route.element) {
routes = [route];
} else {
routes = route.children;
}
}
}
""");
}
return content;
}

private boolean fileAvailable(String fileName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@ import {
matchRoutes,
NavigateFunction,
useLocation,
useNavigate,
RouteObject
useNavigate
} from "react-router-dom";
import { routes } from "%routesJsImportPath%";
//%viewsJsImport%
//%toReactRouterImport%

const flow = new _Flow({
imports: () => import("Frontend/generated/flow/generated-flow-imports.js")
Expand Down Expand Up @@ -427,26 +424,3 @@ export const createWebComponent = (tag: string, props?: Properties, onload?: ()
}
return React.createElement(tag);
};

/**
* Build routes for the application. Combines server side routes and FS routes.
*
* @param routes optional routes are for adding own route definition, giving routes will skip FS routes
* @param serverSidePosition optional position where server routes should be put.
* If non given they go to the root of the routes [].
*
* @returns RouteObject[] with combined routes
*/
export const buildRoute = (routes?: RouteObject[], serverSidePosition?: RouteObject[]): RouteObject[] => {
let combinedRoutes = [] as RouteObject[];
//%buildRouteFunction%
if(serverSidePosition) {
serverSidePosition.push(...serverSideRoutes);
} else {
combinedRoutes.push(...serverSideRoutes);
}
if(routes) {
combinedRoutes.push(...routes);
}
return combinedRoutes;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/******************************************************************************
* This file is auto-generated by Vaadin.
* It configures React Router automatically by adding server-side (Flow) routes,
* which is enough for Vaadin Flow applications.
* Once any `.tsx` or `.jsx` React routes are added into
* `src/main/frontend/views/` directory, this route configuration is
* re-generated automatically by Vaadin.
******************************************************************************/
import { createBrowserRouter, RouteObject } from 'react-router-dom';
import { serverRoute } from 'Frontend/generated/flow/server-route';

export const routes = [
...serverRoute
] as RouteObject[];

export default createBrowserRouter(routes);
Original file line number Diff line number Diff line change
@@ -1,18 +1,56 @@
import { createBrowserRouter, RouteObject } from 'react-router-dom';
import { buildRoute } from "Frontend/generated/flow/Flow";
/******************************************************************************
* This file is auto-generated by Vaadin.
* It configures React Router automatically by looking for React views files,
* located in `src/main/frontend/views/` directory.
* A manual configuration can be done as well, you have to:
* - copy this file or create your own `routes.tsx` in your frontend directory,
* then modify this copied/created file. By default, the `routes.tsx` file
* should be in `src/main/frontend/` folder;
* - use `RouterBuilder` API to configure routes for the application;
* - restart the application, so that the imports get re-generated.
*
* `RouterBuilder` combines a File System-based route configuration or your
* explicit routes configuration with the server-side routes.
*
* It has the following methods:
* - `withFileRoutes` enables the File System-based routes autoconfiguration;
* - `withReactRoutes` adds manual explicit route hierarchy. Allows also to add
* an individual route, which then merged into File System-based routes,
* e.g. Log In view;
* - `withServerFallback` adds server-side routes automatically to either
* autoconfigured routes, or manually configured routes;
* - `protect` adds an authentication later to the routes.
*
tltv marked this conversation as resolved.
Show resolved Hide resolved
* NOTE:
* - You need to restart the dev-server after adding the new `routes.tsx` file.
* After that, all modifications to `routes.tsx` are recompiled automatically.
* - You may need to change a routes import in `index.tsx`, if `index.tsx`
* exists in the frontend folder (not in generated folder) and you copied the file,
* as the import isn't updated automatically by Vaadin in this case.
******************************************************************************/
import { RouterBuilder } from '@vaadin/hilla-file-router/runtime.js';
import { serverRoute } from 'Frontend/generated/flow/server-route';
import fileRoutes from 'Frontend/generated/file-routes';

export const routes = buildRoute();
const routerBuilder = new RouterBuilder()
.withFileRoutes(fileRoutes) // (1)
// To define routes manually, use the following code and remove (1):
// .withReactRoutes([
// {
// element: <MainLayout />,
// handle: { title: 'Main' },
// children: [
// { path: '/hilla', element: <HillaView />, handle: { title: 'Hilla' } }
// ],
// },
// ])
.withServerFallback(serverRoute)
// To add individual routes, use the following code:
// .withReactRoutes([
// { path: '/login', element: <Login />, handle: { title: 'Login' } },
// ])
.protect();

caalador marked this conversation as resolved.
Show resolved Hide resolved
// To define routes manually, use the following code as an example and remove the above code:
// let routing = [
// {
// element: <MainLayout />,
// handle: { title: 'Main' },
// children: [
// { path: '/hilla', element: <HillaView />, handle: { title: 'Hilla' } }
// ],
// },
// ] as RouteObject[];
// export const routes = buildRoute(routing, routing[0].children);
export const routes = routerBuilder.routes;

export default createBrowserRouter(routes);
export default routerBuilder.build();