Co-authored by Jon Cammisuli
Nx Console has been on the Visual Studio Code marketplace for years, and with over 1.2 million downloads, we know that a lot of folks enjoy using it for their day to day Nx related tasks.
That makes it even more exciting for us to officially announce that Nx Console is now available for JetBrains IDEs!! Go grab it on the official store.
👉 **Install link: ** https://plugins.jetbrains.com/plugin/21060-nx-console
Before we go into details of Nx Console for IntelliJ, we’d really want to go and thank our community. * *Issam Guissouma** and Edward Tkachev from the Nx community had their own Nx Console plugins for IntelliJ out there already for a while. And they have been super popular. As such we’d like to take the occasion to give them a shout-out for the awesome work on the community plugins, but also for closely collaborating with us over the last weeks to build our official IntelliJ support for Nx Console.
Especially Issam has been actively helping us port over all the features he built to the official Nx Console plugin. So be sure to look out for the upcoming release because it’s going to be another huge one!
Table of Contents
· Going from one IDE to two
· Nx Language Server
· Generate UI
· Communicating with IntelliJ
· Adapting Styling
· JCEF
· Glueing it together
· Looking ahead
Prefer a video version? We’ve got you covered!
Going from one IDE to two
Nx Console has a ton of features to improve your development experience while working on Nx and Angular repos, some small and some big.
To provide the most value from the get-go for JetBrains users, we first focused on integrating two main areas: The Nx Language Server and the Generate UI.
The Nx Language Server (nxls) is based on the Language Server Protocol (LSP). It serves as a single source of truth for all information about your workspace and its projects. With it, you get features such a code completion for project.json
and nx.json
files, clickable links for all kinds of files and more. Being a standalone process, it’s editor-agnostic per default, which is great! However, IntelliJ doesn’t natively support the language server protocol yet, so we had to write some code that bridges the gap between the two. More about that in the following section!
The great thing about having a central “brain” for both extensions is that it saves a lot of time writing the same functionality multiple times. We only have to deal with platform-specific code for rendering UI or defining actions instead of parsing nx.json
files and the like. This makes both VSCode and IntelliJ extensions thin, DRY wrappers around the nxls.
Another major part of Nx Console is the Generate UI. Instead of combing through CLI commands to fit your specific use-case, you can use the form-based view it provides. It’s a separate web application (currently built with Angular) that runs inside VSCode as a webview. Again, being a standalone application means that we didn’t have to rewrite it completely in order to integrate it into JetBrains-based editors!
Nx Language Server
Development of the Nx Language Server (nxls) began as a way to provide autocompletions in Nx specific files. But we came to the realization that the nxls
could also provide much more information.
The language server is a separate process that is called by the running IDE (that being VSCode, and now Intellij) that communicates via json rpc.
With the Language Server Protocol, there are certain methods that are called between the IDE and the language server, such as textDocument/completion
that must be implemented.
When the nxls
does Language Server Protocol related things (like autocomplete), it uses the local workspace copy of Nx to get information about the workspace. Since this workspace information is already loaded in memory, it presented a solution to just use that same workspace info in the IDEs without us having to rewrite this logic in multiple languages (ie, TypeScript and Kotlin).
We found out quickly that we can implement custom requests within the nxls
to respond to other queries that aren’t actually part of the Language Server Protocol. These requests are what allows us to use the nxls
in multiple IDEs and allow us to quickly iterate.
One of these custom requests is nx/workspace
. Calling this endpoint gives us the same information that you can find when you run nx graph --file=output.json
. (Plus a little more information that is needed for IDEs 🙂)
There are more custom requests that are implemented that help with IDE integration, some of these include:
- Nx version
- Available generators
- Finding projects based on file paths
You can see all the custom requests on Github.
VSCode
Language Server Protocol support is built into the core of VSCode, so using the language server there was straight forward. Previously the logic to get workspace information was just part of the VSCode extension. This was the logic that moved to the nxls
.
IntelliJ
For JetBrains editors (IntelliJ/Webstorm), calling a language server wasn’t so obvious.
Thankfully, there were already plugins that integrated language servers using the lsp4j library. This was a great starting out point for us and we quickly got IntelliJ/Webstorm to boot up nxls
and start sending requests.
In future versions of IntelliJ/Webstorm, language server support is going to be fully baked into the core of the IDE using the same lsp4j library. This means that we can remove some of the scaffolding that we had to do to get it working without the core support.
Generate UI
Screenshot of the generate UI in IntelliJ
As mentioned, the generate UI is a key feature of Nx Console. It allows users to easily generate code by using a form-based view, rather than having to remember and type a series of CLI commands.
For VSCode, the only way to add a custom UI like this is to render it from inside a webview — similar to an iframe. IntelliJ provides the functionality to build more complex native UIs. However, this would mean rewriting everything from scratch: how to process Nx schemas, how to render each kind of field, validation and more. And then, each change to the UI would have to be made in two places, greatly increasing the maintenance overhead and complexity of the codebase. This is why we decided to reuse the existing generate UI for IntelliJ.
The generate UI is a standalone app written in Angular. It consists of a few components for the different field types and a main component that deals with processing the schema, filtering fields and communicating with the IDE. This entire component was quite tightly coupled with VSCode in two regions: receiving and sending data to the IDE and styling. Naturally, these aspects had to be refactored out in order to accommodate a new host IDE.
Communicating with IntelliJ
First, we ripped out all VSCode-specific code from the main component and moved it to a separate ide-communication.service.ts
. Communication with the host IDE is now done via one of two methods:
- for VSCode, we continue to use the straightforward ( and fully typed)
acquireVsCodeApi()
method,window.addEventListener
andwebview.postMessage
. - for IntelliJ, there is no such helper method, so we wrote a custom
window.intellijApi
object. This api will then be called from both the Angular app and from within Kotlin code by injecting it into JCEF (more on that later). This is definitely a more involved solution and less developer-friendly, but it works perfectly fine.
The service uses whatever API it finds to send and receive messages, with the rest of the app none the wiser. The structure of the messages is identical between VSCode and IntelliJ. The fact that the Typescript-based VSCode extension natively understands JSON is a nice upside, since we can just pass around objects between the browser and extension without having to convert anything. For the Kotlin-based IntelliJ plugin, this needs an additional de-/serialization step, which is easily done with kotlinx.serialization
or gson
.
Adapting Styling
One aspect that makes designing webviews for VSCode a breeze is the massive stylesheets they ship by default. Every UI element’s colors are available in primary, secondary and disabled variants as well as fonts, background colors and more.
However, this ended up being a struggle as all styling was very tightly coupled to these VSCode stylesheets, which are obviously not present within a different host IDE. We replaced all of the VSCode styles with custom css variables. In a separate generate-ui-styles
library, we map the VSCode styles to our matching variables. For IntelliJ, we extract the the same style variables using UIUtil
and pass them to the app.
JCEF
To integrate the web application into IntelliJ, Nx Console uses JCEF (Java Chromium Embedded Framework). JCEF enables Java applications to render web pages using Chromium. It comes with great debugging support using Chrome Devtools and we haven’t run into any issues with it yet. The docs are sadly a bit lacking, but with some trial and error we managed to wire everything up. I want to give a special shoutout to Rafal Mucha and his article Creating IntelliJ plugin with WebView. It explains how to enable JCEF to load files from the /resources
folder bundled in the plugin JAR. There’s not much info on this topic so this one blog post was a true lifesaver. It’s written in Scala but I learned a lot rewriting it to Kotlin. Now we have a Kotlin version out there too!
Communication with whatever is running in the browser works by injecting javascript code to be run into the browser - kind of like running something in the console. This creates a little more overhead than simply posting messages to be consumed from a listener like in VSCode. The upside is that it theoretically gives you a lot more flexibility in what you want to do in the browser - you could talk to multiple, independent APIs and register distinct callbacks on the host side.
Glueing it together
One of the unique aspects of the Nx Console for IntelliJ is that it combines different technologies in a polyglot monorepo. While Nx is often used for Typescript- or Javascript-based repos, it’s actually technology-agnostic and can host apps and libraries in any language. With the newly released **Encapsulated Nx ** setting, this is even taken a step further! Now you don’t need a package.json
or node_modules
to run Nx.
The codebase contains both Typescript code for the VSCode extension and Kotlin code for the IntelliJ plugin. Currently, all the Kotlin code resides in a single app. Targets defined in project.json
are available that wrap different gradle tasks like running a development instance, building or formatting the plugin using the nx:run-commands
executor.
Since the plugin depends on artifacts provided by other Nx apps (namely the nxls
and generate-ui
), we have also created gradle tasks that call Nx to build these dependencies under the hood. This roundabout way of calling one tool from the other (and back again) could definitely be improved and we might look into having a more straightforward integration later.
For the generate UI, we were able to keep it as a single app. Using different configurations for the build
target, we’re including the different stylesheets needed for each configuration and copying the files to where they need to be.
Looking ahead
In the upcoming releases, we plan to integrate more features that already exist in VSCode like the nx project view as well as add more IntelliJ-native functionality and quality-of-life changes. Our goal is to provide the same experience and level of productivity, regardless of which IDE you prefer to use. You can join the discussion and vote on what you think should be added on the roadmap issue on GitHub.
We are also looking into automatic type generation for both TypeScript and Kotlin. Currently, all return types of the Nx Language Server have to be maintained in both languages, but maybe we could use a tool like Dukat. This would help us to save time and reduce the risk of inconsistencies between the TypeScript and Kotlin codebases.
Learn more