Diogo Souza Brazilian dev. Creator of altaluna.com.br

Creating cross-platform apps with NW.js

5 min read 1553

Cross Platform Apps Nwjs

NW.js, previously known as node-webkit, is among the community’s favorite Electron alternatives for native application development. Like its competitor, NW.js also enables the easy setup of cross-platform desktop applications simply by using the core trio of HTML, CSS, and JavaScript.

You code as if you’re creating any ordinary web application with your favorite tools (React, Angular, Less, webpack, etc.), and in the end, NW.js takes care of translating everything from the web techs to native ones.

Many apps have adopted NW.js already as their hybrid development framework. And all this is possible due to Chromium, the same open source browser owned by Google that powers not only the company’s web browser, but also dozens of other dependent projects out there.

Apart from that, NW.js offers a range of great integrations, including native OS-based hardware and file system access, integration with the shell, and many more.

In this article, I’m going to walk you through the first steps toward implementing a small web app running on NW.js. Let’s jump right into it!

Project setup

As mentioned before, since there’s no need for any specific installation or special setup, NW.js gives you the flexibility to opt for whatever environment and tech stack you’d prefer. You can have almost any sort of web application built on top of your favorite framework or library running alongside NW.js.

Plus, because the project doesn’t provide any type of scaffolding tool (except for some specific rules for manifest formats, for example), this tutorial explores how you’d create the project from scratch using the most basic setup: HTML, CSS, and JavaScript only.

So, as a first step, make sure to reproduce the same structure of folders and files demonstrated below:

NW Project Folder File Structure
Project’s folder and file structure.

The most important folder here is the src, as you may have guessed. That’s where you separate the NW.js main files. Actually, everything related to NW.js belongs to this file.

For some of the auxiliary files such as images and favicons, you can add whatever you like.

We made a custom demo for .
No really. Click here to check it out.

Note also that there are two package.json files. That happens because a separate src/package.json may be created to store the manifest configs, as stated here. The other is just the ordinary package.json to handle the project dependencies and build commands.

Speaking of the package.json file, its contents are below:

{
  "name": "logrocket_nwjs_example",
  "version": "1.0.0",
  "description": "A demo app to show how NW.js works",
  "main": "index.html",
  "window": {
    "icon": "assets/icon.png",
    "height": 500,
    "width": 400,
    "resizable": true,
    "title": "Hello World, NW.js"
  },
  "dependencies": {
    "bootstrap": "^4.6.0",
    "jquery": "^3.6.0"
  }
}

You can change some of the information as you please, but make sure you always have name and version attributes since they’re obligatory.

Then, run the following command in the src folder to download the required dependencies:

yarn install

This command will download the Bootstrap and jQuery dependencies since you’ll need them to create a better look and feel.

The width and height correspond to the dimensions of your app screen when it first starts up, while the resizable property is important to configure if you allow users to resize the app window.

The next stop is the index.html content. Ddd the following HTML code into it:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>NW.js-example | main</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body class="text-center">
    <form>
      <img
        class="mb-4"
        src="https://res.cloudinary.com/practicaldev/image/fetch/s--TXdRGx5X--/c_fill,f_auto,fl_progressive,h_320,q_auto,w_320/https://dev-to-uploads.s3.amazonaws.com/uploads/organization/profile_image/1506/e0a84c58-6a79-4f06-9149-87a38b84afa8.png"
        alt=""
        width="72"
        height="72"
      />
      <h2>Temperature Converter</h2>
      <p>
        Type the value you want to convert from Fahrenheit to Celsius and vice versa:
      </p>

      <div class="form-group row">
        <label for="fahrenheit" class="col-sm-2 col-form-label"
          >Fahrenheit:</label
        >
        <div class="col-sm-10">
          <input
            id="fahrenheit"
            class="form-control"
            type="number"
            placeholder="Fahrenheit"
          />
        </div>
      </div>
      <div class="form-group row">
        <label for="celsius" class="col-sm-2 col-form-label"
          >Celsius:</label
        >
        <div class="col-sm-10">
          <input
            id="celsius"
            class="form-control"
            type="number"
            placeholder="Celsius"
          />
        </div>
      </div>
    </form>

    <script src="../js/app.js"></script>
    <script src="node_modules/jquery/dist/jquery.min.js"></script>
    <script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
  </body>
</html>

That’s a basic Bootstrap structure for a form component, with the two temperature fields to convert from Fahrenheit to Celsius and vice versa.

Pay attention to the CSS import at the beginning of the file, and the JavaScript corresponding to Bootstrap, jQuery, and the app’s main JS file, respectively.

The following code belongs to the js/app.js file:

const celsius = document.getElementById("celsius");
const fahrenheit = document.getElementById("fahrenheit");

fahrenheit.addEventListener("change", (e) => {
  convertToCelsius(e.target.value);
});

celsius.addEventListener("change", (e) => {
  convertToFahrenheit(e.target.value);
});

function convertToCelsius(valNum) {
  valNum = parseFloat(valNum);
  celsius.value = (valNum - 32) / 1.8;
}

function convertToFahrenheit(valNum) {
  valNum = parseFloat(valNum);
  fahrenheit.value = valNum * 1.8 + 32;
}

They correspond to each of the event listeners for when the fields change their value. They will then trigger the proper conversion and update the field at the end.

The styles are important for the example, too. Aside from those imported from Bootstrap, you need some basic CSS rules to display the content in a centralized way. The following corresponds to the content you should add to the style.css file:

@import url("../node_modules/bootstrap/dist/css/bootstrap.min.css");

html,
body {
  height: 100%;
}

body {
    display: flex;
    align-items: center;
}

form {
  padding: 15px;
  margin: 0 auto;
}

Testing

Before testing this implementation as a real NW.js desktop application, it’s worth running the app as an ordinary web app on your browser. You can easily do so by adding the following content to your index.js file:

const express = require("express");
const path = require("path");

const app = express();

const PORT = process.env.PORT || 3000;
app.use(express.static(path.join(__dirname, "src")));

app.listen(PORT, () => {
  console.log("Server listening on port: %d", PORT);
});

Since it is located at the root folder of your project, you can start an Express.js server and serve the static files naturally.

Finally, let’s take a look at the other package.json file, which is also located in the root project folder. This is the content you should add to it:

{
  "name": "logrocket_nwjs_example",
  "version": "1.0.0",
  "description": "A demo app to show how NW.js works",
  "private": true,
  "scripts": {
    "start": "node index.js",
    "start-nw": "nw src/",
    "build-nw": "nwbuild --platforms win64,osx64,linux64 --buildDir dist/ src/"
  },
  "dependencies": {
    "express": "^4.17.1",
    "nw": "^0.52.0",
    "nw-builder": "^3.5.7"
  }
}

Here, you can see the dependencies related to NW.js itself and the nw-builder, which is a powerful plugin to help with the generation of OS-specific installers.

Make sure to also run the yarn install command on the root folder to download these npm packages.

After that, run the following command to start the application via the Express server:

yarn start

The app will be available at http://localhost:3000/. You’ll see the following screen when you access it:

Testing App Access Screen Temperature Converter
Temperature converter app running on the Express server.

Go ahead and test both fields to check if they’re properly converting and updating the temperatures.

To run the app in NW.js mode, simply execute the following command:

yarn start-nw

This will trigger the NW.js library to generate everything needed for the following screen to show up:

Temperature Converter App Running NW
Temperature converter running on NW.js

And, finally, in order to generate any OS-specific installer you can run the following command:

yarn build-nw

This command may take a while because it’ll download and install all the required dependencies.

Note that based on the configs of this command — which we set up in the package.json file — it’ll generate installers for Windows, Linux, and macOS (64 bits) and store them under the dist/ folder of your project.

You mustn’t change any of the project files during the execution of this command; this can lead to further inconsistencies.

Take a look at the output in the image below:

Command Execution Project Files
Generated installers for Linux, Windows, and OSX.

If you access the folder via Finder (on macOS) and execute the logrocket_nwjs_example.app file, you’ll see the same application start up.

Conclusion

NW.js gives you the flexibility to freely choose whatever tools you’d like to work with. It’s truly great to be able to create hybrid applications by focusing only on the web implementation you’re already familiar with.

Of course, NW.js is not the only Electron alternative out there, but it’s a powerful one if you’re willing to extract the web capabilities into a desktop app. I can’t finish without emphasizing the importance of going through the official docs to learn more about what NW.js is capable of.

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Diogo Souza Brazilian dev. Creator of altaluna.com.br

Leave a Reply