Skip to content

SHA1 digest (Vite)

Chung Leong edited this page Apr 25, 2024 · 4 revisions

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
Need to install the following packages:
create-vite@5.2.2
Ok to proceed? (y) y
✔ Project name: … sha1
✔ Select a framework: › React
✔ Select a variant: › JavaScript + SWC
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({ topLevelAwait: false }) ],
})

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

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.

The build log, you'll notice that there is no gzip estimate for the .wasm file:

vite v5.1.6 building for production...
✓ 34 modules transformed.
dist/index.html                   0.46 kB │ gzip:  0.29 kB
dist/assets/sha1-RsZosFvf.wasm    7.97 kB
dist/assets/index-DiwrgTda.css    1.39 kB │ gzip:  0.72 kB
dist/assets/index-lTKY5Jez.js   190.91 kB │ gzip: 61.49 kB
✓ built in 7.35s

WebAssembly binaries are actually highly compressible (> 50%). Web servers typically don't recognize this, however, and will not apply compression on them. For this reason, it can be profitable to embed the WASM binary in the JavaScript file.

In vite.config.js, we make the following change:

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

We get the following when we rebuild:

vite v5.1.6 building for production...
✓ 34 modules transformed.
dist/index.html                   0.46 kB │ gzip:  0.29 kB
dist/assets/index-DiwrgTda.css    1.39 kB │ gzip:  0.72 kB
dist/assets/index-DoBMgcnF.js   201.57 kB │ gzip: 66.83 kB
✓ built in 1.50s

A saving of about 3K.

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