How to build an NW.js App using Create React App

To be able to create a full-fledged React application, you should install extra many packages like babel, eslint, webpack, etc.

Also, you should think about the configuration and take extra care about settings.

Luckily, facebook developers create a tool which makes bootstrapping a react application a breeze: Create React App (create-react-app).

Create React App is an officially supported way to create single-page React applications. It offers a modern build setup with no configuration.

(https://facebook.github.io/create-react-app/docs/getting-started)

By using Create React App with just a single command, you can create your react application with a modern build setup.

npx create-react-app my-app
cd my-app
yarn start

It starts automatically a browser which goes to http://localhost:3000.

Now you can begin to develop your groundbreaking application.

React Development Workflow contains a development server, which makes hot module replacement possible.

Hot Module Replacement (HMR) means whenever you modify a component and save the file, cra replaces your module on a page without reloading.

NW.js, as you probably know, is a cross-platform desktop application development framework. Using html, css, javascript like in a web application, you can develop desktop applications.

Besides that, all the node js standard modules and thousands of npm packages are available to use.

Likewise, you can use Angular, Vue, React, or any other frontend framework for your desktop application.

In this blog post, you learn to develop NW.js application using Create React App (cra).

You don’t need to eject cra or creating a fork of react-scripts. Also, we don’t need to use npm packages to manage cra like react-app-rewired, rescripts which customize cra setup without eject.

I’m tending to stay away from installing any extra packages. All packages come with its defects and shortcomings.

Why Should You Avoid Ejecting CRA?

Ejecting CRA means, the configuration settings implicitly made by cra transformed and copied right into your project. So you can change every setting as you wish.

However, ejecting is a one-way street. After ejecting, you are on your own. You can’t benefit from any optimization or bugfix on create-react-app. You deal with all the optimizations and improvements single-handedly.

So I searched and found a solution which does not force me to eject. I have significantly benefited from a two blog post which uses cra for an electron app.

Enough talk. Shall we start?

Developing NW.js application with CRA Development Workflow (Hot Loading)

Firstly create your application using cra:

npx create-react-app nwjs-with-cra
cd nwjs-with-cra
yarn start

Now the development server runs, and the browser opens automatically and visits http://localhost:3000.

react development app
react development app

After creating the skeleton of our application, we are installing some npm modules.

concurrently: concurrently enable us to run programs without opening extra terminals.

yarn add concurrently --dev

wait-on: It waits for an application. In our case, it waits for react application running on localhost:3000.

yarn add wait-on --dev

Create a main.js file under the public directory:

const startUrl = process.env.NWJS_START_URL || '../build/index.html';

nw.Window.open(startUrl, {}, function(win) {});

main.js is the starter file for our sample NW.js app.

You should also modify package.json file. Set main attribute to ‘./public/main.js’ and add an npm script (nwjs-dev).

{
  "name": "myapp",
  "version": "0.1.0",
  "description": "description",
  "main": "./src/main.js",  
  "private": true,
  "homepage": ".",
  "chromium-args": "--mixed-context",
  "node-remote": "http://localhost:3000",
  "build": {
    "nwVersion": "0.39.3"
  },
  "dependencies": {
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.1",
    "wait-on": "^3.3.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "nwjs-dev": "concurrently \"set BROWSER=none&&yarn start\" \"wait-on http://localhost:3000 &&set NWJS_START_URL=http://localhost:3000&&nw .\" "
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "concurrently": "^4.1.1",
    "wait-on": "^3.3.0"
  }
}

Let me explain the nwjs-dev script:

"nwjs-dev": "concurrently \"set BROWSER=none&&yarn start\" \"wait-on http://localhost:3000 &&set NWJS_START_URL=http://localhost:3000&&nw .\" "

I’m using a Windows System. Therefore I set environment variables using the syntax above (set BROWSER=none and set NWJS_START_URL=http://localhost:3000).

If you use Linux, you can enter the nwjs-dev script like below:

"nwjs-dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && NWJS_START_URL=http://localhost:3000 nw .\" "
Runs terminal concurrently: One is the dev server, the other one is nw.
Runs terminal concurrently: One is the dev server, the other one is nw.

How do we use Node Modules like fs in React App?

The trick to use fs or other node modules is adding the node-remote parameter to package.json.

"node-remote": "http://localhost:3000"

Now we can use fs in src/App.js file:

import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';

const fs = window.require('fs');

class App extends Component {
  constructor(props) {
      super(props);
      this.state = {files: []}
  }

  componentDidMount() {
    fs.readdir('/', (err, files) => {
        this.setState({files: files});
    });
  }  

  renderFiles = () => {
    return this.state.files.map((file, index) => {
        return (<p key={index}>{file}</p>);
    })
  };

  render() {
      return (
          <div className="App">
              <div className="App-header">
                  <img src={logo} className="App-logo" alt="logo"/>
                  <h2>Welcome to React/NW.js</h2>
              </div>
              <p className="App-intro">
                  Hello NW.js!
              </p>
              {this.renderFiles()}
          </div>
      );
  }
}

export default App;
nw.js with node fs module
nw.js with fs module

Using NW.js with the React App Production Build

Add two new npm scripts to package.json:

  1. nwjs-reactbuild“: “nw .”
  2. prenwjs-reactbuild“: “yarn build”,

To be able to run NW.js with React production build file, you should add another parameter to package.json. Otherwise, cra wouldn’t find the css files.

"homepage":"."

Now enter the yarn command:

yarn nwjs-reactbuild

Packaging The NW.js App

As the official NW.js documentation recommends, we are using nwjs-builder-phoenix to package our NW.js based app for distribution.

Install nwjs-builder-phoenix:

npm install nwjs-builder-phoenix --save-dev

Add build property to the package.json file:

"build": {
    "nwVersion": "0.39.3"
}

Add a two new npm script (nwjs-pack, prenwjs-pack):

"nwjs-pack": "build --tasks win-x86,win-x64,linux-x86,linux-x64,mac-x64 --mirror https://dl.nwjs.io/ ."
"prenwjs-pack": "yarn build"

To create your deployment package, run the command:

yarn nwjs-pack

You see your packaged application under the dist folder.

CONCLUSION

Now you have learned how to build an NW.js desktop app using create-react-app without ejecting or creating a fork.

The crucial parts (both are applied to package.json) are:

  • Adding the node-remote attribute for using node modules (example: fs).
  • Setting the homepage property.

Here is the Github Repo, which is the final version of the sample project.

Related: How to Use React Dev Tools Chrome Extension In NW.js Without Pain?

Leave a Comment