Ever wanted to write a vscode extension in scala that also supports live reloading while you code?
A 'Hello World' vscode extension that reloads when its code changes, written in Scala and built with Mill.
Credits: This project ports Microsoft's vscode extension helloworld-minimal-sample
to Scala with Mill, leveraging mill-scalablyTyped (for deriving scala facades to vscode's npm-based api) and mill-bundler.
# use coursier (cs) to install mill
cs install mill
# make sure npm is installed
sudo apt-get update
sudo apt install nodejs npm
# clone this repo
git clone https://github.com/incrementum/vscode-extension-scala-mill.git
cd vscode-extension-scala-mill
# have mill generate the initial .bloop directory for metals' use
mill mill.contrib.bloop.Bloop/install
# open vscode and have metals import the build file
code .
# compile and watch for changes
mill -w app.fastOpt
In vscode:
F5
to open the extension host window
In the extension host window:
ctrl+shift+p
to open the command panel- invoke the "Hello World" command
- observe in the bottom right corner the information pop window with the "Hello World" message
- make changes to the extension, re-compile; note the extension host window reloads with the changes applied
Scala compiles to JavaScript when using scala.js and since vscode extension can also be written in JavaScript, writing a vscode extension in scala becomes an exercise of integrating our scala code with the vscode api and any other npm modules we would like to call.
Fortunately, mill-scalablyTyped automates the creation of scala facades for all the npm modules we specify in ./package.json
, including the ones that make up the vscode api itself. For this purpose, the build includes a scalablyTypedModule
that the main app
module depends on.
During its initialization upon each build run, the scalablyTypedModule
scans the node_modules
directory for npm modules and creates a scala.js facade library for each, so that the npm functionality can be called from scala more easily. Conveniently, the facade libraries are then transparently added to ivy's local cache and to mill's ivyDeps
.
The app
module's dependency on the scalablyTypedModule
ensures that the main app's dependencies on any npm modules specified in package.json
are fulfilled. To make this work more seamless, the scalablyTypedModule
also checks for the presence of the node_modules
directory, and if it does not exist, calls npm install
, which by npm convention creates this directory and installs all the npm modules specified in package.json
there.
Note that the very first build might take some time to complete, due to the scala facades creation. If you modify the requirements for npm modules in
package.json
, you may need to callnpm install
manually or simply delete thenode_modules
directory and the build will callnpm install
for you and repopulate the directory.
The net effect is that the main app
modules's dependencies are now the sum of the explicitly specified ivyDeps
underneath the app
module and the transparently added scala.js facades of the npm modules specified in package.json
.
Finally, the main app
module not only depends on the scalablytypeModule
described above, but also extends the functionality of mill-bundler for additional ways of including npm modules.
Find below more detailed descriptions of what roles the various files play. Most of which you will be familiar with if you have gone through Microsoft's basic tutorial "vscode | Your First Extension" first (highly recommended).
[npm, typescript, vscode api] The integration with vscode's extension api relies on the npm package manager and the subsequent installation of the typescript and vscode modules.
If the
./node_modules
directory does not yet exist, the build will performnpm install
to install thetypescript
andvscode
modules as specified in the./package.json
file.
[./package.json
], vscode's extension manifest is the central file that describes the dependencies, contributed commands, and the main
reference pointing to the javascript file generated by mill app.fastOpt
. The extension is activated on vscode's onStartupFinished
event to make sure that the reload mechanism described below initializes - and changes to the extension's code are not missed.
[./vscode/launch.json
] specifies to launch vscode's "extension host" when issuing a run/debug command (F5)
[./app/src/extension.scala
] contains the source code of the extension, which on activation registers the contributed command(s) of the extension. The activation also contains
an instruction to reload the extension when its source code changes.
[./app/src/dev/reload.scala
] contains code to trigger the reloading of the vscode extension host window when changes to the file containing the extension's javascript code are detected. This file is updated whenever mill -w app.fastOpt
re-generates it, which in turn happens on any saved changes to the underlying scala sources.
[./build.sc
] contains in-line comments explaining its makeup, but its main feature is provided by scalablytyped
which creates and ivy local caches scalajs facades for all the npm module dependencies listed in package.json. These facades are also added to the jsdeps
of the scalajsmodule.
- Refer to vscode's extension api
- Refer to Publishing Extensions