microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.2k stars 12.51k forks source link

Slow editing in React Native codebase #37763

Closed DanielRosenwasser closed 3 months ago

DanielRosenwasser commented 4 years ago

Hello TS Team!

I appreciate your efforts to improve typescript performance! I'm very happy using TS, but as my project (react native) grew performance started to fall so badly, that it's the biggest factor impacting my productivity.

I have to reset TS server ~every 30 minutes (I'm using VS Code), quite frequently more often than that. Autocomplete with auto-import often works in a weird way - it properly suggests a variable name, but when I use it - it's not importing needed module. Sometimes I have to CMD+Space to see import suggestions, but sometimes it does not work and I have to manually write imports. Often autosuggestions start to work so slowly (3-10s) that it becomes unusable.

I'd like to help you by providing some feedback, but I actually don't know how to collect needed data. I've tried to enable verbose logs, but I'm getting a huge amount of logs (often more than 50mb) which includes so much noise, that I actually have no idea what is going on.

It would be very useful to have some sort of guide related to actually detecting and narrowing down issues with the ts server. It would be awesome to have some 'smart' diagnostics tool that would be able to tell something like 'this module types took 80% of time needed for suggestions collection caused by your last CMD+space'

I'm open to having some sessions with you trying to reproduce the issue. I don't have any specific scenario. Sometimes it works just fine for 2 hours after restarting TS server and sometimes I have to restart it every 10 minutes so I'm worried I'll waste your time if I'll not be able to reproduce it. If you're interested, however - I'd be more than happy to help as much as I can.

Screen Recording 2020-04-02 at 10.44.43 PM.mov.zip

I'm sending screen recording with issue - when I'm typing - it suggest proper variable name I can import, but when I use it - it's not actually imported. I have to CMD+Space at the end of it again to make VS Code suggest me to import given variable

Originally posted by @pie6k in https://github.com/microsoft/TypeScript/issues/33118#issuecomment-608081358

DanielRosenwasser commented 4 years ago

@amcasey do you have that version of the compiler that can print out how many types a given statement is generating?

amcasey commented 4 years ago

@DanielRosenwasser I'm planning to create a PR for an productized version of this, but the rough version is here: https://github.com/microsoft/TypeScript/pull/37417

It dumps a csv to stdout.

pie6k commented 4 years ago

Thank you guys for posting my issue here. I'm sorry I've originally posted it in the roadmap.

The issue I have might be related to how my codebase is organized or how big it is (it has 890 ts/tsx files).

Let me know if I can provide any additional input / prepare some logs (if so what exactly would be useful for you and how can I collect it). I'd prefer to not give you access to the full repo as it's a private project.

I'm also opened to have some session with you showing anything you'd want to see about it.

Some insights:

It works like I have ui/buttons folder and inside this I've got bunch of other folders eg PrimaryButton.tsx, CircleButton.tsx etc. Often I've got 10-20 files like this

Then in ui/buttons/index.ts I re-export all of them like export * from './PrimaryButton' so I can import in later anywhere like import { PrimaryButton } from '~app/ui/buttons'

export function createAsyncStorage<T>(storageKey: string) {
  // here I've got get/set methods that will cast JSON in storage to T type
  return {
    get() {
      return asyncStorage.get() as T;
    }
  }
}

// then in the app in many places I do something like

export const someStorage = createAsyncStorage<{foo: string, bar: number}>('baz');

// than in other place in the app I import it and use typed version
someStorage.get().bar // typed as number properly
amcasey commented 4 years ago

@pie6k Thanks for the details! We have a few avenues we can pursue for figuring out what's taking so long.

1) It's pretty helpful to have the output with --extendedDiagnostics 2) You can generate a CPU profile with --generateCpuProfile - it shouldn't contain any of your code or paths, but it's a json file, so feel free to double check 3) The PR I linked above has a private build that dumps a CSV to stdout with the cost of each statement - you can sort it in your spreadsheet program of choice to figure out if there's a section that's unexpectedly expensive (and, ideally, extract that bit into a smaller repro program for us)

If you normally use an incremental build, please be sure to clean before gathering any of the above.

pie6k commented 4 years ago

I've managed to get some error info

I was trying to get auto-import suggestions for LayoutAnimation which is part of react-native.d.ts, but it was not working around 5 times when I pressed 'CMD + Space', then I've mineralized VS Code and tried again and it worked, I've opened logs and I've got this

Info 2745 [14:10:13.943] getCompletionsAtPosition: isCompletionListBlocker: 0
Info 2746 [14:10:13.949] getSymbolsFromOtherSourceFileExports: Using cached list
Info 2747 [14:10:13.955] getCompletionData: Semantic work: 12

Err 2768  [14:10:16.395] Exception on executing command {"seq":2076,"type":"request","command":"completionEntryDetails","arguments":{"file":"<MY_PROJECT_ROOT>/src/ui/interactions/EditableList/index.tsx","line":60,"offset":18,"entryNames":[{"name":"LayoutAnimation","source":"<MY_PROJECT_ROOT>/node_modules/@types/react-native/index"}]}}:

    Debug Failure. False expression: Some exportInfo should match the specified moduleSymbol

    Error: Debug Failure. False expression: Some exportInfo should match the specified moduleSymbol
        at getImportFixForSymbol (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:128841:22)
        at Object.getImportCompletionAction (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:128836:23)
        at getCompletionEntryCodeActionsAndSourceDisplay (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:111727:33)
        at Object.getCompletionEntryDetails (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:111691:30)
        at Proxy.getCompletionEntryDetails (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:137830:35)
        at <MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:147930:57
        at Object.mapDefined (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:562:30)
        at IOSession.Session.getCompletionEntryDetails (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:147928:33)
        at Session.handlers.ts.createMapFromTemplate._a.<computed> (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:146850:61)
        at <MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:148476:88
        at IOSession.Session.executeWithRequestId (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:148467:28)
        at IOSession.Session.executeCommand (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:148476:33)
        at IOSession.Session.onMessage (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:148500:35)
        at Interface.<anonymous> (<MY_PROJECT_ROOT>/node_modules/typescript/lib/tsserver.js:149816:27)
        at Interface.emit (events.js:203:13)
        at Interface._onLine (readline.js:316:10)
        at Interface._normalWrite (readline.js:461:12)
        at Socket.ondata (readline.js:172:10)
        at Socket.emit (events.js:203:13)
        at addChunk (_stream_readable.js:295:12)
        at readableAddChunk (_stream_readable.js:276:11)
        at Socket.Readable.push (_stream_readable.js:210:10)
        at Pipe.onStreamRead (internal/stream_base_commons.js:166:17)
pie6k commented 4 years ago

About the flags you've mentioned @amcasey - I'm not compiling the code myself as React-Native uses a metro compiler. So I don't have direct control about starting ts-server probably.

Can I include those flags to the VS Code typescript engine somehow? (I've tried to enable it in my vs code settings but it seems there is no such option).

So in general - I never compile TS 'myself'. My issues are solely related to editing experience in my ide typescript engine (VS Code). I'm not sure where can I enable those flags eg. extendedDiagnostics in such case

Running TSC on my files (with noEmit) gives

Files:                        1353
Lines:                      292940
Nodes:                      876492
Identifiers:                294654
Symbols:                    348688
Types:                       90647
Instantiations:             845537
Memory used:               391677K
Assignability cache size:    30362
Identity cache size:          2670
Subtype cache size:           9264
Strict subtype cache size:     985
I/O Read time:               0.12s
Parse time:                  1.25s
ResolveTypeReference time:   0.02s
ResolveModule time:          0.88s
Program time:                2.42s
Bind time:                   0.78s
Check time:                  4.99s
Total time:                  8.19s

I'm attaching CPU profile for tsc command

cpu_profile.json.zip

I've also run CSV tool, it's hard for me to have some solid insights from it - I've got 10280 rows, but only around ~~200 of first ones have deltas bigger than 100

image

I'm not sure if there is some way that would help me analyze those stats to eg. divide operations by category or detect some operation that is repeating all the time.

Number one element is this:

const Animator = styled(Animated.View)`
  z-index: 2;
  flex-grow: 1;
`;

where seems like Animated.View is from react-native and is dynamically computing all the props (converting all regular styles like height: number to animated values like height: Animated.Value (not sure but I guess it happens looking briefly on definitions). Not sure how often it happens in my codebase in total, but for sure those props are rarely used by me (as views have probably 200+ properties related to styles only, but I'm using only 2-3 of them - not sure if it's possible to make it somehow computed in a lazy way eg. only get the list of properties, but compile type of interface properties only if it's actually used ie. to validate some value or to provide a type of selected autocomplete property)

eg. (note I'm totally guessing here)

If I write

function foo() {
  return <Animated.View style={{
    height: new Animated.Value(100),
  }} />
}

I actually don't need to have all types of possible style properties compiled by TS, I only need to validate that height value is matching interface and that I'm not missing any required property (part of the interface without ? after property name)

Same with autosuggestions - if I want to add another style property - the first thing that is interesting for me is the name of this property - eg. opacity. If I type 'opa' - I see 'opacity' in the dropdown and this is the moment when the type of this property starts to be meaningful for me. I also don't really care about types of other (even auto-suggested) interface properties until I highlight them.

Again, this is just guessing - I have no idea how it actually works under the hood.

amcasey commented 4 years ago

Thanks for the call stack - that's https://github.com/microsoft/TypeScript/issues/32853 (which is woefully out of date - we're getting a lot more hits now). I don't suppose you can share a code fragment that reproduces the issue? We've had trouble figuring out how it could happen.

Regarding switches, I'm sorry for being unclear - all of my suggestions were for command line builds because that was top-of-mind from my recent investigations. In any case, command line build stats tend to tell us useful things about the project that apply to the editor as well.

I'm still looking at the perf data you've supplied - I'll reply to that separately.

amcasey commented 4 years ago

FWIW, I suspect your auto-complete woes are separate from any perf problems you're seeing (except insofar as one library may prove to trigger both). What other slowness are you seeing? Completions? Go-to-definition? Spinning "Initializing Project" in the status bar?

amcasey commented 4 years ago

The first row in the CSV is basically guaranteed to be large, because it'll cause a bunch of library types to be created. interactions/drag/index.tsx wasn't first before you sorted, was it?

Other than that, we're looking for a few rows with big deltas and a lot of rows with little deltas. As you've suggested, probably only the top five are interesting. In this case, it sounds like they'll all point at TSX elements, probably ones using StyledComponents and/or React Native. I think you might already be using TypeScript@next, but you may want to try refreshing to pick up this change: https://github.com/microsoft/TypeScript/pull/37749.

There's already laziness around instantiating mapped types (frequently used in StyledComponents), but I can look into being even lazier. My guess is that structural type comparisons will almost immediately require the full type to be instantiated.

Still looking at the profile...

amcasey commented 4 years ago

I don't see anything in the CPU profile that jumps out as different from a normal StyledComponents slowdown - if anything, less time is spent in JSX overload resolution than I would expect.

amcasey commented 4 years ago

Okay, I confirmed with someone more knowledgeable that we're already computing property types lazily, so that's probably not the issue.

RyanCavanaugh commented 3 months ago

Doesn't seem like we managed to get anywhere else with this. We'll need an up-to-date repro to investigate further - please open a separate issue.