This is rare post that is not directly related to chemistry or informatics, even though the screenshot to the right suggests otherwise. Probably the most exciting trends in software is that the web runtime has finally matured into a development target that is somewhat on par with native options, and that is a huge breakthrough for people who want to code up their product just once. I’m going to describe some of the getting started issues & gotchas that I went through with getting SketchEl2 to work using Electron, TypeScript and Visual Studio Code.
There are some syntactical differences, but in general it is quite easy to transliterate between Java and TypeScript, commonly resulting in a line-for-line equivalence. Generally the same rules apply: primitive types, collection types, class objects and functions/lambdas have more or less the same characteristics in both languages, and they are both checked by the compiler while you’re coding, rather than after you execute.
Now getting to the main point: it is possible to use a single TypeScript codebase to create sophisticated apps that target regular web (served from a URL), desktop apps that are often as good as native, and likewise for mobile, deployed within a native app wrapper. Focusing on the desktop part, this is (as of December 2017) not exactly a painfree experience, because the Electron framework is still relatively new, and the various development permutations have not all been fleshed out in great detail. If you’re stuck on how to do something, it’s highly likely that you can’t find a quick answer on StackOverflow.
The example I’m going to describe is the SketchEl2 app (see GitHub), which is currently bare bones (most of the heavy lifting is done by the WebMolKit project – where the interactive molecule sketching is coded up – but that’s another story). I’m not going to describe installing any of the tools because that’s well covered by the internet; you’ll need npm (node package manager), TypeScript and Electron (via npm) and Visual Studio Code (from Microsoft).
The development directory should have several subdirectories in it:
app dist src .vscode
The app directory contains the glue necessary for the Electron framework to wrap your code into a functioning desktop app. This includes the cross-compiled TypeScript output, static libraries, resources like images, and a bunch of rather ugly files that are needed to bootstrap several technologies that were not originally designed to work together. The latter category is currently one of the most awkward parts of getting results out of this new technology.
The dist directory is where the deliverable builds will go. There is one output bundle for each of the three major platforms, and once constructed, these will run as-is on Linux, Mac and Windows.
The src directory is where the TypeScript files go – all the production-quality, highly maintainable code that can be repurposed for a multitude of different platform types.
The .vscode directory contains the launching and debugging details for Visual Studio Code, which is key for making the development experience acceptable. More on that later.
For the benefit of Electron, there needs to be two files called package.json, one of them in the root directory (see GitHub) and the other in the app directory (see GitHub). These provide instructions for building the package, and for executing it once assembled, respectively. For the record, I have assembled these files largely by trial & error, so the examples for SketchEl2 may be under- or overspecified for any given purpose. They are at least sufficient for debugging and building distributable packages that can be executed independently on a different computer.
To establish this compilation step, a tsconfig.json file is required, to tell the TypeScript compiler what to do. This is quite a simple and concise format, and for SketchEl2 it is thus:
These settings are well documented, so no need to go into too much detail. The outFile is set to app/sketchel2.js, and the sources are defined as everything in the src directory, plus all of the files in the companion WebMolKit. The exclude section is a directive to tell the TypeScript compiler not to opportunistically scrape other files that it might dig out of the directory hierarchy (which seems like more of a bug than a feature).
With this established, the cross-compilation process can be accomplished from the commandline by typing “tsc”, or more conveniently from Visual Studio Code with Ctrl-Shift-B (or Command-Shift-B) to execute the default build process.
As soon as you get started with Visual Studio Code, you will end up with a .vscode subdirectory. Because it is naturally paired with TypeScript, some of the settings don’t need any real attention. There should be two files in this directory: tasks.json and launch.json. The former has defaults that are unlikely to need modification, but the latter needs to be configured to work with Electron:
The launch task is the most important one. The program parameter specifies the entrypoint, which is app/main.js, which basically bootstraps everything. The runtimeExecutable is set to use Electron to launch the project.
One of the confusing pieces, which may be resolved by now, is the protocol by which Visual Studio Code communicates with Electron to obtain logging information, stop the app, etc. At a certain point, the various components were defaulting to different protocol versions, which broke the whole development chain. Setting the protocol to legacy fixed this, but by the time you read this article, this particular glitch is quite likely to have been sorted out. This is just one example of the frequent pitfalls of using a heterogeneous collection of open source frameworks, each of them is a fast moving target.
Once this is setup, making changes and running the project can be achieved from Visual Studio Code by pressing Control-Shift-B to do the compilation, and then F5 to execute the thing. If everything is working, the result will be a window with the functioning desktop webapp:
But that’s getting slightly ahead – there is still the app directory which contains all of the glue. The important things that go in here are:
- the main.js file that is loaded first, and sets everything up
- the package.json file (for Electron’s deployment benefit)
- index.html: the web page that is loaded into the browser
- various .js and .css files (including the cross-compiled TypeScript)
- other resources such as images
The main.js file is an interesting beast, and understanding it requires an introduction to the two threads that are part of a baseline Electron process (described in more detail here). The main.js file is the Main thread, and its responsibility is to create a new BrowserWindow, and tell it what to put in there. That browser window then goes on to create a Renderer thread, which behaves almost indistinguishably from a normal web application inside a normal browser page loaded up from the web.
In the Main thread (handled by main.js) the codebase has control over some high level desktop customisation features: it can open new windows, control their size, add menus, and various other things. The Renderer thread on the other hand is completely confined to its own window, and by default has the same sandboxed characteristics as a normal web page – except that it has the option of communicating with the main thread, and also importing some special Node.js packages for privileged access (such as for local files).
At this point, we have to consider the scope of the application: does it need to be able to be used interchangeably as desktop vs. web vs. mobile app? If so, then the Electron-specific functionality should be confined to specific modules, which need to be avoided for the non-Electron parts. For the SketchEl2 example, this is not a major concern because the bulk of the functionality is already abstracted out into the WebMolKit library, which is designed for a pure web environment, so the project itself provides that modular separation.
The interplay from Main-to-Renderer thread goes something like this:
- the Main thread (running main.js) opens a BrowserWindow
- the BrowserWindow is instructed to load index.html
- index.html includes all the resource files, then immediately calls the runSketchEl function
- runSketchEl (which is in the Renderer thread) wakes up and tries to figure out where it is…
The waking up process involves importing Electron-specific libraries, and starts by unpacking the command line parameters. The implementation is shown in startup.ts (see GitHub), which is part of the TypeScript section. Things get a little bit more interesting due to the fact that like most desktop apps, SketchEl2 needs to open an extra window from time to time, so there is also a function called openNewWindow, which accesses the appropriate Electron library to recirculate the process, and pass several parameters that are needed to create an additional Renderer thread, with its own frame.
Menu handling is rather awkward, since the menus are defined and handled in the Main thread, whereas the code that needs to have control over them lives in the Renderer thread. If the menu is constant, then this is straightforward enough: menus can be generated in the Main thread and send actions to the Renderer thread:
The sendCommand function is rather kludgey, but it works. The Renderer thread has to install a custom event receiver, which can be done early on:
So far the configuration steps described should be largely sufficient to execute the project from within Visual Studio Code, or by typing electron app to launch it from the command line. Creating a self-contained package to distribute to someone who does not have all the development tools installed is accomplished by another npm package called electron-packager, and has a number of parameters that are specific to the desired target. In the SketchEl2 GitHub project, these are expressed in the form of three shell scripts: linbuild.sh, macbuild.sh and winbuild.sh. The Linux and macOS versions have been tested, and they include a couple of kludges needed to workaround temporary limitations, but hopefully these will get trimmed down as the platform tools mature.
This overview covers many of the unfamiliar project configuration and glue procedures that are necessary to get up and running using TypeScript, Visual Studio Code and Electron for desktop web development. The rest of the codebase is mostly the same as regular web programming. There are some other key points at which it is necessary to invoke Electron-specific functionality, such as reading/writing from files on the local filesystem, but this is quite well documented: there are plenty of examples to draw from.