From 85e0fcad540d97609b862c25db7fc7bec340a8e7 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 4 May 2022 11:18:26 -0700 Subject: [PATCH] add tsserverWeb to patch in dynamic import --- Gulpfile.js | 16 +++++++++++++--- scripts/produceLKG.ts | 1 + src/tsconfig.json | 1 + src/tsserverWeb/tsconfig.json | 16 ++++++++++++++++ src/tsserverWeb/tsserverWeb.ts | 3 +++ src/webServer/webServer.ts | 30 ++++++++++++++++++++++++++---- 6 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 src/tsserverWeb/tsconfig.json create mode 100644 src/tsserverWeb/tsserverWeb.ts diff --git a/Gulpfile.js b/Gulpfile.js index c9d3feff80b..a5a5536bda9 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -214,20 +214,29 @@ task("watch-services").flags = { " --built": "Compile using the built version of the compiler." }; -const buildServer = () => buildProject("src/tsserver", cmdLineOptions); +const buildServerWeb = () => buildProject("src/tsserverWeb", cmdLineOptions); +task("tsserverWeb", buildServerWeb); + +const buildServerMain = () => buildProject("src/tsserver", cmdLineOptions); +const buildServer = series(buildServerWeb, buildServerMain); +buildServer.displayName = "buildServer"; task("tsserver", series(preBuild, buildServer)); task("tsserver").description = "Builds the language server"; task("tsserver").flags = { " --built": "Compile using the built version of the compiler." }; -const cleanServer = () => cleanProject("src/tsserver"); +const cleanServerWeb = () => cleanProject("src/tsserverWeb"); +const cleanServerMain = () => cleanProject("src/tsserver"); +const cleanServer = series(cleanServerWeb, cleanServerMain); +cleanServer.displayName = "cleanServer"; cleanTasks.push(cleanServer); task("clean-tsserver", cleanServer); task("clean-tsserver").description = "Cleans outputs for the language server"; +const watchServerWeb = () => watchProject("src/tsserverWeb", cmdLineOptions); const watchServer = () => watchProject("src/tsserver", cmdLineOptions); -task("watch-tsserver", series(preBuild, parallel(watchLib, watchDiagnostics, watchServer))); +task("watch-tsserver", series(preBuild, parallel(watchLib, watchDiagnostics, watchServerWeb, watchServer))); task("watch-tsserver").description = "Watch for changes and rebuild the language server only"; task("watch-tsserver").flags = { " --built": "Compile using the built version of the compiler." @@ -549,6 +558,7 @@ const produceLKG = async () => { "built/local/typescriptServices.js", "built/local/typescriptServices.d.ts", "built/local/tsserver.js", + "built/local/tsserverWeb.js", "built/local/typescript.js", "built/local/typescript.d.ts", "built/local/tsserverlibrary.js", diff --git a/scripts/produceLKG.ts b/scripts/produceLKG.ts index 89199df6125..45fd5bd17c7 100644 --- a/scripts/produceLKG.ts +++ b/scripts/produceLKG.ts @@ -62,6 +62,7 @@ async function copyScriptOutputs() { await copyWithCopyright("cancellationToken.js"); await copyWithCopyright("tsc.release.js", "tsc.js"); await copyWithCopyright("tsserver.js"); + await copyWithCopyright("tsserverWeb.js"); await copyFromBuiltLocal("tsserverlibrary.js"); // copyright added by build await copyFromBuiltLocal("typescript.js"); // copyright added by build await copyFromBuiltLocal("typescriptServices.js"); // copyright added by build diff --git a/src/tsconfig.json b/src/tsconfig.json index 8a0f4dbce0e..a23eca9ac34 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -9,6 +9,7 @@ { "path": "./watchGuard" }, { "path": "./debug" }, { "path": "./cancellationToken" }, + { "path": "./tsserverWeb" }, { "path": "./testRunner" } ] } \ No newline at end of file diff --git a/src/tsserverWeb/tsconfig.json b/src/tsserverWeb/tsconfig.json new file mode 100644 index 00000000000..10d9cd19168 --- /dev/null +++ b/src/tsserverWeb/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig-library-base", + "compilerOptions": { + "outDir": "../../built/local", + "rootDir": ".", + "target": "esnext", + "module": "esnext", + "lib": ["esnext"], + "declaration": false, + "sourceMap": true, + "tsBuildInfoFile": "../../built/local/tsserverWeb.tsbuildinfo" + }, + "files": [ + "tsserverWeb.ts", + ] +} diff --git a/src/tsserverWeb/tsserverWeb.ts b/src/tsserverWeb/tsserverWeb.ts new file mode 100644 index 00000000000..aa21ba70a95 --- /dev/null +++ b/src/tsserverWeb/tsserverWeb.ts @@ -0,0 +1,3 @@ +namespace ts.server { + export const dynamicImport = (id: string) => import(id); +} \ No newline at end of file diff --git a/src/webServer/webServer.ts b/src/webServer/webServer.ts index cf24134da4b..fd252656575 100644 --- a/src/webServer/webServer.ts +++ b/src/webServer/webServer.ts @@ -1,5 +1,6 @@ /*@internal*/ /// +/// namespace ts.server { export interface HostWithWriteMessage { @@ -111,11 +112,35 @@ namespace ts.server { } } + export declare const dynamicImport: ((id: string) => Promise) | undefined; + + // Attempt to load `dynamicImport` + if (typeof importScripts === "function") { + try { + // NOTE: importScripts is synchronous + importScripts("tsserverWeb.js"); + } + catch { + // ignored + } + } + export function createWebSystem(host: WebHost, args: string[], getExecutingFilePath: () => string): ServerHost { const returnEmptyString = () => ""; const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(getExecutingFilePath())))); // Later we could map ^memfs:/ to do something special if we want to enable more functionality like module resolution or something like that const getWebPath = (path: string) => startsWith(path, directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined; + + const dynamicImport = async (id: string): Promise => { + // Use syntactic dynamic import first, if available + if (ts.server.dynamicImport) { + return ts.server.dynamicImport(id); + } + + // Fall back to `eval` if dynamic import wasn't avaialble. + return eval(`import(${JSON.stringify(id)})`); // eslint-disable-line no-eval + }; + return { args, newLine: "\r\n", // This can be configured by clients @@ -157,11 +182,8 @@ namespace ts.server { } const scriptPath = combinePaths(packageRoot, browser); - - // TODO: TS rewrites `import(...)` to `require`. Use eval to bypass this - // eslint-disable-next-line no-eval try { - const module = (await eval(`import(${JSON.stringify(scriptPath)})`)).default; + const { default: module } = await dynamicImport(scriptPath); return { module, error: undefined }; } catch (e) {