microsoft / TypeScript

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

Async LanguageServiceHost #29361

Open zikaari opened 5 years ago

zikaari commented 5 years ago

Search Terms

language, service, host, async, api

Suggestion

It'd super awesome if Language Service would accept a LanguageServiceHost with async API

Use Cases

Using LanguageService in environments where readFileSync is not available like browsers (using browserfs package or fs provided by WebSockets)

I'm working on an app like CodeSandbox but a bit more sophisticated.

Examples

class Host implements ts.LanguageServiceHost {
  async getScriptSnapshot(filename: string): Promise<ts.IScriptSnapshot> { }
  async readFile(filename: string): Promise<string> {}
}

Checklist

My suggestion meets these guidelines:

zikaari commented 5 years ago

It'd be a delight if there was an abstraction like so:

interface ITypeScriptServiceHost {
  getFile(absPath: string): string | Promise<string>
  listDir(absPath: string): string[] | Promise<string[]>
  watch?(absDirPath: string, cb): { close(): void }
}

class TypeScriptService extends LanguageService {
  constructor(host: ITypeScriptServiceHost) { }

And the above system would take care of resolving paths, finding tsconfig.jsons (root and nested), considering the module resolution algorithm and what not.

abhishiv commented 5 years ago

I would second this as well. Also would be interested to learn how people are dealing with this limitation in the meantime. As I see, that makes it impossible to integrate LanguageServiceHost/CompileHost in an async filesystem.

kitsonk commented 5 years ago

I would be supportive of this too, but of course async at this level means that the whole of the compiler would become async, which would fundamentally change everything. TypeScript still runs in environments where async is not a native language feature, which would also be a challenge. This isn't as simple adding the APIs.

As I see, that makes it impossible to integrate LanguageServiceHost/CompileHost in an async filesystem.

Almost every filesystem is "async" by nature. It is the bindings to the filesystem that block until a request that resolves. All current implementations of the TypeScript compiler accomplish this one way or another.

kitsonk commented 5 years ago

We've been talking about this further in context of Deno. Because we support remote modules, a level a parallelisation of resolving and fetching modules improves performance. At the moment we have to find ways to do this in the privileged side, but present it back to the JavaScript runtime as a synchronous operation. The only solution we have found causes a silly number of threads to be created and destroyed. Having the compiler be able to support some sort of async version, would really help in managing resources.

oldrich-svec commented 5 years ago

@kitsonk You could do that manually by calling ts.preProcessFile(fileContent) on the main file. From that you get typeReferenceDirectives, referencedFiles and importedFiles that you can then use to async download the referenced files. The files can then again be recursivelly analysed with the preProcessFile. At the end, you get a list of all the files needed and their content. That you can then use in LanguageServiceHost.

Alternativelly, I on the client start a worker in which I do the fetching using a synchronous version of xmlhttprequest - then I can use LanguageServiceHost directly.

kitsonk commented 5 years ago

@oldrich-svec oooh... thank you! that is very very handy! 👍

manuth commented 2 years ago

Does anyone of you know a workaround for creating an asynchronous getSemanticDiagnostics?