-
Notifications
You must be signed in to change notification settings - Fork 3
Hello world (Node)
In this example we're going to use a create a very simple server-side 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 in different server environments.
We'll first initialize the project. Open a terminal window and run the following commands:
mkdir hello
cd hello
npm init -y
Next, we install node-zigar:
npm install node-zigar
Then we create one directory for JavaScript files and another for Zig files:
mkdir src zig
In a text editor, we create hello.zig
:
const std = @import("std");
pub fn hello() void {
std.debug.print("Hello world!", .{});
}
Followed by index.js
:
import { hello } from '../zig/hello.zig';
hello();
In package.json
, we activate ESM and add a script entry for running our app:
"type": "module",
"scripts": {
"start": "node --loader=node-zigar --no-warnings src/index.js"
},
Then we run it:
npm run start
Nothing will happen for some time as the Zig compiler compiles the Zig file into native code. The native addon used by node-zigar is also compiled at this point. After 30 seconds or so, the following should appear in the terminal:
Hello world!
In your text editor, create the new file index.cjs
:
require('node-zigar/cjs');
const { hello } = require('../zig/hello.zig');
hello();
Add an script entry for running it:
"scripts": {
"start": "node --loader=node-zigar --no-warnings src/index.js",
"start:cjs": "node src/index.cjs"
},
And run it:
npm run start:cjs
The CommonJS version of node-zigar has the advantage of not needing command-line flags. For this reason, you might choose to use it even in a ESM project.
Create another file called index.async.js
:
const { createRequire } = await import('node-zigar/cjs');
const { hello } = createRequire(import.meta.url)('../zig/hello.zig');
hello();
Add another script entry:
"scripts": {
"start": "node --loader=node-zigar --no-warnings src/index.js",
"start:cjs": "node src/index.cjs",
"start:async": "node src/index.async.js"
},
And run it:
npm run start:async
When you use import
or 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-6c2c904
📁 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 might not match the eventual server environment.
To prepare our app for deployment, we first change the import
statement so that it references a
.zigar
instead, stored at a more permanent location:
import { hello } from '../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": "linux", "arch": "x64" },
{ "platform": "linux", "arch": "arm64" },
{ "platform": "linux-musl", "arch": "x64" },
{ "platform": "linux-musl", "arch": "arm64" }
]
}
Then we ask node-zigar to create the necessary library files:
npx node-zigar build
Building hello.zigar:
linux.x64.so
linux.arm64.so
linux-musl.x64.so
linux-musl.arm64.so
Building node-zigar-addon:
linux.x64.node
linux.arm64.node
linux-musl.x64.node
linux-musl.arm64.node
Run npm run start
again to confirm that the configuration is correct.
Inclusion of linux-musl
in the configuration above enables our app to work in a Linux
environment where glibc is absent like Alpine. If you have Docker installed, you can verify that
it does indeed work with the following command:
docker run --rm -v ./:/test -w /test node:alpine npm run start
When you upload the app to a server, omit files in the zig
directory and node-zigar.config.json
so that node-zigar will never attempt recompilation.