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.
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 zigThen 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 AppAnd 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
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;
}
};Run the usual command and preview the result:
npm run build
npm run previewYou 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
You can find the complete source code for this example here.
You can see the code in action here.
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.