Electron Adventures: Episode 70: CoffeeScript

History lesson! Once upon a time, not so long ago, JavaScript was trash. Among many things it lacked were:

  • lexical variable scoping
  • classes
  • template literals
  • multiline strings
  • looping over arrays without explicit indexes
  • any sanity with regards to what this referred to
  • concise syntax for declaring and using small functions
  • array or object destructuring
  • ... spread operator
  • and a lot more

It was crazy why anyone would code this way. People were so desperate they were even cross-compiling Java to JavaScript, or coding things in Flash. Or just used jQuery for everything.

The problem was that while JavaScript was terrible, cross-compiling another existing language like Ruby or Python (or Java for some crazy reason) would cause massive issues with interoperability with browser APIs, as they were all designed for JavaScript.

CoffeeScript tried something else - it fixed as much as it could on syntactic level, while keeping mostly to JavaScript-like semantics. It pretty much single-handedly saved JavaScript. I'm pretty sure it it wasn't for CoffeeScript we'd all be coding in Flash today.

Then the mainstream JavaScript incorporated 80% of CoffeeScript features in a way that would be backwards compatible and run natively in the browsers. This put CoffeeScript in an awkward position - not only it lost its main purpose, but ES6 implemented many things in ways that were not quite compatible with how CoffeeScript did it, so targetting ES6 would cause serious issues with existing CoffeeScript codebases.

CoffeeScript also arguably went overboard a bit - there's really no reason why foo is off needs to be a synonym for foo == false (that is foo === false in JavaScript, CoffeeScript intentionally doesn't have sloppy equals).

CoffeeScript 2 attempted to continue CoffeeScript in the post-ES6 world. For sake of nostalgia, let's give it a try.

Let's get started!

The first I discovered is that js2coffee I used years ago no longer works. At least it doesn't support any modern JavaScript features, so I had to write all my CoffeeScript by hand. Oh well, I might still remember some of that.

npm install --save-dev coffeescript electron

package.json

As we're not using any pre-configured template, we need to decide how to structure our source, and write our own package.json.

I decided to put all the source in src, and output it all to public/build.

This results in the following package.json:

{
  "scripts": {
    "build": "coffee -o public/build/ -c src",
    "watch": "coffee -o public/build/ -cw src",
    "electron": "electron ."
  },
  "devDependencies": {
    "coffeescript": "^2.6.0",
    "electron": "^15.1.0"
  },
  "main": "public/build/backend.js"
}

src/backend.coffee

It's a little cleane, but same as before - just open a window with index.html and preload.js. Then quit the app when the window closes.

{app, BrowserWindow} = require("electron")

createWindow = ->
  win = new BrowserWindow
    webPreferences:
      preload: "#{__dirname}/preload.js"
  win.loadFile "#{__dirname}/../index.html"

app.on "ready", createWindow
app.on "window-all-closed", =>
  app.quit()

public/index.html

All we need to do is refer the CSS and compiled JS:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="app.css">
  </head>
  <body>
    <h1>Hello, World!</h1>
    <script src="./build/app.js"></script>
  </body>
</html>

public/app.css

I thought about making it SASS to better match CoffeeScript theme, but it will do like this.

body {
  background-color: #444;
  color: #fff;
}

src/preload.coffee

As a placeholder for something more useful, it just sends a dictionary of version numbers to the frontend:

{ contextBridge } = require("electron")

contextBridge.exposeInMainWorld(
  "api",
  versions: process.versions
)

src/app.coffee

And finally we use browser DOM APIs to print those version numbers:

body = document.querySelector "body"
ul = document.createElement "ul"
body.append ul

for key, val of window.api.versions
  li = document.createElement "li"
  li.append "#{key}: #{val}"
  ul.append li

Run it!

Then we can run it with these two commands:

$ npm run watch
$ npm run electron

Results

Here's the results:

electron-adventures-70-screenshot.png

In the next episode we'll write some games.

As usual, all the code for the episode is here.