Skip to content

Commit

Permalink
feat: Improve logging when pending routes are detected (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
patricklafrance authored Jan 17, 2025
1 parent ca10074 commit 84cf5d0
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/red-badgers-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@squide/react-router": patch
---

Improve logging when pending routes are detected. The registered routes are now logged, as well as the pending routes.
36 changes: 36 additions & 0 deletions docs/upgrading/migrate-to-firefly-v9.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ label: Migrate to firefly v9.0

# Migrate to firefly v9.0

!!!warning
Although this migration guide is labeled for version `9.0.0`, it is actually intended for migrating from version `8.*` to version `9.2.1`. Therefore, please ensure you upgrade to `@squide/firefly` version `9.2.1` instead of `9.0.0`.

We apologize for the confusion.
!!!

This major version of `@squide/firefly` introduces [TanStack Query](https://tanstack.com/query/latest) as the official library for fetching the global data of a Squide's application and features a complete rewrite of the [AppRouter](../reference/routing/appRouter.md) component, which now uses a state machine to manage the application's bootstrapping flow.

Prior to `v9.0`, Squide applications couldn't use TanStack Query to fetch global data, making it **challenging** for Workleap's applications to **keep** their **global data** in **sync** with the **server state**. With `v9.0`, applications can now leverage [custom wrappers](../guides/fetch-global-data.md) of the TanStack Query's [useQueries](https://tanstack.com/query/latest/docs/framework/react/reference/useQueries) hook to fetch and keep their global data up-to-date with the server state. Additionally, the new [deferred registrations update](../reference/registration/useDeferredRegistrations.md#register-or-update-deferred-registrations) feature allows applications to even **keep** their conditional **navigation items in sync** with the **server state**.
Expand Down Expand Up @@ -321,6 +327,36 @@ The `v9.0` release introduces several breaking changes affecting the host applic
9. Convert all deferred routes into static routes. [View example](#removed-support-for-deferred-routes)
10. Add an `$id` option to the navigation item registrations. [View example](#new-id-option-for-navigation-items)

### `waitForMsw`, `waitForPublicData`, `waitForProtectedData`

The `AppRouter` component accepts the `waitForMsw`, `waitForPublicData`, and `waitForProtectedData` properties. These properties are forwarded directly to the Squide bootstrapping flow state machine, where they are used to determine its initial state.

If the application register MSW [request handlers](https://mswjs.io/docs/concepts/request-handler/) with the [runtime.registerRequestHandlers](../reference/runtime/runtime-class.md#register-request-handlers) function, add the `waitForMsw` property to the `AppRouter` component:

```tsx
<AppRouter waitForMsw>
...
</AppRouter>
```

If the application uses the [usePublicDataQueries](../reference/tanstack-query/usePublicDataQueries.md), add the `waitForPublicData` property to the `AppRouter` component:

```tsx
<AppRouter waitForPublicData>
...
</AppRouter>
```

If the application uses the [useProtectedDataQueries](../reference/tanstack-query/useProtectedDataQueries.md), add the `waitForProtectedData` property to the `AppRouter` component:

```tsx
<AppRouter waitForProtectedData>
...
</AppRouter>
```

Otherwise, don't define any of those three properties on the `AppRouter` component.

### Root error boundary

When transitioning to the new `AppRouter` component, make sure to nest the `RootErrorBoundary` component within the `AppRouter` component's render function.
Expand Down
40 changes: 31 additions & 9 deletions packages/react-router/src/reactRouterRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { NavigationItemDeferredRegistrationScope, NavigationItemDeferredRegistra
import { ProtectedRoutesOutletId, PublicRoutesOutletId } from "./outlets.ts";
import { RouteRegistry, type Route } from "./routeRegistry.ts";

function indent(text: string, depth: number) {
return `${" ".repeat(depth * 4)}${text}`;
}

function translateOutletsParentId(parentId?: string) {
if (parentId === PublicRoutesOutletId) {
return "PublicRoutes";
Expand All @@ -15,6 +19,20 @@ function translateOutletsParentId(parentId?: string) {
return parentId;
}

function logRoutesTree(routes: Route[], depth: number = 0) {
let log = "";

routes.forEach(x => {
log += indent(`- ${x.path ?? x.$id ?? (x.index ? "(index route)" : undefined) ?? "(no identifier)"}\r\n`, depth);

if (x.children) {
log += logRoutesTree(x.children, depth + 1);
}
});

return log;
}

export class ReactRouterRuntime extends Runtime<Route, RootNavigationItem> {
readonly #routeRegistry = new RouteRegistry();
readonly #navigationItemRegistry = new NavigationItemRegistry();
Expand Down Expand Up @@ -161,22 +179,26 @@ export class ReactRouterRuntime extends Runtime<Route, RootNavigationItem> {
let message = `[squide] ${pendingRoutes.length} route${pendingRoutes.length !== 1 ? "s were" : " is"} expected to be registered but ${pendingRoutes.length !== 1 ? "are" : "is"} missing:\r\n\r\n`;

pendingRoutes.forEach((x, index) => {
message += `${index}/${pendingRoutes.length} Missing route with the following path or name: "${x}"\r\n`;
message += " Pending registrations:\r\n";
message += `${index + 1}/${pendingRoutes.length} Missing route with the following path or id: "${x}"\r\n`;
message += indent("Pending registrations:\r\n", 1);

const pendingRegistrationsForRoute = pendingRegistrations.getPendingRegistrationsForRoute(x);

pendingRegistrationsForRoute.forEach(y => {
message += ` - "${y.path ?? y.$id ?? "(no identifier)"}"\r\n`;
message += indent(`- "${y.path ?? y.$id ?? "(no identifier)"}"\r\n`, 2);
});

message += "\r\n";
});

message += "Registered routes:\r\n";
message += logRoutesTree(this.#routeRegistry.routes, 1);
message += "\r\n";

message += `If you are certain that the route${pendingRoutes.length !== 1 ? "s" : ""} has been registered, make sure that the following conditions are met:\r\n`;
message += "- The missing routes \"path\" or \"$id\" option perfectly match the provided \"parentPath\" or \"parentId\" (make sure that there's no leading or trailing \"/\" that differs).\r\n";
message += "- The missing routes has been registered with the runtime.registerRoute function. A route cannot be registered under a parent route that has not be registered with the runtime.registerRoute function.\r\n";
message += "For more information about nested routes, refers to https://gsoft-inc.github.io/wl-squide/reference/runtime/runtime-class/#register-nested-routes-under-an-existing-route.\r\n";
message += "- The missing routes has been registered with the runtime.registerRoute function. A route cannot be registered under a parent route that has not be registered with the runtime.registerRoute function.\r\n\r\n";
message += "For more information about nested routes, refers to https://gsoft-inc.github.io/wl-squide/reference/runtime/runtime-class/#register-nested-routes-under-an-existing-route.\r\n\r\n";
message += "For more information about the PublicRoutes and ProtectedRoutes outlets, refers to https://gsoft-inc.github.io/wl-squide/reference/#routing.";

if (this._mode === "development") {
Expand All @@ -197,20 +219,20 @@ export class ReactRouterRuntime extends Runtime<Route, RootNavigationItem> {
pendingSectionIds.forEach((x, index) => {
const [menuId, sectionId] = parseSectionIndexKey(x);

message += `${index}/${pendingSectionIds.length} Missing navigation section "${sectionId}" of the "${menuId}" menu.\r\n`;
message += " Pending registrations:\r\n";
message += `${index + 1}/${pendingSectionIds.length} Missing navigation section "${sectionId}" of the "${menuId}" menu.\r\n`;
message += indent("Pending registrations:\r\n", 1);

const pendingItems = pendingRegistrations.getPendingRegistrationsForSection(x);

pendingItems.forEach(y => {
message += ` - "${y.item.$id ?? y.item.$label ?? y.item.to ?? "(no identifier)"}"\r\n`;
message += indent(`- "${y.item.$id ?? y.item.$label ?? y.item.to ?? "(no identifier)"}"\r\n`, 2);
});

message += "\r\n";
});

message += `If you are certain that the navigation section${pendingSectionIds.length !== 1 ? "s" : ""} has been registered, make sure that the following conditions are met:\r\n`;
message += "- The missing navigation section \"$id\" and \"menuId\" properties perfectly match the provided \"sectionId\" and \"menuId\".\r\n";
message += "- The missing navigation section \"$id\" and \"menuId\" properties perfectly match the provided \"sectionId\" and \"menuId\".\r\n\r\n";
message += "For more information about nested navigation items, refers to: https://gsoft-inc.github.io/wl-squide/reference/runtime/runtime-class/#register-nested-navigation-items.\r\n";

if (this._mode === "development") {
Expand Down

0 comments on commit 84cf5d0

Please sign in to comment.