mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-08 08:14:51 -06:00
* 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 0edf650622da11e89e42238523d57f3ea780cdcf. * Revert "TODO" This reverts commit 04a4fe75563ea9fe4747d42a4448d5dd421fbba5. * Revert "Should also check if there are no external projects before skipping scheduling" This reverts commit 7e4939014a414c7651f1fa01516c81a37a10e9be. * Refactoring so we can test the changes out * Feedback Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
/*@internal*/
|
|
namespace ts.server {
|
|
declare const addEventListener: any;
|
|
declare const postMessage: any;
|
|
declare const close: any;
|
|
declare const location: any;
|
|
declare const XMLHttpRequest: any;
|
|
declare const self: any;
|
|
|
|
const nullLogger: Logger = {
|
|
close: noop,
|
|
hasLevel: returnFalse,
|
|
loggingEnabled: returnFalse,
|
|
perftrc: noop,
|
|
info: noop,
|
|
msg: noop,
|
|
startGroup: noop,
|
|
endGroup: noop,
|
|
getLogFileName: returnUndefined,
|
|
};
|
|
|
|
function parseServerMode(): LanguageServiceMode | string | undefined {
|
|
const mode = findArgument("--serverMode");
|
|
if (!mode) return undefined;
|
|
switch (mode.toLowerCase()) {
|
|
case "partialsemantic":
|
|
return LanguageServiceMode.PartialSemantic;
|
|
case "syntactic":
|
|
return LanguageServiceMode.Syntactic;
|
|
default:
|
|
return mode;
|
|
}
|
|
}
|
|
|
|
export function initializeWebSystem(args: string[]): StartInput {
|
|
createWebSystem(args);
|
|
const modeOrUnknown = parseServerMode();
|
|
let serverMode: LanguageServiceMode | undefined;
|
|
let unknownServerMode: string | undefined;
|
|
if (typeof modeOrUnknown === "number") serverMode = modeOrUnknown;
|
|
else unknownServerMode = modeOrUnknown;
|
|
return {
|
|
args,
|
|
logger: createLogger(),
|
|
cancellationToken: nullCancellationToken,
|
|
// Webserver defaults to partial semantic mode
|
|
serverMode: serverMode ?? LanguageServiceMode.PartialSemantic,
|
|
unknownServerMode,
|
|
startSession: startWebSession
|
|
};
|
|
}
|
|
|
|
function createLogger() {
|
|
const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity"));
|
|
return cmdLineVerbosity !== undefined ? new MainProcessLogger(cmdLineVerbosity, { writeMessage }) : nullLogger;
|
|
}
|
|
|
|
function writeMessage(s: any) {
|
|
postMessage(s);
|
|
}
|
|
|
|
function createWebSystem(args: string[]) {
|
|
Debug.assert(ts.sys === undefined);
|
|
const webHost: WebHost = {
|
|
readFile: webPath => {
|
|
const request = new XMLHttpRequest();
|
|
request.open("GET", webPath, /* asynchronous */ false);
|
|
request.send();
|
|
return request.status === 200 ? request.responseText : undefined;
|
|
},
|
|
fileExists: webPath => {
|
|
const request = new XMLHttpRequest();
|
|
request.open("HEAD", webPath, /* asynchronous */ false);
|
|
request.send();
|
|
return request.status === 200;
|
|
},
|
|
writeMessage,
|
|
};
|
|
// Do this after sys has been set as findArguments is going to work only then
|
|
const sys = server.createWebSystem(webHost, args, () => findArgument("--executingFilePath") || location + "");
|
|
ts.sys = sys;
|
|
const localeStr = findArgument("--locale");
|
|
if (localeStr) {
|
|
validateLocaleAndSetLanguage(localeStr, sys);
|
|
}
|
|
}
|
|
|
|
function hrtime(previous?: [number, number]) {
|
|
const now = self.performance.now(performance) * 1e-3;
|
|
let seconds = Math.floor(now);
|
|
let nanoseconds = Math.floor((now % 1) * 1e9);
|
|
if (previous) {
|
|
seconds = seconds - previous[0];
|
|
nanoseconds = nanoseconds - previous[1];
|
|
if (nanoseconds < 0) {
|
|
seconds--;
|
|
nanoseconds += 1e9;
|
|
}
|
|
}
|
|
return [seconds, nanoseconds];
|
|
}
|
|
|
|
function startWebSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) {
|
|
class WorkerSession extends server.WorkerSession {
|
|
constructor() {
|
|
super(sys as ServerHost, { writeMessage }, options, logger, cancellationToken, hrtime);
|
|
}
|
|
|
|
exit() {
|
|
this.logger.info("Exiting...");
|
|
this.projectService.closeLog();
|
|
close(0);
|
|
}
|
|
|
|
listen() {
|
|
addEventListener("message", (message: any) => {
|
|
this.onMessage(message.data);
|
|
});
|
|
}
|
|
}
|
|
|
|
const session = new WorkerSession();
|
|
|
|
// Start listening
|
|
session.listen();
|
|
}
|
|
}
|