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

In this example we're going to create an app that calculates SHA-1 digests of files, using a built-in function of Zig's standard library.

Creating the app

First, we'll create the basic skeleton:

npm create vite@latest
│
◇  Project name:
│  sha1
│
◇  Select a framework:
│  React
│
◇  Select a variant:
│  JavaScript + SWC
│
◇  Use rolldown-vite (Experimental)?:
│  No
│
◇  Install with npm and start now?
│  No
│
◇  Scaffolding project in /home/rwiggum/sha1...
│
└  Done. Now run:

  cd hello
  npm install
  npm run dev
cd hello
npm install
npm install --save-dev rollup-plugin-zigar
mkdir zig

Then we add 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);
}

Add Zigar plugin to vite.config.js:

import react from '@vitejs/plugin-react-swc';
import zigar from 'rollup-plugin-zigar';
import { defineConfig } from 'vite';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [ react(), zigar() ],
})

Made alterations to App.jsx:

import { useCallback, useState } from 'react';
import { sha1 } from '../zig/sha1.zig';
import './App.css';

function App() {
  const [ digest, setDigest ] = useState('-')
  const onChange = useCallback(async (evt) => {
    const [ file ] = evt.target.files;
    if (file) {
      const buffer = await file.arrayBuffer();
      const { string } = sha1(buffer);
      setDigest(string);
    } else {
      setDigest('-');
    }
  }, []);

  return (
    <>
      <div className="card">
        <input type="file" onChange={onChange} />
        <h2>{digest}</h2>
      </div>
    </>
  )
}

export default App

And we're ready to go:

npm run dev
  VITE v5.1.6  ready in 329 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

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 is something that can be interpreted as a text string (e.g. []const u8). With the above declaration in place, we can simplify our JavaScript:

      setDigest(sha1(buffer));

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;
    }
};

Creating production build

Run the usual command and preview the result:

npm run build
npm run preview

You should notice that the app runs faster now than in dev mode. rollup-plugin-zigar by default sets optimize to ReleaseSmall when building for production. That removes overhead from Zig's runtime safety system.

In the build log, you'll notice that the .wasm file is very small:

vite v7.1.9 building for production...
✓ 80 modules transformed.
dist/index.html                   0.46 kB │ gzip:  0.29 kB
dist/assets/sha1-DVS4wUXA.wasm    7.97 kB │ gzip:  3.49 kB
dist/assets/index-Ca74PG3G.css    1.39 kB │ gzip:  0.72 kB
dist/assets/index-Be2U5uMs.js   259.05 kB │ gzip: 82.86 kB
✓ built in 2.06s

Loading will be more efficient if we embed the WebAssembly code into the .js file. In vite.config.js, we make the following change:

export default defineConfig({
  plugins: [ react(), zigar({ embedWASM: true }) ],
})

We get the following when we rebuild:

vite v7.1.9 building for production...
✓ 80 modules transformed.
dist/index.html                   0.46 kB │ gzip:  0.30 kB
dist/assets/index-Ca74PG3G.css    1.39 kB │ gzip:  0.72 kB
dist/assets/index-B_2rI00R.js   269.75 kB │ gzip: 88.26 kB
✓ built in 1.82s

Source code

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

You can see the code in action 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 filter sample

⚠️ **GitHub.com Fallback** ⚠️