diff --git a/.changeset/red-badgers-marry.md b/.changeset/red-badgers-marry.md new file mode 100644 index 000000000..002295137 --- /dev/null +++ b/.changeset/red-badgers-marry.md @@ -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. diff --git a/docs/upgrading/migrate-to-firefly-v9.0.md b/docs/upgrading/migrate-to-firefly-v9.0.md index c8d024c87..21502a76f 100644 --- a/docs/upgrading/migrate-to-firefly-v9.0.md +++ b/docs/upgrading/migrate-to-firefly-v9.0.md @@ -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**. @@ -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 + + ... + +``` + +If the application uses the [usePublicDataQueries](../reference/tanstack-query/usePublicDataQueries.md), add the `waitForPublicData` property to the `AppRouter` component: + +```tsx + + ... + +``` + +If the application uses the [useProtectedDataQueries](../reference/tanstack-query/useProtectedDataQueries.md), add the `waitForProtectedData` property to the `AppRouter` component: + +```tsx + + ... + +``` + +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. diff --git a/packages/react-router/src/reactRouterRuntime.ts b/packages/react-router/src/reactRouterRuntime.ts index a087763b5..8edffa8aa 100644 --- a/packages/react-router/src/reactRouterRuntime.ts +++ b/packages/react-router/src/reactRouterRuntime.ts @@ -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"; @@ -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 { readonly #routeRegistry = new RouteRegistry(); readonly #navigationItemRegistry = new NavigationItemRegistry(); @@ -161,22 +179,26 @@ export class ReactRouterRuntime extends Runtime { 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") { @@ -197,20 +219,20 @@ export class ReactRouterRuntime extends Runtime { 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") {