SHA1 digest (Rollup) - chung-leong/zigar GitHub Wiki

JavaScript | PHP


In this example we're going to export a function for calculating SHA-1 digests.

Creating the project

We first initialize the Node project and install the necessary modules:

mkdir sha1
cd sha1
npm init -y
npm install --save-dev rollup rollup-plugin-zigar @rollup/plugin-node-resolve
mkdir zig

Next we create sha1.zig:

const std = @import("std");

pub fn sha1(bytes: []const u8) [std.crypto.hash.Sha1.digest_length * 2]u8 {
    var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined;
    std.crypto.hash.Sha1.hash(bytes, &digest, .{});
    return std.fmt.bytesToHex(digest, .lower);
}

Then rollup.config.js:

import nodeResolve from '@rollup/plugin-node-resolve';
import zigar from 'rollup-plugin-zigar';

const input = './zig/sha1.zig';

export default [
  {
    input,
    plugins: [
      zigar({
        optimize: 'ReleaseSmall',
        embedWASM: true,
      }),
      nodeResolve(),
    ],
    output: {
      file: './dist/index.js',
      format: 'esm',
    },
  },
];

Make adjustments to package.json:

  "type": "module",
  "scripts": {
    "build": "rollup -c rollup.config.js"
  },

We're ready to build:

npm run build

Time for some testing in Node:

Welcome to Node.js v22.16.0.
Type ".help" for more information.
> const { sha1 } = await import('./dist/index.js');
undefined
> sha1('hello world').string;
'2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'

We would get the same digest if we calculate it using sha1sum:

echo -n "hello world" | sha1sum
2aae6c35c94fcfb415dbe95f408b9ce91ee846ed  -

Making return value more JS-friendly

In the example, sha1() returns an array of u8. On the JavaScript side it's represented by a object. To get a string, you need to access its string property.

Zigar lets you to flag certain functions as returning strings. To do so, you declare a struct type with a particular name at the root level:

const module_ns = @This();
pub const @"meta(zigar)" = struct {
    pub fn isDeclString(comptime T: type, comptime name: std.meta.DeclEnum(T)) bool {
        return switch (T) {
            module_ns => switch (name) {
                .sha1 => true,
                else => false,
            },
            else => false,
        };
    }
};

During export, isDeclString() is invoked when a function's return value something that can be a interpreted as a text string (e.g. []const u8). With the above declaration in place, we can simplify our JavaScript:

Welcome to Node.js v22.16.0.
Type ".help" for more information.
> const { sha1 } = await import('./dist/index.js');
undefined
> sha1('hello world');
'2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'

You can use the following to threat all occurences of u8 and u16 as text:

pub const @"meta(zigar)" = struct {
    pub fn isDeclString(comptime T: type, comptime _: std.meta.DeclEnum(T)) bool {
        return true;
    }

    pub fn isFieldString(comptime T: type, comptime _: std.meta.FieldEnum(T)) bool {
        return true;
    }
};

Source code

You can find the complete source code for this example here.

Conclusion

This example is still relatively simple. All we're doing is calling a function. It accepts an uncomplicated argument and returns an uncomplicated value. In the next example, the function involved will take more complicated arguments and return something complicated as well.


Image processing sample