Embedding binary data in WebAssembly

There's a few ways to load binary data in JavaScript but most are platform specific. For example the browser you can use fetch, in Node.js the fs module. However with serverless environments which do not support reading from the local filesystem it becomes harder. You can embed the binary data in a base64 string, but it will add to the file size (important when most have a 1MB script limit).

Most of those environments do support WebAssembly by means of a binary .wasm file. I wrote the following Node.js script to embed arbitrary binary files in a .wasm file and export it.

import {readFileSync, writeFileSync} from 'fs'
import {unsigned, signed} from 'leb128'

const data = readFileSync(process.argv[2])

function bin(strings, ...inserts) {
  const res = []
  strings.forEach(function (str, i) {
    res.push(
      Buffer.from(str.replace(/\/\/(.*?)\n/g, '').replace(/\s/g, ''), 'hex')
    )
    if (inserts[i]) res.push(inserts[i])
  })
  return Buffer.concat(res)
}

const size = unsigned.encode(data.length)
const length = signed.encode(data.length)
const globalL = unsigned.encode(5 + length.length)
const dataL = unsigned.encode(5 + size.length + data.length)
const memoryPages = unsigned.encode(Math.ceil(data.length / 65536))
const memoryL = unsigned.encode(2 + memoryPages.length)

const buffer = bin`
  00 61 73 6d                                         // WASM_BINARY_MAGIC
  01 00 00 00                                         // WASM_BINARY_VERSION
  05 ${memoryL} 01                                    // section "Memory" (5)
  00 ${memoryPages}                                   // memory 0
  06 ${globalL} 01 7f 00 41 ${length} 0b              // section "Global" (6)
  07 11 02 04 6461 7461 02 00 06 6c65 6e67 7468 03 00 // section "Export" (7)
  0b ${dataL} 01                                      // section "Data" (11)
  00 41 00 0b ${size}                                 // data segment header 0
  ${data}                                             // data
`

writeFileSync(process.argv[3], buffer)

We can use the script to embed a file:

node script.mjs input.bin output.wasm

The WebAssembly module exports data and length properties by which you can retrieve the data:

import {data, length} from './output.wasm'
const buffer = new Uint8Array(data.buffer, 0, length.value)

Note that loading the wasm file in the snippet above uses the WebAssembly ES Module integration proposal which is not supported everywhere yet. Until it is, base64 embedding data might be your only option.