-
Notifications
You must be signed in to change notification settings - Fork 3
Hello world (Rollup)
In this example we're going to export a simple function written in Zig into a JavaScript library. It demonstrates the basics of using rollup-plugin-zigar.
We'll first initialize the project. Open a terminal window and run the following commands:
mkdir hello
cd hello
npm init -y
Next, we'll install the necessary npm modules, including rollup-plugin-zigar:
npm install --save-dev rollup rollup-plugin-zigar @rollup/plugin-node-resolve
Then we create a directory for zig files:
mkdir zig
And add hello.zig
:
const std = @import("std");
pub fn hello() void {
std.debug.print("Hello world!", .{});
}
We populate rollup.config.js
with the following:
import nodeResolve from '@rollup/plugin-node-resolve';
import zigar from 'rollup-plugin-zigar';
const input = './zig/hello.zig';
export default [
{
input,
plugins: [
zigar({ optimize: 'ReleaseSmall' }),
nodeResolve(),
],
output: {
file: './dist/index.js',
format: 'esm',
},
},
];
And proceed to create the library:
npx rollup -c rollup.config.js
Compilation will takes some a while the first time. When it finishes, we starts Node in interactive mode and try to use our function:
node
Welcome to Node.js v20.10.0.
Type ".help" for more information.
> const { hello } = await import('./dist/index.js');
Before we managed to call it we'd run into an error:
Uncaught TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11730:11)
at async WebAssemblyEnvironment.instantiateWebAssembly (file:///.../hello/dist/index.js:2414:22)
at async file:///.../hello/dist/index.js:2421:28 {
cause: Error: not implemented... yet...```
By default, rollup-plugin-zigar tries to load WASM using the fetch API.
Since fetch
is incapable of fetching local files (or is absent altogether) in Node, that does not work. To fix this, we can
either tell rollup-plugin-zigar to use readFile
whenever Node is detected:
Zigar({
optimize: 'ReleaseSmall',
useReadFile: true, // <---
}),
Or we can embed the WASM bytecodes in our JavaScript file (as Base64 text) so there is no loading at all:
Zigar({
optimize: 'ReleaseSmall',
embedWASM: true, // <---
}),
After making either change the generated code will work in Node:
Welcome to Node.js v20.10.0.
Type ".help" for more information.
> const { hello } = await import('./dist/index.js');
undefined
> hello();
Hello world!
undefined
>
With the help of copy-and-paste, add a new entry to our configuration so that a CommonJS file gets created also:
input,
plugins: [
zigar({
optimize: 'ReleaseSmall',
embedWASM: true,
topLevelAwait: false, // <---
}),
nodeResolve(),
],
output: {
file: './dist/index.cjs',
format: 'cjs', // <---
},
Because top-level await is only supported by ESM, we must disable the feature here.
Our function should work the same when we import it using require
:
Welcome to Node.js v20.10.0.
Type ".help" for more information.
> const { hello } = require('./dist/index.cjs');
undefined
> hello();
Hello world!
undefined
Without top-level await, however, the function can be asynchronous for a brief moment as the computer compile the WASM bytecodes. You can see this if you combine the two lines of code above into a single a line:
Welcome to Node.js v20.10.0.
Type ".help" for more information.
> const { hello } = require('./dist/index.cjs'); hello();
Promise {
<pending>,
[Symbol(async_id_symbol)]: 290,
[Symbol(trigger_async_id_symbol)]: 288
}
> Hello world!
You can deal with this unavoidable shortcoming in two ways. Either always use await
when calling
functions imported in this manner:
Welcome to Node.js v20.10.0.
Type ".help" for more information.
> const { hello } = require('./dist/index.cjs'); await hello();
Hello world!
undefined
Or perform await
on the init
function from the
special exports __zigar
somewhere in your code prior to anything
happening:
Welcome to Node.js v20.10.0.
Type ".help" for more information.
> const { hello, __zigar } = require('./dist/index.cjs'); await __zigar.init(); hello();
Hello world!
undefined
To create a UMD-compatible JavaScript file, which can be loaded using a <script>
tag,
copy-and-paste the settings from the previous section and add an additional entry:
input,
plugins: [
zigar({
optimize: 'ReleaseSmall',
embedWASM: true,
topLevelAwait: false,
}),
nodeResolve(),
],
output: {
file: './dist/index.umd.cjs',
format: 'umd', // <---
name: 'Hello', // <---
},
After building the library once more, you can test it using the following HTML code:
<!DOCTYPE html>
<html>
<head>
<script src="dist/index.umd.cjs"></script>
<script>
window.onclick = () => Hello.hello();
</script>
</head>
<body>
<h1>Click me!</h1>
</body>
</html>
You can find the complete source code for this example here.
You have just learned how you can easily create a JavaScript library powered by WebAssembly with the help of Zig and Zigar. The library we built in this example doesn't do anything. Our code will do something more useful in the next example.