Adds experimental support for running TS Server in a web worker (#39656)

* Adds experimental support for running TS Server in a web worker

This change makes it possible to run a syntax old TS server in a webworker. This is will let serverless versions of VS Code web run the TypeScript extension with minimal changes.

As the diff on `server.ts` is difficult to parse, here's an overview of the changes:

- Introduce the concept of a `Runtime`. Valid values are `Node` and `Web`.
- Move calls to `require` into the functions that use these modules
- Wrap existing server logic into `startNodeServer`
- Introduce web server with `startWebServer`. This uses a `WorkerSession`
- Add a custom version of `ts.sys` for web
- Have the worker server start when it is passed an array of arguments in a message

In order to make the server logic more clear, this change also tries to reduce the reliance on closures and better group function declarations vs the server spawning logic.

**Next Steps**
I'd like someone from the TS team to help get these changes into a shippable state. This will involve:

- Adddress todo comments
- Code cleanup
- Make sure these changes do not regress node servers
- Determine if we should add a new `tsserver.web.js` file instead of having the web worker logic all live in `tsserver.js`

* Shim out directoryExists

* Add some regions

* Remove some inlined note types

Use import types instead

* Use switch case for runtime

* Review and updates

* Enable loading std library d.ts files

This implements enough of `ServerHost` that we can load the standard d.ts files using synchronous XMLHttpRequests.

I also had to patch some code in `editorServices`. I don't know if these changes are correct and need someone on the TS team to review

* Update src/tsserver/webServer.ts

* Update src/tsserver/webServer.ts

Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>

* Addressing feedback

* Allow passing in explicit executingFilePath

This is required for cases where `self.location` does not point to the directory where all the typings are stored

* Adding logging support

* Do not create auto import provider in partial semantic mode

* Handle lib files by doing path mapping instead

* TODO

* Add log message

This replaces the console based logger with a logger that post log messages back to the host. VS Code will write these messages to its output window

* Move code around so that exported functions are set on namespace

* Log response

* Map the paths back to https:
// TODO: is this really needed or can vscode take care of this
How do we handle when opening lib.d.ts as response to goto def in open files

* If files are not open dont schedule open file project ensure

* Should also check if there are no external projects before skipping scheduling
Fixes failing tests

* Revert "Map the paths back to https:"

This reverts commit 0edf650622.

* Revert "TODO"

This reverts commit 04a4fe7556.

* Revert "Should also check if there are no external projects before skipping scheduling"

This reverts commit 7e4939014a.

* Refactoring so we can test the changes out

* Feedback

Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
This commit is contained in:
Matt Bierner
2020-12-08 16:09:43 -08:00
committed by GitHub
parent d8c8e4ff06
commit 49d7de17d6
11 changed files with 1526 additions and 1002 deletions

View File

@@ -689,7 +689,7 @@ namespace ts.server {
typesMapLocation?: string;
}
export class Session implements EventSender {
export class Session<TMessage = string> implements EventSender {
private readonly gcTimer: GcTimer;
protected projectService: ProjectService;
private changeSeq = 0;
@@ -2907,7 +2907,7 @@ namespace ts.server {
}
}
public onMessage(message: string) {
public onMessage(message: TMessage) {
this.gcTimer.scheduleCollect();
this.performanceData = undefined;
@@ -2916,18 +2916,18 @@ namespace ts.server {
if (this.logger.hasLevel(LogLevel.requestTime)) {
start = this.hrtime();
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`request:${indent(message)}`);
this.logger.info(`request:${indent(this.toStringMessage(message))}`);
}
}
let request: protocol.Request | undefined;
let relevantFile: protocol.FileRequestArgs | undefined;
try {
request = <protocol.Request>JSON.parse(message);
request = this.parseMessage(message);
relevantFile = request.arguments && (request as protocol.FileRequest).arguments.file ? (request as protocol.FileRequest).arguments : undefined;
tracing.instant(tracing.Phase.Session, "request", { seq: request.seq, command: request.command });
perfLogger.logStartCommand("" + request.command, message.substring(0, 100));
perfLogger.logStartCommand("" + request.command, this.toStringMessage(message).substring(0, 100));
tracing.push(tracing.Phase.Session, "executeCommand", { seq: request.seq, command: request.command }, /*separateBeginAndEnd*/ true);
const { response, responseRequired } = this.executeCommand(request);
@@ -2965,7 +2965,7 @@ namespace ts.server {
return;
}
this.logErrorWorker(err, message, relevantFile);
this.logErrorWorker(err, this.toStringMessage(message), relevantFile);
perfLogger.logStopCommand("" + (request && request.command), "Error: " + err);
tracing.instant(tracing.Phase.Session, "commandError", { seq: request?.seq, command: request?.command, message: (<Error>err).message });
@@ -2978,6 +2978,14 @@ namespace ts.server {
}
}
protected parseMessage(message: TMessage): protocol.Request {
return <protocol.Request>JSON.parse(message as any as string);
}
protected toStringMessage(message: TMessage): string {
return message as any as string;
}
private getFormatOptions(file: NormalizedPath): FormatCodeSettings {
return this.projectService.getFormatCodeOptions(file);
}