DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

Electron Adventures: Episode 73: NW.js Terminal App

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.

<!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>
Enter fullscreen mode Exit fullscreen mode

app.css

The styling is more or less adapted from previous app.

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

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()
})
Enter fullscreen mode Exit fullscreen mode

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

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.

Discussion (0)