lifehashjs
v1.1.4
Published
A framework-agnostic JavaScript library for generating LifeHash visualizations from strings
Downloads
50
Maintainers
Readme
LifeHashJS
A framework-agnostic JavaScript library for generating LifeHash visualizations from strings. LifeHash is a beautiful method of hash visualization based on Conway's Game of Life.
About This Library
This library is a JavaScript wrapper around the LifeHash reference implementation by Blockchain Commons. It provides a pre-built WebAssembly (WASM) version of LifeHash, so you don't need to compile the C++ source code yourself. Simply install and use!
Platform note: This package targets web environments (browsers and Node.js/bundlers). React Native is not supported directly; for RN, use a WebView wrapper or backend generation.
Original Implementation: BlockchainCommons/LifeHash (Swift/C++ reference implementation)
What This Library Does:
- Provides a pre-built WASM binary compiled from the original LifeHash C++ code
- Exposes a simple, framework-agnostic JavaScript API
- Handles WASM module loading and initialization automatically
- Works in browsers and Node.js environments
Credits:
- Original LifeHash concept and implementation by Blockchain Commons
- Created by Wolf McNally and Christopher Allen
- This JavaScript wrapper makes LifeHash accessible to web developers without requiring C++ compilation
Installation
npm install lifehashjs
# or
pnpm install lifehashjs
# or
yarn add lifehashjsQuick Start
import { generate, ELifeHashVersion } from "lifehashjs";
// Generate a LifeHash from a string
const result = await generate("Hello, World!", {
version: ELifeHashVersion.version2,
});
// Use the data URI in an image element
const img = document.createElement("img");
img.src = result.dataUri;
img.width = result.width;
img.height = result.height;
document.body.appendChild(img);Usage Examples
Vanilla JavaScript / HTML
<!DOCTYPE html>
<html>
<head>
<title>LifeHash Example</title>
</head>
<body>
<img id="lifehash" alt="LifeHash" />
<script type="module">
import { generate, ELifeHashVersion } from "lifehashjs";
async function displayLifeHash() {
const result = await generate("Hello, World!", {
version: ELifeHashVersion.version2,
});
const img = document.getElementById("lifehash");
img.src = result.dataUri;
img.width = result.width;
img.height = result.height;
}
displayLifeHash();
</script>
</body>
</html>React Example (web)
Basic Component Usage
import React, { useEffect, useState } from "react";
import { generate, ELifeHashVersion } from "lifehashjs";
function LifeHashImage({ input }) {
const [dataUri, setDataUri] = useState(null);
useEffect(() => {
generate(input, { version: ELifeHashVersion.version2 }).then((result) =>
setDataUri(result.dataUri)
);
}, [input]);
if (!dataUri) return <div>Loading...</div>;
return <img src={dataUri} alt="LifeHash" />;
}Framework-agnostic core utility
You can also use a shared, framework-agnostic generator that manages cancellation and optional preloading. Import it from the package root:
import {
lifeHashGenerator,
ELifeHashVersion,
TCoreLifeHashOptions,
TCoreLifeHashState,
} from "lifehashjs/lifehash-utils";
const unsubscribe = lifeHashGenerator.subscribe((state: TCoreLifeHashState) => {
// handle updates (e.g., setState in React/Vue/Svelte)
console.log(state);
});
lifeHashGenerator.generate("hello", {
version: ELifeHashVersion.version2,
preload: true,
} as TCoreLifeHashOptions);
// later, to cancel:
lifeHashGenerator.cancel();
// to stop listening:
unsubscribe();Custom Hook Usage (manual, AbortController built-in)
For a more robust implementation with error handling and loading states, you can create a custom hook using generateWithAbort, which accepts an AbortSignal and avoids manual cancel flags. Use the exported TLifeHashAsyncState type instead of defining your own:
import {
ELifeHashVersion,
generateWithAbort,
TLifeHashAsyncState,
} from "lifehashjs";
import { useEffect, useState } from "react";
const useLifeHash = (hash: string): TLifeHashAsyncState => {
const [state, setState] = useState<TLifeHashAsyncState>({
dataUri: null,
error: null,
isLoading: true,
});
useEffect(() => {
setState({ dataUri: null, error: null, isLoading: true });
async function generateLifeHash() {
const controller = new AbortController();
try {
const result = await generateWithAbort(hash, {
version: ELifeHashVersion.version2,
signal: controller.signal,
});
if (result?.dataUri) {
setState({ dataUri: result.dataUri, error: null, isLoading: false });
} else {
setState({ dataUri: null, error: null, isLoading: false });
}
} catch (err) {
if ((err as Error & { name?: string })?.name !== "AbortError") {
setState({ dataUri: null, error: err as Error, isLoading: false });
}
}
return controller;
}
const ctrl = generateLifeHash();
return () => {
ctrl?.abort?.();
};
}, [hash]);
return state;
};
export default useLifeHash;Then use it in your components:
import React from "react";
import useLifeHash from "./hooks/useLifeHash";
function LifeHashImage({ hash }: { hash: string }) {
const { dataUri, error, isLoading } = useLifeHash(hash);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!dataUri) return null;
return <img src={dataUri} alt="LifeHash" />;
}Benefits of the custom hook:
- ✅ Clean async/await syntax (no promise chaining)
- ✅ Automatic cleanup on unmount or hash change
- ✅ Loading and error states included
- ✅ TypeScript support with proper types
- ✅ Automatic initialization (no public
init()required)
Vue Example (web)
<template>
<img v-if="dataUri" :src="dataUri" alt="LifeHash" />
<div v-else>Loading...</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { ELifeHashVersion, generateWithAbort } from "lifehashjs";
const props = defineProps(["input"]);
const dataUri = ref(null);
const error = ref(null);
const isLoading = ref(false);
watch(
() => props.input,
async (newInput) => {
isLoading.value = true;
error.value = null;
dataUri.value = null;
const next = await generateWithAbort(newInput, {
version: ELifeHashVersion.version2,
signal: controller.signal,
});
dataUri.value = next.dataUri;
error.value = null;
isLoading.value = false;
return controller;
},
{ immediate: true }
);
</script>
</script>Node.js Example
const { generate, ELifeHashVersion } = require("lifehashjs");
async function createLifeHash() {
const result = await generate("Hello, World!", {
version: ELifeHashVersion.version2,
});
// The result contains a data URI that can be used or saved
console.log("Data URI:", result.dataUri);
console.log("Dimensions:", result.width, "x", result.height);
// You can convert the data URI to a buffer if needed
const base64Data = result.dataUri.split(",")[1];
const buffer = Buffer.from(base64Data, "base64");
// Save buffer to file, send in HTTP response, etc.
}API Reference
generate(input: string, options?: TLifeHashOptions): Promise<TLifeHashResult>
Generate a LifeHash image from a string input. This is the most common method.
Parameters:
input(string): The string to generate a LifeHash fromoptions(optional): Configuration objectversion(ELifeHashVersion): The LifeHash version to use (default:ELifeHashVersion.version2)moduleSize(number): Module size multiplier (default: 1)
Returns: Promise that resolves to a LifeHashResult object:
dataUri(string): Data URI of the generated image (e.g.,"data:image/png;base64,...")width(number): Image width in pixelsheight(number): Image height in pixelsversion(ELifeHashVersion): The version used
Example:
const result = await generate("My input string");
console.log(result.dataUri); // "data:image/png;base64,iVBORw0KGgo..."generateFromData(data: Uint8Array, options?: TLifeHashOptions): Promise<TLifeHashResult>
Generate a LifeHash image from binary data (Uint8Array).
Example:
const data = new Uint8Array([1, 2, 3, 4, 5]);
const result = await generateFromData(data);generateFromDigest(digest: Uint8Array, options?: TLifeHashOptions): Promise<TLifeHashResult> (advanced / optional)
Generate a LifeHash image from a SHA-256 digest. Use this only if you already have a canonical 32-byte digest and want to skip hashing inside the module. The digest must be exactly 32 bytes.
Example:
const digest = new Uint8Array(32); // Your 32-byte SHA-256 digest
const result = await generateFromDigest(digest);sha256(input: string): Promise<Uint8Array> (advanced / optional)
Compute SHA-256 hash of a string via the WASM module. Optional helper; most users can rely on generate/generateWithAbort instead.
Example:
const hash = await sha256("Hello, World!");
console.log(hash); // Uint8Array(32) [185, 77, 39, ...]LifeHash Versions
ELifeHashVersion.version1- The original (DEPRECATED)ELifeHashVersion.version2- Bug fixes and CMYK-friendly (recommended)ELifeHashVersion.detailed- Twice the resolution, CMYK-friendlyELifeHashVersion.fiducial- Optimized for machine vision recognitionELifeHashVersion.grayscaleFiducial- Highest contrast for low-light situations
Framework Support
This library is framework-agnostic and works with any JavaScript environment:
- ✅ React - Use in components with hooks or effects
- ✅ Vue - Use in composition API or options API
- ✅ Angular - Use in services or components
- ✅ Svelte - Use in components or stores
- ✅ Vanilla JavaScript - Use directly in any JS file
- ✅ Node.js - Works in server-side environments
- ✅ Web Workers - Can be used in worker threads
- ✅ Bundlers - Compatible with webpack, Vite, Rollup, etc.
- ⚠️ React Native - Not supported directly. Options if needed:
- Run on a Node server/edge function and return data URIs/base64 to the app (recommended for simplicity).
- Use a WebView to run the web build and post the data URI back.
- Best native experience: build a native module using the platform LifeHash implementations.
How It Works
This library uses a pre-compiled WebAssembly (WASM) binary of the original LifeHash C++ implementation. The WASM file is included in the package, so you don't need to:
- ❌ Compile C++ code
- ❌ Set up Emscripten
- ❌ Build WASM from source
- ❌ Configure build tools
Simply install the package and start using it! The library handles:
- ✅ WASM module loading and initialization
- ✅ Automatic method attachment after WASM runtime is ready
- ✅ Memory management
- ✅ Image generation and conversion to data URIs
- ✅ Cross-platform path resolution (browser and Node.js)
- ✅ Security: Hardcoded WASM file paths to prevent path traversal attacks
Technical Details
- WASM Binary: Pre-compiled from the original bc-lifehash C++ implementation using Emscripten
- Module Format: ES modules (with CommonJS fallback via
index.cjs) - TypeScript: Full type definitions included
- Browser Support: All modern browsers with WebAssembly support
- Node.js Support: Node.js 18+ (for WASM support)
- Initialization: The library automatically initializes the WASM module on first use. There is no public
init()API; uselifehashjs/lifehash-utilsif you want controlled preloading. - Emscripten Compatibility: The WASM build exports
ccall/cwrapvia Emscripten runtime exports, so no global shims are needed.
Optional: Rebuilding the WASM engine (advanced)
This package ships a prebuilt lifehash.wasm + lifehash.js. You only need this section if you want to rebuild those artifacts yourself (e.g., auditability, custom flags, or upgrading the underlying C++ implementation).
Inputs and outputs
- Input: The
bc-lifehashC++ sources (src/*.cpp) and a small post-js hook (wasm/lifehash.post.js) that attaches the friendly JS API (e.g.makeFromUTF8). - Outputs (engine artifacts):
lifehash.wasm: the compiled WebAssembly binarylifehash.js: the Emscripten JS glue (ES module)
These two files are what the runtime uses. The rest of the package (index.js, build/index.cjs, build/modern/index.js, etc.) is just wrapper + packaging.
Build configuration (what matters)
The important Emscripten settings used for this package:
- ES module output:
EXPORT_ES6=1- Emits an ES module glue file (works with modern bundlers and browsers).
- Factory / no globals:
MODULARIZE=1- The glue exports a factory you
await, instead of writing a globalModule.
- The glue exports a factory you
- Environment targets:
ENVIRONMENT='web,node'- Same engine artifacts work in both browser and Node.
- Optimization:
-Oz- Optimize for size (good for shipping in npm packages). This should not change correctness; runtime errors are almost always “wrong wasm loaded” rather than “optimized too much”.
- C exports:
EXPORTED_FUNCTIONSincludes at least:_malloc,_free_lifehash_make_from_utf8,_lifehash_make_from_data,_lifehash_make_from_digest_lifehash_image_free,_lifehash_sha256,_lifehash_data_to_hex,_lifehash_hex_to_data- These must be exported because the post-js API uses them via
ccall/cwrap.
- Runtime exports:
EXPORTED_RUNTIME_METHODSincludes at least:ccall,cwrap,UTF8ToString- This avoids having to add any “global shim” in userland JS.
- Post-js hook:
--post-js wasm/lifehash.post.js- Attaches the high-level methods (
makeFromUTF8, etc.) and provides a Node-safe return shape when DOM APIs aren’t available.
- Attaches the high-level methods (
Packaging into lifehashjs
This repo publishes only what’s under build/ (see package.json → "files": ["build", ...]).
Why are there “duplicate” artifacts at the repo root?
You’ll see lifehash.wasm and lifehash.js at the repo root and under build/.
- Repo root (
lifehash.wasm,lifehash.js): treated as build inputs during development (e.g. after regenerating the engine). - Published copies (
build/lifehash.wasm,build/lifehash.js): the actual runtime artifacts that consumers load from npm.
This duplication is intentional to keep the build pipeline simple (copy inputs → publishable build/), but it can be confusing when inspecting the repo. When debugging “wrong WASM loaded” issues, always verify you’re serving/reading the build/ artifacts in installed packages.
After you rebuild the engine artifacts (lifehash.js + lifehash.wasm), you must:
- Place them at the repo root:
lifehash.jslifehash.wasm
- Run the package build so they get copied into
build/:pnpm run build
That produces the publishable artifacts:
build/lifehash.js,build/lifehash.wasm(engine)build/index.cjs,build/modern/index.js(wrappers)build/index.d.ts(types)build/lifehash-utils.cjs,build/modern/lifehash-utils.js,build/lifehash-utils.d.ts(controlled generator)
CommonJS build (for older runtimes / require-only consumers)
Even though the WASM engine glue is emitted as an ES module (EXPORT_ES6=1), the package also ships a CommonJS entry so consumers that still use require() (or older build setups that don’t ingest ESM cleanly) can keep working:
- CJS entry:
build/index.cjs- Generated from the ESM wrapper source by
scripts/build-cjs.js. - Exports only the supported public API (
generate*,sha256,ELifeHashVersion).
- Generated from the ESM wrapper source by
- How Node resolves it:
package.jsonexportsmaps"require"→build/index.cjs"node"→build/index.cjs(so Node ESMimportdoes not accidentally consume the bundler-focusedmodernentry)- and
"browser"/"import"→build/modern/index.js(bundler-focused ESM entry)
Important: the engine artifacts are still the same (build/lifehash.js + build/lifehash.wasm), and CJS is only for the wrapper entrypoint. This keeps compatibility broad without introducing global shims.
Initialization Process
- First Call: When you call any function (e.g.,
generate()), the library automatically initializes the WASM module if it hasn't been initialized yet. - WASM Loading: The library loads the
lifehash.wasmbinary file (automatically resolved in browser/bundler or Node.js environments). - Runtime Initialization: After the WASM module loads, the Emscripten runtime initializes and attaches methods like
makeFromUTF8,makeFromData, etc. to the module object. - Ready to Use: Once initialization completes, all methods are available and ready to use.
Note: The initialization is asynchronous, but you don't need to worry about it - all public functions (generate, generateFromData, etc.) automatically await initialization before executing.
Troubleshooting
WASM File Not Found
If you encounter errors about the WASM file not being found:
Bundler Configuration: Ensure your bundler (Vite, Webpack, etc.) is configured to copy WASM files. The library uses
import.meta.urlto locate the WASM file in modern bundlers.Node.js: In Node.js environments, the library automatically searches for
lifehash.wasmin the package directory.Manual Path: If needed, you can verify the WASM file exists in
node_modules/lifehashjs/build/lifehash.wasm(published underbuild/).- You can also resolve it via the explicit subpath export:
lifehashjs/lifehash.wasm.
- You can also resolve it via the explicit subpath export:
Initialization Errors
If you see errors like "LifeHash module methods not available":
- Ensure you're awaiting
generate()/generateWithAbort()(or other exported functions) - Check that WebAssembly is supported in your environment
- Verify the WASM file is accessible (not blocked by CORS in browsers)
Browser Compatibility
This library requires:
- WebAssembly support (available in all modern browsers)
- ES6 modules support (or use the CommonJS version)
- Canvas API (for image generation in browsers)
Related Projects
- LifeHash - Original Swift reference implementation
- bc-lifehash - C++ implementation (source for WASM)
- bc-lifehash-python - Python implementation
License
BSD-2-Clause Plus Patent License
SPDX-License-Identifier: BSD-2-Clause-Patent
Copyright © 2020 Blockchain Commons, LLC
See LICENSE file for full license text.
