wasm_js/
js_bin.rs

1//! Writer the converts a stream of WASM bytes into a JS module
2
3use crate::utils::StrUtils;
4use base64::Engine;
5use std::io::{self, Write};
6use std::sync::LazyLock;
7
8const CHUNK_WORDS: usize = 8192;
9
10pub struct WasmJsWriter<W: Write> {
11    out: W,
12    imports_module: String,
13    wasm_buf: [u8; CHUNK_WORDS * 3],
14    out_buf: [u8; CHUNK_WORDS * 4],
15    n: usize,
16    started: bool,
17    finished: bool,
18}
19
20static PROLOG: LazyLock<Vec<u8>> = LazyLock::new(|| {
21    r#"
22const CHUNK_STACK = [
23""#
24    .to_os_bytes()
25});
26
27static CHUNK_SEP: LazyLock<Vec<u8>> = LazyLock::new(|| {
28    r#"",
29""#
30    .to_os_bytes()
31});
32
33static EPILOG: LazyLock<Vec<u8>> = LazyLock::new(|| {
34    r#""
35].reverse();
36
37async function chunkBytes(base64) {
38  if (typeof Buffer !== 'undefined') {
39    return Buffer.from(base64, 'base64');
40  }
41  const res = await fetch("data:application/octet-stream;base64," + base64);
42  return res.bytes();
43}
44
45export const WASM_PROMISE = (async () => {
46  const compressed = new ReadableStream({
47    type: 'bytes',
48    cancel: () => {
49      CHUNK_STACK.length = 0;
50    },
51    pull: async (ctrl) => {
52      if (CHUNK_STACK.length) {
53        ctrl.enqueue(await chunkBytes(CHUNK_STACK.pop()));
54      } else {
55        ctrl.close();
56      }
57    }
58  });
59  const body = compressed.pipeThrough(new DecompressionStream('deflate'));
60  const response = new Response(body,
61  {
62    status: 200,
63      statusText: 'OK',
64        headers: {
65      'content-type': 'application/wasm'
66    }
67  });
68  const {instance} = await WebAssembly.instantiateStreaming(response, {
69    [IMPORTS_KEY]: importObject
70  });
71  importObject.__wbg_set_wasm(instance.exports);
72  instance.exports.__wbindgen_start();
73  return importObject;
74})();
75
76export function getWasm() {
77  return WASM_PROMISE;
78}
79"#
80    .to_os_bytes()
81});
82
83impl<W: Write> WasmJsWriter<W> {
84    pub fn new(out: W, imports_module: &str) -> Self {
85        Self {
86            out,
87            imports_module: imports_module.to_string(),
88            wasm_buf: [0; CHUNK_WORDS * 3],
89            out_buf: [0; CHUNK_WORDS * 4],
90            n: 0,
91            started: false,
92            finished: false,
93        }
94    }
95
96    fn push_chunk(&mut self) -> std::io::Result<()> {
97        if self.finished {
98            return Err(io::Error::new(
99                std::io::ErrorKind::Other,
100                "Cannot write to finished WasmJsWriter",
101            ));
102        }
103
104        let sz = base64::engine::general_purpose::STANDARD
105            .encode_slice(&self.wasm_buf[..self.n], &mut self.out_buf)
106            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
107
108        if !self.started {
109            let opening = format!(
110                "import * as importObject from '{}';\nconst IMPORTS_KEY = '{}'\n;",
111                self.imports_module, self.imports_module
112            );
113            self.out.write_all(opening.to_os_bytes().as_ref())?;
114            self.out.write_all(PROLOG.as_ref())?;
115            self.started = true;
116        } else {
117            self.out.write_all(CHUNK_SEP.as_ref())?;
118        }
119        self.out.write_all(&self.out_buf[..sz])?;
120        self.n = 0;
121        Ok(())
122    }
123}
124
125impl<W: Write> Write for WasmJsWriter<W> {
126    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
127        if buf.len() == 0 {
128            return Ok(0);
129        }
130
131        let mut written = 0;
132        let mut space = CHUNK_WORDS * 3 - self.n;
133        while buf.len() - written > space {
134            self.wasm_buf[self.n..(self.n + space)]
135                .copy_from_slice(&buf[written..(written + space)]);
136            self.n += space;
137            written += space;
138            self.push_chunk()?;
139            space = CHUNK_WORDS * 3;
140        }
141        let sz = buf.len() - written;
142        self.wasm_buf[self.n..(self.n + sz)].copy_from_slice(&buf[written..]);
143        self.n += sz;
144        Ok(buf.len())
145    }
146
147    fn flush(&mut self) -> std::io::Result<()> {
148        if self.finished {
149            return Ok(());
150        }
151        self.push_chunk()?;
152        self.finished = true;
153        self.out.write_all(EPILOG.as_ref())?;
154        self.out.flush()?;
155        Ok(())
156    }
157}