Skip to content

Commit 9695f53

Browse files
committed
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)
1 parent 852b1c2 commit 9695f53

File tree

3 files changed

+40
-2
lines changed

3 files changed

+40
-2
lines changed

src/server/project.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,7 @@ namespace ts.server {
15771577
}
15781578
}
15791579

1580-
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined) {
1580+
protected async enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined) {
15811581
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
15821582
if (!pluginConfigEntry.name || parsePackageName(pluginConfigEntry.name).rest) {
15831583
this.projectService.logger.info(`Skipped loading plugin ${pluginConfigEntry.name || JSON.stringify(pluginConfigEntry)} because only package name is allowed plugin name`);
@@ -1589,8 +1589,27 @@ namespace ts.server {
15891589
const logError = (message: string) => {
15901590
(errorLogs || (errorLogs = [])).push(message);
15911591
};
1592-
const resolvedModule = firstDefined(searchPaths, searchPath =>
1592+
1593+
let resolvedModule: any | undefined;
1594+
if (this.projectService.host.fetchServicePlugin) {
1595+
for (const searchPath of searchPaths) {
1596+
try {
1597+
resolvedModule = await this.projectService.host.fetchServicePlugin(searchPath, pluginConfigEntry.name);
1598+
}
1599+
catch (e) {
1600+
// TODO: log this?
1601+
continue;
1602+
}
1603+
if (resolvedModule) {
1604+
break;
1605+
}
1606+
}
1607+
}
1608+
else {
1609+
resolvedModule = firstDefined(searchPaths, searchPath =>
15931610
Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined);
1611+
}
1612+
15941613
if (resolvedModule) {
15951614
const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name);
15961615
if (configurationOverride) {

src/server/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ declare namespace ts.server {
1616
gc?(): void;
1717
trace?(s: string): void;
1818
require?(initialPath: string, moduleName: string): RequireResult;
19+
importServicePlugin?(root: string, moduleName: string): Promise<any>;
1920
}
2021
}

src/webServer/webServer.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/*@internal*/
2+
/// <reference lib="dom" />
3+
24
namespace ts.server {
35
export interface HostWithWriteMessage {
46
writeMessage(s: any): void;
@@ -137,6 +139,22 @@ namespace ts.server {
137139
/* eslint-enable no-restricted-globals */
138140

139141
require: () => ({ module: undefined, error: new Error("Not implemented") }),
142+
importServicePlugin: async (root: string, moduleName: string) => {
143+
const packageRoot = combinePaths(root, "node_modules", moduleName);
144+
145+
const packageJsonResponse = await fetch(combinePaths(packageRoot, "package.json"));
146+
const packageJson = await packageJsonResponse.json();
147+
const browser = packageJson.browser;
148+
if (!browser) {
149+
throw new Error("Could not load plugin. No 'browser' field found in package.json.");
150+
}
151+
152+
const scriptPath = combinePaths(packageRoot, browser);
153+
154+
// TODO: TS rewrites `import(...)` to `require`. Use eval to bypass this
155+
// eslint-disable-next-line no-eval
156+
return eval(`import(${JSON.stringify(scriptPath)})`);
157+
},
140158
exit: notImplemented,
141159

142160
// Debugging related

0 commit comments

Comments
 (0)