NW.js is a tool that helps you creating cross platform desktop application using web technologies.

The default way of working with NW.js consist of providing a folder containing:

A package.json with application name and entry point (ex. index.html or index.js)

A index.html/index.json file to use as entry point

So in this folder we must have all source code and resources used by our application.

If you don’t want to use any web framework, you can just work inside this folder and you don’t need anything else, but if you want to use React that’s another story. We need to setup React, Webpack, Babel and, especially, make the start/build flow work with NW.js.

This guide doesn’t require much previous knowledge of Node or React. We we’ll first try out React as a normal web application, then we’ll modify it to make it work with NW.js.

Chapter 1: Requirements

Install the last stable NodeJS (tested with 12.13.1)

Chapter 2: Setup a Web Application with React

Let’s create a normal web application, for now forget about NW.js.

Inizialize the project

mkdir my-react-app

cd my-react-app

npm init

Install Babel

npm install --save-dev @babel/core@7.7.2 @babel/preset-env@7.7.1 @babel/preset-react@7.7.0

Babel is a javascript transpiler, basically since React syntax uses some Javascript features that are not yet avaiable in browsers (ES6+), we need something that converts our code to browser-supported javascript. (currently ES5).

That’s what babel/core and babel/preset-env are used for.

But that babel/preset-react?

Well… To tell the truth React doesn’t only require some cutting-edge javascript feature, but also a javascript extension called JSX that is not part of any javascript standard. This preset will make Babel able to transpile JSX too.

Note: Since we are using NW.js (basically Chromium) most ES6 feature already work, so we could remove some preset if our application must target nw-only. We would still need Babel and React preset though.

Install Webpack

npm install --save-dev webpack@4.41.1 webpack-cli@3.3.10 webpack-dev-server@3.9.0

To create a web application we need a bundle system that traverse the source graph (all the imports) and provides all the dependencies to the script. For Javascript code, by default it embeds all the code in a single big js file created after the build process. For other kind of resources it can also load them from a relative or external path.

How these dependencies are handled is managed by loaders, for each file extension we’ll have to associate a proper loader.

npm install --save-dev css-loader@3.2.0 style-loader@1.0.0 babel-loader@8.0.6

css-loader : When you import a css file from javascript, it puts the css data into the object returned by the import.

: When you import a css file from javascript, it puts the css data into the object returned by the import. style-loader : This is what actually takes the css data loaded with the css-loader and puts that inside the page.

: This is what actually takes the css data loaded with the css-loader and puts that inside the page. babel-loader: Of course all javascript file loaded must be transpiled to browser-supported javascript

npm install --save-dev copy-webpack-plugin@5.0.5

We’ll see why we installed this plugin later.

Install React

npm install --save react@16.11.0 react-dom@16.11.0

—save-dev vs — save

Did you notice that some npm install have --save and others --save-dev ?

If you search for the formal differences you will find:

--save-dev is used to save the package for development purpose

is used to save the package for development purpose --save is used to save the package required for the application to run

When working with node for web application this definition may seem a bit tricky. After all when we will build the application everything will be bundled right? Since we’ll initially have only one bundle.js file that contains everything, we won’t really need react or any other module to run the application. Then why aren’t we putting react as a dev-dependency?

For web applications this is more like a convenction then a rule (we won’t make users npm install a web application).

You should install dependencies as --save if you directly require a module inside your application code, and --save-dev if the module isn’t related to the modules required by the application (for example tools for bundling/building, testing, transcribing). Development dependendency can be replaced with other similar tools without changing the application behaviour, you can replace Webpack with Gulp but you can’t replace React with Angular and keep your application code the same.

Configure Babel and Webpack

Create the following files

.babelrc

webpack.config.js

Let’s understand what we are doing

entry : Entry point of our web application

: Entry point of our web application rules : We bind loaders used when importing js or css files. For js files we must do the babel transpiration, for css files we need to inject them togheter with the javascript code

: We bind loaders used when importing js or css files. For js files we must do the babel transpiration, for css files we need to inject them togheter with the javascript code output : We just set the webpack output directory and the filename of our bundle that contains all the javascript code of our web application.

: We just set the webpack output directory and the filename of our bundle that contains all the javascript code of our web application. plugins: Here we are using the copy-webpack-plugin installed before. When we build our application, only bundle.js is put inside that directory. No html file is provided to load the script.

With this plugin we tell webpack to copy the index.html template from the src/ directory into the dist/ directory.

You could also just put the index.html inside the dist and that would work, but since the dist/ folder mustn’t be commited it’s more clean to work only in src/ directory.

Create the application

Create the following files:

src/index.html

src/index.js

src/App.js

Run the application

Edit the package.json scripts section like this:

"scripts": {

"start": "webpack-dev-server --open --mode development",

"build": "webpack --mode production"

},

Now we can build the application with

npm run build

You can check that a dist/ folder has been created with our build.js and index.html.

If you open the index.html file you will see that the application displays the Hello World message.

We can also run it in the browser with this command for development

npm start

You can find the result until this point here:

Chapter 3: MaterialUI + React

One of the cool thing of developing a desktop application using web technologies is that we can find a lot of UI frameworks to style our application. In this guide i’ll use MaterialUI since it’s one of the most popular. If you prefer a native style, there are many alternatives like React Desktop.

You may just use boostrap and include the style in the index.html file, but since there are framework especially designed for React it’s better to go for them. This article explains why in detail.

MaterialUI also requires Roboto font to be installed. MaterialUI guide suggest to add in the index.html a remote link to the font but that’s not good for us. Since our final goal is to make a local Desktop Application we need to have each dependency locally.

The style from the MaterialUI guide is the following:

https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap

We can see that it requires Google’s Roboto font for weights 300, 400, 500, 700.

Downloading each font manually would be boring, go at this Website, search the robot Font and select the weights 300, Regular, 500, 700. Choose “Modern Browsers” and download the zip file.

Alternativelly, just click here.

Create these folders: src/template-res, src/template-res/css, src/template/fonts

src

└───template-res

├───css

└───fonts

Move index.html inside template-res folder

folder Move all *.woff2 font files into fonts

font files into Create a file roboto-font.css inside css folder

Edit index.html and add a style tag inside head

<link rel="stylesheet" "href="css/roboto-font.css">

Edit webpack.config.js like this (we need to copy entire template-res folder into dist now, so that font and css are avaiable locally)

Edit App.js

Now you can start or build you application and you will see the following:

Note: Check the web console to see if the font has been loaded correctly

You can find the result until this point here:

Chapter 4: NW.js

Let’s install nw.

npm install --nwjs_build_type=sdk -g --unsafe-perm nw@0.42.5

With the -g option we are installing nw globally so that’s you don’t need a copy of that for each project. You can install it locally if you prefer, but consider it would take 200MB.

With the nwjs_build_type option we ask for the sdk build so that we also have developer tools installed.

You also need to add node’s global npm directory into the path in order to test nw, check which directory it is with:

npm get prefix

And add it to your paths.

If you now build your project with

npm run build

and put inside dist/ a packakge.json file like this:

{

"name": "helloworld",

"main": "index.html"

}

and run

nw dist/ # won't work if you didn't set the global path for npm

You will se that it works:

But we still need to setup some stuff to make the development and distribution enviroment fully operational.

At least:

Generate the dist/package.json automatically

Debug the application

Hot reload on source change

Chapter 5: Creating custom debug and build scripts

If you get lost during this chapter, you can check the complete project sure at Github.

To make debugging work we have to do the following:

Passing --remote-debugging-port to nw to allow an external ide to attach to the application

to nw to allow an external ide to attach to the application Compile the application with webpack mode set to development

set to Enable webpack’s source_map

Tell webpack that we are working with node-webkit (nw.js) through target option so that we can import nodejs modules.

To make hot reloading work we need the following:

webpack-dev-server running with HotModuleReplacementPlugin

running with HotModuleReplacementPlugin react-hot-loader babel plugin

Since Nw.js just wants a directory with all the files, running with webpack-dev-server is a bit tricky. First of all, by default it doesn’t copy files into any directory, webpack-dev-server has a ram filesystem that provides all files of the application. (we could force write to disk with writeToDisk option, but hot reload do a lot of IO operation that would strain the disk too much)

What we could do is provide to Nw.js a package.json file created runtime that actually tells Nw.js to load localhost instead of a local file.

That works but i encontered an issue with this approach on the current version of Nw.js, when reloading Nw.js application by doing Right Click -> Reload application it didn’t work.

My solution is to create a debug index.html file that embeds an iframe pointing to localhost.

Ok, that should work. What if i have a local resource that my application needs to load? Like a database or the application icon.

We need to customize the build process and copy or link the files to the build directory.

Let’s first re-think our src directory

src

│ App.js

│ index.js

│ nw_package.json

│

├───res

│ icon.png

│

└───template-res

│ index.html

│

├───css

│ font.css

│

└───fonts

roboto-v20-latin-300.woff2

roboto-v20-latin-500.woff2

roboto-v20-latin-700.woff2

roboto-v20-latin-regular.woff2

template-res: It still contains all view-related resources. These will be handled by webpack and copied in the memory file system when developing.

res: These are resources that are not handled by webpack. These will be copied or symlinked to the build directory, so that the application can access external data like a classic desktop application.

We also have moved package.json into the root of src and renamed it to nw_package.json. This avoid confusion with package.json of node and some issues that makes webpack think that the directory is a different package.

Since we are customizing the build process, we’ll handle the copy of package.json ourselves.

Debug configuration

Let’s create a config folder in the root of our project. Here we are going to write custom scripts for building and debug.

First, we define a common webpack configuration that we will use both for debug and build. Delete the old webpack.config.js and create the following in the config/ folder.

webpack.config.js

Add these js scripts below in the config/ directory.

This script runs webpack-dev-server and launchs nw automatically (nw must be in the enviroment path!), enabling everything we need for debugging. Check the comments for additional informations.

debug.js

utils.js

Add the below html file in the config/ diretory. This is the iframe that loads our application. Only for debug.

index_debug.html

We almost finished, now we need the following modifications:

We require a babel plugin to make hot reloading work with React:

npm install --save-dev react-hot-loader@4.12.18 @hot-loader/react-dom @16.11.0

modify .babelrc like this

Change our src/App.js to this.

I changed the application a bit to test debugging, the important things are the first line importing hot, and the last line enabling App module hot reloading (export default hot(App))

Modify the “start” script of the package.json (the one inside the root of our project. NOT the one of nw).

"scripts": {

"start": "node config/debug.js",

},

Start the application with:

npm start

Build configuration

Add into the config/ directory

build.js

This script only builds the application.

Modify the “build” script of the package.json (the one inside the root of our project. NOT the one of nw).

"scripts": {

"start": "node config/debug.js",

"build": "node config/build.js"

},

Run the build:

npm run build

Debugging with VSCode

We can also debug with VSCode by attachingt the remote debug port.

npm start

Create a new debug configuration inside VSCode and change launch.json into

Start the debugger:

Result

You can get the complete project from here