mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
Enable TS Server plugins on web (#47377)
* Prototype TS plugins on web This prototype allows service plugins to be loaded on web TSServer Main changes: - Adds a new host entryPoint called `importServicePlugin` for overriding how plugins can be loaded. This may be async - Implement `importServicePlugin` for webServer - The web server plugin implementation looks for a `browser` field in the plugin's `package.json` - It then uses `import(...)` to load the plugin (the plugin source must be compiled to support being loaded as a module) * use default export from plugins This more or less matches how node plugins expect the plugin module to be an init function * Allow configure plugin requests against any web servers in partial semantic mode * Addressing some comments - Use result value instead of try/catch (`ImportPluginResult`) - Add awaits - Add logging * add tsserverWeb to patch in dynamic import * Remove eval We should throw instead when dynamic import is not implemented * Ensure dynamically imported plugins are loaded in the correct order * Add tests for async service plugin timing * Update src/server/editorServices.ts Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> * Partial PR feedback * Rename tsserverWeb to dynamicImportCompat * Additional PR feedback Co-authored-by: Ron Buckton <ron.buckton@microsoft.com> Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
/*@internal*/
|
||||
/// <reference lib="dom" />
|
||||
/// <reference lib="webworker.importscripts" />
|
||||
|
||||
namespace ts.server {
|
||||
export interface HostWithWriteMessage {
|
||||
writeMessage(s: any): void;
|
||||
@@ -109,11 +112,34 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
export declare const dynamicImport: ((id: string) => Promise<any>) | undefined;
|
||||
|
||||
// Attempt to load `dynamicImport`
|
||||
if (typeof importScripts === "function") {
|
||||
try {
|
||||
// NOTE: importScripts is synchronous
|
||||
importScripts("dynamicImportCompat.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<any> => {
|
||||
// Use syntactic dynamic import first, if available
|
||||
if (server.dynamicImport) {
|
||||
return server.dynamicImport(id);
|
||||
}
|
||||
|
||||
throw new Error("Dynamic import not implemented");
|
||||
};
|
||||
|
||||
return {
|
||||
args,
|
||||
newLine: "\r\n", // This can be configured by clients
|
||||
@@ -136,7 +162,32 @@ namespace ts.server {
|
||||
clearImmediate: handle => clearTimeout(handle),
|
||||
/* eslint-enable no-restricted-globals */
|
||||
|
||||
require: () => ({ module: undefined, error: new Error("Not implemented") }),
|
||||
importServicePlugin: async (initialDir: string, moduleName: string): Promise<ModuleImportResult> => {
|
||||
const packageRoot = combinePaths(initialDir, moduleName);
|
||||
|
||||
let packageJson: any | undefined;
|
||||
try {
|
||||
const packageJsonResponse = await fetch(combinePaths(packageRoot, "package.json"));
|
||||
packageJson = await packageJsonResponse.json();
|
||||
}
|
||||
catch (e) {
|
||||
return { module: undefined, error: new Error("Could not load plugin. Could not load 'package.json'.") };
|
||||
}
|
||||
|
||||
const browser = packageJson.browser;
|
||||
if (!browser) {
|
||||
return { module: undefined, error: new Error("Could not load plugin. No 'browser' field found in package.json.") };
|
||||
}
|
||||
|
||||
const scriptPath = combinePaths(packageRoot, browser);
|
||||
try {
|
||||
const { default: module } = await dynamicImport(scriptPath);
|
||||
return { module, error: undefined };
|
||||
}
|
||||
catch (e) {
|
||||
return { module: undefined, error: e };
|
||||
}
|
||||
},
|
||||
exit: notImplemented,
|
||||
|
||||
// Debugging related
|
||||
|
||||
Reference in New Issue
Block a user