Skip to content

Hello world (NW.js)

Chung Leong edited this page Apr 25, 2024 · 5 revisions

In this example we're going to create a very simple app that outputs "Hello world!" to the console through Zig. It demonstrates the basics of working with node-zigar and the steps for deploying an app on multiple platforms.

Creating the app

We begin by starting a Node project. Open a terminal window and run the following command:

``sh mkdir hello cd hello npm init -y


Next, we install node-zigar:

```sh
npm install node-zigar

Create directories for storing web contents (.js, .html, .etc) and Zig source code:

mkdir src zig

In the zig directory, add hello.zig:

const std = @import("std");

pub fn hello() void {
    std.debug.print("Hello world!", .{});
}

Then index.js in src:

require('node-zigar/cjs');
const { hello } = require('./zig/hello.zig');
console.log = (s) => process.stderr.write(`${s}\n`);
hello(); 

nw.Window.open('./src/index.html', { width: 800, height: 600 });

And index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>

In package.json, adjust the path to main script:

  "main": "src/index.js",

At this point the app is ready to be tested. If you haven't done so already, download the SDK version of NW.js from its homepage and decompress the package into a directory. In the terminal, run the following command:

[directory]/nw .

You should see our message among various debug messages:

[40512:40512:0322/200448.763374:ERROR:policy_logger.cc(156)] :components/enterprise/browser/controller/chrome_browser_cloud_management_controller.cc(161) Cloud management controller initialization aborted as CBCM is not enabled. Please use the `--enable-chrome-browser-cloud-management` command line flag to enable it if you are not using the official Google Chrome build.
[0322/200448.828318:ERROR:elf_dynamic_array_reader.h(64)] tag not found
[0322/200448.829505:ERROR:elf_dynamic_array_reader.h(64)] tag not found
Hello world!

Configuring the app for deployment

When you use require on a Zig file, node-zigar will place the resultant library file at a temporary location. At the root level of the app you will notice a zigar-cache sub-directory with the following structure:

📁 zigar-cache
  📁 zig-34cc0bd0
    📁 Debug
      📁 hello.zigar
        📑 linux.x64.so
      📁 node-zigar-addon
        📑 linux.x64.node

hello.zigar is a node-zigar module. It's a directory containing dynamic-link libraries for different platforms. node-zigar-addon is the Node.js native addon used to load node-zigar modules. It too comes in platform-specific versions.

The files in the cache directory aren't ones we want delivered to end-users. They're compiled at the Debug level and are therefore large and slow. Moreover, they only cover the platform we're using for development (Linux in this case) and not others (Windows and Mac).

To prepare our app for deployment, we first change the require statement so that it references a .zigar instead, stored at a more permanent location:

const { hello } = require('./lib/hello.zigar');

We then create a configure file for node-zigar with the help of its CLI script:

npx node-zigar init

node-zigar.config.json will be populated with some default options:

{
  "optimize": "ReleaseSmall",
  "sourceFiles": {},
  "targets": [
    {
      "platform": "linux",
      "arch": "x64"
    }
  ]
}

sourceFiles maps .zigar modules to source files. Paths are relative to the config file.

optimize can be Debug, ReleaseSafe, ReleaseSmall, or ReleaseFast.

targets is a list of cross-compile targets. platform and arch can be one of the possible values returned by os.platform and os.arch.

We insert the following into our config file:

{
  "optimize": "ReleaseSmall",
  "sourceFiles": {
    "lib/hello.zigar": "zig/hello.zig"
  },
  "targets": [
    { "platform": "win32", "arch": "x64" },
    { "platform": "win32", "arch": "arm64" },
    { "platform": "win32", "arch": "ia32" },
    { "platform": "linux", "arch": "x64" },
    { "platform": "linux", "arch": "arm64" },
    { "platform": "darwin", "arch": "x64" },
    { "platform": "darwin", "arch": "arm64" }
  ]
}

Then we ask node-zigar to create the necessary library files:

npx node-zigar build
Building hello.zigar:
  win32.x64.dll
  win32.arm64.dll
  win32.ia32.dll
  linux.x64.so
  linux.arm64.so
  darwin.x64.dylib
  darwin.arm64.dylib
Building node-zigar-addon:
  win32.x64.node
  win32.arm64.node
  win32.ia32.node
  linux.x64.node
  linux.arm64.node
  darwin.x64.node
  darwin.arm64.node

When the script finishes, start the app again to confirm that it's correctly configured.

NW.js does not have an official packager. The unofficial ones, meanwhile, are not well maintained. You'll need to create packages manually using these instructions.

It's not a difficult task. Simply download the packages for the platforms you intend to support from the download page, decompress them, then place copies of the app at the appropriate locations. You will need to copy the following:

📁 lib
📁 node_modules
📁 src
📄 package.json

You must omit node-zigar.config.json to prevent node-zigar from attempting recompilation.

Conclusion

Congratulation! You have created your first cross-platform app with the help of NW.js and node-zigar. As console output is hidden when the app is launched from the graphical interface, you might not be entirely convinced it's working correctly. In the next tutorial we're going to create an app that performs more visible (and useful) work.