-
Notifications
You must be signed in to change notification settings - Fork 168
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
DevTools <-> VSCode integration #1497
Conversation
I'll fork off here into additional features - this is a good size for a review already :) |
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 might have more with fresh eyes, but I think this is a good set of feedback to take a look at for now. I'd definitely recommend seeing if you can reduce to a single rpc client/message adapter in the server if possible so you can take advantage of the message bridge.
@@ -0,0 +1,36 @@ | |||
{ | |||
"name": "@apollo/client-devtools-vscode", | |||
"version": "4.18.4-pre.0", |
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.
Should this match the initial version in package.json
? Looks like that is on 4.18.6
if you want to update the patch version here to keep it in sync (though not sure how much it matters yet at this stage or not)
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.
That was just for my local build back when I forked off - when we publish the package, it will go through changesets and inherit the version of the extension :)
}; | ||
} | ||
|
||
function createWsAdapter(ws: WebSocket, signal: AbortSignal): MessageAdapter { |
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 think it would be good to do a bit of organization on the existing code so that this is near the other code, but ok for now and we can refactor in a future PR.
src/extension/vscode/client.ts
Outdated
connected: new Promise<void>((resolve) => | ||
ws.addEventListener("open", () => resolve(), { once: true, signal }) | ||
), |
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 a huge fan of this name mostly because reading this I keep expecting this to be a boolean, but its a promise instead.
Out of curiosity, could we just use a callback here and call this onConnected
? We can remove the promise altogether then.
connected: new Promise<void>((resolve) => | |
ws.addEventListener("open", () => resolve(), { once: true, signal }) | |
), | |
onConnected: (callback: () => void) => { | |
ws.addEventListener("open", () => callback(), { once: true, signal }) | |
}, |
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.
That would miss the event if you added an onConnected
event after the connection is already established and handling that would add extra logic. I'm open to changing the name, but I'd like to keep the promise, as that handles that nicely.
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.
Thats a good point. Let's see if we can rename this then.
Would it be out of the question for registerClient
(or whatever the new name will be) to return a promise instead? If not, my brain is out of names 😆
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.
connectedPromise
- will at least not be confused with a boolean, and also rejects now when there is a connection error
src/extension/vscode/client.ts
Outdated
unregister: cleanup, | ||
onCleanup: registerCleanup, |
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.
unregister: cleanup, | |
onCleanup: registerCleanup, | |
disconnect: cleanup, | |
onDisconnect: registerCleanup, |
What about calling this disconnect
? I think this will probably be a bit more intuitive than "cleanup" since its not immediately clear what is being cleaned up without knowing the implementation details. Thoughts?
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.
It's called unregister
right now, because the function itself is named registerClient
.
We could rename registerClient
to connectClientToDevTools
or something, then disconnect
would make sense 🤔
Not sure if I like that better, though - WDYT?
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.
Actually I do like that better! I had a similar thought this morning before reading your comment so I'm on board. A name like connectClienttoDevTools
makes it immediately obvious what that block of code does without having to follow the import to understand what you're registering your client with. On top of that, it better follows with our connectToDevTools
option naming we've had in Apollo Client for for a while so theres some symmetry there 🙂
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.
Then I'd suggest we make it really obvious with a connectApolloClientToVSCodeDevTools
? :)
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.
Haha either one, but something with connect
and devTools
in it makes sense to me :)
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.
renamed to connectApolloClientToVSCodeDevTools
and also edited the shape of the returned object
src/extension/vscode/server.ts
Outdated
const wsRpcHandler = createRpcHandler(wsAdapter); | ||
const wsActor = createActor(wsAdapter); | ||
wsActor.on("registerClient", ({ payload }) => { | ||
payload.id = id; |
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.
Ah ok I see this is set here instead of in client.ts
, which I missed when adding the other comment.
I'm curious why you decided to go this route of creating a handler/rpc client per apollo client instance? If you're able to use a single client/rpc handler, you would then be able to take advantage of the message bridge since this script would essentially just be the middle man between the client script and devtools.
I'd recommend taking a stab at that, otherwise we'll need to edit this file anytime we want to add a new message from devtools to the client (such as the memory panel that will be added soon). I used to do this in tab.ts
and it there were definitely times that I scratched my head wondering why something wasn't working only to realize I forgot to add the message forwarding between the two. The message bridge definitely helps out since its a bit more automatic.
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 message bridge would work if we had the guarantee of a single connection, but you can totally connect two different processes to one server (e.g. connect an iOS emulator and an Android emulator at the same time). So either at that point I send everything everywhere, or I have to keep track of who is who in the server - I went for the latter.
Also, we could in the future add some "snapshot" functionality here inside the server that would work by creating a "virtual client" that would be fully controlled by the server.
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.
Ah right that makes sense.
I suppose the thing that worries me is diverging some of the client-specific behavior too much from each other between the browser extension and the vscode integration. It makes sense that we have different transport mechanisms (websockets vs ports) and have varying code there, but if we can make some of the core client logic the same, I think it will be easier to maintain long-term.
In this case, I'm specifically referring to where/how the client id
is created and assigned. In the browser extension, this is created when registering the client with the browser extension. Here it is created and assigned in the server instead. If we end up diverging too much on this kind of thing, I fear it will get difficult to maintain as it means some of the common abstractions may only work in one of two contexts.
I'm not opposed to assigning it in the server, but if we go that route, I'd love for the browser extension to resemble this structure as well. It wasn't immediately obvious to me id
was going to be assigned outside of registerClient
in the code when reading through this the first time (hence my barrage of comments).
For now though, what do you think about at least creating the id
over in registerClient
and just reading that here instead of creating/assigning it in the server?
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.
Creating the id
inregisterClient
now, I just hope there won't be any collisions in low-entropy environments
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 don't know that I'm that worried to be honest. At most we'll have maybe 10s of clients connected at once, at least in the 99% of cases. If we get reports of collisions between connected clients because we start getting tons of them, we can always revisit.
* change option names * add docblocks * pass `signal` around further instead of registering cleanup callbacks
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.
This looks great! Thanks for making those changes 💯
src/extension/vscode/server.ts
Outdated
const wsRpcHandler = createRpcHandler(wsAdapter); | ||
const wsActor = createActor(wsAdapter); | ||
wsActor.on("registerClient", ({ payload }) => { | ||
payload.id = id; |
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 don't know that I'm that worried to be honest. At most we'll have maybe 10s of clients connected at once, at least in the 99% of cases. If we get reports of collisions between connected clients because we start getting tons of them, we can always revisit.
This is in very early stages and split up between here and apollographql/vscode-graphql#188