-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Enable TS Server plugins on web #47377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a4719fb
to
9695f53
Compare
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)
9695f53
to
e2bbb6c
Compare
This more or less matches how node plugins expect the plugin module to be an init function
To test this locally:
If you need to test a VS Code extension that contributes a plugin, follow these steps to get your local extension installed into the local web instance of VS Code: https://github.com/microsoft/vscode-docs/blob/vnext/api/extension-guides/web-extensions.md#test-your-web-extension-in-on-vscodedev |
- Use result value instead of try/catch (`ImportPluginResult`) - Add awaits - Add logging
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
Thanks for the hep here @rbuckton! I just tested with the latest changes and think things look good for us on the VS Code side. Any other big concerns for this on the TS side? |
IIRC @RyanCavanaugh mentioned something about not leaving any evals in the TS codebase to avoid the wrath of the security people; should the eval get replaced with a throw if our intent is to just have |
We should throw instead when dynamic import is not implemented
Ok I've updated the PR to throw instead of falling back to |
microsoft/TypeScript#47377 To run plugins on web, we need to shim out `dynamicImport`. This is done in a file call `tsserverWeb.js`, which is added by the linked PR
awsome work |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going through project.ts, editorServices.ts, and related files to determine what issues we'll run into by introducing asynchrony. All of the code paths currently expect things to execute synchronously, so making any of them async means there's a possibility of out of order updates or reading stale data.
@rbuckton good catch but going async with plugins is the only way to go and then awaiting it all plugins are async by design as they can be ESM and that it can be ESM also says that it needs to be async by design no matter what else as ESM is async by design there is no way around that. |
@mjbvz, I've pushed up an update that ensures dynamically imported plugins are processed in the correct order. It essentially splits Asynchronous plugins will be loaded after the first request that creates their related |
f1e8d5c
to
0c78b83
Compare
@rbuckton can we mittiagte the time where the plugins are not loaded via catching all commands that would run against a none existing plugin and then execute them in order as soon as the event that all got loaded is fired we dedupe the commands before that? |
0c78b83
to
186cec9
Compare
I'm not entirely sure whether that would be necessary. New command invocations would run against the project with its plugins after they're loaded. Command invocations that affect resting state (such as diagnostics, semantic highlighting) would likely re-run when they receive the |
@sheetalkamat could you take a look at this as well? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving for the changes that @mjbvz made, but I'd like to have @sheetalkamat or someone else with familiarity with tsserver to review the changes I've made before this is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, although my knowledge is not that deep. @sheetalkamat should take a look too.
Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
src/server/types.ts
Outdated
@@ -16,5 +18,6 @@ declare namespace ts.server { | |||
gc?(): void; | |||
trace?(s: string): void; | |||
require?(initialPath: string, moduleName: string): RequireResult; | |||
importServicePlugin?(root: string, moduleName: string): Promise<ImportPluginResult>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need different name here? Especially if in future we have compile time plugins that get used on compile on save etc ?
May be just import
like require
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1,3 +1,4 @@ | |||
/* eslint-disable boolean-trivia */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really needed and cant be fixed? If it cant be fixed can you pls not disable this for the whole file. instead wherever its needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rule is flagged on every expect(...).eq(true)
(or false
) in the file. It felt like enough individual exceptions that it was worth disabling for the whole file, and labeling every call with .eq(/*value*/ true)
seemed like overkill.
const importPromise = project.beginEnablePluginAsync(pluginConfigEntry, searchPaths, pluginConfigOverrides); | ||
this.pendingPluginEnablements ??= new Map(); | ||
let promises = this.pendingPluginEnablements.get(project); | ||
if (!promises) this.pendingPluginEnablements.set(project, promises = []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle this map when project closes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it should matter. This map is built up during the course of a single request, and then is set to undefined
as soon as the request completes processing (in enableRequestedPluginsAsync
) so it won't hold a reference to the project for very long. The Promise
for the plugin import actually has a longer lifetime than the key in the map, and removing the key early would just mean that we may fail to observe a rejected promise later.
I still have a few more PR feedback items to go through and some additional tests that @sheetalkamat mentioned. I'll put the rest of that together next week. |
@sheetalkamat can you take another look? |
8a3fcdb
to
9bc39d1
Compare
microsoft/TypeScript#47377 To run plugins on web, we need to shim out `dynamicImport`. This is done in a file call `tsserverWeb.js`, which is added by the linked PR
* Add patch for enabling new TS plugins on web approach microsoft/TypeScript#47377 To run plugins on web, we need to shim out `dynamicImport`. This is done in a file call `tsserverWeb.js`, which is added by the linked PR * Update for new files names
Fixes #47376
Fixes microsoft/vscode#140455
This PR adds support for loading TS Service plugins on web. There plugins are loaded slightly differently than on desktop:
We only load plugins that have a
browser
field in theirpackage.json
. This ensures we don't try activating non-web enabled plugins on webPlugins should ship as es modules instead of as a common js module. The top level module should export a default function that activates the plugins.
Instead of using
require
to load the plugin, we useimport(...)