How to Store WebAssembly Modules in Browser Cache
WebAssembly (WASM) modules can significantly improve web application performance, but fetching and compiling them on every page load introduces latency. This article explains how to efficiently store and retrieve WASM modules in the browser using the Cache API and IndexedDB, reducing network overhead and startup times for your web applications.
The Modern Standard: The Cache API
The most efficient and modern way to cache WASM modules is by using
the browser’s Cache API. Instead of storing the compiled machine code,
you cache the HTTP response of the .wasm file.
This approach is highly efficient because it integrates perfectly
with WebAssembly.instantiateStreaming(). When you retrieve
the response from the cache, you can feed it directly into the streaming
compiler. The browser compiles the module in the background while it is
being loaded from the local disk cache, resulting in near-instantaneous
startup times.
Here is how to implement caching with the Cache API:
async function loadWasm(url) {
const cacheName = 'wasm-cache-v1';
const cache = await caches.open(cacheName);
// Try to find the cached response
let response = await cache.match(url);
if (!response) {
// Fetch from network if not cached and add to cache
await cache.add(url);
response = await cache.match(url);
}
// Instantiate directly from the cached response
const { instance } = await WebAssembly.instantiateStreaming(response);
return instance;
}The Alternative: IndexedDB
Historically, developers attempted to store compiled
WebAssembly.Module objects directly in IndexedDB to avoid
the recompilation step entirely. However, storing compiled modules in
IndexedDB has been deprecated or disabled in most modern browsers due to
security vulnerabilities and serialization issues.
If you must use IndexedDB, the best practice is to store the raw
binary data as an ArrayBuffer. When retrieving it, you must
compile it using WebAssembly.instantiate().
// Storing WASM ArrayBuffer in IndexedDB
function storeWasmInDB(db, key, arrayBuffer) {
const transaction = db.transaction(['wasmStore'], 'readwrite');
const store = transaction.objectStore('wasmStore');
store.put(arrayBuffer, key);
}
// Retrieving and instantiating from IndexedDB
async function loadWasmFromDB(db, key) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['wasmStore'], 'readonly');
const store = transaction.objectStore('wasmStore');
const request = store.get(key);
request.onsuccess = async () => {
const arrayBuffer = request.result;
if (arrayBuffer) {
const { instance } = await WebAssembly.instantiate(arrayBuffer);
resolve(instance);
} else {
reject('WASM module not found in IndexedDB');
}
};
request.onerror = () => reject('Database error');
});
}While IndexedDB works for raw binary storage, it requires full
compilation of the ArrayBuffer upon retrieval, which is
less efficient than the streaming compilation offered by the Cache
API.
Summary of Best Practices
For optimal performance and cross-browser compatibility: *
Use the Cache API as your primary caching mechanism. *
Use WebAssembly.instantiateStreaming with
the cached response to compile and instantiate the module
simultaneously. * Avoid storing compiled modules in
IndexedDB, as browser security policies restrict this behavior. Use raw
ArrayBuffer storage in IndexedDB only as a fallback.