-
Notifications
You must be signed in to change notification settings - Fork 3
Hello world (Electron)
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.
We begin by creating a barebone Electron App. Open a terminal window and run the following command:
npm init electron-app@latest hello
Go into the newly created hello
directory and add a zig
sub-directory. This is where
we'll keep our Zig source files:
cd hello
mkdir zig
In a text editor, create hello.zig
:
const std = @import("std");
pub fn hello() void {
std.debug.print("Hello world!", .{});
}
In order to use this code, we need to install node-zigar:
npm install node-zigar
Open index.js
in the src
sub-directory and insert the following line at the bottom:
require('node-zigar/cjs');
const { hello } = require('../zig/hello.zig');
hello();
The first line loads node-zigar (CommonJS version), while the second line imports the function we had written earlier.
Return to the terminal window and start the app:
npm run start
You should see something like the following:
> [email protected] start
> electron-forge start
✔ Checking your system
✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [0.3s]
✔ Running generateAssets hook
A message will then appear below informing you that the Node API addon is being built. After half a minute or so a browser window should open. You will also see our test message in the terminal window:
✔ Checking your system
✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [0.3s]
✔ Running generateAssets hook
Hello world!
That confirms our simple app is working.
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-fa7aee6f
📁 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:
require('node-zigar/cjs');
const { hello } = require('../lib/hello.zigar'); // <--
hello();
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
✓ Built module "hello" (win32/x64)
✓ Built module "hello" (win32/arm64)
✓ Built module "hello" (win32/ia32)
✓ Built module "hello" (linux/x64)
✓ Built module "hello" (linux/arm64)
✓ Built module "hello" (darwin/x64)
✓ Built module "hello" (darwin/arm64)
✓ Built Node.js addon (win32/x64)
✓ Built Node.js addon (win32/arm64)
✓ Built Node.js addon (win32/ia32)
✓ Built Node.js addon (linux/x64)
✓ Built Node.js addon (linux/arm64)
✓ Built Node.js addon (darwin/x64)
✓ Built Node.js addon (darwin/arm64)
When the script completes, start the app again to confirm that it's correctly configured.
Next, we edit forge.config.js
, the config file used by
Electron Forge. The first thing we do is
replacing packagerConfig.asar
's boolean value with an object:
module.exports = {
packagerConfig: {
asar: {
unpack: '*.{dll,dylib,so}',
},
ASAR is the archive format used by Electron to store an app's resources. As the OS is not capable of loading libraries directly off the archive, we need to tell Electron to unpack them into an outside directory.
We then add some Regular Expression filters, telling Forge which files should be omitted:
module.exports = {
packagerConfig: {
/* ... */
ignore: [
/\/(zig|\.?zig-cache|\.?zigar-cache)(\/|$)/,
/\/node-zigar\.config\.json$/,
],
},
Depending on which operation system you're using, you might want to adjust the markers
option.
Each package maker has its own set of requirements.
The Squirrel maker, for instance,
requires Windows (or Wine). The RPM and
DEB makers meanwhile work in Linux only and
expect the presence of particular programs.
For simplicity sake (this is merely an example, after all), you might elect to use Zip for all platforms:
makers: [
{
name: '@electron-forge/maker-zip',
},
],
Now comes the time to build the packages. Let us start with Linux:
npm run make -- --platform linux --arch x64,arm64
The process will take some time, as Forge has to download the Electron executable for each architecture. When it finish, we follow up with Windows:
npm run make -- --platform win32 --arch x64,ia32,arm64
Then MacOS:
npm run make -- --platform darwin --arch x64,arm64
Note: If your computer is not running MacOS, the build process for arm64 will fail due to the
absence of the codesign
utility. To eliminate the need to resign the executable, comment out
the FusesPlugin
plugin in the config file.
Once everything is built, you're find the following in the out
directory:
📁 hello-darwin-arm64
📁 hello-darwin-x64
📁 hello-linux-arm64
📁 hello-linux-x64
📁 hello-win32-arm64
📁 hello-win32-ia32
📁 hello-win32-x64
📁 make
The hello-[platform]-[arch]
directories hold copies of the contents that went into the
installation packages. In the make
directory, you'll find the packages themselves:
📁 deb
📁 arm64
📦 hello_1.0.0_arm64.deb
📁 x64
📦 hello_1.0.0_amd64.deb
📁 rpm
📁 arm64
📦 hello-1.0.0-1.arm64.rpm
📁 x64
📦 hello-1.0.0-1.x86_64.rpm
📁 squirrel.windows
📁 arm64
📦 hello-1.0.0-full.nupkg
📦 hello-1.0.0 Setup.exe
📄 RELEASES
📁 ia32
📦 hello-1.0.0-full.nupkg
📦 hello-1.0.0 Setup.exe
📄 RELEASES
📁 x64
📦 hello-1.0.0-full.nupkg
📦 hello-1.0.0 Setup.exe
📄 RELEASES
📁 zip
📁 darwin
📁 arm64
📦 hello-darwin-arm64-1.0.0.zip
📁 x64
📦 hello-darwin-x64-1.0.0.zip
You can find the complete source code for this example here.
Congratulation! You have created your first cross-platform app with the help of Electron and node-zigar. You can try testing the installation packages if you have the respected systems on hand. 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 example we're going to create an app that performs more visible (and useful) work.