router icon indicating copy to clipboard operation
router copied to clipboard

Slow TS performance

Open antonio-ivanovski opened this issue 1 year ago • 15 comments

Describe the bug

I have a fairly small project (it will grow), right now there are about ~20 pages, but I am already facing some performance issues after including the router package, mainly in the IntelliSense and auto-complete. After some investigations, I have narrowed down part of the issue to be that for every component I use navigate from useNavigate, I get a bad compilation time for that file.

I have identified this using --generateTrace flag for tsc, it points to every single one of these files to the particular line of when the navigate(...) method is being used.

npx @typescript/analyze-trace trace
Hot Spots
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/policiesaddedit/policiesaddedit.tsx (1050ms)
├─ Check file /users/me/dev/project/packages/web/ui/src/components/sidebar/sidebar.tsx (1001ms)
│  └─ Check deferred node from (line 70, char 9) to (line 82, char 16) (979ms)
│     └─ Check expression in /users/me/dev/project/applications/web/dashboard/src/router/index.tsx from (line 84, char 24) to (line 84, char 30) (651ms)
│        └─ Check expression from (line 71, char 16) to (line 77, char 3) (651ms)
│           └─ Check expression from (line 71, char 27) to (line 77, char 2) (649ms)
│              └─ Check expression from (line 72, char 5) to (line 72, char 14) (642ms)
│                 └─ Check expression from (line 32, char 19) to (line 64, char 3) (626ms)
│                    └─ Check expression from (line 32, char 41) to (line 64, char 2) (626ms)
│                       └─ Check expression from (line 34, char 5) to (line 63, char 7) (444ms)
│                          └─ Check expression from (line 34, char 36) to (line 63, char 6) (435ms)
│                             └─ Check expression from (line 35, char 9) to (line 62, char 11) (435ms)
│                                └─ Check expression from (line 35, char 37) to (line 62, char 10) (430ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/signup/index.tsx (814ms)
│  └─ Check deferred node from (line 54, char 9) to (line 223, char 16) (735ms)
│     └─ Check expression from (line 55, char 13) to (line 58, char 44) (732ms)
│        └─ Check expression from (line 57, char 22) to (line 57, char 46) (731ms)
│           └─ Check expression from (line 57, char 23) to (line 57, char 45) (731ms)
│              └─ Compare types 137362 and 137370 (731ms)
│                 └─ Check expression from (line 39, char 20) to (line 48, char 19) (731ms)
│                    └─ Check expression from (line 39, char 20) to (line 45, char 23) (730ms)
│                       └─ Check expression from (line 39, char 20) to (line 44, char 96) (730ms)
│                          └─ Check expression from (line 44, char 23) to (line 44, char 95) (730ms)
│                             └─ Check expression from (line 44, char 29) to (line 44, char 95) (730ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/login/index.tsx (795ms)
│  └─ Check deferred node from (line 57, char 9) to (line 124, char 16) (725ms)
│     └─ Check expression from (line 58, char 13) to (line 61, char 44) (719ms)
│        └─ Check expression from (line 60, char 22) to (line 60, char 46) (718ms)
│           └─ Check expression from (line 60, char 23) to (line 60, char 45) (718ms)
│              └─ Compare types 134742 and 134800 (717ms)
│                 └─ Check expression from (line 34, char 20) to (line 51, char 14) (717ms)
│                    └─ Check expression from (line 38, char 17) to (line 50, char 18) (716ms)
│                       └─ Check expression from (line 45, char 32) to (line 45, char 113) (716ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/apikeysedit/index.tsx (703ms)
│  └─ Check deferred node from (line 35, char 9) to (line 65, char 10) (553ms)
│     └─ Check expression from (line 41, char 20) to (line 64, char 15) (553ms)
│        └─ Check expression from (line 58, char 21) to (line 64, char 14) (537ms)
│           └─ Check expression from (line 59, char 24) to (line 63, char 19) (537ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/accountedit/index.tsx (615ms)
│  └─ Check deferred node from (line 33, char 32) to (line 43, char 6) (516ms)
│     └─ Check expression from (line 36, char 13) to (line 36, char 62) (515ms)
│        └─ Check expression from (line 36, char 19) to (line 36, char 62) (515ms)
└─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/resolversinstancesaddedit/resolversinstancesaddedit.tsx (513ms)
   └─ Check deferred node from (line 55, char 9) to (line 72, char 10) (377ms)
      └─ Check expression from (line 58, char 17) to (line 61, char 40) (373ms)
         └─ Check expression from (line 58, char 17) to (line 61, char 25) (373ms)
            └─ Check expression from (line 58, char 17) to (line 61, char 19) (373ms)

I am using the navigate like (example from SignUp):

export default function SignUpPage() {
    const navigate = useNavigate({ from: '/sign-up' });

    const onSubmit = useCallback(
        async (data: SignUpSchemaType) => {
            // auth code
                .then(() => navigate({ to: '/verify-account', search: { email: data.email } }))
        },
        [context.auth],
    );

From the example, on the line with navigate I get the hotspot from the report.

Is there anything I can do to help out the compiler for faster resolution? Maybe if there are some recommendations, it would be helpful to add on the docs site.

Your Example Website or App

/

Steps to Reproduce the Bug or Issue

  1. Import via useNavigate
  2. Use method navigate
  3. Slow TS compile

Expected behavior

Fast TS compile

Screenshots or Videos

No response

Platform

  • OS: MacOS 2019, Intel i9, 16GB RAM
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: 1.14.0

Additional context

I have may other instnaces where navigation is made via Link and those instances doesn't seem to cause slow compilation. Maybe because they are JSX?

antonio-ivanovski avatar Jan 27 '24 23:01 antonio-ivanovski

Are you using file-based routing? if no, can you try it out and also trace it in comparison?

schiller-manuel avatar Jan 27 '24 23:01 schiller-manuel

Are you using file-based routing? if no, can you try it out and also trace it in comparison?

Using the code based routes, primary because when I was starting the router implementation there was no docs recommendation for the file based one.

I can try it tomorrow. Does the file based one have better performance?

antonio-ivanovski avatar Jan 27 '24 23:01 antonio-ivanovski

Does the file based one have better performance?

It might since it might build the route tree differently. However the useNavigate typing is the same ... looking forward to your results!

schiller-manuel avatar Jan 28 '24 00:01 schiller-manuel

Are you using file-based routing? if no, can you try it out and also trace it in comparison?

Hi! We are using file based routers and the ts compiler completely times out for us. We have around 100 routes but suspect the issues occurs with far fewer routes. I can possibly send you some traces tomorrow.

pontusdacke avatar Jan 28 '24 14:01 pontusdacke

Are you using file-based routing? if no, can you try it out and also trace it in comparison?

Hi! We are using file based routers and the ts compiler completely times out for us. We have around 100 routes but suspect the issues occurs with far fewer routes. I can possibly send you some traces tomorrow.

How is the VS Code auto-complete experience for you? Is it slow to show suggestions on files that include any of the router components?

antonio-ivanovski avatar Jan 28 '24 14:01 antonio-ivanovski

Are you using file-based routing? if no, can you try it out and also trace it in comparison?

Hi! We are using file based routers and the ts compiler completely times out for us. We have around 100 routes but suspect the issues occurs with far fewer routes. I can possibly send you some traces tomorrow.

How is the VS Code auto-complete experience for you? Is it slow to show suggestions on files that include any of the router components?

It's really slow. 20-30s or sometimes no suggestions. A colleague of mine reports that files with no references to route components are quick/normal.

pontusdacke avatar Jan 28 '24 15:01 pontusdacke

Do you have some circular dependencies? e.g. do you use fooRoute.fullPath instead of the string literal /foo?

schiller-manuel avatar Jan 28 '24 18:01 schiller-manuel

In my case, Navigate component rerenders multiple times.

sohamnandi77 avatar Jan 28 '24 23:01 sohamnandi77

Do you have some circular dependencies? e.g. do you use fooRoute.fullPath instead of the string literal /foo?

In our case, we use from: fooRoute.fullPath in our navigates, i.e. const navigate = useNavigate({ from: Route.fullPath });. This only refers to file routes in the same file.

Our trace hotspots also point to navigates both the hook and the <Navigate> component, and also to isRoute() from useMatchRoute(). The problem seems to be linked to the to property.

pontusdacke avatar Jan 29 '24 10:01 pontusdacke

Are you using file-based routing? if no, can you try it out and also trace it in comparison?

I have tried to migrate to file-based routing, and the performance is even worse. For example for the file of policiesaddedit what used to be ~1000ms now is at ~2000ms

Here is the experience with InteliSense experience when the file doesn't have the Router interaction. As you can see, the suggestions are being shown as I type, immediately when i click the shortcut to show suggestions.

https://github.com/TanStack/router/assets/1628876/43456be9-229a-42f4-b381-ba46888c3cf1

Here is the experience when the component has Router interaction. Must note that this is very simple component that even here the experience is not that bad. In my policiesaddedit component it takes ~5s to create the suggestions.

https://github.com/TanStack/router/assets/1628876/da5640c7-6455-4c60-9fe8-3a6dbd938552


Here it is the hotspot log:

Hot Spots
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/policiesaddedit/policiesaddedit.tsx (2052ms)
│  ├─ Check deferred node from (line 95, char 9) to (line 111, char 10) (941ms)
│  │  └─ Check expression from (line 98, char 17) to (line 100, char 18) (940ms)
│  │     └─ Check expression from (line 98, char 17) to (line 98, char 109) (940ms)
│  │        └─ Check expression from (line 98, char 17) to (line 98, char 103) (939ms)
│  └─ Check variable declaration from (line 94, char 11) to (line 113, char 6) (883ms)
│     └─ Check expression from (line 94, char 22) to (line 113, char 6) (883ms)
│        └─ Check expression from (line 112, char 9) to (line 112, char 66) (883ms)
│           └─ Compare types 43842 and 53223 (881ms)
│              ├─ {"id":43842,"kind":"AnonymousType","location":{"path":"/users/me/dev/project/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@tanstack/react-router/dist/esm/usenavigate.d.ts","line":9,"char":5}}
│              └─ {"id":53223,"kind":"AnonymousFunction","location":{"path":"/users/me/dev/project/applications/web/dashboard/src/pages/policiesaddedit/policiesaddedit.tsx","line":64,"char":9}}
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/signup/index.tsx (1919ms)
│  └─ Check deferred node from (line 54, char 9) to (line 223, char 16) (1790ms)
│     └─ Check expression from (line 55, char 13) to (line 58, char 44) (1784ms)
│        └─ Check expression from (line 57, char 22) to (line 57, char 46) (1783ms)
│           └─ Check expression from (line 57, char 23) to (line 57, char 45) (1783ms)
│              └─ Compare types 33165 and 33208 (1783ms)
│                 └─ Check expression from (line 39, char 20) to (line 48, char 19) (1782ms)
│                    └─ Check expression from (line 39, char 20) to (line 45, char 23) (1782ms)
│                       └─ Check expression from (line 39, char 20) to (line 44, char 96) (1782ms)
│                          └─ Check expression from (line 44, char 23) to (line 44, char 95) (1780ms)
│                             └─ Check expression from (line 44, char 29) to (line 44, char 95) (1780ms)
│                                └─ Check expression from (line 44, char 38) to (line 44, char 94) (574ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/login/index.tsx (1897ms)
│  └─ Check deferred node from (line 60, char 9) to (line 127, char 16) (1766ms)
│     └─ Check expression from (line 61, char 13) to (line 64, char 44) (1762ms)
│        └─ Check expression from (line 63, char 22) to (line 63, char 46) (1762ms)
│           └─ Check expression from (line 63, char 23) to (line 63, char 45) (1762ms)
│              └─ Compare types 36665 and 33208 (1762ms)
│                 └─ Check expression from (line 37, char 20) to (line 54, char 14) (1761ms)
│                    └─ Check expression from (line 41, char 17) to (line 53, char 18) (1760ms)
│                       └─ Check expression from (line 48, char 32) to (line 48, char 113) (1760ms)
│                          └─ Check expression from (line 48, char 41) to (line 48, char 112) (575ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/routes/__root.tsx (1572ms)
│  └─ Check variable declaration from (line 42, char 11) to (line 49, char 7) (1226ms)
│     └─ Check expression from (line 42, char 29) to (line 49, char 7) (1225ms)
│        └─ Check expression in /users/me/dev/project/applications/web/dashboard/src/router.ts from (line 22, char 24) to (line 22, char 30) (765ms)
│           └─ Check expression from (line 11, char 16) to (line 17, char 3) (764ms)
│              └─ Check expression from (line 11, char 29) to (line 17, char 2) (763ms)
│                 └─ Check expression from (line 12, char 5) to (line 12, char 14) (762ms)
│                    └─ Check expression in /users/me/dev/project/applications/web/dashboard/src/routetree.gen.ts from (line 264, char 26) to (line 297, char 3) (759ms)
│                       └─ Check expression from (line 264, char 48) to (line 297, char 2) (759ms)
│                          └─ Check expression from (line 265, char 3) to (line 290, char 5) (716ms)
│                             └─ Check expression from (line 265, char 25) to (line 290, char 4) (545ms)
│                                └─ Check expression from (line 266, char 5) to (line 289, char 7) (545ms)
│                                   └─ Check expression from (line 266, char 37) to (line 289, char 6) (539ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/resolversinstancesaddedit/resolversinstancesaddedit.tsx (1552ms)
│  └─ Check deferred node from (line 55, char 9) to (line 72, char 10) (1178ms)
│     └─ Check expression from (line 58, char 17) to (line 61, char 40) (1176ms)
│        └─ Check expression from (line 58, char 17) to (line 61, char 25) (1176ms)
│           └─ Check expression from (line 58, char 17) to (line 61, char 19) (1176ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/accountedit/index.tsx (1224ms)
│  └─ Check deferred node from (line 33, char 32) to (line 43, char 6) (1119ms)
│     └─ Check expression from (line 36, char 13) to (line 36, char 62) (1119ms)
│        └─ Check expression from (line 36, char 19) to (line 36, char 62) (1118ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/policiesdetails/policiesdetails.tsx (966ms)
│  └─ Check deferred node from (line 37, char 31) to (line 37, char 85) (845ms)
│     └─ Check expression from (line 37, char 37) to (line 37, char 85) (845ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/apikeysdetails/apikeysdetails.tsx (963ms)
│  └─ Check deferred node from (line 38, char 31) to (line 38, char 85) (854ms)
│     └─ Check expression from (line 38, char 37) to (line 38, char 85) (854ms)
└─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/apikeysedit/index.tsx (901ms)
   └─ Check deferred node from (line 35, char 9) to (line 65, char 10) (776ms)
      └─ Check expression from (line 41, char 20) to (line 64, char 15) (776ms)
         └─ Check expression from (line 58, char 21) to (line 64, char 14) (774ms)
            └─ Check expression from (line 59, char 24) to (line 63, char 19) (774ms)

Taking as example policiesaddedit, here are the lines that are mentioned:

const onSubmit = useCallback(
        async (data: PolicyFormData) => {
            try {
                const res = props.type === 'add' ? await handleAddSubmit(data) : await handleEditSubmit(data);
// Line 98 bellow with `navigate`
                navigate({ to: '/iam/policies/$policyId', params: { policyId: res.policy.policyId } }).catch(
                    console.error,
                );
            } catch (e) {
                if (e instanceof Error) {
                    if (e.cause instanceof ZodError) {
                        form.setError('root', { message: fromZodError(e.cause).toString() });
                    } else {
                        form.setError('root', { message: e.message });
                    }
                }
                console.error('error creating policy', e);
            }
        },
// Line 112 bellow with the `navigate`
        [props.type, navigate, handleAddSubmit, handleEditSubmit],
    );

I do not have any circular dependencies between the code, route definitions, and generated route tree.

Here is gist with my generated route tree: https://gist.github.com/toteto/e7d7c1fee9f9a4bda315170758c29bd5

antonio-ivanovski avatar Jan 30 '24 13:01 antonio-ivanovski

For what it's worth, I was having extreme slowness on TS 4.8, updating to TS 5.1 has drastically sped up type checking.

Silverwolf90 avatar Feb 02 '24 14:02 Silverwolf90

For what it's worth, I was having extreme slowness on TS 4.8, updating to TS 5.1 has drastically sped up type checking.

Currently on 5.2.2 myself, tried with the latest 5.3 but there is no performance improvement.

antonio-ivanovski avatar Feb 02 '24 14:02 antonio-ivanovski

For what it's worth, I was having extreme slowness on TS 4.8, updating to TS 5.1 has drastically sped up type checking.

Currently on 5.2.2 myself, tried with the latest 5.3 but there is no performance improvement.

We only used 5.3.2 and still had troubles unfortunately.

pontusdacke avatar Feb 05 '24 15:02 pontusdacke

Got a smaller performance hit (faster performance compared to previously) by separating the route tree building into separate files

// index.tsx
import rootRoute, { type DashboardAppContext } from './rootRoute';
import { authRoutes } from './authentication';
import { authenticatedRoutes } from './authenticated';
import termsRoute from './termsRoute';

const routeTree = rootRoute.addChildren([authRoutes, authenticatedRoutes, termsRoute]);

export const router = createRouter({
    routeTree,
    // ....
// authentication/index.ts
export const authRoutes = authLayoutRoute.addChildren([
    loginRoute,
    signUpRoute,
    forgotPasswordRoute,
    verifyAccountRoute,
    renewPasswordRoute,
]);

// same for authenticatedRoutes 

But still, some hotspots are >1000ms

antonio-ivanovski avatar Feb 06 '24 20:02 antonio-ivanovski

Optimizations in this area are very welcome, so please, if you find something out, let us know. Even if those changes would be breaking, we might consider them for v2 if the TS compiler performance can be improved significantly.

schiller-manuel avatar Feb 06 '24 21:02 schiller-manuel

I was looking into using Link with a large number of routes. I wonder if this is the same issue I noticed.

Regarding what I looked into when tracing. I found possibly something interesting:

RelativeToPathAutoComplete has SplitPaths which splits all paths regardless as a defaulted type parameter. If you make SplitPaths inline so it's only evaluated when it's needed and then when the 'from' prop is not specified (TFrom is defaulted to string) then return all paths without splitting all the paths. This skips the crazy splitting of a large union of paths. With these changes I noticed performance improvements when not using the 'from' prop

I tried this with 600 odd routes and check time went from 12.69s (with excessively deep instantiation errors) to 2.49s (with no errors)

useNavigation is more complicated. It's defaulting TFrom to all paths and this is harder to check for (not sure why it's defaulted this way?). TS seems to checking a large union here as well. I was able to get similar performance as Link if TFrom is defaulted to string and some other odd changes

This doesn't fix performance issues when the 'from' prop is specified. There might be a better way to work out the relative paths. But that requires more work and thinking how to approach it. I would maybe try to avoid parsing of huge unions and try to narrow it down

I usually try not to iterate over large unions unnecessarily and minimise checking against them.

I'm happy to contribute if you think it's worth speeding up the non-relative path use case for now

chorobin avatar Feb 19 '24 17:02 chorobin

sounds super interesting, can you please open a PR so I can have a look?

schiller-manuel avatar Feb 19 '24 17:02 schiller-manuel

I raised a PR. Looking into further improvements but this is a good start I think

https://github.com/TanStack/router/pull/1202

chorobin avatar Feb 21 '24 19:02 chorobin

This is amazing @chorobin! Just tried it and there is a huge performance gain.

antonio-ivanovski avatar Feb 26 '24 10:02 antonio-ivanovski

@toteto Glad it helped with your situation. Out of interest, what kind of gains are you seeing?

chorobin avatar Feb 26 '24 16:02 chorobin

@schiller-manuel I have some improvements for relative paths as this is still using Split and slower than it could be. Should I raise another issue for this? At least relative pathing should be faster.

chorobin avatar Feb 26 '24 16:02 chorobin

definitely, please create a PR I noticed that relative paths did not have autocomplete working (already broken before your last PR) so if this works again, it would be great

schiller-manuel avatar Feb 26 '24 16:02 schiller-manuel

Cool. Yeah. I noticed that. It will work. It might be worth getting tests around these types to stop those sorts of regressions

chorobin avatar Feb 26 '24 16:02 chorobin

tests would also be cool 😀

schiller-manuel avatar Feb 26 '24 17:02 schiller-manuel

@toteto Glad it helped with your situation. Out of interest, what kind of gains are you seeing?

Would say it cuts down the auto complete and suggests times by at least half. Used to be ~6s now it is sub 2s. Also I have a PR lined up that updates Zodios to v11-beta that also has magnificent performance improvement... So it could be said that this Monday I felt like I got a brand new supercomputer 😅

@schiller-manuel noticed that all the tests are commented out. Any pointers for an effort to bring some of them up?

antonio-ivanovski avatar Feb 26 '24 23:02 antonio-ivanovski