Electron Adventures: Episode 73: NW.js Terminal App
Tags: javascript, electron
Published: 2021-10-05 12:50:14

Now that we setup NW.js, let's try to write a terminal app. Or really let's just port terminal app from episode 9 with minimal changes.

### `index.html`

First, we need to make some placeholder elements for our terminal app to output to.

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="app.css">
  </head>
  <body>
    <h1>NW.js terminal app</h1>
    <div id="terminal">
      <div id="history">
      </div>

      <div class="input-line">
        <span class="prompt">$</span>
        <form>
          <input type="text" autofocus />
        </form>
      </div>
    </div>
    <script src="app.js"></script>
  </body>
</html>
```

### `app.css`

The styling is more or less adapted from previous app.

```css
body {
  margin: 1em;
  background-color: #333;
  color: #fff;
  font-family: monospace;
}

header {
  text-align: center;
  font-size: 400%;
  font-family: monospace;
}

.input-line {
  display: flex;
}

.input-line > * {
  flex: 1;
}

.input-line > .prompt {
  flex: 0;
  padding-right: 0.5rem;
}

.output {
  padding-bottom: 0.5rem;
}

.input {
  color: #ffa;
}

.output {
  color: #afa;
  white-space: pre;
}

form {
  display: flex;
}

input {
  flex: 1;
  font-family: inherit;
  background-color: #444;
  color: #fff;
  border: none;
}
```

### `app.js`

And finally, the code. Other than the first line to open dev tools in a separate window, everything is exactly as in episode 9.

```javascript
nw.Window.get().showDevTools()

let child_process = require("child_process")

let form = document.querySelector("form")
let input = document.querySelector("input")
let terminalHistory = document.querySelector("#history")

function createInputLine(command) {
  let inputLine = document.createElement("div")
  inputLine.className = "input-line"

  let promptSpan = document.createElement("span")
  promptSpan.className = "prompt"
  promptSpan.append("$")
  let inputSpan = document.createElement("span")
  inputSpan.className = "input"
  inputSpan.append(command)

  inputLine.append(promptSpan)
  inputLine.append(inputSpan)

  return inputLine
}

function createTerminalHistoryEntry(command, commandOutput) {
  let inputLine = createInputLine(command)
  let output = document.createElement("div")
  output.className = "output"
  output.append(commandOutput)
  terminalHistory.append(inputLine)
  terminalHistory.append(output)
}

form.addEventListener("submit", (e) => {
  e.preventDefault()
  let command = input.value
  let output = child_process.execSync(command).toString().trim()
  createTerminalHistoryEntry(command, output)
  input.value = ""
  input.scrollIntoView()
})
```

### So what did we learn?

So basically NW.js is just like running Electron in the least secure mode, except dev tools are awkwardly opened in a separate window.

Extra complexity in Electron exists mainly to better isolate (more secured, and higher access) backend code from (less secure, and lower access) frontend code. And that is really only a tool that helps you secure your app, that division is neither necessary nor sufficient for security.

Internally they use different mechanisms to implement the "browser ui + node modules", and for more complicated programs you'd see a difference, but for something simple, you can honestly just use either of them, and it will only differ by small bit of boilerplate code.

### Results

Here's the results:

![Episode 73 Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zj5lkkja4xdstqe8vvio.png)

That's it for NW.js. In the next episode we'll try to check out some other Electron alternatives.

As usual, [all the code for the episode is here](https://github.com/taw/electron-adventures/tree/master/episode-73-nwjs-terminal-app).